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 testnpm run testnode --testnode --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:
- Phase doc: phases/09-verification-runner.md
- Kodr post: blog/09-verification-runner.md
- The runner: src/verification-runner.mjs