Skip to content

SQLiteKit: embedder-bound hardened policy for untrusted SQL#1

Open
ronaldmannak wants to merge 37 commits into
mainfrom
claude/sqlite-implementations-compare-uktp8y
Open

SQLiteKit: embedder-bound hardened policy for untrusted SQL#1
ronaldmannak wants to merge 37 commits into
mainfrom
claude/sqlite-implementations-compare-uktp8y

Conversation

@ronaldmannak

Copy link
Copy Markdown

What

Adds an opt-in, embedder-bound security policy (SQLitePolicy) to the sqlite3 shell port so it can run untrusted, LLM-generated SQL safely under SwiftBash — without breaking sqlite3 parity or the sqlite-vec / FTS5 feature surface.

This is the SwiftPorts half of a comparison with PicoMLX/SwiftSQLite#1: SwiftSQLite is a purpose-built security-first sandboxed engine; SwiftPorts is a fidelity-first sqlite3 clone. This PR brings SwiftSQLite's escape-boundary and DoS hardening over, while leaving the SQL feature surface (vtables, functions, PRAGMA) untouched.

The core idea: policy is not argv

When the command is a SwiftBash builtin, the LLM writes the command line, so any hardening expressed as an opt-in flag is no boundary — the agent just omits or inflates it. So the policy is bound by the trusted embedder (Sqlite3Builtin(policy:)) or auto-derived from Shell.current.sandbox, and no in-band channel — argv or dot-command — may relax it:

  • argv -hardened can only turn hardening on (no flag turns a bound policy off)
  • .limit may lower/show a limit but not raise it (else .limit attached N undoes ATTACHED=0)
  • .open re-applies the full policy to the new handle (previously it re-armed only safe mode)

Shell-layer controls (all no-ops unless hardened)

  • Result output byte cap with a truncation notice — bounds what flows back to the caller / $(…)
  • Runtime limits: SQLITE_LIMIT_ATTACHED=0, …_LENGTH, …_SQL_LENGTH
  • PRAGMA temp_store=MEMORY (temp confinement)
  • Canonicalize-before-authorize on the DB open, ATTACH targets, and file dot-commands
  • Attempted-tier audit (FileAuditSink, JSON Lines, O_NOFOLLOW + preflight) written outside the DB; destination is embedder-set, never an argv path
  • Script-budget timeout between statements

Feature preservation (deliberate)

Hardened mode does not port SwiftSQLite's blanket default-deny authorizer (which would reject CREATE VIRTUAL TABLE and break vec0/FTS5). It touches only limits / temp / caps / audit / canonicalization, so vector and full-text workloads keep working. There's a test asserting hardened output equals permissive for ordinary queries, and one asserting DDL/DML/PRAGMA still work.

Honest scope

Several deeper guarantees need the raw sqlite3* handle, which lives in the external Cocoanetics/SQLiteKit SDK (not in this package). These are specced in Docs/PORTING-FROM-SWIFTSQLITE.md as follow-up PRs:

  • intra-statement timeout (sqlite3_progress_handler + sqlite3_interrupt) — the shell budget is a script budget, can't interrupt one long query
  • a streaming step API for a true engine-memory row cap (the shell cap bounds output, not materialization)
  • committed/per-row audit hooks; SQLITE_OPEN_NOFOLLOW; DBCONFIG_DEFENSIVE/TRUSTED_SCHEMA=0; compile flags

Notes for review

  • Authored without a Swift toolchain in this environment, so CI is the first real compile. I'm watching it and will push fixes.
  • New flag: -hardened (tighten-only). Existing default and -safe behavior are unchanged — the 60-test parity suite should stay green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01E8VSWQJZ3ZxVcG6kwiwygJ


Generated by Claude Code

Loading
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