v0.3.0¶
This release turns GuardSQL from a parse-and-validate gate into a full
transpiler: untrusted SQL in, trusted parameterized SQL out. It adds a
built-in SQL compiler, a high-level Config with trust profiles, data
classification, identifier lockdown, and a trusted mutation builder.
Built-in SQL Compiler¶
Compile(q, CompileOptions{Dialect, Schema, StrictIdentifiers})transpiles a validated read query into parameterized SQL, returningCompiledSQL{SQL, Args}.- Dialects:
DialectSQLite,DialectPostgres,DialectMySQL. - Every value is bound as a parameter; every identifier is re-resolved against the schema and re-quoted per dialect. The executed SQL is regenerated from the AST, never the original text.
- JSON extraction: a
FieldJSONcolumn can be addressed with a dotted path (payload.finding_info.title), compiled tojson_extract/jsonb_extract_path_text/JSON_EXTRACTper dialect. Postgres casts the extraction tonumeric/booleanfor numeric/boolean comparisons andSUM/AVG. Path segments are ASCII-only.
Config & Trust Modes¶
Configis the recommended entry point (JSON-serializable): parse → validate → policy → compile with secure defaults.NewUntrustedConfig— customer SQL: read-only, secret/PII columns stripped, identifier lockdown,LIMITrequired.NewTrustedConfig— internal SQL: reads + mutations, full column access, still parameterized and identifier-locked.
Data Classification¶
Field.ClassofClassPublic(default),ClassPII,ClassInternal, orClassSecret.-
Classified fields are stripped from the effective schema unless
AllowPII/AllowInternal/AllowSecretare set; a denied reference fails as an unknown field without disclosing the column exists. -
FromCatalogbuilds aSchemafrom agithub.com/grokify/datacatalogcatalog, mapping visibility, PII, and secret tags to the single most restrictiveClass(WithWritable/WithNonFilterableadjust capabilities). This raises the minimum Go version to 1.25.
Identifier Safety¶
IsSafeIdentifierandSchema.ValidateIdentifiers()enforce the unquoted identifier charset (ASCII, no quotes/whitespace/homoglyphs). On by default viaConfigandCompileOptions.StrictIdentifiers; opt out withAllowUnsafeIdentifiers. Because identifiers cannot be parameterized, this removes identifier-based injection as a possibility.
Trusted Mutations¶
Insert,Update,Deletebuilders compile to parameterized INSERT/UPDATE/ DELETE. Deny-by-default (operation must be in policy);WHERErequired on UPDATE/DELETE unlessAllowUnfilteredMutationsis set.- Condition helpers:
Eq,Ne,Lt,Lte,Gt,Gte,In,IsNull,IsNotNull,And,Or,Not. Field.Writable(deny-by-default) gates assignment targets — the mass-assignment defense. A caller cannot write a column unless the schema marks it writable, even for a trusted profile.
Row Scope (IDOR / Multi-Tenant Isolation)¶
Config.RowScope(aScopeFunc) returns a mandatory per-entity predicate, derived from trusted session context, that is ANDed at the top level of every compiled SELECT/UPDATE/DELETE — so no userWHEREcan widen it. On INSERT the scope's equality terms are stamped onto the new row (a non-equality scope refuses the INSERT).- The scope resolves against the full schema, so it may reference a column
hidden from callers (for example an internal-class
tenant_id). - Columns referenced by the scope cannot be assigned by
InsertorUpdate. ScopeEquals(column, value)is a ready-made scope for the commontenant_id = <session>case; it fails closed if a queried entity lacks the column.Config.RequireRowScoperefuses to compile unless a scope is configured.
Compatibility¶
- Existing
Parse,Validate,CheckPolicy,Analyze,Eval, andFormatare unchanged.Fieldgains optionalClassandWritable;Schema/Entity/Fieldgain JSON tags.Writableis deny-by-default but only affects the new mutation builder, so there are no breaking changes to the read pipeline.