security(relay): minor hardening follow-ups from PR review
- Uniform auth responses remove the session-enumeration oracle: unknown session and wrong password now return an identical 401 at the API layer and an identical WS handshake/close; desktop-status and the 503 "not connected" signal are only exposed after successful auth. - Session-count cap re-checked after bcrypt.hash so concurrent creates can't overshoot maxSessions. - Relay web client reconnect backoff resets only after a successful auth response (an accept-then-close server no longer defeats the backoff). - idGenerator comment corrected to match the rejection-sampling; drop unused recordFailure return value. - DEPLOY.md: document ALLOWED_ORIGINS for reverse-proxy deployments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,26 +12,22 @@ export function createApiProxy(
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const sessionId = req.params.sessionId;
|
||||
|
||||
// Check session exists
|
||||
const session = sessionManager.getSession(sessionId);
|
||||
if (!session) {
|
||||
return res.status(404).json({ error: 'Session not found' });
|
||||
}
|
||||
|
||||
// Credentials must be supplied via the header (never the URL/query,
|
||||
// which leaks into logs, history and referrers).
|
||||
const password = req.headers['x-macropad-password'] as string;
|
||||
|
||||
if (!password) {
|
||||
return res.status(401).json({ error: 'Password required' });
|
||||
// Uniform auth failure. A missing session, a missing password, and a
|
||||
// wrong password all return the SAME status + generic message so a
|
||||
// caller cannot infer whether a given session id exists (no
|
||||
// enumeration oracle). validatePassword() safely returns false for an
|
||||
// unknown session id.
|
||||
if (!password || !(await sessionManager.validatePassword(sessionId, password))) {
|
||||
return res.status(401).json({ error: 'Authentication failed' });
|
||||
}
|
||||
|
||||
const valid = await sessionManager.validatePassword(sessionId, password);
|
||||
if (!valid) {
|
||||
return res.status(401).json({ error: 'Invalid password' });
|
||||
}
|
||||
|
||||
// Check desktop is connected
|
||||
// Only AFTER successful auth do we reveal desktop connectivity, so the
|
||||
// 503 "not connected" signal cannot itself be used to probe which
|
||||
// session ids exist.
|
||||
const desktop = connectionManager.getDesktopBySessionId(sessionId);
|
||||
if (!desktop) {
|
||||
return res.status(503).json({ error: 'Desktop not connected' });
|
||||
|
||||
Reference in New Issue
Block a user