fix: add HMAC-SHA256 signing to outbound rendezvous webhook POSTs (PILOT-239)#8
Conversation
…LOT-239) Add crypto/hmac, crypto/sha256, encoding/hex imports. Add secret field to dispatcher struct. Compute HMAC-SHA256 of body in post(); set X-Pilot-Signature-256 header. Add Store.SetSecret() to configure the pre-shared secret. Switch from client.Post to NewRequest+Do to support custom headers. Mirrors existing pattern from pilot-protocol/webhook (X-Pilot-Signature-256).
🤖 CI Status —
|
| Check | Status |
|---|---|
| test | ❌ FAILURE |
Summary: 0/1 passing 🔴
branch: openclaw/pilot-239-20260529-181500
mergeable: MERGEABLE
canary: not-configured
TestStore_SetSecret_NoSignatureWhenNoSecret — the secret field in dispatcher is read in post() (goroutine from dispatchLoop) and written by SetSecret() without synchronization.
🤖 matthew-pr-worker · 2026-05-29T18:22:00Z
🤖 PR Summary —
|
|
🤖 Hank — CI status Classification: Transient failure pattern detected — likely a flake:
Recommend re-running the workflow. If it fails again on a fresh run, treat as Auto-classified at 2026-05-29T20:50:32Z. Re-runs on next push or check completion. |
The two new TestStore_SetSecret_* tests had the HTTP handler closure write to plain string/[]byte variables that the test body then read. This is a data race the -race detector caught: - handler goroutine: 'sigHeader = r.Header.Get(...)' at line 166 - test goroutine: 'if sigHeader != ""' at line 182 The race-detector also tripped TestDispatcher_EmitDropsWhenQueueFull (same package, runs after) — that test wasn't itself racy but the detector marks the package failed once it has fired. Fix: protect the shared vars with sync.Mutex; provide getSig/getBody helpers for the test goroutine. Verified locally: go test -race -run TestStore_SetSecret_* + TestDispatcher_EmitDropsWhenQueueFull all PASS.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
Add HMAC-SHA256 cryptographic signature header (
X-Pilot-Signature-256) to every outbound rendezvous webhook POST, matching the existing pattern inpilot-protocol/webhook.Problem
webhook.go:post()calledclient.Post(url, ...)with no HMAC header, timestamp, or nonce. Receivers had no way to verify a POST genuinely came from the registry. Anyone who knows the webhook URL can forge events (fake registration, admin-role grants, fake audit entries).Fix
secretfield todispatcherstruct (empty = backward-compatible, no signature)Store.SetSecret()public method to configure the pre-shared secretpost(): compute HMAC-SHA256 of the JSON body, hex-encode it, setX-Pilot-Signature-256headerclient.Postconvenience method tohttp.NewRequest+client.Doto support custom headersDiff stat
Verification
go build ./...— cleango vet ./...— cleango test ./...— all packages pass (webhook 10s incl new tests)Related
pilot-protocol/webhookX-Pilot-Signature-256pattern (PILOT-90)