Sparraのエバリュエーターは、成果物を実際に動かすことで役割を果たします。つまり、モデルが選んだコマンドを実行するということ。それはセキュリティの面でもあり、Sparra自身の敵対的なエバリュエーターは、そこを何度もすり抜けてきました。最初は、たぶん多くの人がやるであろう安全なやり方、rmのような危険なバイナリのdeny-listから始めました。エバリュエーターのプローブは、その真横を通り抜けました。find -deleteやperl -eは、ブロックされたコマンドを一度も名指しせずにファイルシステムを書き換えますし、rm${IFS}victim.txtのようなシェル展開は、rmと綴らないので、どのサブストリング判定もすり抜けます。ビルドツールをallowlistするだけでも足りませんでした。npm version patchは、許可されたバイナリが、こっそりpackage.jsonを書き換えるサブコマンドを走らせているのです。
そこで、姿勢を丸ごとallowlist-by-defaultに反転させました。既知の危険なコマンドをブロックする代わりに、エグゼキューターはいまや既知のビルド/テストランナーだけを、しかもその安全なサブコマンド(test、run <script>など)だけを実行します。インタプリタのeval系フラグは拒否し、cargo publishやnpm versionのような書き換え系の動詞は却下し、シェルのメタ文字や展開トークンはspawn前に弾き、コマンドはトークン化してシェルなしで起動します。かつての「危険に見えなければ何でも動く」は、いまは「安全だと認識できる形でなければ何も動かない」になりました。
もっと微妙な修正は、エバリュエーターが見つけた「ロンダリング」の穴を塞いだことです。エグゼキューターが実行を拒むverifyコマンドは、以前は単にスキップされたまま、評決は緑のままでいられました。つまり、一度も実行されなかったチェックに成果物が「合格」できたのです。いまは、安全でないverifyコマンドは、タイプミスのあるコマンドとまったく同じように、プローブの時点でコントラクトの交渉に差し戻され、再実行時には専用のunsafeステータスを帯びて、合格を降格させ、コマンド名を挙げます。エグゼキューターが検めるのは依然としてコマンドの「形」であって、make testのようなプロジェクト自身のレシピの中身ではありません。その残りはworktreeの境界の内側に収まります。Sparraで何度も楽しいと感じるのは、これらのどれも、私が攻撃を想像して生まれたものではないことです。ツール自身のエバリュエーターが、自分自身のエグゼキューターを、ラウンドを重ねて攻撃し、攻撃が通らなくなるまで私が固めていった。そこから生まれたのです。