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