You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ASI-07: Add mTLS and HMAC request signing for agent↔gateway communication (#4679)
Per OWASP Agentic Top 10 ASI-07, the MCP gateway communicated over plain
HTTP with only an API key — no transport encryption, no message signing,
no replay protection. This adds opt-in mTLS and HMAC-SHA256 request
signing.
## Phase 1: mTLS
New flags/env vars: `--tls-cert` / `MCP_GATEWAY_TLS_CERT`, `--tls-key` /
`MCP_GATEWAY_TLS_KEY`, `--tls-ca` / `MCP_GATEWAY_CA_CERT`.
- `internal/server/gateway_tls.go` — `LoadGatewayTLS(cert, key, ca)`:
loads PEM files, sets `tls.RequireAndVerifyClientCert` when `ca` is
non-empty (mTLS)
- `internal/cmd/root.go` — wraps the TCP listener with TLS post-bind;
plain HTTP remains default (no certs = no change in behaviour)
- Partial TLS flag validation: `--tls-cert` and `--tls-key` must both be
provided together; `--tls-ca` requires cert+key to also be set —
prevents silent plaintext fallback from incomplete config
- `writeGatewayConfig` now receives a `tlsEnabled` flag so emitted
server URLs use `https://` when TLS is active
## Phase 2: HMAC Request Signing + Replay Protection
New flag/env var: `--hmac-secret` / `MCP_GATEWAY_HMAC_SECRET`.
- `internal/server/hmac.go` — `hmacMiddleware` injected into
`wrapWithMiddleware` (both routed and unified modes); API key auth runs
**before** HMAC so unauthenticated requests are rejected before paying
the body-read cost
- Validates three headers per request:
- `X-MCP-Timestamp` — must be within ±30 s of server clock
- `X-MCP-Nonce` — must not have been seen before (in-process cache, 60 s
TTL)
- `X-MCP-Signature` — `HMAC-SHA256(secret,
"timestamp\nnonce\npath\nhex(sha256(body))")`
- Nonce is recorded **only after** successful signature verification —
prevents DoS cache poisoning via requests with invalid signatures
- A read-only `seenNonce` pre-check provides fast replay rejection
before the body-read step
- HMAC applies to `/mcp` handlers only; common endpoints (`/health`,
`/close`) are not HMAC-protected
```
# Enable one-way TLS
awmg --tls-cert server.crt --tls-key server.key --config config.toml --routed
# Enable mTLS (client cert required)
awmg --tls-cert server.crt --tls-key server.key --tls-ca ca.crt ...
# Enable HMAC signing (can combine with TLS)
awmg --hmac-secret $(openssl rand -hex 32) ...
```
Both features are **opt-in and backward compatible** — all flags default
to empty/disabled, so omitting them leaves plain-HTTP + API-key
behaviour completely unchanged.
## Test coverage
- `gateway_tls_test.go`: server-only TLS, mTLS config, live mTLS
handshake (proper CA + client cert with `ExtKeyUsageClientAuth`), error
paths
- `hmac_test.go`: valid signature, missing headers, stale/future
timestamps, replay detection, wrong secret, nonce cache concurrency,
nonce cache DoS prevention
(`TestHMACMiddleware_InvalidSigDoesNotPoisonNonceCache`), `seenNonce`
read-only pre-check (`TestNonceCache_SeenNonce`)
- `stdout_config_test.go`: `TestWriteGatewayConfig_TLSScheme` verifies
`http://` vs `https://` URL emission based on TLS state
0 commit comments