Skip to content

fix(sqlite): reject recursive CTE workloads - #2367

Closed
chaliy wants to merge 1 commit into
mainfrom
2026-08-31-evaluate-and-propose-fix-for-sql-vulnerability
Closed

fix(sqlite): reject recursive CTE workloads#2367
chaliy wants to merge 1 commit into
mainfrom
2026-08-31-evaluate-and-propose-fix-for-sql-vulnerability

Conversation

@chaliy

@chaliy chaliy commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Motivation

  • Upstream Turso now accepts WITH RECURSIVE, but Bashkit's enforcement points run only around Statement::step() and on emitted rows, so a recursive CTE can perform unbounded work inside a single step and bypass cooperative limits.
  • Deny tiny attack surface change with a minimal policy until Turso exposes an in-flight progress callback or an engine-level watchdog.

Description

  • Add parser::is_recursive_cte to detect WITH RECURSIVE (comment- and case-aware, keyword-boundary validated) in crates/bashkit/src/builtins/sqlite/parser.rs.
  • Reject recursive CTEs early in check_sql_policy with a clear error: "recursive CTEs are not supported in the bashkit sandbox; query work cannot be bounded" in crates/bashkit/src/builtins/sqlite/mod.rs.
  • Add a security regression test tm_sql_014_recursive_ctes_are_rejected in crates/bashkit/tests/integration/sqlite_security_tests.rs that covers comment-obfuscated forms.
  • Update differential tests to preserve non-recursive CTE parity and update documentation / threat model knowledge/runtimes/sqlite-builtin.md to document TM-SQL-014.

Testing

  • Ran the targeted integration test sqlite_security_tests::tm_sql_014_recursive_ctes_are_rejected and it passed.
  • Ran the non-recursive parity test sqlite_differential_tests::non_recursive_cte_matches_host and it passed.
  • Ran the full sqlite test slice with cargo test --features sqlite -p bashkit -- sqlite (108 unit tests and 67 integration tests for the sqlite feature) and all passed.
  • Ran formatting and lint checks (cargo fmt, cargo clippy -- -D warnings) and just check-okf for knowledge conformance; these passed.

Codex Task

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 31, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit 8e75647 Commit Preview URL Aug 31 2026, 10:15 PM

chaliy commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favour of #2369, which counts the work instead of denying the syntax.

Two reasons the policy denial doesn't hold up:

  1. It doesn't close the class. Recursion isn't the distinguishing property — "produces no rows for a long time" is. SELECT * FROM t a, t b, t c, t d WHERE a.n < 0 burns the same unbounded time inside one Statement::step() with no CTE involved, and would still pass the policy check.
  2. The blocker is gone. The motivation says "until Turso exposes an in-flight progress callback" — Connection::set_progress_handler exists as of 0.8.0-pre.7, which is the version already pinned in Cargo.lock.

#2369 installs that handler: every 1024 VM instructions it charges the request ExecutionBudget one work unit and interrupts the VDBE once the deadline or budget is exhausted, so in-VDBE work is counted where it happens. WITH RECURSIVE stays supported, and the differential parity test this PR had to rewrite stays as-is.


Generated by Claude Code

@chaliy chaliy closed this Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant