Skip to content

Commit 8ea1e4e

Browse files
fix(sql-sanitizer): fix UPDATE prose false block and missed inline li… (#191)
* fix(sql-sanitizer): fix UPDATE prose false block and missed inline literals - UPDATE_RE now requires SET after the table name so that prose text containing the word UPDATE (e.g. "Append UPDATE query to file") is not matched as a DML statement and does not trigger the WHERE-less UPDATE policy. - require_parameterization now also detects inline literal values (numeric and string) in addition to interpolation markers. SQL using bind parameters (?, $1, :name) passes; SQL with hard-coded literals is flagged with a new message. - Add BIND_PARAM_DIGIT_RE to erase $N / :N / @n digit sequences before literal detection so positional bind params are not false-positived. - Regression tests added for: valid UPDATE with WHERE, prose UPDATE word, mixed SQL+prose payload, INSERT/UPDATE with inline literals, and INSERT/UPDATE with bind parameters. - Version bumped 0.1.3 -> 0.1.4. Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * fix(sql-sanitizer): update interpolation_marker test to use bind param The existing test used a quoted LIKE literal '%s%' which the new inline literal detection correctly flags as non-parameterized. Rewrite the predicate to use a bind param so the test stays valid under both the interpolation and inline-literal checks. Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * fix(sql-sanitizer): fix schema-qualified UPDATE bypass and non-SQL field over-flagging Blocker 1 — UPDATE_RE schema-qualified table bypass: The table-name token was \w+ which does not match dots, so 'UPDATE schema.table SET …' failed to match and WHERE-less updates on schema-qualified tables silently bypassed the guard. Change the bare-identifier alternative to (?:\w+\.)*\w+ so that dotted names (schema.table, db.schema.table) match as a single unit before \s+SET\b. Add regression tests: schema_qualified_update_without_where_is_blocked schema_qualified_update_with_where_is_not_blocked three_part_name_update_without_where_is_blocked Blocker 2 — has_inline_literals over-flags non-SQL fields: With fields=null the scanner visits every string in the payload. has_inline_literals fired on any field containing a digit (HTTP status codes, tool-call IDs, version strings, log messages), causing false blocks unrelated to the original parameterization intent. Add a SQL_KEYWORD_RE gate: strings that contain no DML/DQL keyword (SELECT, INSERT, UPDATE, DELETE, MERGE, REPLACE, WITH) are skipped entirely. Add regression tests: non_sql_field_with_number_is_not_flagged tool_call_id_field_is_not_flagged version_string_is_not_flagged error_code_message_is_not_flagged select_one_health_check_is_flagged_as_inline_literal Major — restore interpolation_marker_in_string_literal test: The PR had rewritten the test body to use a bind param with no quoted literal, making it test nothing about literal masking. Replace with two focused tests: percent_s_inside_string_literal_is_not_flagged_as_interpolation — exercises mask_string_literals hiding %s inside a quote, require_parameterization=false so only the interpolation path is active. parameterized_like_with_bind_param_is_not_flagged — require_parameterization=true + LIKE ? passes cleanly. Major — update config.rs require_parameterization doc comment: The field doc described only the interpolation-marker path. Expand it to cover both detection paths, the SQL-context gate, and the fields=null caveat. Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * style(sql-sanitizer): collapse three_part_name test call to single line for rustfmt Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * style(sql-sanitizer): trim verbose comments to 1-2 lines Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * fix(sql-sanitizer): cover UPDATE aliases/ONLY/quoted-schema, SQLite ?N params, hex/exp literals, DQ identifiers, drop WITH from SQL gate P1 — UPDATE_RE now handles: - ONLY keyword: UPDATE ONLY t SET - AS alias / implicit alias: UPDATE t AS e SET, UPDATE t e SET - Quoted schema.table: UPDATE "hr"."employees" SET P2 — BIND_PARAM_DIGIT_RE: add ? so SQLite ?1/?2 params are erased. P2 — SQL_KEYWORD_RE: remove WITH; it fires on common English prose like "Request failed with status 503". P2 — INLINE_LITERAL_RE: extend pattern to cover hex (0xFF) and exponent (1e3, 1.5E-2) numeric literals. P2 — has_inline_literals: strip ANSI double-quoted identifiers (DQ_IDENT_RE) before literal matching so SELECT "2024" FROM t WHERE id = $1 is not incorrectly blocked. Regression tests added for all five cases. Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * style(sql-sanitizer): collapse multi-line find_issues calls for rustfmt Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * style(sql-sanitizer): expand two test call sites to match rustfmt Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * fix(sql-sanitizer): add word boundary to hex literal pattern to prevent mid-identifier match Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * fix(sql-sanitizer): accept quoted UPDATE aliases; require paired SQL keywords for literal gate Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * fix(sql-sanitizer): fix test message mismatch and SELECT 1 gate expectation Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> * fix(sql-sanitizer): enable dot-all in SQL_CONTEXT_RE so multiline SQL is gated correctly Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com> --------- Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
1 parent c105ecd commit 8ea1e4e

6 files changed

Lines changed: 573 additions & 21 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎plugins/rust/python-package/sql_sanitizer/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sql_sanitizer"
3-
version = "0.1.3"
3+
version = "0.1.4"
44
edition.workspace = true
55
authors.workspace = true
66
license.workspace = true

‎plugins/rust/python-package/sql_sanitizer/cpex_sql_sanitizer/plugin-manifest.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
description: "Rust-backed SQL statement sanitizer — blocks risky DDL/DML and WHERE-clause-less mutations"
22
author: "ContextForge Contributors"
3-
version: "0.1.3"
3+
version: "0.1.4"
44
kind: "cpex_sql_sanitizer.sql_sanitizer.SQLSanitizerPlugin"
55
available_hooks:
66
- "prompt_pre_fetch"

‎plugins/rust/python-package/sql_sanitizer/src/config.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ pub struct SqlSanitizerConfig {
3939
pub block_update_without_where: bool,
4040
/// Strip `--` and `/* */` comments before analysis.
4141
pub strip_comments: bool,
42-
/// Heuristic check for non-parameterized interpolation (`+`, `{…}`, `%.`).
42+
/// Require parameterized queries. Flags interpolation markers (`+`, `%s`, `{…}`)
43+
/// and inline literals (`42`, `'Alice'`). Non-SQL fields are skipped via a keyword
44+
/// gate. Most precise when `fields` names only the SQL-bearing fields.
4345
pub require_parameterization: bool,
4446
/// Return `continue_processing=False` when a violation is found.
4547
pub block_on_violation: bool,

0 commit comments

Comments
 (0)