feat(auth): opt-in OIDC DEK unlock for API-key requests (ALLOW_APIKEY_DATA_UNLOCK) (#1064)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* chore: donation bar reporting wrong result

* feat: add Open File Manager to tab right-click menu (#1046)

* Revert "feat: add Open File Manager to tab right-click menu (#1046)" (#1050)

This reverts commit 0712fdd731.

* Remove donation badge from README

Removed donation badge from README.

* Delete .github/workflows/donation-goal.yml

* feat(auth): opt-in OIDC DEK unlock for API-key requests

API keys authenticate but cannot touch the encrypted credential/host store
('User data not unlocked') unless the user has a live interactive session,
making them unusable for headless automation. For OIDC users the DEK is
server-derivable (deriveOIDCSystemKey), so handleApiKeyAuth can unlock it
without a password.

Gated behind ALLOW_APIKEY_DATA_UNLOCK (default off) because enabling it widens
the blast radius of a leaked API key. OIDC-only; password users are untouched.

Refs #1063

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Sankeerth Nara <sankeerthnara@gmail.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
This commit is contained in:
Stephan Groth
2026-07-16 05:59:18 +02:00
committed by GitHub
parent 0a16adf237
commit ced78961a0
2 changed files with 39 additions and 2 deletions
+35
View File
@@ -881,6 +881,41 @@ class AuthManager {
});
});
// Auto-unlock the per-user Data Encryption Key for OIDC-backed users so
// API-key (headless / automation) requests can access encrypted
// credentials/hosts. Without this, every encrypted-table op throws
// "User data not unlocked" because the API-key path never established a
// DEK session. OIDC DEKs are wrapped with a server-derived system key
// (deriveOIDCSystemKey), so no user password is required. Password-based
// users are deliberately NOT unlocked here (their KEK is password-derived
// and cannot be reproduced server-side).
//
// Opt-in and OFF by default: enabling this widens the blast radius of a
// leaked API key (it can then read that user's stored SSH secrets in
// clear text), so operators must turn it on explicitly.
if (
process.env.ALLOW_APIKEY_DATA_UNLOCK === "true" &&
!this.userCrypto.isUserUnlocked(matchedKey.userId)
) {
try {
const { users } = await import("../database/db/schema.js");
const oidcRows = await db
.select()
.from(users)
.where(eq(users.id, matchedKey.userId))
.limit(1);
if (oidcRows[0]?.isOidc) {
await this.authenticateOIDCUser(matchedKey.userId);
}
} catch (err) {
databaseLogger.warn("API-key OIDC auto-unlock failed", {
operation: "api_key_oidc_unlock_failed",
userId: matchedKey.userId,
error: err instanceof Error ? err.message : "Unknown",
});
}
}
req.userId = matchedKey.userId;
req.apiKeyId = matchedKey.id;
next();