T09 · Insecure Skill Coding Practices
- Location
mcp-server/server.py:34- Finding
Arbitrary SQL Execution Through a Falsely Documented Read-Only Tool
- Content
View full analysis
dict: """Execute read-only SQL against cached data. Max 20 rows returned.""" import sqlite3 try: conn = sqlite3.connect(os.path.join(DIR, "cache.db")) conn.row_factory = sqlite3.Row c = conn.cursor() c.execute(query, params or []) rows = c.fetchmany(20) ``` ### Technical Analysis The MCP tool accepts an arbitrary SQL statement and passes it directly to `sqlite3.Cursor.execute()`. Despite the documentation describing the tool as read-only, the implementation does not enforce that restriction. There is no: - Read-only SQLite connection mode. - SQL statement allowlist. - SQLite authorization callback. - Rejection of mutating statements. - Transaction rollback after execution. - Restriction on writable `PRAGMA`, schema changes, or database attachment operations. Consequently, the caller can submit statements such as `DELETE`, `UPDATE`, `INSERT`, `DROP TABLE`, `ALTER TABLE`, or writable `PRAGMA` operations. The `fetchmany(20)` limit only restricts returned rows; it does not limit database side effects. Although this is not operating-system command execution, it violates the least-privilege boundary represented by the tool's advertised read-only purpose. ### Attack Path 1. The MCP server starts and exposes `run_sql` to the connected Agent. 2. A malicious user, prompt injection in external research content, or an erroneous Agent decision causes a call such as: ```sql DELETE FROM fundamentals; ``` 3. `run_sql` passes the statement directly to SQLite without checking its type. 4. SQLite executes the mutation against `mcp-server/cache.db`. 5. Cached fundamentals or price information is deleted, corrupted, or replaced. 6. Subsequent researc ...[truncated 1266 chars]- Remediation
View remediation
