RC RANDOM CHAOS

Nobody checked what came back

The idTech build did not fail. It resolved a version reference exactly as designed, treating a source's identity as proof of its content's integrity.

· 8 min read
Nobody checked what came back

Id Software’s idTech engine is not a single artifact. It is the visible output of a build system, and that build system resolves dependencies the way every modern software pipeline resolves them: by reference. A name and a version string are handed to a package manager, and the package manager returns an object. The engine that ships is assembled from components retrieved this way, each one pulled because a manifest asked for it by identifier, not because anything inspected what came back.

The retrieval mechanism does exactly what it was designed to do. Semantic versioning, the convention that a string like 2.4.1 denotes one specific release, exists so a build can be reproduced without a person selecting files by hand. The registry resolves the string to an artifact and delivers it. Nothing in that transaction evaluates the content of the artifact against an expectation of what it should contain. The system was built to answer the question of which version, and it answers that question completely. It was never built to answer whether this is the code that was reviewed.

This is the part worth naming before anything else. The idTech build did not fail because an engine subsystem broke. It behaved as designed. Version reference in, artifact out. The delegation of trust to a publicly accessible source, one ostensibly maintained by Id Software itself, was the architecture, not a deviation from it. When the idTech incident is described as a hack, that describes the outcome. The mechanism is more ordinary: a dependency management design that optimized for retrieving assets by version reference, irrespective of any ongoing validation of what those references now point to.

The assumption underneath this architecture is that trust, once granted, persists. A source is vetted once. It is added to a manifest, pinned to a version range, and from that point the build treats it as a known quantity. The model carries no notion of trust expiring. It carries a notion of trust being established and then referenced, again and again, on every build, without the establishing step ever running a second time. Establishment happens once. Reference happens forever.

There is a second assumption stacked on the first, and it is the more consequential one. Trust is treated as transferable across versions. When a manifest permits 2.4.1 and a later build resolves 2.4.2, the confidence that applied to the reviewed release is silently extended to a release no one has examined. Semantic versioning encodes intent about compatibility. It encodes nothing about integrity. The string that announces a minor update is a claim made by the source, and the build accepts the claim as though it were a verification. The number went up by a decimal, so the system treats the code as continuous with what preceded it.

The assumption was that a publicly accessible source, even one presented as maintained by Id Software, would consistently deliver unmodified code. Consistency of identity was treated as consistency of content. The name of the repository, the ownership of the namespace, the fact that yesterday’s fetch returned exactly what was expected: these became the grounds for tomorrow’s fetch. The trust model was not enforced on each retrieval. It was inherited. And an inherited trust is a trust that no longer carries the reason it was granted, only the record that it was.

What changed was not the capability of anyone attacking idTech. Nothing about the retrieval mechanism became weaker. The registries still resolved versions correctly. The checksums the tooling did compute still matched the artifacts that were actually delivered. Every component of the pipeline continued to function to specification. What changed was the validity of the assumption the specification rested on. The machinery was healthy. The premise had rotted underneath it.

A trust granted at one moment describes the state of a source at that moment. The build resolves that trust on the next run, and the run after that, and each resolution assumes the source has not moved since. That assumption holds until it does not. The moment the content behind a version reference differs from the content that was reviewed, the reference points at something new while carrying the confidence of something old. The system does not detect the substitution because the system was never measuring content. It was measuring reference, and the reference still resolves cleanly to an object. A correct answer to the wrong question.

That assumption no longer holds when trust is inherited from past states rather than re-established on each retrieval. The idTech build did not re-evaluate the source. It had no step that could. Semantic versioning gave it a way to request a specific release, and a lockfile gave it a way to pin a specific hash, but pinning a hash freezes a decision made once; it does not revalidate the source that produced the decision. The gap is not between a good version and a bad one. The gap is between the last time trust was actually established and every subsequent build that assumed it still was. Over time, that distance only widens, and nothing in the architecture is positioned to watch it widen.

