ADR 0007: The DSL is a sandboxed AST interpreter

Status:

Accepted

Context

Custom scoring strategies are authored in a browser by people who are not GAME developers, then run on the scoring hot path. Executing arbitrary user code there is a non-starter, both for safety (no arbitrary Python) and for stability (no unbounded CPU).

Decision

A custom strategy is a JSON AST walked node-by-node through a fixed handler table (app/engine/dsl_interpreter.py). The design rests on four guarantees:

  1. No dynamic Python - no eval, exec or getattr on AST-supplied strings; an unknown node type is rejected.

  2. Whitelist - node types, operators, functions and field paths must each appear in an allow-list in app/engine/dsl_ast.py, checked up front.

  3. Frozen precomputed context - field access is a lookup in a frozen dict that ExecutionContext.build_for_ast precomputes; the walk never touches the database.

  4. Cooperative-yield cancellation - the walk awaits asyncio.sleep(0) periodically so a CPU-bound tree can actually be cancelled by the timeout.

Consequences

  • Execution is bounded, deterministic, cancellable, and injection-proof.

  • The DSL is intentionally a small total language, not general-purpose; node and depth limits cap expressiveness.

See also