Skip to content

LCT binding level is self-declared: default sits in canon's HARDWARE band, level 5 has no evidence carrier #730

Description

@dp-web4

Summary

An LCT's hardware-binding level is a self-declared number that nothing derives, validates, or can
falsify
— and the struct cannot even express what canon requires to justify the top level. Two
concrete defects fall out, plus a semantic divergence between the code's ladder and canon's.

Raised by dp's key-custody ruling (2026-08-18, issue #729): "passphrase from environment … is NOT a
production-acceptable solution."
If custody posture is going to mean anything, the capability level
that advertises it must be derived from how the key is actually held, not set by whoever
constructs the struct.

Defect 1 — the code's default sits inside canon's HARDWARE band

web4-core/src/lct.rs:85-93:

impl Default for HardwareBinding {
    fn default() -> Self {
        Self {
            level: 4,
            description: "Software-bound keys (development)".into(),
            trust_ceiling: 0.85,
        }
    }
}

web4-standard/core-spec/lct-capability-levels.md §2.1:

Level Name Trust Tier
4 FULL 0.6–0.8
5 HARDWARE 0.8–1.0

0.85 is outside level 4's band and inside level 5's. Every LCT built with the default therefore
advertises a ceiling canon reserves for hardware-bound identity, while its own description says
"Software-bound keys (development)". Lct::trust_ceiling() (lct.rs:590) exposes it as an
accessor, so it is built to be consumed by relying parties.

Defect 2 — level 5 is unfalsifiable by construction

Canon §2.7 requires, for level 5:

  • binding.hardware_anchor — a valid EAT token
  • binding.hardware_type"tpm2", "trustzone", or "secure_element"

HardwareBinding (lct.rs:71-83) has exactly three fields: level, description,
trust_ceiling. There is no field for the anchor or the type. So an LCT can be assigned
level: 5 with no attestation attached and nothing anywhere can check the claim — the evidence canon
requires has no carrier. This is the same shape as a drift check that cannot fire: the indicator
exists, and nothing can make it false.

Searched web4-core/src, hub/hub-lib/src, hub/hub-daemon/src for CapabilityLevel|capability_level:
no hits. Nothing derives the level from anything.

Defect 3 — the two ladders disagree about what the levels mean

lct.rs:73-76 documents the scale as:

/// 0-3: None/Weak (testing only)
/// 4: Software (encrypted keys)
/// 5: Hardware (TPM/SE)

Canon documents the same 0–5 scale as STUB / MINIMAL / BASIC / STANDARD / FULL / HARDWARE, where
level 4 is "Society-issued with birth certificate" — a statement about provenance, not about key
storage. The code's ladder is a key-custody ladder; canon's is a capability/provenance ladder.
They collide at every level except 5.

This matters beyond tidiness: a consumer reading level: 4 cannot tell whether it means "encrypted
keys on disk" or "society-issued with a birth certificate", and those licence very different
decisions.

Requested

  1. Derive, don't declare. The binding level should be computed from how the key is actually held
    at the time the LCT is built (in-memory / env-fed / file / OS keystore / hardware-attested), not
    passed in. Where derivation is impossible, the level should be the lowest consistent with the
    evidence, not the most flattering.
  2. Give level 5 its carrier: hardware_anchor and hardware_type fields per canon §2.7, and
    refuse to construct level 5 without a well-formed anchor. A level with no evidence field cannot be
    audited.
  3. Fix the default — level 4 with trust_ceiling: 0.85 is out of band. Either the default drops
    into 0.6–0.8, or the default level changes, or canon's band moves; the three must agree.
  4. Reconcile the two ladders (this issue's canon half). Decide whether 0–5 is capability or key
    custody. If both are wanted they are two axes and need two fields — which is the same lesson as
    the interface-planes work (canon: specify interface planes (fact planes x exposure classes) #727): orthogonal things wearing one name drift silently.
  5. A hub whose vault key is env-fed MUST NOT advertise level 5 (dp's ruling, key custody tier 1: TPM/secure-module sealing is specified everywhere and implemented nowhere #729).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions