Changelog
v4.0.0 (2026-08-19)
Authorization Code Flow for Web (⚠️ Breaking for web apps)
GET /loginnow returns a one-timecodefor web/httpsredirect_uris instead of tokens in the callback URL. Exchange the code at the newPOST /token/exchange({code, app_id[, code_verifier]}) for the token pair. Codes are single-use with a 60-second TTL.- Tokens-in-URL is retained only for native custom-scheme redirect URIs (mobile deep links). Callback delivery is scheme-gated: web/https →
?code=; native →?access_token=&refresh_token=. Auth failures return the user to Keymaster's own login page (/login?error=), not to the app's redirect_uri. - ⚠️ Breaking change: web apps that read
access_token/refresh_tokendirectly from the callback URL will break (redirect loop). They must switch to the code exchange.
PKCE Support
GET /loginacceptscode_challenge+code_challenge_method=S256; pass the matchingcode_verifiertoPOST /token/exchange.
Client Credentials Scope Entitlement
POST /auth/token(client_credentials) now grants only scopes the app is entitled to — each requested scope must be listed in the app'sconfig["service_scopes"], otherwise the request returns400 invalid_scope.
Token Type Enforcement
- User access tokens now carry a
token_type: "access"claim (service tokens usetoken_type: "service"). POST /token/verifynow requires anaudience(pass yourapp_id) and rejects service tokens presented as user tokens.
Security Hardening
Full pre-open-source security audit and remediation (see docs/SECURITY-AUDIT.md, tracked in docs/REMEDIATION.md).
- Account takeover fixed —
/auth/signupand invite redemption no longer issue tokens for an existing account without credentials; an unbound invite can no longer attach to (or set a password on) an existing user. - Audience enforcement — account, TOTP, console, push, and
/token/verifyendpoints now pin the expected audience; service tokens are rejected as user tokens. - Tokens out of redirect URLs for web (see the code flow above).
- Auth codes are no longer replayable from the in-memory fallback after their Redis TTL lapses.
- Atomic refresh rotation — concurrent refreshes can no longer fork a token family and evade reuse detection.
- SSO revocation propagates across workers (logout / password change now take effect everywhere within ~30s).
- SSRF — webhook and push-receipt delivery resolve once and verify the connected peer IP (DNS-rebinding defense).
- Client IP is derived from a trusted-proxy hop count (
TRUSTED_PROXY_HOPS) instead of the rawX-Forwarded-For, restoring IP rate limits. - Security headers — CSP,
X-Frame-Options: DENY,nosniff,Referrer-Policy, HSTS. - Startup validation — refuses to boot on a non-local deployment with a default admin password or missing encryption/HMAC keys.
- Console — stored-XSS sink removed (inline handlers →
data-*);km_console/km_accountare nowHttpOnly; push webhook secret no longer rendered into the page. - OAuth — email auto-linking requires a provider-verified email;
stateis bound to the browser via an HttpOnly nonce cookie; Appleid_tokenpins the algorithm and verifies a nonce. - Authorization — tenant-admin identity now derives from the
is_console_appcolumn rather than a mutablebundle_id; assigned roles are validated against the app'savailable_roles; last-platform-admin protection. - TOTP replay — a code's timestep is recorded and reused codes are rejected.
Tenant-Level App Provisioning (new)
- New machine scopes
apps:readandapps:writelet an app's client credentials act as a tenant-scoped provisioning key: list apps in its tenant, and create/update them viaPOST /admin/apps/PUT /admin/apps/{id}. - Hard constraints: same-tenant only, inactive tenants rejected, and a service token may never set
service_scopes,is_console_app, or a Console-reservedbundle_id(each would be a self-escalation path).apps:readresponses redactservice_scopesand push webhook secrets. - Platform-admin-only to grant. Secret rotation remains Console-only.
- See Tenant-Level App Provisioning.
Console
- API Scopes editor on the app page — grant
client_credentialsscopes without touching the DB.users:createis platform-admin only (it writes to the global cross-tenant user table). - Pagination on Users / Apps / Tenants, and on the app page's push log, enrolled users, and invites.
- The duplicate audit viewer was consolidated into
/console/audit-log(/console/auditnow redirects).
Migrations (auto-applied at startup)
apps.is_console_app(with backfill from the historical bundle_id convention)totp_devices.last_timestepmagic_link_tokens.code_challenge/code_challenge_methodwebhook_endpoints.secret_encself-heal for databases that missed migration 0012
⚠️ Upgrade Notes
- Web integrations must exchange the
code— see the breaking change above. Native custom-scheme apps are unaffected. - Set required secrets or the app will not start on a non-local
GATEWAY_BASE_URL:PLATFORM_ADMIN_PASSWORD,TOTP_ENCRYPTION_KEY,WEBHOOK_SECRET_ENCRYPTION_KEY,INVITE_CODE_HMAC_KEY(andPUSH_TOKEN_ENCRYPTION_KEYwhen push is enabled). - Set
TRUSTED_PROXY_HOPSto match your reverse-proxy chain (default 1). - Grant
config["service_scopes"]to every app usingclient_credentials, or its token requests will return400 invalid_scope. This is not the same key as the OIDCconfig["scopes"]. - Setting
INVITE_CODE_HMAC_KEYinvalidates existing invite codes and TOTP backup codes (they were hashed without it). Re-issue invites and have users regenerate backup codes. Authenticator apps are unaffected.
v3.3.0 (2026-03-22)
Expanded Client Credentials Scopes
invites:create— apps can programmatically create and email invites for their own users viaPOST /admin/invitesusing a service JWTusers:read— apps can list their own enrolled users viaGET /admin/users?app_id=using a service JWTusers:create— apps can create user stubs (email + display_name) viaPOST /admin/usersusing a service JWT (cannot setis_platform_admin)- All new scopes enforce own-app-only access — the service token's
sub(app_id) must match the target resource - Audit log entries from service tokens include
"source": "service_token"in detail - See Server-to-Server Guide for updated client examples (Python + Node.js)
Cleanup
- v2.0 Phase 4 complete —
UserTenantRolemodel, migration shim, and DB table removed. All tenant admin access via per-tenant Console appUserAppRoleexclusively.
v3.1.0 (2026-03-19)
Per-App Push Configuration
- Push config endpoint:
PATCH /push/config/{app_id}— enable/disable push notifications, set webhook URL, configure daily quota per app - Push status endpoint:
GET /push/config/{app_id}— returns enabled state, webhook URL, webhook secret, daily quota, device count, and today's notification count - Device listing endpoint:
GET /push/devices/{app_id}— list all registered devices for an app (admin-only) - Console UI: Notifications tab on App Detail page for managing push config
v3.0.0 (2026-03-19)
Push Notification System
- FCM-based push delivery — Keymaster brokers notifications to Android, iOS, and Web via Firebase Cloud Messaging
- PushDevice model — device registration with platform tracking (web, ios, android), deduplication on
(app_id, token_hash) - Encrypted push tokens — all FCM/APNs tokens encrypted at rest (AES-256-GCM)
- Device registration:
POST /push/devices/register— register a device (user JWT), rate limited to 10/user/hour - Device unregister:
DELETE /push/devices/{device_id}— user or app-initiated removal - Send single:
POST /push/send— send to one user's devices (service JWT,push:sendscope) - Send bulk:
POST /push/send/bulk— same message to up to 500 users - Send batch:
POST /push/send/batch— different messages in one call (up to 500) - Delivery receipts — webhook callbacks with HMAC-SHA256 signature verification
- Auto-cleanup — invalid tokens automatically removed on FCM
invalid_tokenresponse - Daily quota enforcement — 10,000 notifications/day per app (default), 429 with
Retry-Afteron exceeded
v2.4.0 (2026-03-19)
Passwordless Accounts
- Password is now optional on all registration flows: invite redeem, accept-invite, and open signup
- Users can create accounts via OAuth only and set a password later from the Account page
- New endpoint:
POST /account/set-password— allows OAuth-only users to add a password to their account
Export/Import
- App export:
GET /admin/apps/{id}/export— export app configuration as JSON - Optional
?include_users=truequery param to include user enrollments - Tenant export:
GET /admin/tenants/{id}/export— export tenant and all its apps - App import:
POST /admin/apps/import— import an app into a tenant - Body:
{ tenant_id, app, preserve_bundle_id } - Tenant import:
POST /admin/tenants/import— import a tenant and all its apps - Body:
{ tenant, apps } - Import always generates new IDs and client secrets. Users are optionally imported.
Fixes
- User delete cascade — deleting a user now properly cascades all related records (SSO sessions, TOTP secrets, backup codes, magic links, email tokens)
v2.1.0 (2026-03-17)
Console — Tiered Impersonation
- Tenant admin → app admin impersonation — tenant admins can enter any app in their tenant as app admin (Console-only, audited, session-scoped)
- Platform admin → tenant admin impersonation (introduced in v2.0.0) now stacks with app impersonation
- Amber banner with exit link for both impersonation tiers
- Audit events:
app_impersonation_started,app_impersonation_ended
Console — SSO-Based Authentication
- Console login now uses SSO session (
km_sso) directly — no app enrollment required to access the Console - Console JWT issued with audience
keymaster-console(decoupled from any specific app) - Platform Keymaster Console app serves as auth gateway only (open policy)
- Per-tenant Console apps serve as role containers (tenant admin =
adminrole on tenant Console app)
Console — Multi-Tenant UX
- Tenant picker for users who are admin on multiple tenants
- "Switch Tenant" sidebar link only shown when user has 2+ tenants
- Active tenant stored in session cookie (
km_active_tenant)
Fixes
get_user_tenant_idsnow impersonation-aware — all routes work correctly during impersonation- Platform admins auto-enrolled in Console apps on login regardless of registration policy
v2.0.0 (2026-03-17)
Per-Tenant Console Isolation
- Each tenant now gets its own Console app (
{Tenant Name} Console), auto-created on tenant bootstrap - Tenant admin access is determined by
adminrole on the per-tenant Console app (replacesUserTenantRole) - Platform admins access tenant contexts via impersonation, not enrollment
- Tenant creation now requires an admin email — no orphan tenants
Platform Admin Impersonation
- Platform admins can impersonate any tenant admin (Console-only, no SSO tokens issued)
- Session-scoped via
km_impersonatecookie (1-hour TTL) - All impersonated actions logged with
impersonated_byin audit trail - Visible to tenant admins in their audit log
SDK Developer Docs Portal
- Full SDK documentation at
/docs/(also accessible from Console sidebar) - LLM-digestible endpoints:
/llms.txt,/llms-full.txt - OpenAPI/Swagger moved to
/api/docs build-docs.shscript for regenerating docs on feature changes- Version display in Console sidebar footer
Breaking Changes
UserTenantRoletable deprecated — tenant admin access now via per-tenant Console app enrollment- Console JWT audience changed from app ID to
keymaster-console
v1.0.1 (2026-03-17)
Fixes
- Docs routing: handle
.mdextensions in URLs - Swagger/OpenAPI moved to
/api/docs(was/docs) - Landing page updated with SDK docs link
v1.0.0 (2026-03-17)
Initial public release.
Authentication
- Password + Google + GitHub + Microsoft + Apple OAuth (shared provider registration)
- Cross-app SSO via
km_ssocookie (8-hour sessions) - Magic links (passwordless email login, 15-min expiry)
- TOTP 2FA with encrypted secrets + backup codes
- OAuth invite-required flow (policy-aware routing for invite-only apps)
Tokens
- RS256 JWT access tokens (configurable TTL, default 15 min)
- Refresh token rotation with replay detection (30-day rolling window)
- Client credentials grant (
POST /auth/token,grant_type=client_credentials) - Service tokens with scope enforcement (
push:send)
User Management
- Three-tier admin: platform admin → tenant admin → app admin
- App roles:
user,manager,admin(standardized) - Manager role: invite/revoke users only, no config access
- Invite system with email delivery, per-app branding, expiration
- Password policy: 16-char minimum, configurable per tenant
Console
- Full admin Console with dark/light theme
- App configuration: branding, auth methods, token TTLs, redirect URIs
- User management: invite, roles, suspend, rate limit viewer
- Tenant admin dashboard with admin management
- Audit log viewer with filtering and pagination
- Session management (view/revoke active SSO sessions)
- In-app help system (role-scoped, searchable)
Infrastructure
- App logo upload (base64 encoded, served via public endpoint for emails)
- Outbound webhooks (HMAC-SHA256 signed, retry with backoff, SSRF protection)
- Redis session store (OAuth state, SSO cache, rate limiting)
- Health check endpoint (
GET /health) - Email infrastructure (Postfix DKIM, branded templates)
- Auto-apply migrations on startup
- OIDC discovery + JWKS endpoints
Security
- Argon2id password hashing
- TOTP secret encryption (AES-256-GCM)
- Push token encryption at rest
- Webhook URL SSRF validation (blocks private/reserved IP ranges)
- Rate limiting (Redis + in-memory fallback, auto-clear on success)
- Refresh token replay detection with automatic revocation