Skip to content

fix: add HMAC-SHA256 signing to outbound rendezvous webhook POSTs (PILOT-239)#8

Merged
TeoSlayer merged 4 commits into
mainfrom
openclaw/pilot-239-20260529-181500
May 29, 2026
Merged

fix: add HMAC-SHA256 signing to outbound rendezvous webhook POSTs (PILOT-239)#8
TeoSlayer merged 4 commits into
mainfrom
openclaw/pilot-239-20260529-181500

Conversation

@matthew-pilot
Copy link
Copy Markdown
Collaborator

Summary

Add HMAC-SHA256 cryptographic signature header (X-Pilot-Signature-256) to every outbound rendezvous webhook POST, matching the existing pattern in pilot-protocol/webhook.

Problem

webhook.go:post() called client.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

  • Added secret field to dispatcher struct (empty = backward-compatible, no signature)
  • Added Store.SetSecret() public method to configure the pre-shared secret
  • In post(): compute HMAC-SHA256 of the JSON body, hex-encode it, set X-Pilot-Signature-256 header
  • Switched from client.Post convenience method to http.NewRequest + client.Do to support custom headers
  • Two new tests: one verifies the signature header is set and correct, one verifies no header when secret is empty

Diff stat

webhook/webhook.go      | 36 +++++++++++++++++++++-
webhook/zz_more_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 115 insertions(+), 1 deletion(-)

Verification

  • go build ./... — clean
  • go vet ./... — clean
  • go test ./... — all packages pass (webhook 10s incl new tests)
  • New HMAC signature test verifies header presence and correctness
  • No-signature test verifies backward compatibility when secret is empty

Related

  • PILOT-239
  • Mirrors pilot-protocol/webhook X-Pilot-Signature-256 pattern (PILOT-90)

…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).
@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🤖 CI Status — pilot-protocol/rendezvous#8

Check Status
test ❌ FAILURE

Summary: 0/1 passing 🔴

branch: openclaw/pilot-239-20260529-181500
mergeable: MERGEABLE
canary: not-configured

⚠️ Data race detected in 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

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🤖 PR Summary — fix: add HMAC-SHA256 signing to outbound rendezvous webhook POSTs (PILOT-239)

Problem

webhook.go:post() called client.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

  • Added secret field to dispatcher struct (empty = no signature, backward-compatible)
  • Added Store.SetSecret() public method to configure the pre-shared secret
  • In post(): compute HMAC-SHA256 of the JSON body → hex-encode → set X-Pilot-Signature-256 header
  • Switched from client.Post convenience method to http.NewRequest + client.Do to support custom headers
  • Two new tests: signature header presence + correctness, and no-header-when-secret-empty

Scope

  • 2 files: webhook/webhook.go (+35/−1), webhook/zz_more_test.go (+80)
  • +115/−1 lines

⚠️ CI Alert

Test TestStore_SetSecret_NoSignatureWhenNoSecret detected a data race on dispatcher.secret. The field is read in post() (called from dispatchLoop goroutine) and written by SetSecret() without synchronization — needs a sync.Mutex or atomic value.

Risk

Low blast radius (webhook package only). Backward-compatible when secret is empty. Race fix is straightforward (guard secret with mutex or use configLock already in dispatcher).

Related

  • PILOT-239
  • Mirrors pilot-protocol/webhook X-Pilot-Signature-256 pattern (PILOT-90)

🤖 matthew-pr-worker · 2026-05-29T18:22:00Z

@hank-pilot
Copy link
Copy Markdown

hank-pilot commented May 29, 2026

🤖 Hank — CI status

Classification: flake
Run: https://github.com/pilot-protocol/rendezvous/actions/runs/26661018126
At commit: 925be77

Transient failure pattern detected — likely a flake:

2026/05/29 20:40:26 WARN registry webhook POST failed action=flood attempt=1 error="Post "http://127.0.0.1:41095\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)"

Recommend re-running the workflow. If it fails again on a fresh run, treat as real.

Auto-classified at 2026-05-29T20:50:32Z. Re-runs on next push or check completion.

TeoSlayer and others added 2 commits May 29, 2026 13:39
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
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!

@TeoSlayer TeoSlayer merged commit 8be882e into main May 29, 2026
2 checks passed
@TeoSlayer TeoSlayer deleted the openclaw/pilot-239-20260529-181500 branch May 29, 2026 21:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

canary-passed Canary harness passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants