sql-guard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # sql-guard
2
+
3
+ Validate AI generated PostgreSQL queries against explicit allowlists. This package parses SQL into an AST and denies anything outside your policy.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install sql-guard
9
+ ```
10
+
11
+ ## Quickstart
12
+
13
+ ```typescript
14
+ import { validate, assertSafeSql, ErrorCode } from 'sql-guard';
15
+
16
+ const policy = {
17
+ allowedTables: ['public.users', 'public.orders'],
18
+ allowedFunctions: ['count', 'lower'],
19
+ };
20
+
21
+ const result = validate('SELECT * FROM public.users', policy);
22
+ if (!result.ok) {
23
+ console.log('Denied:', result.errorCode);
24
+ console.log('Violations:', result.violations);
25
+ }
26
+
27
+ // Or fail fast with an exception
28
+ assertSafeSql('SELECT lower(u.email) FROM public.users u', policy);
29
+ ```
30
+
31
+ ## API Reference
32
+
33
+ ### validate(sql, policy)
34
+
35
+ Validates SQL against a policy.
36
+
37
+ - Returns: `ValidationResult`
38
+ - On failure: `ok === false`, `violations` populated, and `errorCode` set
39
+
40
+ ### assertSafeSql(sql, policy)
41
+
42
+ Validates SQL and throws when validation fails.
43
+
44
+ - Returns: `void`
45
+ - Throws: `SqlValidationError` with `code: ErrorCode` and `violations: Violation[]`
46
+
47
+ ```typescript
48
+ import { assertSafeSql, SqlValidationError, ErrorCode } from 'sql-guard';
49
+
50
+ try {
51
+ assertSafeSql('SELECT pg_catalog.current_database() FROM public.users', {
52
+ allowedTables: ['public.users'],
53
+ allowedFunctions: ['lower'],
54
+ });
55
+ } catch (err) {
56
+ if (err instanceof SqlValidationError) {
57
+ if (err.code === ErrorCode.FUNCTION_NOT_ALLOWED) {
58
+ console.error('Blocked a function call:', err.violations);
59
+ }
60
+ }
61
+ throw err;
62
+ }
63
+ ```
64
+
65
+ ### ErrorCode
66
+
67
+ Enum of error codes returned by `validate()` and used by `SqlValidationError`.
68
+
69
+ ### Policy
70
+
71
+ Policy settings that drive validation.
72
+
73
+ ```ts
74
+ export interface Policy {
75
+ allowedTables: string[];
76
+ allowedStatements?: ('select' | 'insert' | 'update' | 'delete')[];
77
+ allowMultiStatement?: boolean;
78
+ allowedFunctions?: string[];
79
+ tableIdentifierMatching?: 'strict' | 'caseInsensitive';
80
+ resolver?: (unqualified: string) => string | null;
81
+ }
82
+ ```
83
+
84
+ Defaults and behavior:
85
+
86
+ - `allowedTables` is required.
87
+ - `allowedTables` entries must be schema-qualified (`schema.table`). Invalid entries return `INVALID_POLICY`.
88
+ - `allowedStatements` defaults to `['select']`.
89
+ - `allowMultiStatement` defaults to `false`.
90
+ - `allowedFunctions` defaults to `[]`, which means any function call is denied unless allowlisted.
91
+ - `tableIdentifierMatching` defaults to `'strict'` (exact case-sensitive table matching).
92
+ - Set `tableIdentifierMatching: 'caseInsensitive'` to preserve case-insensitive table matching.
93
+ - Unqualified table references in SQL are denied unless you provide `resolver` to map them to `schema.table`.
94
+ - Unqualified function allowlist entries (for example, `lower`) match only unqualified calls (`lower(...)`).
95
+ - Schema-qualified function calls require schema-qualified allowlist entries (`pg_catalog.current_database`).
96
+
97
+ Strict policy examples:
98
+
99
+ ```ts
100
+ const strictPolicy = {
101
+ allowedTables: ['public.users', 'analytics.events'],
102
+ allowedFunctions: ['lower', 'pg_catalog.current_database'],
103
+ resolver: (unqualified: string) =>
104
+ unqualified === 'users' ? 'public.users' : null,
105
+ };
106
+ ```
107
+
108
+ ## Security Model
109
+
110
+ - AST based validation, not regex matching.
111
+ - Fail closed: unsupported or uncertain parser features are denied.
112
+ - Data-modifying CTE payloads (for example `WITH x AS (INSERT ...) SELECT ...`) are denied as unsupported.
113
+ - `SELECT INTO` is denied as unsupported.
114
+ - Table allowlists: every referenced table must be in `policy.allowedTables` by fully qualified name.
115
+ - Statement type restrictions: only `select` is allowed unless you opt in via `allowedStatements`.
116
+ - Multi statement restriction: `SELECT 1; SELECT 2` is denied unless `allowMultiStatement: true`.
117
+ - Function allowlists: schema-qualified calls are allowed only by exact schema-qualified entries.
118
+ - Metadata table protection: relations in `information_schema` and `pg_catalog` are denied unless explicitly allowlisted by fully qualified name.
119
+
120
+ This is a guardrail for LLM output. It helps enforce least privilege at the query shape level. Use it alongside parameterization, prepared statements, and database permissions.
121
+
122
+ ## Limitations
123
+
124
+ - PostgreSQL focused (v1). Other dialects are not supported.
125
+ - No SQL rewriting or sanitization. This package validates, it doesn't transform queries.
126
+ - Not a complete SQL injection defense by itself. Treat it as defense in depth.
127
+ - No database context: it can't check column level permissions, RLS policies, or runtime schema changes.
128
+
129
+ ## Error Codes
130
+
131
+ `validate()` returns a single `errorCode` plus a list of `violations`. Invalid policy configuration is reported before SQL parsing.
132
+
133
+ | Code | Description |
134
+ |------|-------------|
135
+ | `PARSE_ERROR` | SQL could not be parsed into an AST. |
136
+ | `UNSUPPORTED_SQL_FEATURE` | Parsed SQL contains features outside the supported subset (fail closed). |
137
+ | `TABLE_NOT_ALLOWED` | A referenced table is not in `policy.allowedTables`, or an unqualified table can't be resolved. |
138
+ | `STATEMENT_NOT_ALLOWED` | Statement type is not allowed (defaults to `select` only). |
139
+ | `FUNCTION_NOT_ALLOWED` | A function call is not in `policy.allowedFunctions`. |
140
+ | `MULTI_STATEMENT_DISABLED` | Query contains multiple statements while `allowMultiStatement` is disabled. |
141
+ | `INVALID_POLICY` | Policy configuration is invalid (for example non-qualified table allowlist entries). |
142
+
143
+ ## Violation Types
144
+
145
+ `Violation.type` can be:
146
+
147
+ - `parse`
148
+ - `unsupported`
149
+ - `policy`
150
+ - `statement`
151
+ - `table`
152
+ - `function`
153
+
154
+ ## Publishing
155
+
156
+ For first time npm publish:
157
+
158
+ ```bash
159
+ npm login
160
+ npm publish --access public
161
+ ```
162
+
163
+ Notes:
164
+
165
+ - `prepublishOnly` runs typecheck, tests, and build, so publishing requires Bun in your environment.
166
+
167
+ ## License
168
+
169
+ MIT
@@ -0,0 +1,3 @@
1
+ import { FunctionCall } from '../parser/types';
2
+ export declare function extractAllFunctions(ast: unknown): FunctionCall[];
3
+ //# sourceMappingURL=functions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../src/analysis/functions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,EAAE,CAoChE"}
@@ -0,0 +1,3 @@
1
+ import { TableReference } from '../parser/types';
2
+ export declare function extractAllTables(ast: unknown): TableReference[];
3
+ //# sourceMappingURL=relations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../src/analysis/relations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,EAAE,CAyE/D"}