You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
POST /certify: a new authenticated (session-cookie) endpoint that certifies a caller-supplied verification key under the caller's own realm_id/sub/auth_scheme, returning a long-lived, ES256-signed certificate. It is signed with a dedicated certificate signing key (certificate_jwt_params), entirely separate from the session JWT key, so it can never be presented back as a session cookie/token even if algorithms collide.
Added GET /.well-known/certificate-jwks.json, a JWKS document for the new certificate signing key, kept separate from the existing session /.well-known/jwks.json for the same isolation reason.
Added a per-realm certificate_max_age_seconds field controlling how long issued certificates remain valid (defaults to one year), following the same pattern as the existing session_max_age_seconds.
POST /login's per-IP rate limiter is now configurable via ServerParams::login_rate_limit_per_second / login_rate_limit_burst (both optional, default to the previous hardcoded values: 5 req/s, burst of 10).
UserPass gained extra_claims, arbitrary claims a realm admin sets at enrollment that are merged into the session JWT on username/password login (rejected with 400 on collision with a typed claim or if serialized size exceeds 4 KiB), and POST /certify gained a claims field (plus exclude_sub) to selectively copy a session's extra claims into an isolated, separately-signed certificate.
UserPass gained password_input, a PasswordInput enum (Plaintext(String) or Hashed(String)) letting a caller provision a credential either from a plaintext password or from an already-computed Argon2id PHC string without ever sending its plaintext to this server; the latter is rejected with 400 unless it uses exactly this server's own algorithm/version/cost parameters.
CredentialModal (create mode): added a plaintext/pre-hashed password toggle (PasswordFields) so an admin can provision a credential from an already-computed Argon2 PHC string instead of a plaintext password, and a key/value extra-claims editor (ExtraClaimsEditor) for UserPass.extra_claims, matching the new server-side password_input/extra_claims fields.
Bug Fixes
POST /admins, POST /admins/realms, and POST /realms/{realm_id}/userpass used to leak a raw 500 with database internals on a duplicate ID/username; all three now return a clean 409 Conflict (the userpass case unconditionally, since distinguishing a genuine conflict from a byte-for-byte resubmission would require checking the submitted password against the stored hash on this unrate-limited, admin-authenticated endpoint, turning it into a password-guessing oracle).
CredentialModal's roles-fetch effect had no unmount guard: if the list() call resolved after the modal/component was torn down, the resulting setAvailableRoles call could fire against a dead environment. Added the same cancelled guard already used by the form-validity effect right below it.
Column migrations (e.g. userpass.roles) now use the same atomic ADD COLUMN IF NOT EXISTS on PostgreSQL as other columns instead of a SELECT-then-ALTER check missing a schema filter, and all three backends (PostgreSQL, MySQL, SQLite) now propagate a failure of that check instead of silently treating it as "column missing".
Plaintext passwords are now wrapped in Zeroizing through the HTTP Basic Auth extraction path and the userpass create/update handlers, reducing how long they remain in cleartext in application-held memory.
Raised the server's Argon2id parameters to RFC 9106 §4's recommended t=3, m=64 MiB (65536), p=4 (was t=3, m=4 MiB (4096), p=1, a wrongly-documented value inherited from the crate's own default) and now pin/validate the algorithm version (v=19) in addition to the variant and cost parameters when accepting a pre-hashed password_input: { hashed: ... }.
Bumped h2 from 0.4.13 to 0.4.16 (hyper/reqwest instance) to fix RUSTSEC-2026-0258 (unbounded empty DATA frames, low severity); the other instance, pulled in transitively via actix-http 3.13.1 (pinned to h2 ^0.3, no patched 0.3.x release exists), is now explicitly ignored in deny.toml. Also removed a stale RUSTSEC-2023-0071 ignore entry.
Bumped eight admin-ui dependencies flagged by Dependabot to pick up upstream fixes: antd, react-router, @testing-library/react, @types/react-dom, @types/node, eslint, eslint-plugin-react-refresh, and typescript-eslint.
Refactor
Removed public_key_pem from POST /login and the as_pk private claim it populated on the session JWT: this was dead weight (no caller ever set it) and the wrong shape for the job — VELO, the intended consumer, now uses /certify instead, which produces a purpose-built, long-lived, cryptographically isolated certificate rather than piggybacking on the short-lived session token.
UserPass.password/hashed_password (two separate, manually-mutually-exclusive fields) replaced by a single password_input: Option<PasswordInput> field, enforcing the plaintext-vs-pre-hashed exclusivity at the type level instead of at runtime — a request providing both is now structurally impossible rather than a 400 caught after the fact.
Renamed UserPass.password to password_hash and changed its type from Vec<u8> to String: the stored value has always been the full Argon2id PHC string, so the old name/type obscured what it actually holds and forced an unnecessary byte/string round-trip. The three database backends still store it as a binary column (BYTEA/BLOB, unchanged — no migration needed) and convert at the Rust boundary.
Tests
Added certify_tests.rs: session requirement, empty-key rejection (400), missing server configuration (500), certificate JWKS availability, and cryptographic isolation (a certificate signed with the certificate key is rejected by the session JWT decoding key).
Added a userpass duplicate-creation regression test and renamed a misleading pre-existing duplicate-admin test for clarity.
Fixed a flaky admin-ui unit test (window is not defined, RealmContext.test.tsx) by guarding RealmContext's fetchRealms against post-unmount setState and extending the scheduler-drain loop in afterEach.
Docs
Added SECURITY.md, a hand-maintained security policy and vulnerability-disclosure ledger, so the auth server has the same advisory-tracking surface as the KMS repository.
Documented the advisory-ledger lifecycle in AGENTS.md §11 and mirrored a short pointer in .github/copilot-instructions.md, defining the COSMIAN-AUTH-<YYYY>-NNN ID scheme, the released-vs-unreleased rule, and the three-part internal-consistency requirement.
CI
Regenerated the Nix admin-ui pnpm dependencies hash (nix/expected-hashes/admin-ui.pnpm.*.sha256) for all three platforms after several Dependabot bumps to admin-ui/pnpm-lock.yaml went unmatched by a hash regeneration, breaking the Nix Packaging CI job; also corrected a stale darwin hash.
Fixed the admin-ui package-update pipeline: regenerated pnpm-lock.yaml to record the pnpm.overrides block (was causing ERR_PNPM_LOCKFILE_CONFIG_MISMATCH), added a browser User-Agent to Nix's fetchurl/importCargoLock calls to stop crates.io HTTP 403s, and pinned npm_config_node_version=22.12.0 so pnpm's engine check picks up the @rolldown/binding-linux-x64-gnu optional dependency.
Security
Recorded previously shipped and fixed vulnerabilities in SECURITY.md: COSMIAN-AUTH-2026-001 and COSMIAN-AUTH-2026-002 (plaintext password storage via the create_userpass/update_userpass endpoints), COSMIAN-AUTH-2026-003 (TLS private key baked into the Docker image), and COSMIAN-AUTH-2026-004 (vulnerable admin UI transitive dependencies).