Skip to content

feat: align request signing with AgentWire v0.2 spec#90

Merged
Jing-yilin merged 5 commits intofeat/world-typesfrom
feat/agentwire-v02-signing
Mar 18, 2026
Merged

feat: align request signing with AgentWire v0.2 spec#90
Jing-yilin merged 5 commits intofeat/world-typesfrom
feat/agentwire-v02-signing

Conversation

@Jing-yilin
Copy link
Copy Markdown
Contributor

Summary

Aligns the DAP plugin's request signing with the AgentWire v0.2 spec. Previously, requests used body-only signatures (v0.1 style). Now outbound HTTP requests include X-AgentWorld-* headers with method/path/authority/Content-Digest binding for cross-endpoint replay resistance.

Changes

Client-side (outbound)

  • peer-client.ts: sendViaHttp now produces all 6 X-AgentWorld-* signing headers
  • peer-discovery.ts: announceToNode sends v0.2 header-signed announces
  • Body still carries legacy signature field for backward compat with un-upgraded receivers

Server-side (inbound)

  • peer-server.ts: Dual-mode verification — checks X-AgentWorld-Signature header first, falls back to body signature if absent
  • Added rawBody content parser to preserve original body for Content-Digest verification
  • Refactored response signing to use shared signHttpResponse from identity module

Shared utilities

  • identity.ts: Added signHttpRequest, verifyHttpRequestHeaders, signHttpResponse, verifyHttpResponseHeaders, computeContentDigest
  • types.ts: Added AwRequestHeaders / AwResponseHeaders interfaces

v0.2 signing input (per spec §6.6)

{"v":"0.2","from":"aw:sha256:...","kid":"#identity","ts":"ISO8601","method":"POST","authority":"host:port","path":"/peer/message","contentDigest":"sha-256=:...:" }

Tests

  • 12 new tests in test/request-signing.test.mjs covering:
    • Header production (all 6 required headers)
    • Round-trip sign + verify
    • Tampered body rejection (Content-Digest mismatch)
    • Wrong public key rejection
    • Path replay rejection (cross-endpoint)
    • Expired timestamp rejection
    • sendP2PMessage end-to-end delivery with v0.2 headers
    • Legacy body-only signature backward compat
    • Server-side dual-mode acceptance
  • All 109 tests pass (97 existing + 12 new)

What this does NOT change

  • Body format (P2PMessage) unchanged — backward compat preserved
  • UDP/QUIC path unchanged (no HTTP headers for UDP)
  • No Agent Card requirement added
  • No A2A JSON-RPC migration

- Add v0.2 HTTP header signing to outbound requests (peer-client, peer-discovery)
  with method/path/authority/Content-Digest binding for cross-endpoint replay resistance
- Add dual-mode verification on server: prefer v0.2 header signature, fall back to
  legacy body-only signature for backward compatibility
- Add rawBody parser to preserve original body for Content-Digest verification
- Refactor response signing to use shared signHttpResponse from identity module
- Add AwRequestHeaders/AwResponseHeaders type interfaces
- Add 12 new tests covering round-trip signing, tamper detection, replay resistance,
  timestamp skew rejection, and legacy backward compatibility
…om cross-check

- C1: Strip query string from req.url before v0.2 signature verification
- C2: Use X-AgentWorld-Version from header in verification signing input
  instead of hardcoded local PROTOCOL_VERSION (enables rolling upgrades)
- W2: Cross-check X-AgentWorld-From header matches body 'from' field
- W3: Add TODO for key-rotation dual-mode verification (deferred)
- N3: Add empty body Content-Digest test
- N4: Add verifyHttpResponseHeaders round-trip test
- Add from-mismatch rejection test
@Jing-yilin
Copy link
Copy Markdown
Contributor Author

Codex Review — Findings & Fixes

CRITICAL (fixed in f99e28d)

C1. req.url may include query strings, breaking path verification

  • peer-server.ts: Now strips query string with req.url.split("?")[0] before passing to verifyHttpRequestHeaders

C2. Version in signing input used local PROTOCOL_VERSION instead of header value

  • identity.ts: verifyHttpRequestHeaders and verifyHttpResponseHeaders now read v from X-AgentWorld-Version header, enabling rolling upgrades between nodes running different versions

WARNINGS (fixed in f99e28d)

W2. No from cross-check between header and body

  • peer-server.ts: After v0.2 verification, asserts X-AgentWorld-From === body.from. Added test for mismatch rejection.

W3. /peer/key-rotation not dual-mode

  • Added TODO comment. Key rotation uses its own dual-signature proof structure; v0.2 transport-level signing deferred.

W1. _identity exposes private key in module state

  • Accepted as-is. The Identity type is the project-standard representation and the module scope is not serialized. Documenting for awareness.

W4. Authority format fragility (client vs server)

  • Accepted as-is. Node fetch sets Host header to match URL authority. Behind a reverse proxy, the Host header config is the operator's responsibility.

NOTES (addressed in f99e28d)

  • N3: Added empty body computeContentDigest test
  • N4: Added verifyHttpResponseHeaders round-trip test against live server response

Test results: 112 pass, 0 fail

Reverts the peerPort default introduced in 209b7da. Remote peers
almost always listen on the network default 8099; using the local
peerPort caused connection refusals when the gateway ran on a
non-standard port.
buildManifest() previously only injected hostAgentId/hostCardUrl/
hostEndpoints when config.worldType === 'hosted'. If the manifest
was marked hosted via hooks (manifest.type = 'hosted') while
worldType was left at its default, joiners received a hosted manifest
with no host connection details. Now checks result.type instead.
@Jing-yilin Jing-yilin merged commit 272befe into feat/world-types Mar 18, 2026
@Jing-yilin Jing-yilin deleted the feat/agentwire-v02-signing branch March 18, 2026 10:49
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