Skip to content

Commit 6494cbe

Browse files
committed
fixed review and the issues
1 parent 2fc6edb commit 6494cbe

3 files changed

Lines changed: 71 additions & 17 deletions

File tree

graphile/graphile-sql-expression-validator/__tests__/validator.test.ts

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -419,10 +419,10 @@ describe('parseAndValidateSqlExpression', () => {
419419
expect(result.error).toContain('ParamRef');
420420
});
421421

422-
it('should reject any unrecognized/new node type via allowlist', async () => {
422+
it('should reject any unrecognized/new node type via allowlist', () => {
423423
// Craft a fake AST with a node type not in the allowlist
424424
const unknownAst = { SomeNewDangerousNode: {} };
425-
const result = await validateAst(unknownAst);
425+
const result = validateAst(unknownAst);
426426
expect(result.valid).toBe(false);
427427
expect(result.error).toContain('Disallowed node type "SomeNewDangerousNode"');
428428
});
@@ -536,23 +536,57 @@ describe('parseAndValidateSqlExpression', () => {
536536
expect(result.error).toBeDefined();
537537
});
538538
});
539+
540+
// ─── SQL injection patterns ───────────────────────────────────
541+
542+
describe('SQL injection patterns', () => {
543+
it('should reject UNION injection', async () => {
544+
const result = await parseAndValidateSqlExpression(
545+
'1) UNION SELECT secret FROM users --'
546+
);
547+
expect(result.valid).toBe(false);
548+
});
549+
550+
it('should reject UNION ALL injection', async () => {
551+
const result = await parseAndValidateSqlExpression(
552+
'1) UNION ALL SELECT 1 --'
553+
);
554+
expect(result.valid).toBe(false);
555+
});
556+
557+
it('should reject FROM clause injection', async () => {
558+
const result = await parseAndValidateSqlExpression(
559+
'(SELECT 1 FROM pg_shadow)'
560+
);
561+
expect(result.valid).toBe(false);
562+
expect(result.error).toMatch(/disallowed SQL clause|Disallowed node type/i);
563+
});
564+
565+
it('should reject WHERE clause injection', async () => {
566+
const result = await parseAndValidateSqlExpression(
567+
'(SELECT 1 WHERE true)'
568+
);
569+
expect(result.valid).toBe(false);
570+
expect(result.error).toMatch(/disallowed SQL clause|Disallowed node type/i);
571+
});
572+
});
539573
});
540574

541575
describe('validateAst', () => {
542-
it('should reject null AST', async () => {
543-
const result = await validateAst(null);
576+
it('should reject null AST', () => {
577+
const result = validateAst(null);
544578
expect(result.valid).toBe(false);
545579
expect(result.error).toBe('AST must be a non-null object');
546580
});
547581

548-
it('should reject undefined AST', async () => {
549-
const result = await validateAst(undefined);
582+
it('should reject undefined AST', () => {
583+
const result = validateAst(undefined);
550584
expect(result.valid).toBe(false);
551585
expect(result.error).toBe('AST must be a non-null object');
552586
});
553587

554-
it('should reject non-object AST', async () => {
555-
const result = await validateAst('string');
588+
it('should reject non-object AST', () => {
589+
const result = validateAst('string');
556590
expect(result.valid).toBe(false);
557591
expect(result.error).toBe('AST must be a non-null object');
558592
});
@@ -561,18 +595,18 @@ describe('validateAst', () => {
561595
const parseResult = await parseAndValidateSqlExpression('42');
562596
expect(parseResult.valid).toBe(true);
563597

564-
const result = await validateAst(parseResult.ast);
598+
const result = validateAst(parseResult.ast);
565599
expect(result.valid).toBe(true);
566600
expect(result.canonicalText).toBe('42');
567601
});
568602

569-
it('should reject AST containing disallowed nodes', async () => {
603+
it('should reject AST containing disallowed nodes', () => {
570604
const forbiddenAst = {
571605
ColumnRef: {
572606
fields: [{ String: { sval: 'username' } }]
573607
}
574608
};
575-
const result = await validateAst(forbiddenAst);
609+
const result = validateAst(forbiddenAst);
576610
expect(result.valid).toBe(false);
577611
expect(result.error).toContain('Disallowed node type');
578612
expect(result.error).toContain('ColumnRef');
@@ -582,7 +616,7 @@ describe('validateAst', () => {
582616
const parseResult = await parseAndValidateSqlExpression('now()');
583617
expect(parseResult.valid).toBe(true);
584618

585-
const result = await validateAst(parseResult.ast, {
619+
const result = validateAst(parseResult.ast, {
586620
allowedFunctions: ['gen_random_uuid']
587621
});
588622
expect(result.valid).toBe(false);

graphile/graphile-sql-expression-validator/src/validator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ export async function parseAndValidateSqlExpression(
389389
'fromClause', 'whereClause', 'larg', 'rarg', 'valuesLists',
390390
'intoClause', 'withClause', 'groupClause', 'havingClause',
391391
'windowClause', 'sortClause', 'limitOffset', 'limitCount',
392-
'lockingClause'
392+
'lockingClause', 'distinctClause'
393393
];
394394
for (const dangerousKey of DANGEROUS_SELECT_KEYS) {
395395
if (selectStmt[dangerousKey] !== undefined) {

graphql/server/src/middleware/graphile.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,40 @@ import { HandlerCreationError } from '../errors/api-errors';
1515
const maskErrorLog = new Logger('graphile:maskError');
1616

1717
const SAFE_ERROR_CODES = new Set([
18-
'UNAUTHENTICATED',
19-
'FORBIDDEN',
20-
'BAD_USER_INPUT',
18+
// GraphQL standard
2119
'GRAPHQL_VALIDATION_FAILED',
2220
'GRAPHQL_PARSE_FAILED',
2321
'PERSISTED_QUERY_NOT_FOUND',
2422
'PERSISTED_QUERY_NOT_SUPPORTED',
23+
// Auth
24+
'UNAUTHENTICATED',
25+
'FORBIDDEN',
26+
'BAD_USER_INPUT',
27+
'INCORRECT_PASSWORD',
28+
'PASSWORD_INSECURE',
29+
'ACCOUNT_LOCKED_EXCEED_ATTEMPTS',
30+
'ACCOUNT_DISABLED',
31+
'ACCOUNT_EXISTS',
32+
'PASSWORD_LEN',
33+
'INVITE_NOT_FOUND',
34+
'INVITE_LIMIT',
35+
'INVITE_EMAIL_NOT_FOUND',
36+
'INVALID_CREDENTIALS',
37+
// PublicKeySignature
2538
'FEATURE_DISABLED',
2639
'INVALID_PUBLIC_KEY',
2740
'INVALID_MESSAGE',
2841
'INVALID_SIGNATURE',
2942
'NO_ACCOUNT_EXISTS',
3043
'BAD_SIGNIN',
31-
'UPLOAD_MIMETYPE'
44+
// Upload
45+
'UPLOAD_MIMETYPE',
46+
// PostgreSQL constraint violations (surfaced by PostGraphile)
47+
'23505', // unique_violation
48+
'23503', // foreign_key_violation
49+
'23502', // not_null_violation
50+
'23514', // check_violation
51+
'23P01', // exclusion_violation
3252
]);
3353

3454
/**

0 commit comments

Comments
 (0)