diff --git a/configs/mcp-test.example.yaml b/configs/mcp-test.example.yaml
index d4a2f3f..498eeb2 100644
--- a/configs/mcp-test.example.yaml
+++ b/configs/mcp-test.example.yaml
@@ -59,7 +59,10 @@ database:
audit:
enabled: true
retention_days: 30
- redact_keys: [password, token, secret, authorization, api_key, credentials, cookie]
+ # Case-insensitive substring match on tool-call argument keys and HTTP
+ # header names. Setting this replaces the default list entirely — it does
+ # not merge — so include the defaults below when extending it.
+ redact_keys: [password, token, secret, authorization, api_key, credentials, bearer, cookie, jwt, session_id, private_key, passwd]
# Inspection / debugging capture: full request and response envelopes
# land in the sibling audit_payloads table for portal drill-down. Set
# capture_payloads to false to keep audit_events summary-only.
diff --git a/docs/configuration/environment.md b/docs/configuration/environment.md
index bfc3374..762aaf8 100644
--- a/docs/configuration/environment.md
+++ b/docs/configuration/environment.md
@@ -104,7 +104,7 @@ Required when OIDC is enabled.
-Required `aud` claim value.
+Optional `aud` claim enforcement. The example config uses `${MCPTEST_OIDC_AUDIENCE:-mcp-test}`. Leave the variable unset and clear `oidc.audience` if you want to skip audience validation.
diff --git a/docs/configuration/reference.md b/docs/configuration/reference.md
index dc3df21..7246259 100644
--- a/docs/configuration/reference.md
+++ b/docs/configuration/reference.md
@@ -255,16 +255,16 @@ The IdP's issuer URL. mcp-test fetches `/.well-known/openid-configuratio
-
+
oidc.audience
typestring
-required whenoidc.enabled
+default""
-Required `aud` claim value. Tokens that don't carry this audience are rejected.
+Optional `aud` claim enforcement. When non-empty, tokens whose `aud` doesn't match are rejected. When empty, the audience check is skipped entirely. The bundled Keycloak realm and `MCPTEST_OIDC_AUDIENCE` env interpolation default to `mcp-test`.
@@ -430,11 +430,11 @@ If true, missing credentials on `/mcp` resolve to a synthetic Anonymous identity
auth.require_for_mcp
typebool
-defaulttrue
+defaultfalse
-Gate the `/` endpoint. Currently the auth gateway checks for credential *presence* and 401s without one (unless anonymous is allowed).
+Advisory toggle, currently not read by the binary. MCP-endpoint gating is governed by `auth.allow_anonymous` and the composed auth chain: with anonymous off and no matching credential, `MCPAuthGateway` returns 401 with an RFC 9728 `WWW-Authenticate` header regardless of this flag. Shipped configs still set it for forward compatibility.
@@ -443,11 +443,11 @@ Gate the `/` endpoint. Currently the auth gateway checks for credential *presenc
auth.require_for_portal
typebool
-defaulttrue
+defaultfalse
-Gate every `/portal/*` and `/api/v1/*` route. The portal auth middleware is independent of `allow_anonymous`.
+Advisory toggle, currently not read by the binary. The portal API is always behind `pkg/httpsrv.PortalAuth` middleware; that middleware doesn't consult this flag. Setting it has no runtime effect today.
@@ -532,7 +532,11 @@ Audit-log behavior.
audit:
enabled: true
retention_days: 30
- redact_keys: [password, token, secret, authorization, cookie, api_key, credentials]
+ redact_keys: [password, token, secret, authorization, api_key, credentials, bearer, cookie, jwt, session_id, private_key, passwd]
+ capture_payloads: true
+ capture_headers: true
+ max_payload_bytes: 65536
+ max_notifications: 100
```
@@ -542,11 +546,11 @@ audit:
audit.enabled
typebool
-defaulttrue
+defaultfalse
-Disables the audit pipeline entirely when false (no rows written, no portal data).
+Master switch for the audit pipeline. When false, `audit.NoopLogger` is wired up — no rows are written and the portal Audit page has nothing to show. The shipped example, dev, and live configs all set this to `true` because audit is the headline feature of mcp-test; the Go zero-value default only takes effect if you author a fresh config without an `audit:` block.
@@ -568,11 +572,63 @@ Documented retention target. mcp-test does not currently auto-prune; deploy a cr
audit.redact_keys
type[]string
-default[password, token, secret, authorization, api_key, credentials]
+default[password, token, secret, authorization, api_key, credentials, bearer, cookie, jwt, session_id, private_key, passwd]
+
+
+
+Case-insensitive substring match. Any tool-call argument key matching one of these gets its value replaced with `[redacted]` before the row is written. The same list is applied to HTTP header names by the `headers` tool. Extend it for domain-specific secret naming; setting it replaces the default, it does not merge.
+
+
+
+
+
+
audit.capture_payloads
+
+typebool
+defaulttrue
+
+
+
+Controls whether the `audit_payloads` sibling row (full request/response envelope, captured notifications, error categories) is written alongside the `audit_events` summary. Off by default in privacy-sensitive deployments; on by default here because mcp-test is a test fixture and full visibility is the point. The portal Inspection drawer and replay endpoint require this to be on.
+
+
+
+
+
+
audit.capture_headers
+
+typebool
+defaulttrue
-Case-insensitive substring match. Any tool-call argument key matching one of these gets its value replaced with `[redacted]` before the row is written.
+Whether redacted HTTP request headers are included in the payload row. No effect when `capture_payloads` is false. Headers are passed through the same redaction list as parameter keys (case-insensitive substring match).
+
+
+
+
+
+
audit.max_payload_bytes
+
+typeint
+default65536
+
+
+
+Per-side cap (request and response counted separately). Payloads beyond this are dropped and the corresponding `request_truncated` / `response_truncated` flag on the payload row is set. Raise this when testing tools that legitimately return large blobs (e.g. `sized_response` at high `size_bytes`).
+
+
+
+
+
+
audit.max_notifications
+
+typeint
+default100
+
+
+
+Caps the number of notifications stored per call in `audit_payloads.notifications`. The streaming tools (`progress`, `chatty`) can emit hundreds; raise or lower based on how much detail the operator needs in the portal's Inspection view.
@@ -637,11 +693,11 @@ At least 16 bytes, 32+ recommended. HMAC key for cookie signing.
portal.cookie_secure
typebool
-defaulttrue
+defaultfalse
-Sets the `Secure` cookie attribute. Leave on in production; turn off for local HTTP-only dev.
+Sets the `Secure` cookie attribute on the portal session cookie and on the PKCE state cookie used during the OIDC login flow. The shipped example and live configs set this to `true`; the dev config leaves it `false` for plain-HTTP localhost browsing. Always `true` in production.
diff --git a/docs/operations/audit.md b/docs/operations/audit.md
index d102674..f7437f3 100644
--- a/docs/operations/audit.md
+++ b/docs/operations/audit.md
@@ -19,6 +19,19 @@ As of v1.1.0 audit data lives in two tables joined 1:1 by event ID:
Cascade delete on the foreign key keeps retention atomic: deleting an `audit_events` row drops its payload row in the same statement.
+### Capture knobs
+
+Four config keys under `audit:` control how much detail the payload row carries; see the [configuration reference](../configuration/reference.md#audit) for full definitions.
+
+| Key | Default | Effect |
+|---|---|---|
+| `audit.capture_payloads` | `true` | Master switch for the `audit_payloads` table. Off ⇒ summary-only mode; portal Inspection drawer and replay are disabled for those rows. |
+| `audit.capture_headers` | `true` | Include the redacted request headers blob in the payload row. |
+| `audit.max_payload_bytes` | `65536` | Per-side cap (request and response counted separately). Excess bytes are dropped and `request_truncated` / `response_truncated` is set on the payload row. |
+| `audit.max_notifications` | `100` | Caps notifications stored per call. The streaming tools can emit hundreds; tune to taste. |
+
+A privacy-sensitive deployment usually sets `capture_payloads: false` and accepts the loss of inspection fidelity. A reproducibility-focused deployment raises `max_payload_bytes` to whatever its largest test payload demands so truncation never silently happens.
+
## What gets recorded
@@ -309,14 +322,20 @@ ORDER BY ts DESC;
`audit.redact_keys` is a list of case-insensitive substrings.
Tool-call argument keys matching any substring have their *values*
replaced with `"[redacted]"` before the row is written. The default
-list includes `password`, `token`, `secret`, `authorization`,
-`cookie`, `api_key`, `credentials`.
+list is `password`, `token`, `secret`, `authorization`, `api_key`,
+`credentials`, `bearer`, `cookie`, `jwt`, `session_id`,
+`private_key`, `passwd`. Setting `redact_keys` replaces the default
+list entirely — it does not merge — so include the defaults when
+extending it.
The match is on argument *keys*, not values. So an argument like
`{"password": "hunter2"}` is redacted, but a free-text body that
happens to contain the word "password" is not.
-Sanitization is recursive: nested objects and arrays are walked.
+Sanitization is recursive: nested objects and arrays are walked. The
+same list is also applied to HTTP header *names* by the `headers`
+tool, and to the redacted-headers blob captured into
+`audit_payloads.request_headers` when payload capture is on.
## Retention