Skip to content

Commit 067e39d

Browse files
authored
fix(data-store): validate sorted queries in data store (#1482)
1 parent 0e4a3f7 commit 067e39d

File tree

4 files changed

+68
-41
lines changed

4 files changed

+68
-41
lines changed

__tests__/shared/saveClaims.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,30 @@ export default (testContext: {
106106
expect(count).toEqual(credentials.length)
107107
})
108108

109+
it('blocks injection attempts', async () => {
110+
expect.assertions(1);
111+
const query = agent.dataStoreORMGetVerifiableCredentialsByClaims({
112+
where: [
113+
{
114+
value: ['string'],
115+
not: true,
116+
op: 'zx' as any,
117+
column: 'isObj',
118+
},
119+
],
120+
skip: 0,
121+
take: 0,
122+
order: [
123+
{
124+
direction: 'ASC',
125+
column:
126+
'issuanceDate" AS "issuanceDate" FROM "claim" "claim" LEFT JOIN "identifier" "issuer" ON "issuer"."did"="claim"."issuerDid" LEFT JOIN "identifier" "subject" ON "subject"."did"="claim"."subjectDid" LEFT JOIN "credential" "credential" ON "credential"."hash"="claim"."credentialHash" where not(claim.isObj in (?)) and 1=0 UNION ALL SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,(SELECT json_object(\'alias\', alias, \'type\', type, \'privateKeyHex\', privateKeyHex) ),22,23,24,25,26,27,28,29 from `private-key`-- -' as any,
127+
},
128+
],
129+
})
130+
await expect(query).rejects.toThrow(/Invalid column name/);
131+
})
132+
109133
it('should be able to find all the credentials when query by claim type and value', async () => {
110134
const credentials = await agent.dataStoreORMGetVerifiableCredentialsByClaims({
111135
where: [
@@ -192,7 +216,7 @@ export default (testContext: {
192216

193217
expect(credentialsAllDesc[0].verifiableCredential.issuanceDate).toEqual(credentials1[0].verifiableCredential.issuanceDate)
194218
expect(credentialsAllDesc[1].verifiableCredential.issuanceDate).toEqual(credentials2[0].verifiableCredential.issuanceDate)
195-
219+
196220
expect(credentialsAllDesc[0].verifiableCredential.issuanceDate).toEqual(credentialsAllAsc[2].verifiableCredential.issuanceDate)
197221
expect(credentialsAllDesc[1].verifiableCredential.issuanceDate).toEqual(credentialsAllAsc[1].verifiableCredential.issuanceDate)
198222
expect(credentialsAllDesc[2].verifiableCredential.issuanceDate).toEqual(credentialsAllAsc[0].verifiableCredential.issuanceDate)

packages/core-types/src/types/IDataStoreORM.ts

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@ import { IIdentifier } from './IIdentifier.js'
33
import { IAgentContext, IPluginMethodMap } from './IAgent.js'
44
import { IMessage } from './IMessage.js'
55

6+
/**
7+
* The allowed columns for querying different data types in the {@link IDataStoreORM} interface.
8+
* @internal
9+
*/
10+
export const ALLOWED_COLUMNS = {
11+
message: ['from', 'to', 'id', 'createdAt', 'expiresAt', 'threadId', 'type', 'raw', 'replyTo', 'replyUrl'],
12+
claim: [
13+
'context',
14+
'credentialType',
15+
'type',
16+
'value',
17+
'isObj',
18+
'id',
19+
'issuer',
20+
'subject',
21+
'expirationDate',
22+
'issuanceDate',
23+
],
24+
credential: ['context', 'type', 'id', 'issuer', 'subject', 'expirationDate', 'issuanceDate', 'hash'],
25+
presentation: ['context', 'type', 'id', 'holder', 'verifier', 'expirationDate', 'issuanceDate'],
26+
identifier: ['did', 'alias', 'provider'],
27+
} as const
28+
629
/**
730
* Represents the sort order of results from a {@link FindArgs} query.
831
*
@@ -70,25 +93,15 @@ export interface FindArgs<TColumns> {
7093
* @deprecated This type will be removed in future versions of this plugin interface.
7194
* @beta This API may change without a BREAKING CHANGE notice.
7295
*/
73-
export type TIdentifiersColumns = 'did' | 'alias' | 'provider'
96+
export type TIdentifiersColumns = (typeof ALLOWED_COLUMNS.identifier)[number]
7497

7598
/**
7699
* The columns that can be queried for an {@link IMessage}
77100
*
78101
* See {@link IDataStoreORM.dataStoreORMGetMessagesCount}
79102
* @beta This API may change without a BREAKING CHANGE notice.
80103
*/
81-
export type TMessageColumns =
82-
| 'from'
83-
| 'to'
84-
| 'id'
85-
| 'createdAt'
86-
| 'expiresAt'
87-
| 'threadId'
88-
| 'type'
89-
| 'raw'
90-
| 'replyTo'
91-
| 'replyUrl'
104+
export type TMessageColumns = (typeof ALLOWED_COLUMNS.message)[number]
92105

93106
/**
94107
* The columns that can be searched for a {@link VerifiableCredential}
@@ -98,15 +111,7 @@ export type TMessageColumns =
98111
*
99112
* @beta This API may change without a BREAKING CHANGE notice.
100113
*/
101-
export type TCredentialColumns =
102-
| 'context'
103-
| 'type'
104-
| 'id'
105-
| 'issuer'
106-
| 'subject'
107-
| 'expirationDate'
108-
| 'issuanceDate'
109-
| 'hash'
114+
export type TCredentialColumns = (typeof ALLOWED_COLUMNS.credential)[number]
110115

111116
/**
112117
* The columns that can be searched for the claims of a {@link VerifiableCredential}
@@ -116,17 +121,7 @@ export type TCredentialColumns =
116121
*
117122
* @beta This API may change without a BREAKING CHANGE notice.
118123
*/
119-
export type TClaimsColumns =
120-
| 'context'
121-
| 'credentialType'
122-
| 'type'
123-
| 'value'
124-
| 'isObj'
125-
| 'id'
126-
| 'issuer'
127-
| 'subject'
128-
| 'expirationDate'
129-
| 'issuanceDate'
124+
export type TClaimsColumns = (typeof ALLOWED_COLUMNS.claim)[number]
130125

131126
/**
132127
* The columns that can be searched for a {@link VerifiablePresentation}
@@ -136,14 +131,7 @@ export type TClaimsColumns =
136131
*
137132
* @beta This API may change without a BREAKING CHANGE notice.
138133
*/
139-
export type TPresentationColumns =
140-
| 'context'
141-
| 'type'
142-
| 'id'
143-
| 'holder'
144-
| 'verifier'
145-
| 'expirationDate'
146-
| 'issuanceDate'
134+
export type TPresentationColumns = (typeof ALLOWED_COLUMNS.presentation)[number]
147135

148136
/**
149137
* This context can be used for Veramo Agents that are created behind an authorization mechanism, that attaches a DID

packages/data-store-json/src/data-store-json.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ALLOWED_COLUMNS,
23
AuthorizedDIDContext,
34
FindArgs,
45
IAgentPlugin,
@@ -587,6 +588,8 @@ function buildQuery<T extends Partial<Record<PossibleColumns, any>>>(
587588
})
588589
}
589590

591+
const allowedColumns = Object.values(ALLOWED_COLUMNS).flat()
592+
590593
if (input.order && input.order.length > 0) {
591594
filteredCollection.sort((a: T, b: T) => {
592595
let result = 0
@@ -597,6 +600,9 @@ function buildQuery<T extends Partial<Record<PossibleColumns, any>>>(
597600
if (!col) {
598601
break
599602
}
603+
if (!allowedColumns.includes(col)) {
604+
throw new Error(`Invalid column name: ${col}`)
605+
}
600606
const colA = a[col]
601607
const colB = b[col]
602608
if (typeof colA?.getTime === 'function') {

packages/data-store/src/data-store-orm.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ALLOWED_COLUMNS,
23
AuthorizedDIDContext,
34
FindArgs,
45
IAgentPlugin,
@@ -422,7 +423,11 @@ function decorateQB(
422423
if (input?.take) qb = qb.limit(input.take)
423424

424425
if (input?.order) {
426+
const allowedColumns = getAllowedColumnsForTable(tableName)
425427
for (const item of input.order) {
428+
if (!allowedColumns.includes(item.column)) {
429+
throw new Error(`Invalid column name: ${item.column}`)
430+
}
426431
qb = qb.addSelect(
427432
qb.connection.driver.escape(tableName) + '.' + qb.connection.driver.escape(item.column),
428433
item.column,
@@ -432,3 +437,7 @@ function decorateQB(
432437
}
433438
return qb
434439
}
440+
441+
function getAllowedColumnsForTable(tableName: string): readonly string[] {
442+
return ALLOWED_COLUMNS[tableName as keyof typeof ALLOWED_COLUMNS] || []
443+
}

0 commit comments

Comments
 (0)