Files
upstream-atomic-crm/supabase/functions/mcp/validateSql.ts
upstream-mirror 93ecc1a6f8
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:48:50 +05:00

74 lines
2.4 KiB
TypeScript

import { parse, type Statement } from "npm:pgsql-ast-parser@^12";
const ALLOWED_READ_TYPES = new Set(["select", "with"]);
const ALLOWED_WRITE_TYPES = new Set(["insert", "update", "delete", "with"]);
// Collect all statement types found in a parsed AST, including inner
// statements in WITH (CTE) bindings which can contain writable DML.
function collectStatementTypes(stmts: Statement[]): Set<string> {
const types = new Set<string>();
for (const stmt of stmts) {
types.add(stmt.type);
if (stmt.type === "with" && "bind" in stmt && Array.isArray(stmt.bind)) {
for (const cte of stmt.bind) {
if (cte.statement?.type) {
types.add(cte.statement.type);
}
}
}
}
return types;
}
export function validateReadOnly(sql: string): string | null {
let stmts: Statement[];
try {
stmts = parse(sql);
} catch (err) {
const raw = err instanceof Error ? err.message : String(err);
// pgsql-ast-parser appends a parse-table dump that is useless to the
// LLM and consumes many tokens; keep the diagnostic prefix only.
const message = raw.split("Here is the state of my parse table")[0].trim();
return `Failed to parse SQL: ${message}`;
}
if (stmts.length === 0) {
return "Empty query.";
}
if (stmts.length > 1) {
return "Only a single statement is allowed.";
}
const types = collectStatementTypes(stmts);
for (const type of types) {
if (!ALLOWED_READ_TYPES.has(type)) {
return `Statement type "${type}" is not allowed in read-only queries. Use the mutate tool for data modifications.`;
}
}
return null;
}
export function validateWrite(sql: string): string | null {
let stmts: Statement[];
try {
stmts = parse(sql);
} catch (err) {
const raw = err instanceof Error ? err.message : String(err);
// pgsql-ast-parser appends a parse-table dump that is useless to the
// LLM and consumes many tokens; keep the diagnostic prefix only.
const message = raw.split("Here is the state of my parse table")[0].trim();
return `Failed to parse SQL: ${message}`;
}
if (stmts.length === 0) {
return "Empty query.";
}
if (stmts.length > 1) {
return "Only a single statement is allowed.";
}
const types = collectStatementTypes(stmts);
for (const type of types) {
if (!ALLOWED_WRITE_TYPES.has(type)) {
return `Statement type "${type}" is not allowed. Only INSERT, UPDATE, and DELETE statements are supported.`;
}
}
return null;
}