Skip to content

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

Lattice defines signature verification but never defines a trust anchor — lttc verify has no key to verify against

Источник: dev/fera-research/docs/oc5/lattice-no-trust-anchor-defined.md (read-only; сгенерировано scripts/sync-docs.mjs)

Lattice defines signature verification but never defines a trust anchor — lttc verify has no key to verify against

Section titled “Lattice defines signature verification but never defines a trust anchor — lttc verify has no key to verify against”
  • kind: risk
  • area: trust / modules / security
  • severity: mid
  • evidence:
    • ~/dev/fera/lattice/SPEC.md:517-534 (§5.4, frozen) — full signature mechanics: key_id = BLAKE3("lttc-key-v1" || ed25519_pubkey), sig_input = BLAKE3-keyed(context = "lttc-sig-v1") over "lttc-slot" || slot_index || subtree_name || superblock[0..160] || super_node_digest, signed with Ed25519
    • SPEC.md:701-714 (§10.1, frozen) — disagreement policy, “fail closed”, LttcError::SignatureDisagreement
    • SPEC.md:793-796 (§11 CLI) — lttc verify <plugin.lttc> — “verify superblock, manifest, Merkle root, signatures; exit 0 on success”
    • SPEC.md:17 — “use it, with or without native code, under its own trust rules”
    • ~/dev/fera/lattice/crates/lttc-format/src/error.rs:31-36 — SignatureInvalid { slot }, SignatureDisagreement { key_ids, digests } — both implemented as error variants
    • grep of SPEC.md for trust root / trusted key / key store / keystore / revocation / revoke / TOFU / trust anchor / pre-pinned / known key / embedded key / out-of-band → zero hits
    • ~/dev/fera/lattice/OPEN-DECISIONS.md — Round 1 closed, all decisions moved to SPEC.md §14; no trust-model question was ever opened
    • contrast: ~/dev/fera/project-design/context/ADR.md:81-87 (ADR-003) — the host does own a trust model (“A root Ed25519/SSH keypair (ours) is embedded in the launcher binary”; “Users manage a local trusted-key list”)

The Lattice container format is cryptographically thorough. Domain separation per slot, replay protection between subtrees, a frozen fail-closed disagreement policy, a detached-signature mode that binds the blob to the specific container (§5.4, “a valid detached signature from a different container cannot be replayed”), and reader rules ordered so cheap arithmetic rejects precede any hash or signature work. On the mechanics this spec is better than most shipping container formats.

What it does not define, anywhere, is the one question that makes any of it mean anything: which keys does a reader trust?

Concretely, lttc verify <plugin.lttc> (§11) is specified as “verify … signatures; exit 0 on success”. Verification requires a public key. The container carries a key_id — which, per §5.4:521, is BLAKE3("lttc-key-v1" || ed25519_pubkey), i.e. a digest of a key the container names. That is an identifier, not an authority. A reader that “verifies” by looking up the key the container itself supplies verifies nothing at all: an attacker generates their own keypair, packages it with their content, and the signature checks out.

Every one of the following greps returns nothing in SPEC.md: trust root, trusted key, key store, keystore, revocation, revoke, TOFU, trust anchor, pre-pinned, known key, embedded key, out-of-band. And OPEN-DECISIONS.md — the file whose entire purpose is to hold unresolved trust questions — has no such entry. The question was never asked.

This is not an oversight in the sense of a missing paragraph. It is a correct scoping decision made implicitly and never written down: Lattice is a container format, and its job is to be verifiable against whatever key policy the host has. The host’s policy is ADR-003’s. SPEC.md:17 gestures at this — “under its own trust rules”.

The problem is the seam has no name and no test, and the spec’s own language pulls the reader toward the wrong reading. §5.4 and §11 say “verify signatures” without ever saying “against a caller-supplied trusted key set”. LttcError::SignatureInvalid { slot } is implemented as if a single trusted key exists. And §10.1’s disagreement policy compares two signatures against each other — which is a consistency check between the container’s own signature and the ADR-021 manifest signature, again both named by content the container references.

So the concrete risk is an implementation trap, not a hole in the format: the first person to write lttc verify will implement the only thing the spec describes — pull the key from the container (or from a hardcoded test vector), verify, exit 0 — and ship a verifier that passes for attacker-generated bundles. And lttc verify is exactly the command a user or a CI job runs to decide “is this plugin safe?”.

Secondary point: there is no revocation story in Lattice. Even with a correct host keyring, the format has no way to express “this key was compromised on date X”, so revocation is entirely the host’s problem (ADR-021 does specify “Key revocation, keyed by the existing key_id” — so the host has it, and Lattice inherits it. Fine — but again, nowhere in the spec is that inheritance stated).

  1. State the scoping explicitly in §5.4. One sentence: “Lattice defines verification only. The caller supplies a trusted key_id → ed25519_pubkey map; a container naming a key absent from that map MUST fail with SignatureInvalid { slot }. Verification against a key supplied by the container is not verification.” Cost: ~1 paragraph. Kills the trap.
  2. Make it normative in the reader’s parse order. Add a reader rule beside §3.2 step 9 (“Signature verification”) requiring the key map as an input to lttc verify. Cost: makes the CLI take a --trusted-keys argument, which is the honest interface anyway.
  3. Add a trust-model entry to OPEN-DECISIONS even if the answer is “delegated to the host” — so the next reader sees it was decided rather than missed. Cost: trivial. Value: high, because the absence currently reads as an oversight and will be “fixed” by an implementer who assumes it.
  4. Bundle a trust store into Lattice. Wrong at this layer: it would duplicate ADR-003 and create two keyrings (which is the failure mode in trust-tier-flat-keyring-authority-collapse.md).

Options 1 + 3, and 2 if the CLI is built soon.

  • Add to §5.4 a normative “Trust anchor” paragraph stating that verification is parameterized over a caller-supplied trusted-key map, that a key_id not present in that map is SignatureInvalid, and that the format performs no trust establishment of its own.
  • Add to §10.1 (or a new §10.3) the note that revocation is the host’s concern, keyed on key_id per ADR-021 — so the two specs reference each other explicitly instead of by coincidence.
  • Open an OPEN-DECISIONS entry recording the scoping decision, so the next implementer reads “decided: host-supplied” rather than “unspecified, therefore my problem”.
  • Add a CI gate: a test that a container signed by a freshly generated, unknown key must fail lttc verify against a trust store that does not contain it. Without such a test, any verifier built from this spec will pass its own test suite while accepting attacker bundles — the tests would use the same key to write and verify.

Note: severity is mid rather than high because the Fera host does have a real trust model (ADR-003) and QA.md already gates “the trust manifest itself is signature-verified before any of its contents are trusted” (QA.md:135-137). The exposure is the standalone tool and any future second host — which is exactly what “other hosts: their choice” (SPEC.md:713) invites.