Repository navigation
fix(security): close ScanSQL bypasses via quoted dollar tags, truncation and inline comments - #885
Conversation
…ion and inline comments - Dollar-quote stripping ran inside quoted strings, so SELECT '$x$'; DROP TABLE users; SELECT '$x$' scanned clean. A $tag$ is now stripped only when every quoting interpretation (backslash or doubled quote escapes, PostgreSQL E-strings, "..." strings, [bracket] identifiers, nested comments, # comments) agrees it is outside strings and comments. - Tautology and comment checks only looked at the first 10,000 bytes. They now cover the whole input; inputs above 10 MiB get a CRITICAL INPUT_TOO_LARGE finding regardless of the severity filter. - Comments between keywords (UNION/**/SELECT, SLEEP/**/(5), ;--\nDROP, # comments, /*/, MySQL 1--1) evaded matching. Patterns now also run over comment-free views of the text for each quoting convention; views only add findings. /*! */ and /*M! */ bodies are scanned. - The AST UNION NULL-padding check now matches the parser's null literal. - The tautology check compares operands without allocating per candidate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @ajitpratap0's task in 36s —— View job Review of PR #885: ScanSQL bypass fixesI read the diff for Overall: The approach is sound. Matching over several comment-free views while always keeping the raw view means the extra views can only add findings. Stripping a dollar tag only when every quoting interpretation agrees it is outside strings and comments is the right conservative rule. The allocation-free tautology comparison and the Concerns
Test coverageThe coverage looks strong: audit repros, differential fuzzing, boundary and linear-time tests, and false-positive guards. A test that pins the peak allocation for a worst-case 10 MiB input would guard concern 1. No blocking correctness bugs found in the code I read. I'd address concern 1 or explicitly accept it, and fix concern 4 before merge. |
…age timeout Scanning the whole input made the large-input tests much slower under the race detector, and CI's race job hit the 120 s package timeout. Under -race, TestScanSQL_Concurrent_FoldPool runs 25 iterations per goroutine instead of 200, TestScanSQL_PrefilterEquivalence checks a quarter of the corpus without inputs over 16 KiB, and the timing-ratio test is skipped. Non-race runs keep the full coverage. Local race run: 50 s -> 17 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
|
Claude finished @ajitpratap0's task in 26s —— View job Review of PR #885: ScanSQL bypass fixesI read Overall: The design is sound. Matching over extra comment-free views while always keeping the raw view means the new views can only add findings. Stripping a dollar tag only when every quoting interpretation agrees it is outside strings and comments is the right conservative rule. I found no blocking correctness bugs. Corrections to the earlier automated review
Remaining suggestions
Test coverageCoverage looks strong: audit repros, differential fuzzing, the exact 10 MiB boundary, a linear-time ratio, and false-positive guards. The only gap is the worst-case peak memory test in suggestion 1. Verdict: Fine to merge once suggestion 1 is either addressed or explicitly accepted. The rest is optional. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
|
Claude finished @ajitpratap0's task in 19s —— View job Review of PR #885: ScanSQL bypass fixes (re-review on synchronize)I read the commit list and Overall: The design is sound. The raw view is always kept and the comment-free views only add findings, so they can't hide anything. The mode gating in Still open
Already handled
Verdict: Fine to merge once item 1 is either addressed or explicitly accepted. Fixing the comment in item 2 is a cheap change. The rest is optional. |
Summary
Closes the
ScanSQLbypasses verified in the 2026-10-28 audit.ScanSQLalso backs the MCPsecurity_scantool, the C binding and WASM.SELECT '$x$'; DROP TABLE users; SELECT '$x$'scanned clean because$x$...$x$was stripped as a dollar quote. A tag is now stripped only when every quoting interpretation agrees it sits outside strings and comments. The interpretations are backslash vs doubled-quote escapes, PostgreSQL E-strings, MySQL"..."strings,[bracket]identifiers, nested comments and#comments.INPUT_TOO_LARGEfinding at everyMinSeverity, so unscanned input never looks clean.UNION/**/SELECT,SLEEP/**/(5),;/**/DROP,UNION--\nSELECT,#comments,/*/and MySQL1--1all evaded matching. Patterns now also run over comment-free views of the text, one per quoting convention. The raw text is still checked, and the extra views can only add findings. MySQL/*! */and MariaDB/*M! */bodies are unwrapped and still flagged.*ast.Identifier "NULL"; the parser produces a null*ast.LiteralValue. It now matches that.1=2drops from about 50 MB to about 1 MB allocated.This overlaps #879 (Scan AST walker) only in
checkUnionSelect, kept as a separate hunk.Test Plan
wp_p_bypass_test.go: every audit repro and every input from two independent Sonnet security reviews, plus false-positive guards for benign SQL with comments and strings. Also covers allocation bounds, the exact 10 MiB boundary, and an n vs 4n linear-time ratio (4.04×).sqltext_test.gowithFuzzSQLTextViews. The reviewer ran 3M differential reference-lexer cases (MySQL, SQL Server, SQLite, PostgreSQL, standard SQL) and 400k prefilter-on/off cases with no differences after the fixes.task check(55 packages ok, golangci-lint 0 issues) andtask test:cbindingpass locally. The security race suite takes about 50 s (was 34 s), within the 120 s package timeout.BenchmarkScanSQLCleanSQL_RawTautologyDetectionMixedPatternsBreaking Changes
INPUT_TOO_LARGEfinding.Known limits, documented in code: a MySQL
$x$identifier shaped like a dollar quote is still stripped (ScanSQLhas no dialect parameter). Fragments that start with an unbalanced quote (x' OR/**/TRUE--) and some existing tautology shapes (1=01,'1'=1,(1)=(1)) are not detected. At 10 MiB, an adversarial input that turns on every view can take about 6 s.🤖 Generated with Claude Code
https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2