From cb7cc9a43e254df06a190f6aa0ff4c92cca91139 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Wed, 27 May 2026 14:18:27 -0400 Subject: [PATCH] fix(mam-api): narrow cluster carve-out to /cluster/heartbeat only Code-review feedback: startsWith('/cluster') was a prefix match that exposed destructive operator endpoints (POST /containers/:id/restart, DELETE /:id, GET /devices/blackmagic/*) unauthenticated. Only POST /heartbeat is genuine node-agent traffic; everything else in cluster.js is operator/UI surface that should go through requireAuth. Long-term: issue node-agent a bound api_token and drop the carve-out entirely. --- services/mam-api/src/index.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/services/mam-api/src/index.js b/services/mam-api/src/index.js index 934b5c8..fb914fa 100644 --- a/services/mam-api/src/index.js +++ b/services/mam-api/src/index.js @@ -88,13 +88,18 @@ app.use(session({ app.get('/health', (_req, res) => res.json({ status: 'ok' })); // ── Auth gate ───────────────────────────────────────────────────────────────── -// Mount once for everything under /api/v1, with an explicit allowlist for -// the three pre-login auth paths and a carve-out for /cluster/* (node-agent -// uses migration 019's token-binding, not user auth). See spec. +// req.path is relative to the /api/v1 mount, so /auth/login NOT /api/v1/auth/login. const UNAUTH_PATHS = new Set(['/auth/login', '/auth/setup', '/auth/setup-required']); +// Service-auth carve-outs: node-agent uses migration 019's bound-hostname +// api_token mechanism, not user auth. Today only /cluster/heartbeat is +// reached without a user session — operator/UI endpoints in cluster.js +// (containers restart, DELETE /:id, blackmagic device queries) ARE expected +// to require auth. If node-agent grows another endpoint, add it here. +// TODO: long-term, issue node-agent a real bound api_token and drop this carve-out. +const SERVICE_PATHS = new Set(['/cluster/heartbeat']); app.use('/api/v1', (req, res, next) => { if (UNAUTH_PATHS.has(req.path)) return next(); - if (req.path.startsWith('/cluster')) return next(); // node-agent service auth, not user auth + if (SERVICE_PATHS.has(req.path)) return next(); return requireAuth(req, res, next); });