@askdb/core 1.0.0-beta.44 → 1.0.0-beta.46
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/README.md +20 -19
- package/dist/ask.d.ts +45 -45
- package/dist/ask.d.ts.map +1 -1
- package/dist/ask.js +153 -310
- package/dist/ask.js.map +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/enrichment/index.d.ts.map +1 -1
- package/dist/enrichment/prompt.d.ts +3 -3
- package/dist/enrichment/prompt.d.ts.map +1 -1
- package/dist/enrichment/prompt.js +43 -17
- package/dist/enrichment/prompt.js.map +1 -1
- package/dist/enrichment/suggest.d.ts +2 -2
- package/dist/enrichment/suggest.d.ts.map +1 -1
- package/dist/enrichment/suggest.js +3 -1
- package/dist/enrichment/suggest.js.map +1 -1
- package/dist/enrichment/types.d.ts +29 -9
- package/dist/enrichment/types.d.ts.map +1 -1
- package/dist/errors.d.ts +97 -6
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/logging/askdb-logger.d.ts +2 -0
- package/dist/logging/askdb-logger.d.ts.map +1 -1
- package/dist/logging/create-askdb-logger.d.ts.map +1 -1
- package/dist/logging/create-askdb-logger.js +3 -0
- package/dist/logging/create-askdb-logger.js.map +1 -1
- package/dist/logging/log-events.d.ts +8 -0
- package/dist/logging/log-events.d.ts.map +1 -1
- package/dist/logging/log-events.js +8 -0
- package/dist/logging/log-events.js.map +1 -1
- package/dist/retrieval/synthesize-ddl.d.ts +7 -1
- package/dist/retrieval/synthesize-ddl.d.ts.map +1 -1
- package/dist/retrieval/synthesize-ddl.js +36 -33
- package/dist/retrieval/synthesize-ddl.js.map +1 -1
- package/dist/schema/normalize.d.ts +6 -2
- package/dist/schema/normalize.d.ts.map +1 -1
- package/dist/schema/normalize.js +1 -4
- package/dist/schema/normalize.js.map +1 -1
- package/dist/schema/v2/format.d.ts +28 -1
- package/dist/schema/v2/format.d.ts.map +1 -1
- package/dist/schema/v2/format.js +53 -19
- package/dist/schema/v2/format.js.map +1 -1
- package/dist/schema/v2/index.d.ts +4 -3
- package/dist/schema/v2/index.d.ts.map +1 -1
- package/dist/schema/v2/index.js +4 -3
- package/dist/schema/v2/index.js.map +1 -1
- package/dist/schema/v2/loader.d.ts +28 -7
- package/dist/schema/v2/loader.d.ts.map +1 -1
- package/dist/schema/v2/loader.js +220 -96
- package/dist/schema/v2/loader.js.map +1 -1
- package/dist/schema/v2/mentions.d.ts +12 -0
- package/dist/schema/v2/mentions.d.ts.map +1 -1
- package/dist/schema/v2/mentions.js +65 -11
- package/dist/schema/v2/mentions.js.map +1 -1
- package/dist/schema/v2/save-file.d.ts +56 -0
- package/dist/schema/v2/save-file.d.ts.map +1 -0
- package/dist/schema/v2/save-file.js +138 -0
- package/dist/schema/v2/save-file.js.map +1 -0
- package/dist/schema/v2/tenant-policy-loader.d.ts +1 -10
- package/dist/schema/v2/tenant-policy-loader.d.ts.map +1 -1
- package/dist/schema/v2/tenant-policy-loader.js +65 -51
- package/dist/schema/v2/tenant-policy-loader.js.map +1 -1
- package/dist/schema/v2/tenant-policy.d.ts +7 -2
- package/dist/schema/v2/tenant-policy.d.ts.map +1 -1
- package/dist/schema/v2/tenant-policy.js +26 -0
- package/dist/schema/v2/tenant-policy.js.map +1 -1
- package/dist/sql/bind.d.ts +27 -7
- package/dist/sql/bind.d.ts.map +1 -1
- package/dist/sql/bind.js +33 -18
- package/dist/sql/bind.js.map +1 -1
- package/dist/sql/dialect-spec.d.ts +1 -0
- package/dist/sql/dialect-spec.d.ts.map +1 -1
- package/dist/sql/dialect-spec.js +7 -2
- package/dist/sql/dialect-spec.js.map +1 -1
- package/dist/sql/generate.d.ts +17 -11
- package/dist/sql/generate.d.ts.map +1 -1
- package/dist/sql/generate.js +65 -33
- package/dist/sql/generate.js.map +1 -1
- package/dist/sql/guardrail-decide.d.ts +58 -0
- package/dist/sql/guardrail-decide.d.ts.map +1 -0
- package/dist/sql/guardrail-decide.js +161 -0
- package/dist/sql/guardrail-decide.js.map +1 -0
- package/dist/sql/guardrails.d.ts +52 -0
- package/dist/sql/guardrails.d.ts.map +1 -0
- package/dist/sql/guardrails.js +100 -0
- package/dist/sql/guardrails.js.map +1 -0
- package/dist/sql/index.d.ts +2 -1
- package/dist/sql/index.d.ts.map +1 -1
- package/dist/sql/index.js +2 -1
- package/dist/sql/index.js.map +1 -1
- package/dist/sql/lexer.d.ts +13 -0
- package/dist/sql/lexer.d.ts.map +1 -1
- package/dist/sql/lexer.js +42 -7
- package/dist/sql/lexer.js.map +1 -1
- package/dist/sql/prompt-identifiers.d.ts +9 -0
- package/dist/sql/prompt-identifiers.d.ts.map +1 -1
- package/dist/sql/prompt-identifiers.js +22 -3
- package/dist/sql/prompt-identifiers.js.map +1 -1
- package/dist/sql/prompt.d.ts.map +1 -1
- package/dist/sql/prompt.js +5 -2
- package/dist/sql/prompt.js.map +1 -1
- package/dist/sql/rebind.d.ts +116 -0
- package/dist/sql/rebind.d.ts.map +1 -0
- package/dist/sql/rebind.js +215 -0
- package/dist/sql/rebind.js.map +1 -0
- package/dist/sql/sensitive-guardrail.d.ts +7 -2
- package/dist/sql/sensitive-guardrail.d.ts.map +1 -1
- package/dist/sql/sensitive-guardrail.js +14 -33
- package/dist/sql/sensitive-guardrail.js.map +1 -1
- package/dist/sql/tenant-guardrail.d.ts +3 -14
- package/dist/sql/tenant-guardrail.d.ts.map +1 -1
- package/dist/sql/tenant-guardrail.js +16 -65
- package/dist/sql/tenant-guardrail.js.map +1 -1
- package/dist/sql/tenant-placeholders.d.ts +40 -14
- package/dist/sql/tenant-placeholders.d.ts.map +1 -1
- package/dist/sql/tenant-placeholders.js +92 -48
- package/dist/sql/tenant-placeholders.js.map +1 -1
- package/dist/sql/tenant-policy-warnings.d.ts +8 -0
- package/dist/sql/tenant-policy-warnings.d.ts.map +1 -0
- package/dist/sql/tenant-policy-warnings.js +21 -0
- package/dist/sql/tenant-policy-warnings.js.map +1 -0
- package/dist/sql/tenant-scope-expand.d.ts +46 -0
- package/dist/sql/tenant-scope-expand.d.ts.map +1 -0
- package/dist/sql/tenant-scope-expand.js +101 -0
- package/dist/sql/tenant-scope-expand.js.map +1 -0
- package/dist/sql/tsql-batch.d.ts +56 -0
- package/dist/sql/tsql-batch.d.ts.map +1 -0
- package/dist/sql/tsql-batch.js +246 -0
- package/dist/sql/tsql-batch.js.map +1 -0
- package/dist/sql/validate.d.ts +18 -3
- package/dist/sql/validate.d.ts.map +1 -1
- package/dist/sql/validate.js +127 -8
- package/dist/sql/validate.js.map +1 -1
- package/package.json +2 -2
package/dist/sql/generate.js
CHANGED
|
@@ -1,35 +1,62 @@
|
|
|
1
1
|
import { generateText as defaultGenerateText } from "ai";
|
|
2
|
-
import { SqlGenerationError } from "../errors.js";
|
|
2
|
+
import { SqlGenerationError, SqlValidationError } from "../errors.js";
|
|
3
3
|
import { AskDbLogEvent } from "../logging/log-events.js";
|
|
4
4
|
import { extractSqlFromModelText, extractUnboundSqlFromModelText } from "./extract-sql.js";
|
|
5
5
|
import { crossValidateManifestAgainstSql, parseParameterManifest, } from "./parameter-manifest.js";
|
|
6
6
|
import { buildNlToSqlSystemPrompt, buildNlToSqlUserPrompt } from "./prompt.js";
|
|
7
7
|
import { assertNlToSqlInputs, nlToSqlAmbiguityNotes } from "./schema-question-precheck.js";
|
|
8
|
-
import {
|
|
8
|
+
import { decide, tenantGuardrailResult, throwIfDenied } from "./guardrail-decide.js";
|
|
9
|
+
import { evaluateGuardrails, guardrailPlan, logGuardrailVerdict } from "./guardrails.js";
|
|
9
10
|
import { buildSelectGuardrailExplanation, validateSelectSql, } from "./validate.js";
|
|
10
11
|
/**
|
|
11
12
|
* Dialect-parameterized NL→SQL generator. Validates inputs, builds the user/system
|
|
12
13
|
* prompt with the dialect's syntax brief, calls the model, extracts the fenced SQL,
|
|
13
|
-
* and runs the
|
|
14
|
+
* and runs the guardrails through the same decision point as `ask()`: the read-only
|
|
15
|
+
* check (plus any dialect-specific `extraValidate`), and, when `deps.tenantPolicy` and
|
|
16
|
+
* `deps.tenantScope` are supplied, the tenant check under the policy's `enforcement`.
|
|
17
|
+
* Both check the returned `sql` and, when returned, `unboundNamedSql`. No sensitive check:
|
|
18
|
+
* that needs a mode, which `ask()` takes.
|
|
14
19
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* `
|
|
18
|
-
* place — `ask()` substitutes them and re-checks the final SQL.
|
|
20
|
+
* A `deny` throws the check's typed error (`SqlValidationError` before
|
|
21
|
+
* `TenantGuardrailError`) with the verdict attached; otherwise the result carries
|
|
22
|
+
* `verdict`. Tenant placeholders are left in place; `ask()` substitutes them.
|
|
19
23
|
*/
|
|
20
24
|
export async function generateSelectSql(dialect, question, schema, model, deps = {}) {
|
|
21
|
-
|
|
25
|
+
const generated = await runGenerateSelectSql(dialect, question, schema, model, deps);
|
|
26
|
+
const tenant = deps.tenantPolicy && deps.tenantScope ? { policy: deps.tenantPolicy, scope: deps.tenantScope } : undefined;
|
|
27
|
+
const { checks, modes } = guardrailPlan("generate", { dialect, schema, tenantPolicy: tenant?.policy });
|
|
28
|
+
const findings = evaluateGuardrails({
|
|
29
|
+
forms: {
|
|
30
|
+
sql: generated.sql,
|
|
31
|
+
...(generated.unboundNamedSql !== undefined ? { template: generated.unboundNamedSql } : {}),
|
|
32
|
+
},
|
|
33
|
+
dialect,
|
|
34
|
+
schema,
|
|
35
|
+
...(tenant ? { tenant } : {}),
|
|
36
|
+
}, checks);
|
|
37
|
+
const verdict = decide(findings, modes, "return");
|
|
38
|
+
logGuardrailVerdict(deps.logger, verdict, { tenantPolicy: tenant?.policy, sensitiveChecked: false });
|
|
39
|
+
try {
|
|
40
|
+
throwIfDenied(verdict, modes, "return");
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
logGenerateFailed(deps.logger, e);
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
const out = { ...generated, verdict };
|
|
47
|
+
if (tenant)
|
|
48
|
+
out.tenantGuardrail = tenantGuardrailResult(findings);
|
|
49
|
+
return out;
|
|
22
50
|
}
|
|
23
51
|
/**
|
|
24
|
-
* Internal entry used by `ask()`:
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* from the package index.
|
|
52
|
+
* Internal entry used by `ask()`: the model call and extraction only. `sql` is returned
|
|
53
|
+
* even when it fails the read-only check, because `ask()` runs every guardrail itself,
|
|
54
|
+
* through one decision point, before rendering. Not exported from the package index.
|
|
28
55
|
*/
|
|
29
|
-
export async function
|
|
30
|
-
return runGenerateSelectSql(dialect, question, schema, model, deps
|
|
56
|
+
export async function generateSelectSqlForAsk(dialect, question, schema, model, deps = {}) {
|
|
57
|
+
return runGenerateSelectSql(dialect, question, schema, model, deps);
|
|
31
58
|
}
|
|
32
|
-
async function runGenerateSelectSql(dialect, question, schema, model, deps
|
|
59
|
+
async function runGenerateSelectSql(dialect, question, schema, model, deps) {
|
|
33
60
|
assertNlToSqlInputs(schema, question);
|
|
34
61
|
const ambiguityNotes = nlToSqlAmbiguityNotes(question, schema);
|
|
35
62
|
const generateText = deps.generateText ?? defaultGenerateText;
|
|
@@ -82,8 +109,9 @@ async function runGenerateSelectSql(dialect, question, schema, model, deps, runT
|
|
|
82
109
|
const message = e instanceof Error ? e.message : String(e);
|
|
83
110
|
throw new SqlGenerationError(`Model call failed: ${message}`, e);
|
|
84
111
|
}
|
|
85
|
-
|
|
86
|
-
|
|
112
|
+
// The read-only check is a guardrail finding, decided by the caller; here the SQL is
|
|
113
|
+
// only normalized (trailing `;` removed) when it passes.
|
|
114
|
+
const sql = normalizeSelectSql(dialect, extractSqlFromModelText(text));
|
|
87
115
|
// Optional parameterized extras — any failure clears them (never throws).
|
|
88
116
|
let unboundNamedSql;
|
|
89
117
|
let parameterManifest;
|
|
@@ -92,14 +120,6 @@ async function runGenerateSelectSql(dialect, question, schema, model, deps, runT
|
|
|
92
120
|
unboundNamedSql = extras.unboundNamedSql;
|
|
93
121
|
parameterManifest = extras.parameterManifest;
|
|
94
122
|
}
|
|
95
|
-
// Tenant guardrail: check every SQL form this function returns — the bound
|
|
96
|
-
// `sql` callers execute, plus the unbound form when it is returned too. Never
|
|
97
|
-
// check only the unbound form: both blocks come from the model and can
|
|
98
|
-
// disagree (e.g. a scoped unbound block next to an unscoped bound one).
|
|
99
|
-
let tenantGuardrail;
|
|
100
|
-
if (runTenantGuardrail && deps.tenantPolicy && deps.tenantScope) {
|
|
101
|
-
tenantGuardrail = enforceTenantGuardrails([sql, unboundNamedSql], deps.tenantPolicy, deps.tenantScope, logger, undefined, dialect);
|
|
102
|
-
}
|
|
103
123
|
const explain = deps.explain ? buildSelectGuardrailExplanation(sql) : undefined;
|
|
104
124
|
logger?.info({
|
|
105
125
|
event: AskDbLogEvent.PipelineGenerateComplete,
|
|
@@ -108,8 +128,6 @@ async function runGenerateSelectSql(dialect, question, schema, model, deps, runT
|
|
|
108
128
|
const out = { sql };
|
|
109
129
|
if (explain !== undefined)
|
|
110
130
|
out.explain = explain;
|
|
111
|
-
if (tenantGuardrail !== undefined)
|
|
112
|
-
out.tenantGuardrail = tenantGuardrail;
|
|
113
131
|
if (usage !== undefined)
|
|
114
132
|
out.usage = usage;
|
|
115
133
|
if (unboundNamedSql !== undefined)
|
|
@@ -119,12 +137,26 @@ async function runGenerateSelectSql(dialect, question, schema, model, deps, runT
|
|
|
119
137
|
return out;
|
|
120
138
|
}
|
|
121
139
|
catch (e) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
logGenerateFailed(logger, e);
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function logGenerateFailed(logger, e) {
|
|
145
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
146
|
+
logger?.error({
|
|
147
|
+
event: AskDbLogEvent.PipelineFailed,
|
|
148
|
+
phase: "generate",
|
|
149
|
+
errMessage: msg,
|
|
150
|
+
}, "nl-to-sql generate failed");
|
|
151
|
+
}
|
|
152
|
+
/** `validateSelectSql`'s normalized SQL when it passes; the trimmed SQL when it doesn't. */
|
|
153
|
+
function normalizeSelectSql(dialect, sql) {
|
|
154
|
+
try {
|
|
155
|
+
return validateSelectSql(dialect, sql);
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
if (e instanceof SqlValidationError)
|
|
159
|
+
return sql.trim();
|
|
128
160
|
throw e;
|
|
129
161
|
}
|
|
130
162
|
}
|
package/dist/sql/generate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/sql/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,mBAAmB,EAAE,MAAM,IAAI,CAAC;AAEzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/sql/generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type GuardrailFinding, type GuardrailForm, type GuardrailVerdict, type SensitiveReference, type SensitiveReferenceRuleCode, type SensitiveScopeReport, type TenantGuardrailWarning } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* The decision point (ADR 0010, decision 2): the only code that knows what `strict`,
|
|
4
|
+
* `warn` and `off` mean for findings about the SQL, at each enforcement point. (The
|
|
5
|
+
* tenant policy loader also reads `enforcement`, to decide whether a policy that doesn't
|
|
6
|
+
* match schema.json loads; ADR 0023.) A leaf module: the checks and the enforcement
|
|
7
|
+
* points import it, and it imports only the error vocabulary.
|
|
8
|
+
*/
|
|
9
|
+
/** `return`: `ask()` / `generateSelectSql()` handing SQL back. `rebind`: `bindPreparedQuery()`. */
|
|
10
|
+
export type GuardrailPoint = "return" | "rebind";
|
|
11
|
+
export type GuardrailModes = {
|
|
12
|
+
/** The tenant policy's `enforcement`; absent without a policy. */
|
|
13
|
+
tenant?: "warn" | "strict";
|
|
14
|
+
/** The caller's `sensitiveGuardrailMode`, default `"warn"`. With `"off"` the check doesn't run. */
|
|
15
|
+
sensitive: "off" | "warn" | "strict";
|
|
16
|
+
/** Rebind only: checks whose `warn` the caller accepts. */
|
|
17
|
+
acceptWarnings?: ReadonlyArray<"tenant">;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Map findings to a verdict. Read-only findings always deny. Tenant findings deny under
|
|
21
|
+
* `strict`; under `warn` they warn when returned, and deny at rebind unless the caller
|
|
22
|
+
* lists `"tenant"` in `acceptWarnings`. Sensitive findings deny under `strict` and warn
|
|
23
|
+
* otherwise, at both points. The outcome is the most severe finding's.
|
|
24
|
+
*/
|
|
25
|
+
export declare function decide(findings: GuardrailFinding[], modes: GuardrailModes, point: GuardrailPoint): GuardrailVerdict;
|
|
26
|
+
/**
|
|
27
|
+
* Throw the typed error for a `deny` verdict, with the verdict attached. When several
|
|
28
|
+
* checks deny, the precedence is read-only (`SqlValidationError`), then tenant
|
|
29
|
+
* (`TenantGuardrailError`), then sensitive (`SensitiveReferenceError`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function throwIfDenied(verdict: GuardrailVerdict, modes: GuardrailModes, point: GuardrailPoint): void;
|
|
32
|
+
/** Tenant findings for one form, from the rule's warnings. */
|
|
33
|
+
export declare function tenantFindings(warnings: readonly TenantGuardrailWarning[], form: GuardrailForm | "generator"): GuardrailFinding[];
|
|
34
|
+
/** The tenant findings as warnings, once each across forms (same rule, table and message). */
|
|
35
|
+
export declare function tenantWarnings(findings: readonly GuardrailFinding[]): TenantGuardrailWarning[];
|
|
36
|
+
/** `result.tenantGuardrail`, derived from the verdict. */
|
|
37
|
+
export declare function tenantGuardrailResult(findings: readonly GuardrailFinding[]): {
|
|
38
|
+
passed: boolean;
|
|
39
|
+
warnings: TenantGuardrailWarning[];
|
|
40
|
+
};
|
|
41
|
+
type SensitiveScan = {
|
|
42
|
+
references: SensitiveReference[];
|
|
43
|
+
unresolvedScope?: SensitiveScopeReport;
|
|
44
|
+
};
|
|
45
|
+
/** Render a reference as `schema.table.column` (or `table.column` when the schema has no namespace). */
|
|
46
|
+
export declare function formatSensitiveReference(ref: SensitiveReference): string;
|
|
47
|
+
/** Sensitive findings for one form: one per reference, plus one when the scope wasn't resolved. */
|
|
48
|
+
export declare function sensitiveFindings(scan: SensitiveScan, form: GuardrailForm): GuardrailFinding[];
|
|
49
|
+
/** `result.sensitiveGuardrail`, derived from the verdict: references once each across forms. */
|
|
50
|
+
export declare function sensitiveGuardrailResult(findings: readonly GuardrailFinding[]): {
|
|
51
|
+
passed: boolean;
|
|
52
|
+
references: SensitiveReference[];
|
|
53
|
+
unresolvedScope?: SensitiveScopeReport;
|
|
54
|
+
};
|
|
55
|
+
export declare function sensitiveRuleFor(scan: SensitiveScan): SensitiveReferenceRuleCode;
|
|
56
|
+
export declare function sensitiveStrictMessage(scan: SensitiveScan): string;
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=guardrail-decide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrail-decide.d.ts","sourceRoot":"","sources":["../../src/sql/guardrail-decide.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAC;AAEtB;;;;;;GAMG;AAEH,mGAAmG;AACnG,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,MAAM,cAAc,GAAG;IAC3B,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC3B,mGAAmG;IACnG,SAAS,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;IACrC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CAC1C,CAAC;AAIF;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,cAAc,GACpB,gBAAgB,CAOlB;AAiBD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI,CAgC3G;AAMD,8DAA8D;AAC9D,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,sBAAsB,EAAE,EAC3C,IAAI,EAAE,aAAa,GAAG,WAAW,GAChC,gBAAgB,EAAE,CAEpB;AAED,8FAA8F;AAC9F,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG,sBAAsB,EAAE,CAW9F;AAED,0DAA0D;AAC1D,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG;IAC5E,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACpC,CAGA;AAMD,KAAK,aAAa,GAAG;IAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC;IAAC,eAAe,CAAC,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAElG,wGAAwG;AACxG,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CAGxE;AAED,mGAAmG;AACnG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,GAAG,gBAAgB,EAAE,CAuB9F;AAED,gGAAgG;AAChG,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG;IAC/E,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,eAAe,CAAC,EAAE,oBAAoB,CAAC;CACxC,CAiBA;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,0BAA0B,CAIhF;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAOlE"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { SensitiveReferenceError, SqlValidationError, TenantGuardrailError, } from "../errors.js";
|
|
2
|
+
const SEVERITY = { allow: 0, warn: 1, deny: 2 };
|
|
3
|
+
/**
|
|
4
|
+
* Map findings to a verdict. Read-only findings always deny. Tenant findings deny under
|
|
5
|
+
* `strict`; under `warn` they warn when returned, and deny at rebind unless the caller
|
|
6
|
+
* lists `"tenant"` in `acceptWarnings`. Sensitive findings deny under `strict` and warn
|
|
7
|
+
* otherwise, at both points. The outcome is the most severe finding's.
|
|
8
|
+
*/
|
|
9
|
+
export function decide(findings, modes, point) {
|
|
10
|
+
let outcome = "allow";
|
|
11
|
+
for (const finding of findings) {
|
|
12
|
+
const o = outcomeOf(finding.check, modes, point);
|
|
13
|
+
if (SEVERITY[o] > SEVERITY[outcome])
|
|
14
|
+
outcome = o;
|
|
15
|
+
}
|
|
16
|
+
return { outcome, findings };
|
|
17
|
+
}
|
|
18
|
+
function outcomeOf(check, modes, point) {
|
|
19
|
+
switch (check) {
|
|
20
|
+
case "read-only":
|
|
21
|
+
return "deny";
|
|
22
|
+
case "tenant":
|
|
23
|
+
// A tenant finding without a recognized mode (no policy, or an unknown value) fails closed.
|
|
24
|
+
if (modes.tenant !== "warn")
|
|
25
|
+
return "deny";
|
|
26
|
+
if (point === "rebind" && !modes.acceptWarnings?.includes("tenant"))
|
|
27
|
+
return "deny";
|
|
28
|
+
return "warn";
|
|
29
|
+
case "sensitive":
|
|
30
|
+
// Like the tenant case, a mode it doesn't recognize fails closed.
|
|
31
|
+
return modes.sensitive === "warn" ? "warn" : modes.sensitive === "off" ? "allow" : "deny";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Throw the typed error for a `deny` verdict, with the verdict attached. When several
|
|
36
|
+
* checks deny, the precedence is read-only (`SqlValidationError`), then tenant
|
|
37
|
+
* (`TenantGuardrailError`), then sensitive (`SensitiveReferenceError`).
|
|
38
|
+
*/
|
|
39
|
+
export function throwIfDenied(verdict, modes, point) {
|
|
40
|
+
if (verdict.outcome !== "deny")
|
|
41
|
+
return;
|
|
42
|
+
const denied = (check) => verdict.findings.filter((f) => f.check === check && outcomeOf(check, modes, point) === "deny");
|
|
43
|
+
let error;
|
|
44
|
+
const readOnly = denied("read-only");
|
|
45
|
+
const tenant = denied("tenant");
|
|
46
|
+
if (readOnly.length > 0) {
|
|
47
|
+
const first = readOnly[0];
|
|
48
|
+
error = new SqlValidationError(first.message, first.rule, first.hint);
|
|
49
|
+
}
|
|
50
|
+
else if (tenant.length > 0) {
|
|
51
|
+
const warnings = tenantWarnings(tenant);
|
|
52
|
+
const detail = warnings.map((w) => w.message).join("; ");
|
|
53
|
+
error = new TenantGuardrailError(modes.tenant === "warn"
|
|
54
|
+
? "Tenant guardrail found issues (policy enforcement: warn), and bindPreparedQuery() refuses a " +
|
|
55
|
+
`tenant warning unless acceptWarnings includes "tenant": ${detail}`
|
|
56
|
+
: `Tenant guardrail validation failed (strict mode): ${detail}`, warnings);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const result = sensitiveGuardrailResult(denied("sensitive"));
|
|
60
|
+
error = new SensitiveReferenceError(sensitiveStrictMessage(result), sensitiveRuleFor(result), result.references, result.unresolvedScope);
|
|
61
|
+
}
|
|
62
|
+
error.verdict = verdict;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Tenant findings ↔ the TenantGuardrailResult shape
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/** Tenant findings for one form, from the rule's warnings. */
|
|
69
|
+
export function tenantFindings(warnings, form) {
|
|
70
|
+
return warnings.map((w) => ({ check: "tenant", form, rule: w.rule, message: w.message, tableId: w.tableId }));
|
|
71
|
+
}
|
|
72
|
+
/** The tenant findings as warnings, once each across forms (same rule, table and message). */
|
|
73
|
+
export function tenantWarnings(findings) {
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const f of findings) {
|
|
77
|
+
if (f.check !== "tenant")
|
|
78
|
+
continue;
|
|
79
|
+
const key = `${f.rule}\u0000${f.tableId}\u0000${f.message}`;
|
|
80
|
+
if (seen.has(key))
|
|
81
|
+
continue;
|
|
82
|
+
seen.add(key);
|
|
83
|
+
out.push({ rule: f.rule, tableId: f.tableId, message: f.message });
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** `result.tenantGuardrail`, derived from the verdict. */
|
|
88
|
+
export function tenantGuardrailResult(findings) {
|
|
89
|
+
const warnings = tenantWarnings(findings);
|
|
90
|
+
return { passed: warnings.length === 0, warnings };
|
|
91
|
+
}
|
|
92
|
+
/** Render a reference as `schema.table.column` (or `table.column` when the schema has no namespace). */
|
|
93
|
+
export function formatSensitiveReference(ref) {
|
|
94
|
+
const table = ref.schema ? `${ref.schema}.${ref.table}` : ref.table;
|
|
95
|
+
return `${table}.${ref.column}`;
|
|
96
|
+
}
|
|
97
|
+
/** Sensitive findings for one form: one per reference, plus one when the scope wasn't resolved. */
|
|
98
|
+
export function sensitiveFindings(scan, form) {
|
|
99
|
+
const findings = scan.references.map((reference) => {
|
|
100
|
+
const table = reference.matchKind === "table";
|
|
101
|
+
return {
|
|
102
|
+
check: "sensitive",
|
|
103
|
+
form,
|
|
104
|
+
rule: table ? "SENSITIVE_TABLE_REFERENCED" : "SENSITIVE_COLUMN_REFERENCED",
|
|
105
|
+
message: table
|
|
106
|
+
? `SQL references sensitive table ${formatSensitiveReference(reference).replace(/\.\*$/, "")}`
|
|
107
|
+
: `SQL references sensitive column ${formatSensitiveReference(reference)}`,
|
|
108
|
+
reference,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
if (scan.unresolvedScope) {
|
|
112
|
+
findings.push({
|
|
113
|
+
check: "sensitive",
|
|
114
|
+
form,
|
|
115
|
+
rule: "UNRESOLVED_TABLE_SCOPE",
|
|
116
|
+
message: scan.unresolvedScope.message,
|
|
117
|
+
unresolvedScope: scan.unresolvedScope,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return findings;
|
|
121
|
+
}
|
|
122
|
+
/** `result.sensitiveGuardrail`, derived from the verdict: references once each across forms. */
|
|
123
|
+
export function sensitiveGuardrailResult(findings) {
|
|
124
|
+
const references = [];
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
let unresolvedScope;
|
|
127
|
+
let any = false;
|
|
128
|
+
for (const f of findings) {
|
|
129
|
+
if (f.check !== "sensitive")
|
|
130
|
+
continue;
|
|
131
|
+
any = true;
|
|
132
|
+
if (f.unresolvedScope)
|
|
133
|
+
unresolvedScope ??= f.unresolvedScope;
|
|
134
|
+
if (!f.reference)
|
|
135
|
+
continue;
|
|
136
|
+
const r = f.reference;
|
|
137
|
+
const key = `${r.schema ?? ""}\u0000${r.table}\u0000${r.column}\u0000${r.matchKind}`;
|
|
138
|
+
if (seen.has(key))
|
|
139
|
+
continue;
|
|
140
|
+
seen.add(key);
|
|
141
|
+
references.push(r);
|
|
142
|
+
}
|
|
143
|
+
return { passed: !any, references, ...(unresolvedScope ? { unresolvedScope } : {}) };
|
|
144
|
+
}
|
|
145
|
+
export function sensitiveRuleFor(scan) {
|
|
146
|
+
if (scan.references.some((r) => r.matchKind === "table"))
|
|
147
|
+
return "SENSITIVE_TABLE_REFERENCED";
|
|
148
|
+
if (scan.references.length > 0)
|
|
149
|
+
return "SENSITIVE_COLUMN_REFERENCED";
|
|
150
|
+
return "UNRESOLVED_TABLE_SCOPE";
|
|
151
|
+
}
|
|
152
|
+
export function sensitiveStrictMessage(scan) {
|
|
153
|
+
const parts = [];
|
|
154
|
+
if (scan.references.length > 0) {
|
|
155
|
+
parts.push(`SQL references sensitive identifiers: ${scan.references.map(formatSensitiveReference).join(", ")}`);
|
|
156
|
+
}
|
|
157
|
+
if (scan.unresolvedScope)
|
|
158
|
+
parts.push(scan.unresolvedScope.message);
|
|
159
|
+
return `Sensitive-identifier guardrail failed (strict mode): ${parts.join("; ")}`;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=guardrail-decide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrail-decide.js","sourceRoot":"","sources":["../../src/sql/guardrail-decide.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type GuardrailCheckId, type GuardrailFinding, type GuardrailForm, type GuardrailVerdict } from "../errors.js";
|
|
2
|
+
import type { AskDbLogger } from "../logging/askdb-logger.js";
|
|
3
|
+
import type { AnyNormalizedSchema } from "../schema/types.js";
|
|
4
|
+
import type { NormalizedTenantPolicy, TenantScope } from "../schema/v2/tenant-policy.js";
|
|
5
|
+
import type { DialectSpec } from "./dialect-spec.js";
|
|
6
|
+
import { type GuardrailModes } from "./guardrail-decide.js";
|
|
7
|
+
import { type SensitiveGuardrailMode } from "./sensitive-guardrail.js";
|
|
8
|
+
/**
|
|
9
|
+
* The checks as pure rules (ADR 0010, decision 1): each takes a candidate and returns
|
|
10
|
+
* findings. They know nothing about modes and never throw for a finding; `decide`
|
|
11
|
+
* (`guardrail-decide.ts`) maps the findings to a verdict. Internal to `@askdb/core`.
|
|
12
|
+
*/
|
|
13
|
+
/** What every enforcement point checks: the untrusted forms, before AskDB renders them. */
|
|
14
|
+
export type Candidate = {
|
|
15
|
+
/** `sql`: the model's bound SQL. `template`: its `sql-unbound` block (`preparedQuery.namedSql`). */
|
|
16
|
+
forms: Partial<Record<GuardrailForm, string>>;
|
|
17
|
+
/** Undefined for a custom `AskDialect`. */
|
|
18
|
+
dialect: DialectSpec | undefined;
|
|
19
|
+
schema: AnyNormalizedSchema;
|
|
20
|
+
/** A v2 schema's tenant policy, with the enforcement point's (expanded) scope. */
|
|
21
|
+
tenant?: {
|
|
22
|
+
policy: NormalizedTenantPolicy;
|
|
23
|
+
scope: TenantScope;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* ADR 0010, decision 3: which checks each enforcement point runs, and the modes `decide`
|
|
28
|
+
* reads there. `ask()` with a custom `AskDialect` has no `dialect`, so no read-only check;
|
|
29
|
+
* `generateSelectSql()` takes no sensitive mode, so no sensitive check; `acceptWarnings`
|
|
30
|
+
* means something at rebind only.
|
|
31
|
+
*/
|
|
32
|
+
export declare function guardrailPlan(path: "ask" | "generate" | "rebind", input: {
|
|
33
|
+
dialect: DialectSpec | undefined;
|
|
34
|
+
schema: AnyNormalizedSchema;
|
|
35
|
+
tenantPolicy: NormalizedTenantPolicy | undefined;
|
|
36
|
+
sensitiveGuardrailMode?: SensitiveGuardrailMode | "off";
|
|
37
|
+
acceptWarnings?: ReadonlyArray<"tenant">;
|
|
38
|
+
}): {
|
|
39
|
+
checks: GuardrailCheckId[];
|
|
40
|
+
modes: GuardrailModes;
|
|
41
|
+
};
|
|
42
|
+
/** Run each listed check on every form in the candidate. */
|
|
43
|
+
export declare function evaluateGuardrails(candidate: Candidate, checks: readonly GuardrailCheckId[]): GuardrailFinding[];
|
|
44
|
+
/**
|
|
45
|
+
* Log a verdict the way `ask()` and `generateSelectSql()` always have: one tenant
|
|
46
|
+
* pass/fail event (with a tenant policy), and the sensitive references (when checked).
|
|
47
|
+
*/
|
|
48
|
+
export declare function logGuardrailVerdict(logger: AskDbLogger | undefined, verdict: GuardrailVerdict, context: {
|
|
49
|
+
tenantPolicy: NormalizedTenantPolicy | undefined;
|
|
50
|
+
sensitiveChecked: boolean;
|
|
51
|
+
}): void;
|
|
52
|
+
//# sourceMappingURL=guardrails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../src/sql/guardrails.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,KAAK,cAAc,EAMpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,0BAA0B,CAAC;AAIlC;;;;GAIG;AAEH,2FAA2F;AAC3F,MAAM,MAAM,SAAS,GAAG;IACtB,oGAAoG;IACpG,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,2CAA2C;IAC3C,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,kFAAkF;IAClF,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,sBAAsB,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,CAAC;CACjE,CAAC;AAgCF;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,QAAQ,EACnC,KAAK,EAAE;IACL,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,YAAY,EAAE,sBAAsB,GAAG,SAAS,CAAC;IACjD,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,KAAK,CAAC;IACxD,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CAC1C,GACA;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,CAcvD;AAED,4DAA4D;AAC5D,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAClC,gBAAgB,EAAE,CASpB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE;IAAE,YAAY,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAA;CAAE,GACvF,IAAI,CA4BN"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { SqlValidationError, } from "../errors.js";
|
|
2
|
+
import { AskDbLogEvent } from "../logging/log-events.js";
|
|
3
|
+
import { formatSensitiveReference, sensitiveFindings, sensitiveGuardrailResult, tenantFindings, tenantWarnings, } from "./guardrail-decide.js";
|
|
4
|
+
import { scanSensitiveReferences, schemaHasSensitiveIdentifiers, } from "./sensitive-guardrail.js";
|
|
5
|
+
import { tenantRuleWarnings } from "./tenant-guardrail.js";
|
|
6
|
+
import { validateSelectSql } from "./validate.js";
|
|
7
|
+
const CHECKS = {
|
|
8
|
+
// Needs a DialectSpec: a custom AskDialect may target non-SELECT SQL (decision 3).
|
|
9
|
+
"read-only": ({ dialect }, form, sql) => {
|
|
10
|
+
if (!dialect)
|
|
11
|
+
return [];
|
|
12
|
+
try {
|
|
13
|
+
validateSelectSql(dialect, sql);
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (!(error instanceof SqlValidationError))
|
|
18
|
+
throw error;
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
check: "read-only",
|
|
22
|
+
form,
|
|
23
|
+
rule: error.rule,
|
|
24
|
+
message: error.message,
|
|
25
|
+
...(error.hint !== undefined ? { hint: error.hint } : {}),
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
tenant: ({ tenant, dialect }, form, sql) => tenant ? tenantFindings(tenantRuleWarnings(sql, tenant.policy, tenant.scope, dialect), form) : [],
|
|
31
|
+
sensitive: ({ schema, dialect }, form, sql) => sensitiveFindings(scanSensitiveReferences(sql, schema, dialect), form),
|
|
32
|
+
};
|
|
33
|
+
const FORMS = ["sql", "template"];
|
|
34
|
+
/**
|
|
35
|
+
* ADR 0010, decision 3: which checks each enforcement point runs, and the modes `decide`
|
|
36
|
+
* reads there. `ask()` with a custom `AskDialect` has no `dialect`, so no read-only check;
|
|
37
|
+
* `generateSelectSql()` takes no sensitive mode, so no sensitive check; `acceptWarnings`
|
|
38
|
+
* means something at rebind only.
|
|
39
|
+
*/
|
|
40
|
+
export function guardrailPlan(path, input) {
|
|
41
|
+
const sensitive = path === "generate" ? "off" : (input.sensitiveGuardrailMode ?? "warn");
|
|
42
|
+
const checks = [];
|
|
43
|
+
if (input.dialect)
|
|
44
|
+
checks.push("read-only");
|
|
45
|
+
if (input.tenantPolicy)
|
|
46
|
+
checks.push("tenant");
|
|
47
|
+
if (sensitive !== "off" && schemaHasSensitiveIdentifiers(input.schema))
|
|
48
|
+
checks.push("sensitive");
|
|
49
|
+
return {
|
|
50
|
+
checks,
|
|
51
|
+
modes: {
|
|
52
|
+
...(input.tenantPolicy ? { tenant: input.tenantPolicy.enforcement } : {}),
|
|
53
|
+
sensitive,
|
|
54
|
+
...(path === "rebind" && input.acceptWarnings ? { acceptWarnings: input.acceptWarnings } : {}),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Run each listed check on every form in the candidate. */
|
|
59
|
+
export function evaluateGuardrails(candidate, checks) {
|
|
60
|
+
const findings = [];
|
|
61
|
+
for (const check of checks) {
|
|
62
|
+
for (const form of FORMS) {
|
|
63
|
+
const sql = candidate.forms[form];
|
|
64
|
+
if (sql !== undefined)
|
|
65
|
+
findings.push(...CHECKS[check](candidate, form, sql));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return findings;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Log a verdict the way `ask()` and `generateSelectSql()` always have: one tenant
|
|
72
|
+
* pass/fail event (with a tenant policy), and the sensitive references (when checked).
|
|
73
|
+
*/
|
|
74
|
+
export function logGuardrailVerdict(logger, verdict, context) {
|
|
75
|
+
if (context.tenantPolicy) {
|
|
76
|
+
const warnings = tenantWarnings(verdict.findings);
|
|
77
|
+
if (warnings.length === 0) {
|
|
78
|
+
logger?.info({ event: AskDbLogEvent.TenantGuardrailPassed }, "tenant guardrail validation passed");
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
logger?.info({
|
|
82
|
+
event: AskDbLogEvent.TenantGuardrailFailed,
|
|
83
|
+
warningCount: warnings.length,
|
|
84
|
+
enforcement: context.tenantPolicy.enforcement,
|
|
85
|
+
}, "tenant guardrail validation found issues");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!context.sensitiveChecked)
|
|
89
|
+
return;
|
|
90
|
+
const references = sensitiveGuardrailResult(verdict.findings).references;
|
|
91
|
+
if (references.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
const sensitiveColumns = references.map(formatSensitiveReference);
|
|
94
|
+
logger?.info({
|
|
95
|
+
event: AskDbLogEvent.PipelineSensitiveSqlWarning,
|
|
96
|
+
sensitiveColumnCount: sensitiveColumns.length,
|
|
97
|
+
sensitiveColumns,
|
|
98
|
+
}, "generated SQL references sensitive identifiers");
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=guardrails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrails.js","sourceRoot":"","sources":["../../src/sql/guardrails.ts"],"names":[],"mappings":"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"}
|
package/dist/sql/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { extractSqlFromModelText, extractUnboundSqlFromModelText } from "./extract-sql.js";
|
|
2
|
-
export {
|
|
2
|
+
export { escapeSqlLiteral, type QueryParameterType, type QueryParameterValue, type QueryParamSlot, type QueryParameterBinding, type PreparedQuery, } from "./bind.js";
|
|
3
|
+
export { bindPreparedQuery, type BindGuardrails, type BoundQuery } from "./rebind.js";
|
|
3
4
|
export { parseParameterManifest, type ParameterManifest, type ManifestParameter, } from "./parameter-manifest.js";
|
|
4
5
|
export { generateSelectSql, type GenerateSelectSqlResult, type GenerateSqlDeps, } from "./generate.js";
|
|
5
6
|
export { validateSelectSql, buildSelectGuardrailExplanation, type SelectGuardrailExplain, } from "./validate.js";
|
package/dist/sql/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,+BAA+B,EAC/B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,GACf,MAAM,mBAAmB,CAAC"}
|
package/dist/sql/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { extractSqlFromModelText, extractUnboundSqlFromModelText } from "./extract-sql.js";
|
|
2
|
-
export {
|
|
2
|
+
export { escapeSqlLiteral, } from "./bind.js";
|
|
3
|
+
export { bindPreparedQuery } from "./rebind.js";
|
|
3
4
|
export { parseParameterManifest, } from "./parameter-manifest.js";
|
|
4
5
|
export { generateSelectSql, } from "./generate.js";
|
|
5
6
|
export { validateSelectSql, buildSelectGuardrailExplanation, } from "./validate.js";
|
package/dist/sql/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,gBAAgB,GAMjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAwC,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,sBAAsB,GAGvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,iBAAiB,GAGlB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,+BAA+B,GAEhC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAIL,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,GACf,MAAM,mBAAmB,CAAC"}
|