Files
Void-Homelab/lib/api/middleware/agent_auth.js
root 459a7749c9 fix(auth): constant-time owner-token comparison
Owner bearer token was compared with === / !==, which short-circuits on the
first differing byte and leaks token length+prefix via response timing
(security-sweep-2026-06-01.md). New timingSafeStrEqual (crypto.timingSafeEqual
with a length pre-check so it never throws on length mismatch); wired into both
owner.js and agent_auth.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 23:26:46 +10:00

34 lines
1.1 KiB
JavaScript

import * as agents from '../../db/repos/agents.js';
import { timingSafeStrEqual } from '../../auth/safe_compare.js';
export async function agentOrOwner(req, res, next) {
const expectedOwner = process.env.OWNER_TOKEN;
if (!expectedOwner) {
return res.status(500).json({
error: { code: 'no_owner_token', message: 'OWNER_TOKEN not configured' }
});
}
const auth = req.headers.authorization || '';
const [scheme, token] = auth.split(' ');
if (scheme !== 'Bearer' || !token) {
return res.status(401).json({ error: { code: 'unauthorized', message: 'missing bearer token' } });
}
if (timingSafeStrEqual(token, expectedOwner)) {
req.actor = { kind: 'user', id: null };
return next();
}
try {
const agent = await agents.verifyToken(token);
if (!agent) {
return res.status(401).json({ error: { code: 'unauthorized', message: 'invalid token' } });
}
req.actor = {
kind: 'agent',
id: agent.id,
capabilities: agent.capabilities || {},
scopes: agent.scopes || {}
};
next();
} catch (e) { next(e); }
}