The failure has no moment. There is no instant where a subsystem returns an error, no log line where the build announces that something is wrong. Observed from outside, the pipeline that produced the altered idTech build behaves identically to the pipeline that produced every clean build before it. The package manager receives a name and a version, contacts the registry, and returns an object. The checksum the tooling computes against that object matches, because the object delivered is the object hashed. The build links and compiles. Every externally observable signal reads as success, because success, as the system defines it, is resolution completing without error. Resolution completed. The mechanism of failure is that resolution completing is the whole of what the system was built to confirm.

Reference replaced validation at the only point where the two could have diverged. A build asks for idTech’s dependency by identifier and receives whatever content currently sits behind that identifier in the namespace. The registry’s answer is precise about one thing and silent about another. It is precise that this is the object published under the name 2.4.2 by the account that owns the namespace. It says nothing about whether that object is the code that was reviewed when the source was first trusted. The system accepts the precise answer as if it settled the silent question. Because the identifier resolves and the ownership resolves, the content is treated as validated. Identity of source stood in for integrity of content, and the substitution leaves no observable trace, because both produce the same result: an object, delivered, matching its own hash.

There was nothing to bypass. Defeating a control implies a control positioned in the path, checking content against an expectation and returning a verdict. The idTech retrieval path is not built around such a step. Semantic versioning provides a way to name a release. A lockfile provides a way to freeze the hash of the object resolved at one moment. Neither compares the object against the review that once justified trusting its source; the lockfile freezes the object, not the reason. So the altered content did not slip past a validator. It arrived through the front of a system executing its designed behavior exactly: version reference in, matching artifact out. What is called the compromise and what is called normal operation are, at the level of observable system behavior, the same transaction. The only difference is what was sitting behind the reference, and inspecting that difference was never part of what the transaction did.

The pattern is execution based on reference, not verification. A system holds a pointer to something it trusted once. On every subsequent use, it resolves the pointer, confirms the pointer resolves cleanly, and proceeds. The confirmation that the pointer is well formed and currently resolvable is mistaken for confirmation that the thing it points to still deserves the trust granted to an earlier state of it. The act of establishing trust and the act of referencing it collapse into one, and only the reference survives into the future. Everything the system does afterward rests on a decision it no longer re-examines.

The same mechanism operates in X.509 certificate validation, on the other side of the software supply chain entirely. When a client establishes a TLS session, it receives a certificate and checks that the certificate chains to a trusted root and falls within its validity window. Those checks confirm identity and timing. They confirm that a certificate authority, at some past moment, signed a binding between a name and a key, and that the present time sits between two dates. What they do not confirm is whether that binding is still valid now. A certificate can be revoked after issuance and before expiry. The mechanism that would establish current validity, OCSP or a certificate revocation list, is a separate step, and when a client omits it, the certificate is accepted on the strength of a signature that describes a decision made in the past. The client executes the connection on the reference. The certificate is a pointer to an act of validation performed once by the CA, and the client resolves the pointer without asking whether the act it points to still holds.

These are not two problems. They are one mechanism wearing two costumes. A package manager resolving a version and a TLS client resolving a certificate are doing the same thing: treating the clean resolution of a reference as evidence about the current state of what the reference names. In both, trust was real when it was established. In both, the system retained the pointer and discarded the conditions under which the pointer meant anything. The vulnerability is not in the registry or in the certificate authority. It is in an architecture that resolves trust once and references the result forever, and that architecture is not specific to idTech, to package management, or to any single vendor. It is the default shape of trust in systems built for scale, because re-establishing trust on every use is expensive and referencing it is nearly free. The system optimized for the cheap operation.

Trust is a measurement of a source at a single moment. A system that references that measurement on every build, every handshake, every retrieval, is reporting a past state as though it were the present, and calling the report a control.

The build resolves the source once. It does not resolve it again. The reference exists. The verification does not.

Share

Keep Reading

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.