T09 · Insecure Skill Coding Practices
- Location
scripts/mysql_query.py:14- Finding
Read-Only Enforcement Can Be Bypassed with CTE-Prefixed Write Statements
- Content
View full analysis
Vulnerability Details
File Location:
scripts/mysql_query.py, lines 14-21, 51-55, and 193-196
Vulnerability Type: Incorrect SQL statement classification
Risk Level: HighVulnerable Code
python READ_PREFIXES = ( "select", "show", "desc", "describe", "explain", "with", )python def is_read_only(sql: str) -> bool: cleaned = compact_sql(sql).lower() if not cleaned: return False return cleaned.startswith(READ_PREFIXES)python if not args.allow_write: for statement in iter_statements(sql): if not is_read_only(statement): raise SystemExit("Refusing non-read SQL without --allow-write.")Technical Analysis
The script classifies SQL as read-only solely by checking its initial keyword. Every statement beginning with
WITHis accepted, even though MySQL permits common table expressions to prefix modifying statements such asUPDATEandDELETE.The separate blocked-pattern validation does not reliably prevent this bypass. Its
DELETEpattern only rejects an apparently unconstrained deletion, while itsUPDATEpattern only rejects updates without a detectedWHEREclause. A constrained modifying statement beginning withWITHcan therefore pass both controls without--allow-write.For example, the following statement is classified as read-only:
sql WITH ids AS (SELECT id FROM target WHERE id = 1) DELETE FROM target WHERE id IN (SELECT id FROM ids)The application-level check is not a security boundary because it does not parse the effective statement following the CTE.
Attack Path
- An attacker, untrusted input source, or mistaken operator supplies SQL beginning with
WITH. - The CTE is followed by a constrained
UPDATEorDELETE. is_read_only()accepts the statement because its first keyword isWITH.- The broad-statement regular express ...[truncated 997 chars]
- An attacker, untrusted input source, or mistaken operator supplies SQL beginning with
- Remediation
View remediation
Remediation Suggestions
- Replace prefix-based detection with a maintained MySQL-aware parser that resolves the effective statement type after any CTE.
- Explicitly reject CTE-prefixed
INSERT,UPDATE,DELETE,REPLACE, DDL, administrative statements, and stored procedure calls in read-only mode. - Use a dedicated MySQL account with server-enforced
SELECT-only grants for normal query execution. - Use separate write-capable credentials only after explicit approval.
- Set the session or transaction to read-only where supported, while retaining account-level restrictions as the primary control.
- Do not allow
--autocommitin read-only mode unless the statement has been securely classified. - Add regression tests for CTE-prefixed
UPDATE,DELETE,INSERT, nested comments, mixed case, and multiple statements.
