Skip to content

Add optional Ed25519 pubkey pinning to trusted-agents (H4)#21

Merged
TeoSlayer merged 1 commit into
mainfrom
feat/optional-pubkey-pinning
Jun 22, 2026
Merged

Add optional Ed25519 pubkey pinning to trusted-agents (H4)#21
TeoSlayer merged 1 commit into
mainfrom
feat/optional-pubkey-pinning

Conversation

@TeoSlayer

Copy link
Copy Markdown
Contributor

What

Adds optional per-agent Ed25519 pubkey pinning to the trusted-agents auto-approve allowlist, addressing security audit finding H4.

Today the allowlist keys trust purely on the node_id integer with no pubkey binding. Taking over any of the ~436 trusted node_ids — or a registry that maps a trusted node_id to an attacker key — inherits full auto-approve trust. This PR makes per-entry pubkey pinning possible and enforced, fully backward-compatibly.

Changes

  • Agent.PublicKey (json:"public_key,omitempty"): optional base64 std-encoded Ed25519 key. All 436 current entries have none, so nothing changes for them.
  • IsTrustedWithKey(nodeID, pubKey) (string, bool): matches by node_id; if the entry has a pin, requires pubKey to equal it via crypto/subtle.ConstantTimeCompare; if the entry has no pin, falls back to node_id-only trust (current behavior) and logs the unpinned match at debug.
  • IsTrusted(nodeID) unchanged — still the key-less node_id-only check for callers without a peer key in scope.
  • Loader hardening: Load/SetForTest decode the pin at load time; a malformed public_key (bad base64 or wrong length) fails the whole load rather than silently degrading to unpinned (which would re-open H4 for that agent).
  • Service.IsTrustedWithKey adapter on both the real and no_trustedagents stub services (fail-closed in the stub).
  • Tests: pinned+correct ⇒ trusted; pinned+wrong ⇒ NOT trusted; pinned+empty/short ⇒ NOT trusted; unpinned ⇒ trusted by node_id (backward compat); unknown node ⇒ not trusted; IsTrusted unchanged; Load round-trip + bad-pin rejection; Service delegation. All green under -race.

Inbound auto-accept wiring (NOT done here — needs upstream)

The inbound auto-accept call site is not in this module. It lives in protocol/plugins/handshake/handshake.go (~L639), which calls hm.rt.IsTrusted(peerNodeID) through the common/coreapi.TrustChecker interface. The authenticated peer key is in scope there as msg.PublicKey (already stored into the TrustRecord), so enforcement is feasible — but it requires two upstream changes:

  1. common/coreapi.TrustChecker: add IsTrustedWithKey(nodeID uint32, pubKey []byte) (string, bool).
  2. protocol/plugins/handshake: swap the auto-accept call to hm.rt.IsTrustedWithKey(peerNodeID, msg.PublicKey).

This is documented as a TODO(H4 wiring) on Service.IsTrustedWithKey. Until it lands, pins are stored and validated on load but enforcement at auto-accept still routes through IsTrusted — safe, because every shipped entry is unpinned.

Validation

GOWORK=off go build ./... && go vet ./... && go test -race ./... all pass; gofmt clean.

Follow-up to actually enforce pinning

  1. Release a tagged version of this module with the new API.
  2. Bump the trustedagents version pin in web4 (this PR does not touch web4).
  3. Land the two upstream wiring changes above (common + protocol).
  4. Add real public_key values to high-value entries in trusted-agents.json (not invented here).

Each agent entry may carry an optional base64 public_key that pins the
node_id to a specific Ed25519 key. IsTrustedWithKey enforces the pin
with a constant-time compare when present and falls back to node_id-only
trust when absent, so the change is backward-compatible with every entry
shipped today. IsTrusted is unchanged for key-less callers.

Closes audit finding H4 (node_id-only trust lets a takeover of any
trusted node_id inherit auto-approve). Inbound auto-accept enforcement
needs upstream wiring; documented as a TODO at the Service call site.
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@TeoSlayer TeoSlayer merged commit 32ef4c2 into main Jun 22, 2026
5 checks passed
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.

2 participants