Skip to content

fix(trustedagents): add ed25519 signature verification for runtime-fetched allowlist (PILOT-135)#4

Merged
TeoSlayer merged 1 commit into
mainfrom
openclaw/pilot-135-20260528-160038
May 28, 2026
Merged

fix(trustedagents): add ed25519 signature verification for runtime-fetched allowlist (PILOT-135)#4
TeoSlayer merged 1 commit into
mainfrom
openclaw/pilot-135-20260528-160038

Conversation

@matthew-pilot
Copy link
Copy Markdown
Collaborator

What failed

fetchOnce() at runtime.go:51-76 fetched the trusted-agents JSON from GitHub raw with HTTPS as the sole integrity check. A compromised host, BGP hijack, or forged TLS cert could inject malicious node IDs into the allowlist. The embedded list fallback only triggers on HTTP-level failure — a successful malicious fetch bypasses it.

Why this fix

Adds VerifyAndStripSig() which checks an optional ed25519 "signature" field in the fetched JSON:

  • No signature field → accept as-is (backward compatible, WARN log)
  • Signature present + key configured → verify against embedded public key; reject on mismatch
  • Signature present + key not configured → reject (can't verify)

The embedded public key is a 32-byte zero placeholder. Once the operator generates a key pair and adds signatures to the JSON, verification becomes mandatory.

Verification

  • go build ./... — clean
  • go vet ./... — clean
  • go test ./... -count=3 — all 23 tests pass (consistent across 3 runs)
  • New tests cover: unsigned accept, bad signature reject, key-not-configured reject, valid signature accept (end-to-end with fresh keypair)

Files changed

File Lines
data.go +76 (embeddedPubKey, VerifyAndStripSig, isZeroKey)
runtime.go +9/-1 (call VerifyAndStripSig before Load)
zz_fetch_test.go +119/-16 (5 new tests, updated BodyIsBadJSON prefix)

Closes PILOT-135

…tched allowlist (PILOT-135)

fetchOnce() at runtime.go:51-76 previously loaded the trusted-agents
JSON from GitHub raw with HTTPS as the sole integrity check. A
compromised host, BGP hijack, or forged TLS cert could inject
malicious node IDs into the allowlist.

This commit adds VerifyAndStripSig() which:
- Accepts unsigned JSON when no signature field is present (backward
  compatible — existing unsigned lists still work, with a WARN log).
- Verifies an ed25519 signature when the field IS present, rejecting
  the fetch on mismatch (fallback to embedded list).
- Reports an error when a signature exists but embeddedPubKey is
  still the zero-value placeholder (not yet configured).

The embedded public key is a 32-byte zero placeholder; the operator
replaces it when signing infrastructure is deployed. Once the real
key is embedded and the JSON is signed, verification becomes
mandatory.

Tests:
- Backward compat: unsigned JSON still accepted
- Bad signature: rejected
- Signature present, key not configured: rejected
- Valid signature: accepted (end-to-end with fresh keypair)

Closes PILOT-135
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 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 — #4 PILOT-135

Status

  • State: OPEN · MERGEABLE ✅
  • CI: 2/2 green (test ✅, codecov/patch ✅)
  • Created: 2026-05-28 16:04 UTC
  • Files: 3 (+188 / −16) — data.go +76, runtime.go +8/-1, zz_fetch_test.go +104/-15

Verdict

CLEAN — all CI green, no blockers, MERGEABLE. Adds ed25519 signature verification for runtime-fetched allowlist JSON with backward-compatible unsigned fallback.

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🦜 Matthew Explains — #4 PILOT-135

What this does

Adds VerifyAndStripSig() which validates an optional ed25519 "signature" field in the runtime-fetched trusted-agents allowlist JSON before loading it. This prevents BGP-hijack or MITM attacks from silently injecting malicious node IDs.

How it works

  1. fetchOnce() → GETs the JSON from GitHub raw
  2. VerifyAndStripSig() → checks for a "signature" field:
    • No signature → accept as-is (backward compatible, WARN log)
    • Signature + key configured → verify against embedded public key; reject on mismatch
    • Signature + key NOT configured → reject (can't trust)
  3. The embedded public key is a 32-byte zero placeholder — becomes mandatory once the operator generates a real keypair

Files

File Change
data.go +76 lines: embeddedPubKey, VerifyAndStripSig(), isZeroKey()
runtime.go +8/−1: call VerifyAndStripSig() before Load()
zz_fetch_test.go +104/−15: 5 new cases (unsigned accept, bad sig reject, no-key reject, valid sig accept, round-trip)

🔗 PILOT-135

@TeoSlayer TeoSlayer merged commit 93b62c6 into main May 28, 2026
2 checks passed
@TeoSlayer TeoSlayer deleted the openclaw/pilot-135-20260528-160038 branch May 28, 2026 16:56
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