mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-10 10:49:35 +00:00
fix(fleet-secrets): restrict bundle management to admin hub sessions (#1274)
* fix(fleet-secrets): restrict bundle management to admin hub sessions Fleet Secrets exposes decrypted environment-variable values and writes credentials across the fleet, so every route now requires an admin role, runs only on the instance you are signed into, and rejects long-lived API tokens: - Add an admin-role check to all secrets routes; the frontend Secrets tab renders only for admin users so the affordance matches the backend gate. - Add /api/secrets/ to the hub-only path list so a request carrying a remote node id cannot be proxied to read another node's decrypted values. - Reject API tokens on every secrets route (browser admin sessions only), matching how registry credentials are handled. Also adds lifecycle and developer-mode diagnostic logging (never the secret values) and tests covering the admin boundary on every endpoint, API-token rejection, hub-only enforcement, and diagnostic gating. * fix(fleet-secrets): require a signed-in user session for all secrets routes The earlier API-token rejection only blocked opaque API tokens. node_proxy and pilot_tunnel JWTs are mapped to an admin role by the auth middleware without an API-token scope, so they still passed the admin gate and could read decrypted bundles via GET /api/secrets/:id. Replace the API-token check with requireUserSession, which rejects API tokens and node_proxy / pilot_tunnel machine credentials (userId 0) on every secrets route, returning SESSION_REQUIRED. The admin role is still enforced after. Tests now assert SESSION_REQUIRED for a full-admin API token across all nine routes and for node_proxy and pilot_tunnel JWTs. * test(fleet-secrets): mint the rejection-test token via the real endpoint The machine-credential test reconstructed an API token by sha256-hashing a raw key inline. That duplicated a hashing sink that CodeQL's js/insufficient-password-hash query flags (a false positive for a 256-bit random token, but a new occurrence in the diff). Create the token through POST /api/api-tokens instead, so the hashing stays in the production path and the test carries none of its own. Behavior and coverage are unchanged.
This commit is contained in:
@@ -50,6 +50,20 @@ export const requireAdmin = (req: Request, res: Response): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Require a genuine signed-in user session. Rejects opaque API tokens
|
||||
* (`req.apiTokenScope`) and node_proxy / pilot_tunnel machine credentials,
|
||||
* which authMiddleware maps to role 'admin' with userId 0. Endpoints that
|
||||
* expose decrypted secrets use this so a long-lived machine credential cannot
|
||||
* reach them; the admin role itself is still enforced separately by requireAdmin.
|
||||
*/
|
||||
export const requireUserSession = (req: Request, res: Response): boolean => {
|
||||
if (req.apiTokenScope || !req.user || req.user.userId === 0) {
|
||||
return deny(res, 'SESSION_REQUIRED', 'This action requires a signed-in user session.');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Accept only calls from a sibling Sencho using its node_proxy Bearer token.
|
||||
* Browser sessions, API tokens, and console tokens are all rejected.
|
||||
|
||||
Reference in New Issue
Block a user