A pin rejects any command it does not equal, so the remaining attack is to change what
the pinned text MEANS. Four such vectors, found by attacking the new mechanism rather
than reading it; the first was MEASURED escaping and the rest are the same class:
* A NEW stage taking the SPA source across with `COPY --from=web-build /source/web`
and running the suite there. `copies_spa_source` excluded every `--from=` copy on the
grounds that a stage copy is not a context copy — true, but it can still carry the
SOURCE TREE from a stage that has it. The receiving stage was therefore unpinned and
unchecked, which is exactly the false-NEGATIVE direction this file's own residual
warns about. A stage copy now counts when its SOURCE has a whole `web` path segment,
which keeps the built-artifact copy this repo actually makes
(`/source/ErsatzTV/wwwroot/app/.`) correctly out.
* `working-directory:` moved off `web` — `npm test` somewhere else runs a different
package, or none, with the pinned command text unchanged. Now pinned.
* `defaults.run.shell` changed from `bash` to `sh`. `bash` here is `bash -e`, which is
what makes a failing command fail the step; changing it changes whether a red suite
blocks the image without touching the step at all. Now pinned.
* A `SHELL` instruction in a pinned stage, which redefines what every later `RUN`
executes. Refused outright rather than modelled — there is none in this repo, so the
honest move is to reject the construct, not to reason about a replacement
interpreter.
Battery 41 -> 45, 0 missed; the stage-copy mutant reddens three assertions. The residual
list gains the two cases that remain in this class and are NOT covered: a stage copy that
RENAMES the tree on the way in (no `web` segment in its source), and an `ENV` altering
`PATH` so a pinned `RUN` resolves a different `npm`. Naming them is the point — the
previous rounds' residual lists read as exhaustive while omitting the largest holes.
Refs: #887
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019T79beF1Ufid3dXju4yqkF