Skip to content

P3: prove InjectionFree (level 5) as a real query-checking guarantee#33

Merged
hyperpolymath merged 3 commits into
mainfrom
claude/new-session-1fphit
Jun 27, 2026
Merged

P3: prove InjectionFree (level 5) as a real query-checking guarantee#33
hyperpolymath merged 3 commits into
mainfrom
claude/new-session-1fphit

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

What & why

typedqliser's ten type-safety levels were, in the ABI, abstract labels carrying a per-level ProofStatus — nothing tied a Proven verdict to an actual property of an actual query. This makes the flagship level a real, machine-checked semantic guarantee: InjectionFree (level 5) — "no string interpolation in structure", i.e. the no-SQL-injection property.

Change — new module Typedqliser.ABI.Semantics

  • A minimal but faithful query AST whose value leaves are bound parameters / literals (safe) or RawSplice interpolated strings (the injection vector).
  • QueryInjectionFree — the proposition that no RawSplice occurs anywhere. ValueOk has no constructor for RawSplice, so it is genuinely uninhabited.
  • decInjectionFree — a sound + complete decision procedure returning a real Dec proof.
  • certifyProvenSound — a theorem that a Proven InjectionFree verdict entails the property: the certifier cannot lie.
  • Positive control: a parameterized query (… WHERE id = $1) is provably injection-free and certifies Proven by reduction.
  • Negative control: … WHERE name = '; DROP TABLE users;--' is proven to have no injection-free proof (Not (QueryInjectionFree …)).

Verification (Idris2 0.7.0)

  • Full ABI builds clean (idris2 --build typedqliser-abi.ipkg, all 5 modules, zero warnings).
  • Fakery scan: no believe_me / postulate / holes.
  • Adversarial / non-vacuity: an attempt to prove the injection query safe is rejected by the type checker — Mismatch between Param ?i and RawSplice "'; DROP TABLE users;--". The guarantee genuinely cannot be subverted.

This is the first of the priority-repo P3 deepenings (typedqliser #1); the same approach extends to the other nine levels.

Note: this branch also carries the ABI↔FFI gate (scripts/abi-ffi-gate.py + workflow), which had not yet merged into main for this repo — it rides along here.

CI note

rust-ci / Hypatia / governance reds are pre-existing estate-infra unrelated to this change; the ABI-FFI Gate check is green.

🤖 Generated with Claude Code

https://claude.ai/code/session_019xMKB3T4Vo5FYC7Czx3JSH


Generated by Claude Code

claude added 3 commits June 26, 2026 22:03
The Zig FFI (src/interface/ffi/src/main.zig) was generic scaffolding that drifted
from the Idris2 ABI, which is the source of truth
(src/interface/abi/Typedqliser/ABI/Foreign.idr and .../Types.idr).

Two core domain functions declared in Foreign.idr were missing from the Zig:

  * typedqliser_check_query  : Bits64 -> String -> Bits32 -> PrimIO Bits32
      (handle pointer, query C-string, requested safety level) -> result code.
      Validates a query against the registered schema up to the requested level
      and records the achieved certificate level on the handle.

  * typedqliser_certificate_level : Bits64 -> PrimIO Bits32
      (handle pointer) -> highest safety level the last checked query achieved
      (0 if none), within the ten cumulative type-safety levels.

Both are now exported `callconv(.C)` functions with arities/types matching the
Idris %foreign declarations exactly (the handle is threaded as a Bits64 pointer
value, reconstructed FFI-side).

Result-code semantics corrected to match Typedqliser.ABI.Types.resultToInt:
  Ok=0, Error=1, InvalidQuery=2, SchemaError=3, NullPointer=4.
The generic scaffold encoded codes 2 and 3 as invalid_param / out_of_memory,
so cross-FFI those codes meant the WRONG thing. They are renamed to
invalid_query (2) and schema_error (3) and all usages updated.

The pre-existing extra exports (build_info, free_string, get_string,
is_initialized, last_error, process, process_array, register_callback) are kept
as a harmless superset; init/free/version are unchanged in behaviour.

Verification:
  * zig test src/interface/ffi/src/main.zig -lc  -> all 8 tests pass
    (added tests exercising check_query + certificate_level, the malformed-query
     InvalidQuery path, the SchemaError path, null-handle rejection, and a
     guard pinning the result codes to resultToInt).
  * idris2 --build src/interface/abi/typedqliser-abi.ipkg  -> exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xMKB3T4Vo5FYC7Czx3JSH
Adds scripts/abi-ffi-gate.py + .github/workflows/abi-ffi-gate.yml enforcing
that the Zig FFI matches the Idris2 ABI (source of truth), on top of this
branch's FFI fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xMKB3T4Vo5FYC7Czx3JSH
The ten type-safety levels were abstract labels carrying a per-level
ProofStatus, with nothing tying a "Proven" verdict to an actual property of
an actual query. This makes the flagship level — InjectionFree (level 5,
"no string interpolation in structure", the no-SQL-injection guarantee) — a
genuine, machine-checked semantic property.

New module Typedqliser.ABI.Semantics:
- a minimal but faithful query AST whose value leaves are bound parameters /
  literals (safe) or RawSplice interpolated strings (the injection vector);
- QueryInjectionFree: the proposition that no RawSplice node occurs anywhere
  (ValueOk has no constructor for RawSplice, so it is genuinely uninhabited);
- decInjectionFree: a sound + complete decision procedure returning a real
  Dec proof;
- certifyProvenSound: a theorem that a `Proven` InjectionFree verdict entails
  the property — the certifier cannot lie;
- positive control (a parameterized query is provably injection-free and
  certifies Proven by reduction) and a negative control proving the
  interpolated `'; DROP TABLE users;--` query has NO injection-free proof.

Adversarially verified under Idris2 0.7.0: the full ABI builds clean (zero
warnings, no believe_me/postulate/holes), and an attempt to prove the
injection query safe is rejected by the type checker (Param vs RawSplice
mismatch) — the guarantee is non-vacuous.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xMKB3T4Vo5FYC7Czx3JSH
@hyperpolymath hyperpolymath marked this pull request as ready for review June 27, 2026 04:33
@hyperpolymath hyperpolymath merged commit a03cd3d into main Jun 27, 2026
26 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants