Skip to content

fix(mesh): a peer's claim about its own trustworthiness is not evidence (#1253) - #1278

Merged
dc0sk merged 1 commit into
mainfrom
fix/1253-mesh-peer-asserted-trust
Sep 5, 2026
Merged

fix(mesh): a peer's claim about its own trustworthiness is not evidence (#1253)#1278
dc0sk merged 1 commit into
mainfrom
fix/1253-mesh-peer-asserted-trust

Conversation

@dc0sk

@dc0sk dc0sk commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Closes #1253.

The defect

wire_trust_level mapped a PeerQueryResponse's trust_state = 0x00 to TrustLevel::Verified and
wrote it straight into the peer cache that trust_filter_allows consults:

0x00 => TrustLevel::Verified,

That byte is the responder's claim about itself, carried in an envelope this node does not
authenticate before importing. So a station could assert its own trustworthiness and thereby satisfy
the operator's strict relay policy — the policy that exists precisely to decide whose third-party
traffic this station retransmits. The operator-configured policy was being enforced against a
peer-supplied value.

Found by a verifier while refuting a neighbouring audit finding about unknown-code catch-alls. The
catch-all was harmless (Reduced is the most restrictive level the enum has); this was a different
byte, in the opposite direction.

The fix is a deletion, not a correction

There is no honest mapping from a self-assertion to a trust level, because this crate has no local
trust store to resolve the claim against
— verified: no TrustStore reference anywhere in
crates/openpulse-mesh/src/, and no such dependency in its Cargo.toml. A function whose only
correct answer is one constant is not a mapping, so wire_trust_level is deleted rather than
corrected, and every peer-asserted state imports as Unknown.

The consequence, and why the node still starts

With every import Unknown, TrustedOnly can never be satisfied — so strict relays nothing.

The node warns loudly and still runs rather than refusing to start. That follows the maintainer's
standing ruling: untrusted is this project's default for synchronized operation including relay and
mesh
, and operators restrict upward by accepting trust levels, not by the protocol refusing to
run. The operator asked for a restriction this crate cannot implement; the honest response is to run,
relay nothing, and say why — naming the fix (balanced, or the full daemon, which resolves trust
against the signed handshake).

Tests — unit tests, deliberately

The properties are about peer_cache and trust_filter_allows, both private. My first draft was an
integration test that added peer_trust_level and relay_allowed_for to the public API to reach
them — which is the "public API for an instrument" shape this project bans and #1271 is currently
about. That draft was deleted rather than shipped.

Three tests in mod peer_asserted_trust_tests, driving the real receive path (step
handle_peer_query_response) with a frame injected over a loopback backend:

  • every claimed state — 0x00, 0x01, 0x02, 0x03, 0xFF — imports as Unknown, with a
    positive control first that the import path ran at all, so the assertion cannot pass because
    nothing was imported;
  • a self-vouching peer does not satisfy TrustedOnly;
  • strict refuses even a peer we genuinely heard, with a balanced control so it cannot pass in
    a build where relaying is broken outright.

All three fail against the restored defect, each on its own assertion
(trust_state = 0x00 must import as Unknown; a peer's claim about itself is not evidence); restored
by sha256sum.

Fixture note: the response carries an empty descriptor_signature, and that is part of the
point — the import path never verifies it, which is exactly why the trust_state beside it cannot be
treated as evidence. (A 64-byte one also pushes the envelope past one 255-byte frame.)

Gate

suites=324 tests_passed=2456 tests_failed=0
GATE: PASS c5650a694874f0c74efc4f6b341b043c250c6468 clean 20260905T080313Z

Docs

architecture.md's TrustFilter table now says the filter is only as good as where the cached level
came from; openpulse-manual.md warns that strict relays nothing in this binary and names the
alternatives.

Compounds with #1251 — same binary, and since that change openpulse-mesh has no route to a sound
card at all, which bounds the on-air exposure but not the correctness.

🤖 Generated with Claude Code

https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6

…ce (#1253)

`wire_trust_level` mapped a PeerQueryResponse's `trust_state = 0x00` to `TrustLevel::Verified` and
wrote it into the peer cache that `trust_filter_allows` consults. That byte is the responder's
claim ABOUT ITSELF, carried in an envelope this node does not authenticate before importing — so a
station could assert its own trustworthiness and thereby satisfy the operator's `strict` relay
policy, which exists precisely to decide whose third-party traffic we retransmit.

The mapping is deleted rather than corrected: there is no honest mapping, because this crate has no
local trust store to resolve the claim against (no TrustStore reference, no such dependency). A
function whose only correct answer is one constant is not a mapping. Every peer-asserted state now
imports as Unknown.

Consequence: `TrustedOnly` can no longer be satisfied, so `strict` relays nothing. The node warns
loudly and still runs rather than refusing to start — untrusted is this project's default for
synchronized operation including relay and mesh, and operators restrict upward by accepting trust
levels, not by the protocol refusing to run. The operator asked for a restriction this crate cannot
implement; the honest response is to run, relay nothing, and say why.

Tests are UNIT tests deliberately: the properties are about `peer_cache` and `trust_filter_allows`,
both private, and exporting accessors so an integration test could reach them is the "public API
for an instrument" shape #1271 is about. The first draft did exactly that and was deleted.

Results: 3/3 pass, and all three FAIL against the restored defect, each on its own assertion;
restored by sha256sum. Whole crate green.

Implements: REQ-SEC-09

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6
@dc0sk
dc0sk merged commit 6dff9bb into main Sep 5, 2026
5 of 6 checks passed
@dc0sk
dc0sk deleted the fix/1253-mesh-peer-asserted-trust branch September 5, 2026 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mesh imports a peer's self-asserted trust_state=0x00 as TrustLevel::Verified into the cache its relay policy enforces against

1 participant