Skip to content

fix(eventstream): gate subscription on TrustChecker (PILOT-251)#5

Open
matthew-pilot wants to merge 1 commit into
mainfrom
openclaw/pilot-251-20260529-221059
Open

fix(eventstream): gate subscription on TrustChecker (PILOT-251)#5
matthew-pilot wants to merge 1 commit into
mainfrom
openclaw/pilot-251-20260529-221059

Conversation

@matthew-pilot
Copy link
Copy Markdown
Collaborator

What failed

handleConn in service.go added any connecting peer to the requested topic map without authorization. Any peer that completed the L6 key exchange (no pre-trust required) could connect to port 1002 and subscribe to any topic, leaking metadata and unencrypted content.

What this changes

After reading the subscribe envelope, handleConn now checks the peer against the TrustChecker (optional Deps.Trust). If the checker is present and the peer is not trusted, the subscription is rejected. If no TrustChecker is loaded, subscription is denied by default (fail-closed) — a missing trust subsystem is an unusual state, and the security-safe default is to reject.

Verification

  • go build ./...
  • go vet ./...
  • go test ./... -count=1 ✅ (20+ tests pass)
  • New test: TestBroker_HandleConn_SubscriptionRejectedByTrustGate covers the trust rejection path
  • Existing handleConn tests pass with an allow-all stub TrustChecker

Scope

 service.go                | 28 ++++++++++++++--
 zz_added_coverage_test.go | 82 +++++++++++++++++++++++++++++++++++++------
 zz_more_test.go           |  6 ++--
 zz_resilience_test.go     |  8 ++---
 zz_service_e2e_test.go    |  2 +-
 5 files changed, 106 insertions(+), 20 deletions(-)

Closes PILOT-251

handleConn previously added any connecting peer to the requested
topic map without authorization. Any peer that completed L6 key
exchange (no pre-trust required) could subscribe to any topic,
leaking metadata and unencrypted content.

Fix: after reading the subscribe envelope, check the peer against
the TrustChecker (optional Deps field). If the checker is present
and the peer is not trusted, reject the subscription. If no
TrustChecker is loaded, deny by default (fail-closed) — a missing
trust subsystem is an unusual state and the security-safe default
is to reject.

This mirrors the registryBound gate pattern from the handshake
plugin (PILOT-228, handshake/handshake.go:603/630): trust must be
established before automatic access grants.

Closes PILOT-251
@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🦾 Matthew PR Check — #5 PILOT-251

Status

  • State: OPEN · MERGEABLE ✅
  • CI: 2/2 green (test ✅, codecov/patch ✅)
  • Author: matthew-pilot
  • Branch: openclaw/pilot-251-20260529-221059main
  • Changes: 5 files · +106 −20
  • Created: 2026-05-29 22:11 UTC

What this does

Gates handleConn subscription in service.go on the TrustChecker. After reading the subscribe envelope, the handler now checks the peer against an optional Deps.Trust. If the checker is present and the peer is not trusted, the subscription request is rejected — preventing any peer that completes L6 key exchange (no pre-trust required) from subscribing to arbitrary topics.

Verdict

CLEAN — all CI green, mergeable, small focused change.

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🛠️ Matthew PR Check — #5 PILOT-251

State: OPEN · MERGEABLE
CI: 2/2 passing (test ✅, codecov/patch ✅)
Canary: not-configured (eventstream repo)
Jira: PILOT-251 — trust-gate for eventstream subscriptions
Author: matthew-pilot · Created: 2026-05-29 22:11 UTC

Verdict: CLEAN — all CI green, 106 additions / 20 deletions across 5 files. TrustChecker gate is fail-closed by default (deny if no checker loaded), with a new dedicated rejection-path test. No merge conflicts.

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🧬 Matthew Explains — #5 PILOT-251

What this does

Any peer that completes L6 key exchange (no pre-trust required) can connect to port 1002. Before this fix, added the peer to the subscription map unconditionally — anyone could subscribe to any topic (metadata leak + content exposure for unencrypted topics). This gates subscription behind .

File-by-file

service.go:69newBroker(deps.Events, deps.Trust)
Plumbs the optional TrustChecker into the broker at construction time.

service.go:149trust coreapi.TrustChecker field
New optional field on the broker struct. Nil means "no trust subsystem loaded."

service.go:233-253 — Trust gate in handleConn
After reading the subscribe envelope, checks b.trust.IsTrusted(sub.conn.RemoteAddr().Node). If the checker is present and the peer is NOT trusted → rejects with a warning log. If no TrustChecker is loaded → fail-closed: also rejects (the security-safe default; a missing trust subsystem is anomalous).

zz_added_coverage_test.go:66-98stubTrustChecker test helper
Configurable trust/deny for tests. newAllowAllTrustChecker() returns a stub that trusts everyone — used to keep existing handleConn tests passing without real trustedagents plumbing.

zz_added_coverage_test.go:702-740TestBroker_HandleConn_SubscriptionRejectedByTrustGate
New dedicated test: deny-all checker → subscription rejected, subs map stays empty after handleConn returns.

zz_added_coverage_test.go, zz_more_test.go, zz_resilience_test.go, zz_service_e2e_test.go — Mechanical call-site updates
Every existing newBroker(...) call updated to pass either newAllowAllTrustChecker() (tests that go through handleConn) or nil (unit tests of publish/rate-limit internals that bypass handleConn).

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🧬 Matthew Explains — #5 PILOT-251

What this does

Any peer that completes L6 key exchange (no pre-trust required) can connect to port 1002. Before this fix, handleConn added the peer to the subscription map unconditionally — anyone could subscribe to any topic (metadata leak + content exposure for unencrypted topics). This gates subscription behind TrustChecker.

File-by-file

service.go:69newBroker(deps.Events, deps.Trust)
Plumbs the optional TrustChecker into the broker at construction time.

service.go:149trust coreapi.TrustChecker field
New optional field on the broker struct. Nil means "no trust subsystem loaded."

service.go:233-253 — Trust gate in handleConn
After reading the subscribe envelope, checks b.trust.IsTrusted(sub.conn.RemoteAddr().Node). If the checker is present and the peer is NOT trusted → rejects with a warning log. If no TrustChecker is loaded → fail-closed: also rejects (the security-safe default; a missing trust subsystem is anomalous).

zz_added_coverage_test.go:66-98stubTrustChecker test helper
Configurable trust/deny for tests. newAllowAllTrustChecker() returns a stub that trusts everyone — used to keep existing handleConn tests passing without real trustedagents plumbing.

zz_added_coverage_test.go:702-740TestBroker_HandleConn_SubscriptionRejectedByTrustGate
New dedicated test: deny-all checker → subscription rejected, subs map stays empty after handleConn returns.

zz_added_coverage_test.go, zz_more_test.go, zz_resilience_test.go, zz_service_e2e_test.go — Mechanical call-site updates
Every existing newBroker(...) call updated to pass either newAllowAllTrustChecker() (tests that go through handleConn) or nil (unit tests of publish/rate-limit internals that bypass handleConn).

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.

1 participant