Skip to content

fix(exousia): harden auth paths (constant-time keys, DB-authoritative bearer, refresh TOCTOU) - #476

Merged
forkwright merged 1 commit into
mainfrom
fix/exousia-auth-hardening
Jul 1, 2026
Merged

fix(exousia): harden auth paths (constant-time keys, DB-authoritative bearer, refresh TOCTOU)#476
forkwright merged 1 commit into
mainfrom
fix/exousia-auth-hardening

Conversation

@forkwright

Copy link
Copy Markdown
Owner

Closes #473, #418, #419, #420.

Changes

  • api_key.rs compares hashed API keys with non-constant-time equality #473 — constant-time API-key comparison. validate_api_key compared SHA-256 hex digests with ==, leaking a timing side-channel on an auth path. Now uses subtle::ConstantTimeEq. Class-swept the crate for other secret == comparisons (none remained). Near-miss + empty-hash tests added.
  • validate_bearer trusts JWT role claim and skips is_active check, diverging from the API-key path #418 — DB-authoritative bearer. validate_bearer trusted the JWT role claim and never re-checked the user, diverging from validate_api_key. It now fetches the user row and takes role + is_active from the DB, so revocation and role demotion take effect within a token's lifetime. The JWT is authoritative for identity only. Tests: role-demotion-reflected, deactivated-user-rejected, deleted-user-rejected.
  • Refresh token rotation has a TOCTOU window allowing double-use #419 — refresh-token TOCTOU. refresh() ran read-check-revoke-insert as three unsynchronized statements across pools, allowing double-use under concurrency. Now one BEGIN IMMEDIATE transaction on the single-writer pool (adds apotheke begin_immediate/commit_tx and generalizes the refresh-token repo fns over an Executor). Concurrent-double-use test asserts exactly one success and no orphaned rows.
  • Query-parameter bearer token (?token=) places credential in URIs — logs, history, Referer #420 — query-param token removal. The ?token= path minted a full-duration bearer session; a repo-wide grep found no consumer (theatron uses bearer_auth only). Removed extract_query_token, its call site, and AuthMethod::QueryParam. Docs updated to describe the header-only model and the short-TTL stream-token precondition for any future URL-delivered credential.

Verification

kanon gate --full green (fmt, check, advisory-parity, cargo-deny, clippy workspace, nextest 1166 tests, kanon lint). Cross-crate: full-workspace clippy+test confirm archon and callers compile against the generalized apotheke signatures.

… bearer, refresh TOCTOU)

- #473: constant-time API-key hash comparison via subtle::ConstantTimeEq;
  the plain == on hex digests leaked a timing side-channel.
- #418: validate_bearer now fetches the user row and takes role + is_active
  from the DB, not the JWT claim, so revocation and role demotion take
  effect within a token's lifetime (matching validate_api_key). The JWT is
  authoritative for identity only.
- #419: refresh() runs read-check-revoke-insert inside one BEGIN IMMEDIATE
  transaction on the single-writer pool, closing the refresh-token
  double-use TOCTOU. Adds apotheke begin_immediate/commit_tx helpers and
  generalizes the refresh-token repo fns over an Executor.
- #420: remove the ?token= query-param auth path (credential-in-URL leak);
  grep confirmed no consumer. Drops AuthMethod::QueryParam.

Closes #473
Closes #418
Closes #419
Closes #420

Gate-Passed: kanon 0.1.5 +stages:fmt,check,clippy,nextest,lint sha:79d6e7cb5466037cf22e6b409e3730ca0bbb199c
@forkwright
forkwright merged commit fae78ab into main Jul 1, 2026
11 checks passed
@forkwright
forkwright deleted the fix/exousia-auth-hardening branch July 1, 2026 21:41
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.

api_key.rs compares hashed API keys with non-constant-time equality

1 participant