Skip to content

Socket admission: identity binding, chi-scoped delivery, and revocation #66

Closed

Description

@adiled

Follow-up to #65, which enforced the protoVersion half of the socket admission contract. The rest is unwritten.

Why this exists

A bee reaches humd over $XDG_RUNTIME_DIR/hum/thrum.sock. After #65 it must declare a compatible protocol version. That is the whole of what is checked.

Everything below the version line is absent.

1. Declared chis filter nothing

A hello's chis list is parsed into manifest.chis, gossiped to the ensemble as advertised vocabulary, and then never read again. Delivery to a connected client is not filtered by it.

So a bee declaring three chis receives every tone the nest sends it. And a bee declaring none is treated identically. The field is descriptive metadata pretending to be a capability statement.

Wanted: chis becomes an admission scope. A client receives only tones whose chi it declared. Open question — what a bee must declare to receive a chunk in a session it never opened, and whether an empty chis means "nothing" or "everything".

Blocker to resolve first: Sim::inject_tone bypasses the socket and exists to drive shapes directly. Synthetic clients must be distinguishable from real ones before any delivery gate can be added, or the sim suite loses the ability to inject tones without a hello.

2. Identity is self-asserted

HumdId on the socket path is whatever the hello claims. There is no challenge, no signature, no proof of possession.

Any local process can claim to be any bee. For a bee holding a key that authorises chain writes, or that receives settlement tones, that is a confused-deputy problem, not a theoretical one.

The primitives already exist for the peer path and should be reused rather than reinvented — HumdKey around ed25519_dalek::SigningKey, HumdId = sha256(pubkey), domain-separated handshake_message with a signed timestamp and skew tolerance, HelloParse::{Verified, Unsigned, Invalid}, and Ensemble::with_strict_auth. The socket does not use any of them.

Wanted: the socket admits on a signature, not a claim.

3. Nothing is revocable

HumdId is a hash of a public key. That is fine for pseudonymity and useless for accountability: there is no name, no authority, and no way to withdraw a peer that behaved badly. HumdRegistry exists on-chain (ensemble/src/onchain.rs, IHumdRegistry) and is currently used for optional manifest anchoring.

Wanted: a revocation root for bee identity. Note this is the primitive the README's "zero-trust access control" row already claims and does not have.

4. The 60-second skew window is a replay window

The peer handshake accepts signed_at within 60s. Over an unauthenticated socket that is a comfortable replay budget. Constrain it once the socket has a key to bind it to.

Explicitly out of scope

Routing integrity. find-the-worker selects a worker from gossiped manifests with no distance proof, so a peer that gossips a false manifest influences where prompts are sent. That belongs to routing, not admission, and needs its own issue.

Ordering

1 is independent and can land on its own. 2 is the one that matters — it is what makes the socket safe for a bee that holds authority. 3 and 4 follow from 2.

Activity

  1. adiled commented on Oct 4, 2026

    @adiled
    OwnerAuthor

    Revising this after reading the code properly rather than grepping it. Two corrections, one addition, and this issue is now partly wrong about scope.

    Correction: routing is no longer out of scope

    The "Explicitly out of scope" section is stale. #67 fixed part of it, and the rest is a bigger deal than I implied there.

    find-the-worker routes on a self-declared gossiped claim. #67 adds a provenance gate (an announce must claim the same Hid as its tone origin) and makes remote routing opt-in via HUM_TRUST_REMOTE_WORKERS, default off. That stops impersonation and stops a claim alone from moving prompt content — it does not solve capability routing.

    The real finding is a structural split:

    • Kademlia — 645 lines, kad_find at ensemble/src/lib.rs:524, full iterative lookup with alpha batches and LookupShortlist. Peers here come from signed HumdAddr. Has no notion of a model or capability claim (grep for models/worker in kad.rs returns nothing).
    • Gossip — carries capability claims with no binding to a verified identity.

    pick_remote_worker (humd/src/lib.rs:527) reads the second. kad_find has no caller outside ensemble/tests/kad_integration.rs. Both key on Hid, so they collide in name only.

    Worse, one layer up: handshake_message is domain:hid:signed_at. The signature covers identity only — nests, hosts, can_relay, free_slots, and headroom are parsed after verification and never covered. So any peer completing a signed handshake can over-claim relay rights or capacity, and those feed real decisions. Model claims are the same defect in the layer above.

    Kademlia proves location, not capability — closest_to("who has model X") is still a self-declared claim. Proof-of-possession or stake is required and does not exist in any form.

    Correction: strict_auth has a default-off posture

    Section 2 says the socket uses none of the signing primitives. True, but the peer path is not uniformly protected either — sim spawns with with_strict_auth(id, false), and production does too unless configured. Under lax auth, Hid is self-declared with no signature.

    The cheap next win

    Extend handshake_message to cover nests/can_relay/free_slots, then have announces carry that signature. Closes relay and capacity over-claiming for a small diff. Model claims still need proof-of-possession.

    Sections 1–4 stand. Want me to split the routing half into its own issue so this one stays about socket admission?

  2. adiled commented on Oct 18, 2026

    @adiled
    OwnerAuthor

    All four pieces landed on socket/admission-integrity (PR #70).

    1. Declared chis filter nothing — done (cd15d0b)

    Broadcast now respects declared scope. Both the sigil pass and the no-owner fallback filter on it.

    The blocker was resolved without solving it as described. The issue says synthetic clients must be distinguishable before a delivery gate can be added. Rather than tag them, I made an empty chis mean unfiltered — every bee predating the field sends an empty set, so refusing it traffic would be a wire break dressed as a security fix. That answers the issue's open question directly and means the sim suite keeps injecting tones without a hello. thrum_all is scoped too; it has no callers today, but leaving it open would make it a bypass for whoever reaches for it next.

    2. Identity is self-asserted — done (fb11231)

    Bees already held an Ed25519 key whose pubkey hashes to the hid they already send. It was never used to sign anything on this path.

    Signs pubkey/signedAt/signature; humd verifies against the claimed hid under its own domain, so a peer-handshake signature cannot be replayed onto a socket hello. Unsigned stays admitted, marked hid_verified: false. Present-but-wrong is a hard reject.

    3. Nothing is revocable — done (721e817, f0de28c)

    Operator lists hids; humd refuses them at hello — closed, error echo, not registered, not announced. Checked whether or not the bee proved the hid, because dropping the signature is the obvious evasion. humctl revoke|unrevoke|revocations.

    Two corrections to the issue's premises:

    • HumdRegistry has no callers anywhere in the tree. It is not "currently used for optional manifest anchoring" — it sits behind an onchain feature flag with zero production references. There was nothing to hook into, and on-chain revocation is a contract change regardless.
    • Revocation here is local, not mesh-wide. A bee refused by this daemon can still reach another. Closing that needs the registry to carry revocations.

    4. The skew window is a replay window — closed as a consequence of 2

    The peer path already enforced 60s; the socket path does now. Both bind it to a key.

    Rule 0 found a coverage hole worth recording

    Removing the hid/pubkey match check failed nothing. It protects against a valid signature over a hid the signing key does not own, and no test isolated it — the two tests nominally covering it were caught downstream by the signature check instead. Now there is a test that fails when that gate is removed. Every gate in this work has been checked the same way.

    Tests

    Workspace 381 → 404, 0 failures. Each gate neutered in turn and confirmed to fail.

    Not included

    • No hive sends a signature yet. Hives are excluded from the workspace and build hum-identity from git main, so sign_socket_hello does not exist for them until this merges. Wiring paid-oracle was attempted and reverted rather than ship code I could not compile.
    • A revocation takes effect on daemon restart, not immediately. Deliberate — re-reading per hello puts disk on the admission path — and humctl says so.
    • Routing integrity stays out of scope, as written.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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