Add optional Ed25519 pubkey pinning to trusted-agents (H4)#21
Merged
Conversation
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 Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_idinteger with no pubkey binding. Taking over any of the ~436 trustednode_ids — or a registry that maps a trustednode_idto 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 bynode_id; if the entry has a pin, requirespubKeyto equal it viacrypto/subtle.ConstantTimeCompare; if the entry has no pin, falls back tonode_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.Load/SetForTestdecode the pin at load time; a malformedpublic_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.IsTrustedWithKeyadapter on both the real andno_trustedagentsstub services (fail-closed in the stub).IsTrustedunchanged; 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 callshm.rt.IsTrusted(peerNodeID)through thecommon/coreapi.TrustCheckerinterface. The authenticated peer key is in scope there asmsg.PublicKey(already stored into theTrustRecord), so enforcement is feasible — but it requires two upstream changes:common/coreapi.TrustChecker: addIsTrustedWithKey(nodeID uint32, pubKey []byte) (string, bool).protocol/plugins/handshake: swap the auto-accept call tohm.rt.IsTrustedWithKey(peerNodeID, msg.PublicKey).This is documented as a
TODO(H4 wiring)onService.IsTrustedWithKey. Until it lands, pins are stored and validated on load but enforcement at auto-accept still routes throughIsTrusted— safe, because every shipped entry is unpinned.Validation
GOWORK=off go build ./... && go vet ./... && go test -race ./...all pass;gofmtclean.Follow-up to actually enforce pinning
trustedagentsversion pin in web4 (this PR does not touch web4).common+protocol).public_keyvalues to high-value entries intrusted-agents.json(not invented here).