# Unauthenticated secret leak via endpoints

Anonymous-reachable handlers returning tokens, keys, or secrets in bodies or error branches

_Area: Web access control and IDOR. Seen in 3 disclosed reports; latest 2026-10-09. Updated 2026-11-01._

## How it works
An endpoint returns a value that functions as a credential, but the code treats the caller as trusted or the response as harmless. Three assumptions break: a rejection branch is assumed to be safe because it exits [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qm9p-p5pw-jrx2.md); a serialized model is assumed to be fine because the caller is authenticated [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-32gc-wf3m-78w9.md); a masking step is assumed to cover every secret field [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8x84-r2ff-h8pq.md). When one breaks, a credential leaves the server to a caller who never proved authorization for it.

## Where it shows up
- Error/rejection branches that interpolate a shared secret into the body before exiting [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qm9p-p5pw-jrx2.md)
- CLI-style scripts (reading `$argv`) served under a web root; `$argv` is unset under web SAPI, so `empty($argv[1])` is always true [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qm9p-p5pw-jrx2.md)
- List/detail handlers returning a raw model whose credential field is JSON-serialized [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-32gc-wf3m-78w9.md)
- A secret both stored in a model and accepted as an auth credential (e.g. a header) [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-32gc-wf3m-78w9.md)
- Masking functions that enumerate secret fields by name; diff the list against the struct's JSON-tagged fields [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8x84-r2ff-h8pq.md)
- Sibling endpoints applying different filtering to the same config object [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8x84-r2ff-h8pq.md)

## How to review
For each anonymous-reachable route, trace every response path, including error and early-exit branches, and confirm no credential, key, salt, verifier, or wrapped key is emitted. The invariant: a value usable as an auth credential, or to derive one, must never appear in a response to a caller who has not proven authorization for it. Check that masking is allowlist-based or derived from the struct, not a hand-maintained denylist [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8x84-r2ff-h8pq.md). Confirm secrets accepted as credentials are not returned by list/detail handlers; use DTOs [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-32gc-wf3m-78w9.md).

## Variants seen
- Secret echoed in an unauthenticated rejection branch, static and non-rotating [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qm9p-p5pw-jrx2.md)
- Node token returned to any authenticated user (authenticated but unauthorized, not anonymous) and replayable as an auth header [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-32gc-wf3m-78w9.md)
- KDF salt, params, verifier and wrapped DEK leaked to anonymous readers, enabling offline cracking [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8x84-r2ff-h8pq.md)

## Why fixes fail
Masking by enumerated field names misses new fields: `HideConfSecret` omitted `NotebookCrypto` entirely [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8x84-r2ff-h8pq.md). A branch keyed on `empty($argv[1])` fires for every web request, so the leak is unconditional [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qm9p-p5pw-jrx2.md).

## Impact and severity
High: cross-tenant DB dump [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qm9p-p5pw-jrx2.md), cross-node impersonation as initUser [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-32gc-wf3m-78w9.md), offline master-password cracking [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-8x84-r2ff-h8pq.md). Static, non-rotating secrets and secrets doubling as credentials make it worse.

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-qm9p-p5pw-jrx2.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-32gc-wf3m-78w9.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-8x84-r2ff-h8pq.md
