ADR-004's "hash mismatch is a warning" applied to the update path makes artifact verification decorative
Источник:
dev/fera-research/docs/oc1/021-downloaded-artifact-hash-warning-is-decorative.md(read-only; сгенерировано scripts/sync-docs.mjs)
ADR-004’s “hash mismatch is a warning” applied to the update path makes artifact verification decorative
Section titled “ADR-004’s “hash mismatch is a warning” applied to the update path makes artifact verification decorative”- kind: risk
- area: trust
- evidence:
ADR.md:104-107(ADR-004) — “On module load, if the on-disk hash doesn’t match the trusted manifest entry, show a non-blocking warning … not a refusal to run.”ADR.md:109-113— the rationale: user freedom to self-modify; “The trust manifest signature (ADR-003) is the real security boundary against supply-chain tampering of the manifest itself — the hash check alone is trivially bypassable”.ADR.md:442-446(ADR-024) — “Developer mode: unsigned/local modules are an explicit opt-in developer mode”; and:455-458— “Developer mode is the only path where hash-mismatch-as-warning becomes a routine state rather than a tamper signal”.- The update path —
ADR.md:426-431— “background poll every few hours- an explicit in-app ‘check now’ button + a lazy check at startup”;
hosting “the same infrastructure as the signed manifest”; plus
TECHSPEC.md:405-411: “1. Read local trust manifest cache (or fetch/verify a fresh one…); 2. Check installed core-module version;
- If outdated/missing: download, verify signature, replace on-disk file“.
- an explicit in-app ‘check now’ button + a lazy check at startup”;
hosting “the same infrastructure as the signed manifest”; plus
QA.md:168-170tests only that no plaintext user-editable config can set the update endpoint; no gate tests that a downloaded artifact whose hash does not match the signed entry is refused.- Nothing in the corpus specifies the transport for artifact downloads
(HTTPS-only? certificate pinning? signed index?).
ADR.md:430-431says “our own site/repo; CDN later if needed”.
- severity: high
Суть / Substance
Section titled “Суть / Substance”ADR-004 is a deliberate, well-argued user-freedom decision and it is correct for locally modified modules. The problem is that the corpus applies it with no distinction to the update path, where the threat is different in kind:
| Local modification (ADR-004’s case) | Update download | |
|---|---|---|
| Who acts | The user, deliberately, on their own machine | A network attacker, at scale, automatically |
| Intent | “I broke my own build” | “Execute code in every Fera install” |
| Cadence | Once, observed | Every few hours (ADR.md:427) |
| Blast radius | One machine | Every install that polls |
| Detection | The user knows | Silent |
With the current rules the update path works like this: fetch the signed
manifest (verified — good), read artifacts[].sha256, download the
artifact, compare the hash, find a mismatch, show a warning, and load it
anyway. So the hash comparison is performed and its result is then
deliberately ignored on the one path where a mismatch means compromise
rather than experimentation. The signature still protects the manifest,
which is genuinely valuable (it stops an attacker from redirecting the
endpoint) — but it does not protect the bytes that actually get
executed. An attacker who can tamper with artifact delivery (CDN
compromise, DNS/BGP hijack, a compromised mirror — ADR.md:430-431 allows
mirroring) gets code execution in every polling client, and the user sees a
warning they’ve been trained to ignore.
The corpus also makes this easier than it should be: ADR-024 permits
mirroring and a CDN, ADR.md:85-87 already treats the manifest as
mirrored, and the dev-mode design (ADR.md:442-446) means a user who has
ever flipped the toggle has hash warnings as a routine state — the exact
condition under which this warning loses all signal value.
There is a second-order issue worth stating: ADR.md:109-113’s reasoning
(“an attacker who controls the manifest trivially bypasses the hash check,
hence the manifest must be signed”) is correct but only if the hash
mismatch is enforced. Under a warning-only policy the chain is: signed
manifest → hash → ignored → executed. The corpus’s stated security
argument depends on a check it has decided not to act on.
Варианты решения (tradeoffs)
Section titled “Варианты решения (tradeoffs)”- (A) Split the policy by provenance: enforced on the update path, warning-only on the local path. A hash mismatch on a freshly downloaded artifact = hard refuse, delete, retry, report. A hash mismatch on a locally present module the user may have edited = warning (ADR-004 unchanged). This preserves ADR-004’s purpose exactly while removing the decorative check where it is dangerous.
- (B) Keep warning-only everywhere, but require HTTPS + certificate pinning for downloads. Fixes transport, not the logic; pinning has operational cost (rotation, CDN changes) and does nothing if the CDN origin itself is compromised.
- (C) Verify after load in a sandboxed helper process. Defends against a bad artifact even when the hash is ignored, at the cost of a verification process — a big design addition, and it does not help first-party native modules (ADR-002: no sandbox by design).
- (D) Pin the manifest’s artifact list and download only from manifest-declared URLs with a strict allowlist. Good hardening, compatible with (A).
Предлагаемое решение
Section titled “Предлагаемое решение”(A) + (D), as an ADR-004 amendment plus one new QA gate. This is a small text change with a large security effect, and it preserves the project’s explicit design intent (user freedom to modify their own installation) rather than trading it away.
- Amend ADR-004 with a provenance distinction: provenance = freshly downloaded from the update channel vs already on disk. Mismatch on download → refuse, do not write, report, and count it against the manifest key (possible tampering). Mismatch on a local file → today’s warning. State plainly which half of ADR-004 survives and why.
- Amend ADR-024’s developer mode to say that dev mode suppresses the
local warning only, and explicitly cannot suppress a download-time
refusal — otherwise dev mode is a one-click way to re-enable the
dangerous state, which
ADR.md:455-458shows is already the design’s weak point. Consider requiring dev mode to be per-module and session-scoped (the OQ-020 question, still open) so it cannot arm the whole installation. - Require artifact URLs to be manifest-declared (no URL construction outside the signed entry), so the signed manifest is genuinely the only channel — this is what makes the hash meaningful.
- New QA gates:
- a downloaded artifact whose bytes do not match the signed entry’s
sha256is refused, never loaded (today: untested, and the current behaviour is to load); - a tampered
artifacts[]URL in an unsigned manifest edit is rejected by the whole-entry signature check (QA.md:149-151already covers “any field”, andartifactsis a field — so this is consistent); - dev mode does not downgrade a download-time refusal.
- a downloaded artifact whose bytes do not match the signed entry’s
- One line in the threat model: the corpus’s existing model is manifest-supply-chain; this adds artifact-delivery, which ADR-024’s “CDN later if needed” makes a real category. Whoever adds the CDN must re-read this decision.