Running Checks Without Handing Over a Shell


If you want a coding harness to be useful, at some point it has to run your tests. The lazy way to do that is to let the model emit a shell command and run it. The lazy way is also how you end up running rm -rf because a 7B model hallucinated it inside a JSON string. Phase 09 of kodr takes the boring, safe path instead.

An allowlist, not a shell

The verification runner does not have a shell. It parses a command against a tiny allowlist and runs it with spawn(..., { shell: false }), so there is no shell to inject into in the first place. The supported set is deliberately small:

  • npm test
  • npm run test
  • node --test
  • node --check <file>

That is the whole menu. node --check is the only one that takes an argument, and it only accepts a relative path with no .. - same instinct as the path jail from phase 08. No arbitrary args, no shell metacharacters, no pipes, no creativity.

The tests for this phase are mostly about rejection: feed it injection-shaped commands and confirm it refuses. There is also a timeout, so a check that hangs does not hang the harness. Results land in .kodr/last-test.md.

Why bother being this strict

Because the next phase wires this up to model output, and a model-provided shell command is just too broad a primitive to hand untrusted text. The allowlist keeps verification genuinely useful - I can run the tests after a write - while making command injection physically impossible rather than something I’m hoping a regex catches.

It is a small module doing a small job, on purpose. The interesting part is everything it refuses to do.

Links: