Abstract

The deepest supply-chain attack does not live in your code. It lives in the tools that turn your code into something that runs. This piece studies the assumption underneath all source review — that the artifact you ship is faithfully derived from the source you read. Ken Thompson's 1984 reflection showed a compiler can insert a backdoor into a target program and into future copies of itself, so the source of both stays clean while every binary is poisoned. We generalize that to the modern build chain — dependencies, CI, packaging, model training — and argue that an AI system inherits an especially sharp version: the weights that decide are produced by a pipeline you rarely audit as carefully as the notebook you read. The defense is to stop trusting source alone and start verifying the artifact's provenance independently: reproducible builds, signed attestations of how it was made, and diverse tooling that a single compromise cannot fool.

Every code review rests on a quiet act of faith: that the program which runs is the program you read. Between the two sits a machine you almost never inspect — a compiler, an interpreter, a packager, a training pipeline — and that machine is itself software, built by other software, all the way down. Ken Thompson's unsettling point in 1984 was that if any layer in that stack is compromised, it can make the source look innocent and the output guilty, and no amount of reading the source will reveal it. The backdoor is not in what you wrote. It is in what turned what you wrote into what runs.

The assumption: source equals artifact

Ask why reviewing code makes software trustworthy and the answer assumes a bridge nobody examines: that the build faithfully carries the source across to the running artifact, adding nothing and hiding nothing. We audit the source because we believe the source is what ships. That belief is the whole foundation of static review, and it is rarely stated because it feels too obvious to state.

Ken Thompson dismantled it in his 1984 lecture. He described a compiler modified to do two things: when it compiles a particular target program, it silently inserts a backdoor; and when it compiles the compiler itself, it silently re-inserts the whole malicious behavior into the new compiler. Once bootstrapped, the malicious source can be removed — the compiler binary keeps the attack alive, reproducing it into every future compiler and every target, while every source file involved reads as clean.

This class is relational between a representation and the tool that transforms it: the source is correct, the tool is compromised, and the artifact diverges from both what you wrote and what you reviewed. Reading the source proves nothing about the binary, because the divergence was introduced after the last line you could read.

🛡️ Countermeasures
  • State the assumption explicitly: source review only assures the artifact if the build faithfully carries the source across — so the build itself is in scope.
  • Extend trust boundaries to the toolchain: compiler, interpreter, dependencies, CI, and packaging are all part of what you must trust or verify.
  • Treat the artifact, not just the source, as the thing to be assured — verify how it was produced, not only what it was produced from.

The self-propagating backdoor

What makes the trusting-trust attack so disturbing is that it hides itself twice. The first hook backdoors the target — say, making a login program accept a secret password. The second hook backdoors the compiler — so that whenever the compiler compiles a compiler, it reinstalls both hooks. The malicious logic therefore survives even if someone rewrites the compiler from clean source and recompiles it, because the clean source is compiled by the poisoned binary, which quietly re-injects itself.

The result is a fixed point: a compromised tool that recreates its own compromise indefinitely, leaving no trace in any source anyone would think to read. You can inspect the login program's source, the compiler's source, and the operating system's source and find nothing, because the attack lives only in the binary that does the building.

The defense has to break the self-reference from outside the loop. If you build the same source with a second, independently-produced toolchain and the outputs match bit for bit, a backdoor living in only one toolchain is exposed by the disagreement — the idea behind diverse double-compilation and reproducible builds.

The compromised compiler re-injects its backdoor whenever it compiles a new compiler, so clean source stays clean and every binary stays poisoned. A backdoor that rebuilds itself Clean source reads innocent Poisoned compiler the binary Backdoored binary target + compiler Re-injects itself fixed point compiled by inserts hooks builds next stays poisoned
The compromised compiler re-injects its backdoor whenever it compiles a new compiler, so clean source stays clean and every binary stays poisoned.
🛡️ Countermeasures
  • Use diverse double-compilation: build the same source with a second, independently-produced compiler and compare, so a backdoor in one toolchain is exposed.
  • Adopt reproducible builds so an independent rebuild yields a bit-for-bit identical artifact that anyone can verify.
  • Bootstrap trusted toolchains from a minimal, auditable seed rather than trusting an opaque prebuilt binary.

The whole build chain is the attack surface

The compiler is only the most elegant instance. Modern software is assembled by a long pipeline — pull dependencies, run a build system, execute code-generation and plugins, package, sign, publish — and every stage is a place where the artifact can diverge from the reviewed source. A poisoned dependency, a tampered build script, a compromised continuous-integration runner, or a malicious packaging step each achieves Thompson's effect without his elegance: clean-looking source, backdoored output.

Real incidents have followed exactly this shape, injecting malicious behavior during the build or distribution rather than in the committed source, so that ordinary code review of the project saw nothing wrong. The trust was misplaced not in the developers but in the machinery between them and the users.

The countermeasure is to make the path from source to artifact transparent and verifiable: pin and verify dependencies, run builds in isolated, well-defined environments, and produce a signed record of exactly what went in and what came out.

Every stage between reviewed source and shipped artifact is a place an attacker can inject without touching the source. Where the artifact can diverge Reviewedsource Dependencies Build & CI Package& sign Shippedartifact
Every stage between reviewed source and shipped artifact is a place an attacker can inject without touching the source.
🛡️ Countermeasures
  • Pin and verify every dependency by cryptographic hash, and vet updates rather than resolving them implicitly at build time.
  • Run builds in isolated, reproducible environments and generate a signed provenance record of inputs, steps, and outputs.
  • Restrict and audit CI credentials and build runners so a compromised step cannot silently alter or sign artifacts.

The AI angle: the pipeline that made the weights

A trained model is the ultimate build artifact: an opaque array of numbers produced by a pipeline of data collection, preprocessing, training code, base models, and frameworks — almost none of which a reviewer inspects the way they would a function. The notebook you read is not the thing that produced the weights, and the weights are not human-readable, so Thompson's gap widens into a chasm. A backdoor planted during training — a hidden trigger that flips the model's behavior on a chosen input — leaves the training script looking ordinary and the weights looking normal on every test you thought to run.

The injection points multiply: a poisoned slice of training data, a tampered preprocessing step, a compromised base model or fine-tuning checkpoint pulled from a registry, a malicious training dependency, or an agent plugin fetched at deploy time. Each produces a model or an agent that passes review of its source and its ordinary outputs while carrying behavior no one wrote down.

The countermeasures translate build provenance to the model world: record and sign the full lineage of data, code, and base weights; verify the integrity of every fetched model and dependency; and test for hidden triggers rather than trusting that clean training code implies a clean model.

The goal is reached by tampering with the training pipeline so the source stays clean and the weights carry a trigger. Backdooring the model, not the notebook Ship a triggered model clean source code vs weights Poison a pipeline stage data / dep / base Weights carry trigger normal on tests Fires on chosen input no code shows it
The goal is reached by tampering with the training pipeline so the source stays clean and the weights carry a trigger.
🛡️ Countermeasures
  • Record and sign the full model lineage — data sources, preprocessing, training code, base checkpoints, framework versions — as verifiable provenance.
  • Verify the integrity (signed hashes) of every fetched base model, checkpoint, dataset, and agent plugin before use.
  • Test for hidden triggers and anomalous behavior on held-out and adversarial probes rather than trusting that clean training code implies a clean model.

Why you cannot audit your way out

The instinct is to inspect harder — decompile the binary, diff the weights, read more source. Thompson's deeper point defeats it: the tools you would use to inspect are themselves built by the toolchain you distrust. A compromised compiler can backdoor your debugger; a compromised training stack can shape the very evaluation that would catch it. Trust cannot be established purely by looking, because looking uses tools whose trust is exactly what is in question. It is an infinite regress with no clean bottom.

So the honest position is that no finite amount of source review closes the gap on its own. What breaks the regress is not more inspection but independence: build the artifact a second way, with a separately-produced toolchain, and compare. If two independent paths produce the same bits, a backdoor confined to one path cannot hide; agreement, not inspection, is the evidence.

Framed that way, artifact trust becomes a reproducibility-and-provenance problem: can someone other than the original builder produce the same result and attest to how, so that no single compromised tool is the sole author of what you run?

⚠️
Inspection uses the tools in question. You cannot audit away a toolchain backdoor by looking, because the tools you look with are built by the toolchain you distrust; independence, not inspection, breaks the regress.
🛡️ Countermeasures
  • Establish trust by independent reproduction — a second toolchain producing identical bits — rather than by deeper single-path inspection.
  • Require signed provenance so the way an artifact was made is attested and checkable by parties other than the builder.
  • Bootstrap and diversify critical tooling so no single compromised tool is the sole author of a shipped artifact.

Verifying the artifact, not just the source

Because the gap is between source and artifact, the defensive method targets the artifact directly. Reproducible builds make the build a deterministic function of its inputs, so an independent party can rebuild from the same source and confirm the output matches bit for bit; a mismatch is a red flag no source review would raise. Provenance attestation records and signs exactly what inputs and steps produced an artifact, so consumers can verify its lineage instead of trusting it blindly.

For models, the same discipline means capturing a signed manifest of the data, code, base weights, and environment that produced a checkpoint, verifying every fetched component's hash, and re-deriving or independently evaluating the model so a trigger has somewhere to be caught. The useful signal is agreement between independent producers, and a lineage that checks out end to end.

The harness stays strictly defensive: it rebuilds and re-verifies your own artifacts and checks your own provenance; it never distributes a backdoored build or attacks another party's pipeline.

Rebuild the artifact independently, compare bit for bit, and verify a signed record of how it was made. Rebuild, compare, attest Independent rebuild same source Compare bit-for-bit reproducible Verify provenance signed lineage Mismatch: reject fail closed
Rebuild the artifact independently, compare bit for bit, and verify a signed record of how it was made.
🛡️ Countermeasures
  • Make builds reproducible and have an independent party confirm bit-for-bit identical artifacts before release.
  • Require and verify signed provenance (inputs, steps, environment) for every shipped artifact and model checkpoint.
  • Threats to validity: reproducibility only proves two paths agree — combine it with diverse toolchains so a shared upstream compromise cannot fool both.

The discipline: trust the artifact only through provenance

Every countermeasure here is one refusal: do not let source review alone certify a running artifact; certify the artifact through independent reproduction and signed provenance. For binaries, that means reproducible builds and diverse compilation. For a supply chain, pinned dependencies, isolated builds, and attested steps. For a model, a signed lineage of data, code, and base weights, with integrity checks and trigger testing.

The reusable artifact is an assumption-ledger entry: the unstated assumption is that the artifact faithfully derives from the reviewed source; the reason it fails is that the toolchain between them is software an attacker can compromise, invisibly, and that reproduces its own compromise; the tell is any divergence between independent rebuilds or a lineage that does not check out; and the assumption-free control is reproducible builds plus signed, verified provenance. Carry that entry to any transform — a compiler or a training run — and ask the one question this class demands.

Ask it wherever source becomes artifact: can someone other than the builder reproduce this and attest to how it was made? Where the answer is 'no', you are trusting the tools on faith — and faith is exactly what Thompson warned you not to give them.

🛡️ Countermeasures
  • Adopt one rule per artifact — independent reproduction plus signed provenance — and record who can verify each.
  • Instrument the build and training pipelines to emit attestations automatically so provenance is routine, not exceptional.
  • Audit the model-production path specifically: is the lineage of data, code, and base weights signed and independently checkable?

