skip to content

How can a payload committed inside a binary test fixture end up executing during a build?

level: middleimportance: should knowfreq 46%

answer

  1. the executing half never changes
  2. data becomes code at consumption
  3. binary diffs hide the payload
  4. extraction, deserialisation, linking, interpolation
  5. regenerate the fixture from committed source

basics

~20 s

A fixture is only data until something interprets it. If a build or test helper reads that file and evaluates, extracts or links what it holds, committing bytes there is committing code — through a diff no reviewer opened.

solid answer

~50 s

The trick is that the executing half of the pair does not change. A helper script already decodes, decompresses, links or shells out with the contents of a fixture file; that behaviour was reviewed once and looks unchanged in every later diff. The contributor changes only the data, and the data is a binary blob that renders as `Binary files differ`. Behaviour changes without a single readable line changing. Common shapes: an archive the test extracts into a build directory, a capture whose contents are interpolated into a command, an object blob linked into the output, a serialised object the harness deserialises. Defences are structural: generate fixtures at test time from committed source, or require a committed script that reproduces the blob and pin its hash; review script and fixture as one unit; and run builds and tests with no production credentials and constrained egress so a payload that does execute reaches nothing.

go deeper

for a junior

Know that a test fixture is not automatically harmless: if a build or test script reads the file and acts on its contents, changing the file changes what the build does.

for a middle

Explain the mechanism end to end - an unchanged consumer plus changed opaque data - and name the consumption points that turn data into behaviour: archive extraction, deserialisation, linking, and interpolation into a command.

for a senior

Show which defence you would actually deploy first, and say clearly why signing, reproducibility and dependency scanning all confirm the poisoned result rather than catching it.

for a principal

Argue the policy: where your organisation permits committed binaries at all, what provenance you require for each, and who pays for the tooling that regenerates fixtures instead of trusting them.

## Why a data file is not data The intuitive model is that source files execute and asset files do not, so review effort belongs on source. That model is wrong wherever a build or test step *interprets* an asset. The moment a script decompresses a fixture into the build tree, deserialises it, interpolates it into a shell command, or hands it to a linker, the fixture's bytes are part of the program. The fixture is inert only relative to a reader; relative to the tooling that consumes it, it is instructions. This makes fixtures the ideal target for someone with legitimate commit rights. The attack needs two halves — a consumer that executes, and data that directs it — and the essential move is to change **only the half that is not readable**. ## The mechanism, step by step 1. **The consumer already exists and is legitimate.** A test helper unpacks `testdata/session-capture.bin` into a temporary directory before running an integration test. A build script decompresses a reference archive to compare output against it. A benchmark loads a serialised object. Each was written for a good reason and reviewed when it was added. 2. **The contributor changes only the fixture.** The commit touches one binary file. The diff shows no readable change. The commit message says the capture was refreshed against the new upstream format, which is a thing that genuinely happens. 3. **The consumer's behaviour changes.** An archive whose member paths escape the extraction directory writes over a build script. A capture whose contents are interpolated into a command line carries a shell metacharacter. A serialised object triggers code paths at deserialisation. A committed object file is linked into the artifact, so the shipped binary contains code that appears in no source file. 4. **Every downstream control agrees the result is legitimate.** The build was authentic, so provenance for it is truthful. The build is reproducible, so it reproduces the payload exactly. Signing attaches a valid signature to the poisoned artifact. Dependency scanners find nothing, because nothing about the dependency set changed. These controls exist to prove the artifact came from this source through this build — and it did. ## Where the payload's damage actually lands Two different assets are at stake, and candidates who only see one give a shallow answer. **Build-time compromise.** The code executes on the build machine, which typically holds registry credentials, cloud tokens and signing material. The payload's goal may be to steal those and never touch the shipped artifact at all. **Artifact compromise.** The payload is compiled or linked in, so the released binary carries behaviour that no source file describes. This is what makes the class so damaging: an auditor reading the repository will not find it, because the repository does not contain readable evidence of it. A third variant is worth naming because it looks even more routine: an opaque blob that *is* the product's behaviour. A model-weights file swapped under a weekly-refresh commit changes what the system predicts without a line of code changing, and no reviewer, scanner or signature has an opinion about the numbers inside it. ## Controls that actually address it **Generate rather than commit.** The strongest form is to derive fixtures at test time from committed, readable source. Then the fixture has no independent existence for anyone to poison. **Reproducible fixtures with recorded hashes.** Where a real binary must be committed — a recorded capture, a compressed sample — require a committed script that regenerates it, store the expected hash, and treat a hash change without a corresponding change to the generator or its input as an event worth explaining. This does not stop the change; it makes the change *visible* to a reviewer who cannot read bytes. **Review the pair.** Train reviewers that a fixture and the code that consumes it form one unit of behaviour. A change to either side is a behaviour change, so a fixture-only commit is not a no-op commit. **Harden the consumer.** Extract archives with path traversal rejected, never interpolate file contents into a shell, never deserialise untrusted formats, treat every fixture as untrusted input in the same way you would treat a request body. This is the control that survives even when review misses the commit. **Constrain the environment.** Run builds and tests without production credentials, with narrow egress, and with short-lived tokens. A payload that executes but can reach neither a secret nor the internet has a much smaller outcome. **Detect after the fact.** Look for fixtures whose hash changed without a matching change to their generator, for build steps that read files they were never intended to read, and for outbound connections from build runners. ## The framing to say out loud The class is not exotic: any pipeline where reviewed code consumes unreviewed data has this shape. The reason it belongs in a supply chain conversation is that it defeats the whole downstream stack — attestation, reproducibility, signing and scanning all confirm that a faithful build of the committed source took place, which is exactly what happened.

  • A departing engineer replaces a model-weights blob under a routine weekly-refresh commit. Is this the same problem?
    Structurally yes — an opaque committed artifact that determines behaviour, changed through a diff nobody can read. The controls rhyme: bind the blob to a reproducible generation process, record who produced it and from which training inputs, store its hash, and gate on comparing the committed file against a regenerated one. What differs is the asset at risk, which is model integrity and intellectual property rather than build credentials.
  • Would reproducible builds or a provenance attestation have caught this?
    No. Both attest to the relationship between source and artifact, and that relationship is intact — the build faithfully compiled a repository that contains the payload. Reproducibility reproduces it byte for byte, and provenance truthfully records which repository and which build produced it. They close tampering after the source, not hostile content inside it.
  • How would you detect this after the fact in a repository you already have?
    Correlate fixture history with generator history: list binary files whose hash changed in commits that touched nothing else, and check whether a script or input change should have accompanied each. Then look at build-time behaviour — which steps read files outside their expected set, and whether any runner made outbound connections it does not normally make.

The recipe on the wall never changes; someone refills the jar labelled sugar. Every inspection confirms the cook followed the recipe exactly.

saying these in an interview costs you the question

  • Says binary files cannot execute so they are harmless
  • Assumes commit signing prevents a legitimate contributor's payload
  • Claims a reproducible build would have caught it
  • Expects a dependency or vulnerability scanner to flag it
  • Treats test and build tooling as lower risk than production code

context