Files
pad/internal/server
xarmian c10023ea8f fix(server): deny-by-default whitelist for API token scopes (TASK-667) (#192)
* fix(server): deny-by-default whitelist for API token scopes (TASK-667)

tokenScopeAllows previously fell open on unrecognized scopes and on
unparseable scope JSON. A typo like "read-only" silently granted full
access — exactly the kind of landmine that a fresh token minted by an
admin who misremembers the vocabulary would step on.

New policy (deny-by-default):
- Unparseable JSON → deny + warn (was allow). Data corruption or
  tampering should never fall open.
- Unrecognized scopes → never contribute to allow; all unknowns on a
  given request get a single warning log so operators can spot typos.
- Explicit wildcard "*" and "write" still allow all methods; "read"
  still allows safe methods only.
- Empty scope string and empty JSON array `[]` still allow — these
  represent legacy pre-enforcement rows we don't want to break on
  upgrade.

Test table updated:
- old "unknown scope allows GET/POST" flipped to deny
- new "read-only typo denies GET" regression pin
- new "unknown+write/wildcard still allow" guard rails confirming that
  a recognized allow-granting scope alongside an unknown one still
  grants (unknown is logged, not failing the request)
- old "invalid json allows all" flipped to deny

Parent: PLAN-643 (OSS Security Hardening).

* fix(server): reject JSON null token scopes (TASK-667)

Addresses Codex P2 on PR #192: json.Unmarshal accepts the literal
\`null\` without error and leaves the target slice nil, so "scopes": "null"
would match the legacy empty-array allow-path and grant full access —
bypassing the new deny-by-default intent whenever a client-side
serializer emits null for a missing field.

- Gate the "unrestricted" path on the raw string being "", ["*"],
  [ "*" ], or [] only (with whitespace trimming on the outside). "null"
  no longer slips through.
- Post-unmarshal, any empty slice that wasn't one of those explicit
  allow-forms is logged as "non-array or null scopes; denying" and
  denied.
- New test cases: "json null denies POST" / "json null denies GET".

Parent: PLAN-643 (OSS Security Hardening).

* fix(server): distinguish JSON null from empty array in token scopes (TASK-667)

Addresses Codex P2 on PR #192: the previous raw-string whitelist for
legacy empty-array tokens rejected valid whitespace-padded forms like
\`[ ]\` or \`[\\n]\` that some clients emit. Those decoded to a non-nil
empty slice, so a smarter check works: use the Go json package's
nil-vs-empty distinction.

- scopes == nil → JSON was literal null. Deny + warn (unchanged intent).
- scopes != nil && len == 0 → explicit empty array regardless of
  whitespace. Allow (legacy unrestricted form, as documented).
- scopes has entries → existing whitelist logic.

Empty-string fast path kept for the no-column case; wildcard fast path
now trims whitespace too.

New tests: \`[ ]\`, \`[\\n]\`, \`[\\t]\` empty arrays and \`[ "*" ]\`
wildcard all allow; \`null\` still denies.

Parent: PLAN-643 (OSS Security Hardening).
2026-04-22 11:28:44 -04:00
..
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00