Skip to content

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, returning CompiledSQL{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 FieldJSON column can be addressed with a dotted path (payload.finding_info.title), compiled to json_extract / jsonb_extract_path_text / JSON_EXTRACT per dialect. Postgres casts the extraction to numeric/boolean for numeric/boolean comparisons and SUM/AVG. Path segments are ASCII-only.

Config & Trust Modes

  • Config is the recommended entry point (JSON-serializable): parse → validate → policy → compile with secure defaults.
  • NewUntrustedConfig — customer SQL: read-only, secret/PII columns stripped, identifier lockdown, LIMIT required.
  • NewTrustedConfig — internal SQL: reads + mutations, full column access, still parameterized and identifier-locked.

Data Classification

  • Field.Class of ClassPublic (default), ClassPII, ClassInternal, or ClassSecret.
  • Classified fields are stripped from the effective schema unless AllowPII / AllowInternal / AllowSecret are set; a denied reference fails as an unknown field without disclosing the column exists.

  • FromCatalog builds a Schema from a github.com/grokify/datacatalog catalog, mapping visibility, PII, and secret tags to the single most restrictive Class (WithWritable / WithNonFilterable adjust capabilities). This raises the minimum Go version to 1.25.

Identifier Safety

  • IsSafeIdentifier and Schema.ValidateIdentifiers() enforce the unquoted identifier charset (ASCII, no quotes/whitespace/homoglyphs). On by default via Config and CompileOptions.StrictIdentifiers; opt out with AllowUnsafeIdentifiers. Because identifiers cannot be parameterized, this removes identifier-based injection as a possibility.

Trusted Mutations

  • Insert, Update, Delete builders compile to parameterized INSERT/UPDATE/ DELETE. Deny-by-default (operation must be in policy); WHERE required on UPDATE/DELETE unless AllowUnfilteredMutations is 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 (a ScopeFunc) 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 user WHERE can 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 Insert or Update.
  • ScopeEquals(column, value) is a ready-made scope for the common tenant_id = <session> case; it fails closed if a queried entity lacks the column.
  • Config.RequireRowScope refuses to compile unless a scope is configured.

Compatibility

  • Existing Parse, Validate, CheckPolicy, Analyze, Eval, and Format are unchanged. Field gains optional Class and Writable; Schema/Entity/Field gain JSON tags. Writable is deny-by-default but only affects the new mutation builder, so there are no breaking changes to the read pipeline.