Bug F2 — RCE via parse_recipe AST-filter bypass
Asset: Mercury (tool-execution lambda) — mercury/parsers/recipe_parser.py:148-203. Pre-prod local target (make run-lambda :8000), production-equivalent code path. Found during authorized internal CISO-led security audit; confirmed locally on commit 16e9b9c48d of master.
Severity: Critical (P0)
Bug Class: Remote Code Execution
Verified: Yes — marker file /tmp/mercury_rce_F2 written by the Mercury process at 2026-05-08 14:03 IST.
Description
Mercury's POST / endpoint accepts a parse_recipe op that calls _extract_model_schemas at mercury/parsers/recipe_parser.py:148. The function uses an AST filter to keep only ClassDef nodes whose base is BaseModel or Enum, then calls exec(models_code, namespace) at line 176. The filter is bypassable because Python evaluates class-body default-arg expressions during exec(). An attacker stashes arbitrary code inside a default-value expression on a BaseModel field — the AST filter sees a benign ClassDef, but exec() runs the side-effect when constructing the class.
The FastAPI endpoint at mercury/serverless/api.py:9 has no auth. Any unauthenticated caller who can reach the lambda HTTP URL gets arbitrary Python execution. Same blast radius as F1 (full os.environ exfil in real Lambda).
How to replicate
- From the mercury repo, start the local lambda server:
source .venv/bin/activate && make run-lambda. - POST
audit/repros/F2_rce_recipe.jsontohttp://localhost:8000/. The envelope contains aBaseModelsubclass with a default-arg expression that opens/tmp/mercury_rce_F2for write. - Verify side-effect:
ls /tmp/mercury_rce_F2andcat /tmp/mercury_rce_F2— file exists, contents .