fix(gateway): verify client signature before payload_hash #41

Merged
owner merged 1 commits from feature/issue-39-verification-order into development 2026-05-24 05:59:44 +00:00
Owner

What

Make the gateway verify the client signature before the payload_hash, matching docs/ARCHITECTURE.md §15 "Verification order".

Why

ARCHITECTURE.md §15 lists the signature (step 4) before payload_hash (step 5), but the authenticated-edge decorator chain in gateway/internal/grpcapi/server.go wrapped the payload-hash gate outside the signature gate, so the hash was checked first. gateway/README.md and gateway/docs/flows.md had drifted to match the code (hash-first), leaving ARCHITECTURE.md as the only source describing the intended order.

Signature-first is the cryptographically sound order: the signature covers the payload_hash field, so the request is authenticated before any of its content is processed. Per the owner's decision on #39 (option A): move the code under ARCHITECTURE.md and bring the other docs in line.

Changes

  • server.go: swap the two decorators so newSignatureVerifyingService wraps newPayloadHashVerifyingService (signature runs first).
  • payload_hash.go / signature.go: update the decorator doc comments to the new order.
  • gateway/README.md (steps 5↔6) and gateway/docs/flows.md (mermaid): align to signature→hash.
  • payload_hash_integration_test.go: re-sign the four tests over the tampered hash so they keep exercising the hash gate (otherwise the signature gate, now first, would reject them with UNAUTHENTICATED).

Observable side effect

A request with a tampered payload_hash whose signature was computed over the original hash is now rejected at the signature gate (UNAUTHENTICATED "invalid request signature") instead of the hash gate (INVALID_ARGUMENT). Security is unchanged — both refusals occur before the payload is handled. The cross-service integration test (TestGatewayEdge_PayloadHashMismatch) signs over the overridden hash and already accepts both codes.

Tests

  • gofmt -l (changed files) and go vet ./gateway/internal/grpcapi/ — clean.
  • go test -count=1 ./gateway/... — pass.
  • go vet ./integration/... — compiles.

Closes #39

🤖 Generated with Claude Code

## What Make the gateway verify the **client signature before the `payload_hash`**, matching `docs/ARCHITECTURE.md` §15 "Verification order". ## Why `ARCHITECTURE.md` §15 lists the signature (step 4) before `payload_hash` (step 5), but the authenticated-edge decorator chain in `gateway/internal/grpcapi/server.go` wrapped the payload-hash gate *outside* the signature gate, so the hash was checked first. `gateway/README.md` and `gateway/docs/flows.md` had drifted to match the code (hash-first), leaving `ARCHITECTURE.md` as the only source describing the intended order. Signature-first is the cryptographically sound order: the signature covers the `payload_hash` field, so the request is authenticated before any of its content is processed. Per the owner's decision on #39 (option A): move the code under `ARCHITECTURE.md` and bring the other docs in line. ## Changes - `server.go`: swap the two decorators so `newSignatureVerifyingService` wraps `newPayloadHashVerifyingService` (signature runs first). - `payload_hash.go` / `signature.go`: update the decorator doc comments to the new order. - `gateway/README.md` (steps 5↔6) and `gateway/docs/flows.md` (mermaid): align to signature→hash. - `payload_hash_integration_test.go`: re-sign the four tests over the tampered hash so they keep exercising the hash gate (otherwise the signature gate, now first, would reject them with `UNAUTHENTICATED`). ## Observable side effect A request with a tampered `payload_hash` whose signature was computed over the *original* hash is now rejected at the signature gate (`UNAUTHENTICATED "invalid request signature"`) instead of the hash gate (`INVALID_ARGUMENT`). Security is unchanged — both refusals occur before the payload is handled. The cross-service integration test (`TestGatewayEdge_PayloadHashMismatch`) signs over the overridden hash and already accepts both codes. ## Tests - `gofmt -l` (changed files) and `go vet ./gateway/internal/grpcapi/` — clean. - `go test -count=1 ./gateway/...` — pass. - `go vet ./integration/...` — compiles. Closes #39 🤖 Generated with [Claude Code](https://claude.com/claude-code)
developer added 1 commit 2026-05-24 00:42:37 +00:00
fix(gateway): verify client signature before payload_hash
Tests · Go / test (push) Successful in 2m1s
Tests · Go / test (pull_request) Successful in 2m58s
Tests · Integration / integration (pull_request) Successful in 1m39s
91e34a0929
ARCHITECTURE.md §15 "Verification order" specifies signature verification
(step 4) before payload_hash (step 5), but the authenticated-edge
decorator chain wrapped the payload-hash gate outside the signature gate,
so the hash was checked first. gateway/README.md and gateway/docs/flows.md
had drifted to match the code (hash-first), leaving ARCHITECTURE.md as the
lone source describing the intended order.

Swap the two decorators in server.go so the signature gate runs first, and
align README + flows.md to ARCHITECTURE.md. Signature-first is the
cryptographically sound order: the signature covers the payload_hash field,
so the request is authenticated before any of its content is processed.

Observable side effect: a request carrying a tampered payload_hash whose
signature was computed over the original hash is now rejected at the
signature gate (UNAUTHENTICATED "invalid request signature") instead of the
hash gate (INVALID_ARGUMENT). Security is unchanged — both refusals happen
before the payload is handled. The four payload-hash unit tests re-sign
over the tampered hash so they keep exercising the hash gate; the
cross-service integration test signs over the overridden hash and already
accepts both codes.

Refs #39

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
owner merged commit d44ad9b6eb into development 2026-05-24 05:59:44 +00:00
owner deleted branch feature/issue-39-verification-order 2026-05-24 05:59:44 +00:00
Sign in to join this conversation.