T09 · Insecure Skill Coding Practices
- Location
scripts/read_sqlite.py:44- Finding
Unrestricted SQL Execution Against a Writable SQLite Database
- Content
View full analysis
Vulnerability Details
File Location:
scripts/read_sqlite.py, lines 44–48, 97, and 125
Vulnerability Type: Unrestricted state-changing SQL execution in a purported read-only utility
Risk Level: HighVulnerable Code
python def execute_query(conn, query, limit=None): """Execute a SQL query and return results""" cursor = conn.cursor() try: cursor.execute(query)python conn = sqlite3.connect(args.database)python elif args.query: result = execute_query(conn, args.query, args.limit)Technical Analysis
The Skill is documented as a SQLite reader that supports SELECT queries, but the implementation passes the entire user- or agent-supplied
--queryvalue directly tocursor.execute()without enforcing a read-only SQL policy.The database is opened through a normal
sqlite3.connect(args.database)call rather than a read-only URI. Subject to filesystem permissions, this grants the connection write access. As a result, the query interface can execute state-changing statements such asINSERT,UPDATE,DELETE,DROP, and writablePRAGMAoperations. The--limitoption only controls how many result rows are fetched; it does not limit or neutralize SQL side effects.Although
cursor.execute()generally rejects multiple statements in one call, this does not mitigate the issue because a single destructive statement is sufficient to alter or destroy the database. Because the documentation specifically identifies OpenClaw'smain.sqlitememory database as an intended target, the affected data may include sessions, messages, user details, and transcript paths.Attack Path
- A user or untrusted instruction causes the Skill to inspect a writable SQLite database.
- The agent passes a state-changing statement through the
--queryargument, for example:sql DELETE FROM messages; - The script opens the selected database using a writable SQLite connection.
- `cursor.execu ...[truncated 994 chars]
- Remediation
View remediation
Remediation Suggestions
-
Open the database in read-only mode using a SQLite URI:
python from pathlib import Path database_uri = Path(args.database).resolve().as_uri() + "?mode=ro" conn = sqlite3.connect(database_uri, uri=True) -
Enforce read-only operations with SQLite's authorizer API. Deny insert, update, delete, schema modification, transaction manipulation, attach/detach, and unsafe PRAGMA actions:
python def authorize(action, arg1, arg2, database_name, trigger_name): denied = { sqlite3.SQLITE_INSERT, sqlite3.SQLITE_UPDATE, sqlite3.SQLITE_DELETE, sqlite3.SQLITE_CREATE_TABLE, sqlite3.SQLITE_DROP_TABLE, sqlite3.SQLITE_ALTER_TABLE, sqlite3.SQLITE_ATTACH, sqlite3.SQLITE_DETACH, } return sqlite3.SQLITE_DENY if action in denied else sqlite3.SQLITE_OK conn.set_authorizer(authorize) -
Permit only read-only statements. Do not rely solely on a textual prefix check, because comments, PRAGMAs, and complex SQL syntax can bypass simplistic validation. Use the read-only connection and authorizer as the primary controls.
-
Reject queries that return no result-set metadata and handle
cursor.description is Nonesafely. -
Add explicit confirmation and a separate administrative mode if write functionality is ever intentionally introduced. It should not share the reader's default execution path.
-
Add security tests confirming that
INSERT,UPDATE,DELETE,DROP,ALTER,ATTACH,DETACH, and writable PRAGMA statements are denied while legitimateSELECTand schema-inspection operations remain available. -
Update the documentation to accurately state and guarantee that arbitrary queries are constrained to read-only behavior.
-
