ADR-0033: An export verifies its components, and leaves the records to do it¶
Date: 2026-10-07. Status: accepted. Card: KAN-1837. Builds on ADR-0010, ADR-0026, ADR-0031.
Context¶
An export copies the exact bytes of every component it uses (KAN-1886) and bakes in each component's
digest, but nothing compared the two: the README said so. An exported project cannot be revoked
remotely and no longer has a kampong.lock reviewer in front of it, so the only party who can notice a
changed component is the project itself, and the person who holds it.
Decision¶
- Startup re-verification.
src/components.generated.tsre-hashes every file undercomponents/with a vendoredruntime/component-verify.tsand compares with the baked digests before the dispatcher exists. A mismatch throws aComponentVerificationErrornaming each component and, from the baked per-file hashes, the files that were changed, added or missing. Nothing runs. - The digest is the registry's. The check mirrors the engine's directory hash (sorted
path\0sha256(file)lines;node_modulesand.DS_Storeskipped; symlinks and non-regular files refused), so an export verifies to the digestkampong lockpinned. It is hand-vendored, with no engine counterpart, and a unit test asserts the two agree. - Records. An export that carries components also writes
kampong.lock(the filekampong lockwrites: digest and permissions perid@version) andsbom.json(CycloneDX 1.5: each component with its digest and kind, each file's SHA-256, and the moduledepsaspkg:npmentries). Both are deterministic: no timestamp, no serial number. -
npm run verifycheckscomponents/againstsbom.json, a record independent of the digests baked into the code, so changing one of them is not enough to hide a change. -
Nothing unhashed can supply code. Digests skip
node_modules, but a bare import inside a component resolves from anode_modulesnext to it first, and apackage.jsonabove it changes how.jsloads. The exported check therefore fails on anynode_modulesinside a component and on any entry undercomponents/that was not exported. The exporter refuses files at those paths. .gitattributes(components/** -text) keeps line-ending conversion on a clone from changing the hashed bytes.
Not covered¶
- Signing and provenance of the export itself (the tarball or image), and Sigstore: KAN-1838.
node_modules: dependencies are exact-pinned inpackage.jsonand installed by npm; verifying them is npm's lockfile integrity, not this check.- Time of check to time of use. Components are verified once at startup, and a module is imported on its first call: a file changed while a server is up, before that call, is not noticed until the next start. Preloading the entries inside the check would narrow this. The owner of the machine is not the threat this addresses.
- Someone who can edit
src/can remove the check. It protects against a swapped or altered component folder, not against an edited project.