File: apps/web/src/lib/settings-config.ts:37, apps/web/src/lib/actions/settings.ts:573,736
Observation: When an API key is created or rotated, createApiKeyAction / rotateApiKeyAction returns { ok: true, plaintextKey: "ayg_..." } as the Next.js Server Action response. This value is serialized over the RSC Flight wire protocol and is visible in the browser's Network tab (the action's POST response body) before useActionState picks it up and renders it in the ShowOnceDialog.
The key is NOT persisted to local storage, session storage, or any server-side log. It lives only in the useActionState hook's in-memory state and is cleared when the dialog's onClose fires (setRevealed(null)). The risk is limited to:
- Browser DevTools / network inspector capturing the raw action response during the creation flow.
- Browser extensions with access to
XMLHttpRequest / fetch responses.
- Any intermediary proxy (Cloudflare, CDN) that logs response bodies at debug level.
Recommendation (defense in depth): Consider returning only a one-time retrieval token instead of the raw key in the action response, with the raw key fetched via a short-lived signed URL or stored server-side for 60 s behind a second authenticated GET. This removes the secret from the wire format entirely. Alternatively, add a Content-Security-Policy: report-uri extension to detect extension injection into the settings page. At minimum, ensure Cloudflare / any CDN layer is configured NOT to log response bodies for /[orgSlug]/settings POST requests.
File:
apps/web/src/lib/settings-config.ts:37,apps/web/src/lib/actions/settings.ts:573,736Observation: When an API key is created or rotated,
createApiKeyAction/rotateApiKeyActionreturns{ ok: true, plaintextKey: "ayg_..." }as the Next.js Server Action response. This value is serialized over the RSC Flight wire protocol and is visible in the browser's Network tab (the action's POST response body) beforeuseActionStatepicks it up and renders it in theShowOnceDialog.The key is NOT persisted to local storage, session storage, or any server-side log. It lives only in the
useActionStatehook's in-memory state and is cleared when the dialog'sonClosefires (setRevealed(null)). The risk is limited to:XMLHttpRequest/fetchresponses.Recommendation (defense in depth): Consider returning only a one-time retrieval token instead of the raw key in the action response, with the raw key fetched via a short-lived signed URL or stored server-side for 60 s behind a second authenticated GET. This removes the secret from the wire format entirely. Alternatively, add a
Content-Security-Policy: report-uriextension to detect extension injection into the settings page. At minimum, ensure Cloudflare / any CDN layer is configured NOT to log response bodies for/[orgSlug]/settingsPOST requests.