Skip to content

audit,portal: render bodies as utf-8 strings; propagate identity through Try-It and replay#9

Merged
cjimti merged 1 commit into
mainfrom
fix-audit-body-and-tryit-auth
May 12, 2026
Merged

audit,portal: render bodies as utf-8 strings; propagate identity through Try-It and replay#9
cjimti merged 1 commit into
mainfrom
fix-audit-body-and-tryit-auth

Conversation

@cjimti
Copy link
Copy Markdown
Contributor

@cjimti cjimti commented May 12, 2026

Summary

  • Audit detail panel now shows request/response bodies as decoded JSON instead of base64 dumps. Payload.MarshalJSON emits utf-8 strings (with a request_body_encoding/response_body_encoding=base64 flag fallback for non-utf-8 bytes so binary payloads still round-trip).
  • Try-It returns 200 when a logged-in operator clicks Send instead of 401 "missing credential". The portal-resolved identity now bridges into the dispatched request's context; httpmw.Identity short-circuits when one is already set, except when a real credential is on the wire — so typing X-API-Key: <other-key> into the Try-It headers field to "test as a different principal" still resolves through the chain.
  • Audit replay got the same identity bridge plus a filter that drops captured credential headers (Authorization, X-API-Key, Cookie) and the api_key query param before re-emission, since those values are persisted as the [redacted] sentinel and would otherwise look like wire credentials.

Why the user ran into this

Both defects show up immediately on the live api-test-server.plexara.io deployment of v1.1.0 (audit panel showed base64 strings, Try-It returned 401 while the user was logged in via the portal session). The deployment manifest itself is correct; the defects are upstream in v1.1.0. Cutting a v1.1.1 tag here unblocks the cluster.

Known limitation (deferred with TODO)

hasInboundCredential and redactedReplayHeaders hardcode the default api-key header/query names (X-API-Key, api_key). Deployments that customize APIKeysConfig.HeaderName / QueryParamName will silently lose Try-It "test as someone else" semantics under the custom name, and replay will leak the captured redaction sentinel for the custom header. The live deployment runs the defaults so this isn't a regression for the immediate rollout. Follow-up would either thread the configured names through Identity() or add a HasCredential(r) method to inbound.Chain that delegates to each authenticator.

Test plan

  • make verify clean (fmt, vet, test, golangci-lint, gosec, semgrep, coverage gate, SPA-bundle in-sync check)
  • Unit tests: Payload.MarshalJSON (utf-8 / binary / empty); httpmw.Identity pre-set bypass + wire-cred override
  • Integration test: TestAuditReplay_RedactedCredentialsThroughRealIdentityMW mounts the real httpmw.Identity in front of the replay target with a chain that would reject [redacted], asserts replay reaches the inner handler — would 401 if the redacted-header filter regressed
  • Local visual verify in browser via make dev: audit detail shows decoded JSON, Try-It on /v1/whoami returns 200 with {"subject":"devkey","auth_type":"apikey","key_name":"devkey"}
  • Cut v1.1.1, redeploy to gpu1, confirm the live portal's audit detail and Try-It both render correctly

…ugh Try-It and replay dispatch

Two defects surfaced in the live portal:

1. audit_payloads.request_body/response_body are []byte, which Go's
   default JSON encoder emits as base64. The audit detail panel showed
   raw base64 instead of the captured request/response JSON. Add a
   custom MarshalJSON on Payload that emits the body as a utf-8 string
   when valid; falls back to base64 with a sibling _encoding=base64
   flag for non-utf-8 bytes so binary payloads still round-trip.

2. Try-It (and audit replay) dispatch through the local mux via
   replayTarget.ServeHTTP. The dispatched request carries no
   Authorization/X-API-Key on the wire (Try-It deliberately strips
   operator-supplied Authorization/Cookie), so the inbound auth
   middleware would 401 a request the portal had already accepted.
   Fix by translating the portal-resolved auth.Identity into an
   inbound.Identity on the dispatched ctx, and short-circuiting
   httpmw.Identity when one is already set — except when a real
   credential is on the wire, so "type X-API-Key into Try-It to test
   as a different principal" still resolves through the chain.

Replay also has to drop credential headers (Authorization, X-API-Key,
Cookie) and the api_key query param before re-emission: those values
are persisted as the "[redacted]" sentinel and would otherwise look
like wire credentials, defeating the bypass and 401'ing through chain
validation of the redaction string.

Tests:
- Payload MarshalJSON: utf-8, binary, empty cases.
- httpmw.Identity: pre-set bypass; wire-cred override of pre-set.
- portal_audit_replay: end-to-end through real httpmw.Identity with
  a chain that rejects "[redacted]" — would 401 if the redacted-header
  filter regressed.

Known limitation (deferred):
- hasInboundCredential and redactedReplayHeaders hardcode the default
  api-key header/query names (X-API-Key, api_key). Deployments that
  customize APIKeysConfig.HeaderName / QueryParamName will silently
  lose Try-It "test as someone else" semantics under the custom name,
  and replay will leak the captured redaction sentinel for the custom
  header. The live api-test-server.plexara.io deployment runs the
  defaults so this is not a regression for the immediate rollout;
  TODOs left in code at the two call sites.

make verify clean.
@cjimti cjimti merged commit a9e161b into main May 12, 2026
7 checks passed
@cjimti cjimti deleted the fix-audit-body-and-tryit-auth branch May 12, 2026 23:53
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