Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

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;
      1. If outdated/missing: download, verify signature, replace on-disk file“.
    • QA.md:168-170 tests 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-431 says “our own site/repo; CDN later if needed”.
  • severity: high

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).

(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.

  1. 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.
  2. 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-458 shows 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.
  3. 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.
  4. New QA gates:
    • a downloaded artifact whose bytes do not match the signed entry’s sha256 is 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-151 already covers “any field”, and artifacts is a field — so this is consistent);
    • dev mode does not downgrade a download-time refusal.
  5. 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.