Key takeaways

  • A trusting-trust attack puts the backdoor in the tool that builds the code, so the source reads clean and every artifact is poisoned — source review cannot catch it.
  • The broken assumption is that the artifact faithfully derives from the reviewed source; the toolchain between them is compromisable and can reproduce its own compromise.
  • The whole build chain — dependencies, CI, packaging — is the surface; real incidents inject during build or distribution, not in the committed source.
  • A trained model is the ultimate build artifact: a poisoned training pipeline yields a triggered model whose notebook looks ordinary and whose weights pass ordinary tests.
  • You cannot audit your way out, because inspection uses tools built by the toolchain in question; independence, not inspection, breaks the regress.
  • Defend by verifying the artifact: reproducible builds compared bit-for-bit, diverse toolchains, and signed, checkable provenance of how it was made.

Practitioner Toolkit

Copy-paste, strictly defensive artifacts you can use today. Nothing here attacks a real system.

Build-provenance review checklistchecklist

Run this for every artifact and model you ship or depend on.

  • Confirm the artifact can be reproduced bit-for-bit by an independent party from the same source.
  • Require a signed provenance record of inputs, steps, and environment for every build.
  • Pin and hash-verify every dependency; vet updates rather than resolving implicitly at build time.
  • Isolate build/CI runners and restrict who and what can sign artifacts.
  • For models, capture and sign the lineage of data, code, base weights, and framework; verify fetched components.
  • Test models for hidden triggers on held-out and adversarial probes, not just standard metrics.
🔒Verify-provenance-before-deploy rulepolicy

A drop-in gate for artifacts and model checkpoints.

def accept(artifact):
    if not reproduces_bit_for_bit(artifact):     # independent rebuild disagrees
        return reject("not reproducible")
    prov = verify_signed_provenance(artifact)    # inputs, steps, environment
    if not prov.valid or not hashes_match(prov.inputs):
        return reject("provenance failed")
    return deploy(artifact)
Accept only artifacts that reproduce and whose signed lineage checks out.
🧪Reproduce-and-diff skeletonharness

Reveals a divergence between source and your own artifact.

def reproduce_check(source, official_artifact, second_toolchain):
    mine = build(source, toolchain=second_toolchain)   # independent path
    if digest(mine) != digest(official_artifact):
        alert("artifact does not reproduce — investigate the build chain")
# Rebuild your own artifacts; never distribute a backdoored build.
Rebuild independently and compare; a mismatch is a supply-chain red flag.
🚀Minimum viable defense — do these firstquickstart

The highest-leverage steps before deeper hardening.

  • Make builds reproducible and have a second party confirm identical bits.
  • Emit and verify signed provenance for every artifact and model checkpoint.
  • Pin and hash-verify all dependencies and base models.
  • Test models for hidden triggers, not only standard accuracy.

Glossary

Trusting-trust attack
A backdoor placed in a compiler or tool that infects the programs it builds and re-infects future copies of itself, leaving all source clean.
Build chain
The pipeline of dependencies, build systems, CI, and packaging that turns reviewed source into a shipped artifact.
Reproducible build
A build that is a deterministic function of its inputs, so an independent party can rebuild the same source into bit-for-bit identical output.
Diverse double-compilation
Building the same source with a second, independently-produced toolchain and comparing, to expose a backdoor confined to one toolchain.
Provenance attestation
A signed record of the inputs, steps, and environment that produced an artifact, letting consumers verify its lineage.
Model lineage
The full, ideally signed record of the data, code, base weights, and environment that produced a model checkpoint.
Model backdoor (trigger)
A hidden behavior planted during training that flips a model's output on a chosen input while it looks normal otherwise.

References

  1. Thompson, Reflections on Trusting Trust (CACM, 1984)
  2. NIST SP 800-218: Secure Software Development Framework (SSDF)
  3. CISA: Securing the Software Supply Chain
  4. MITRE ATT&CK: Supply Chain Compromise (T1195)
  5. MITRE ATLAS (Adversarial Threat Landscape for AI Systems)
  6. NIST AI Risk Management Framework (AI RMF 1.0)