thedotmack/claude-mem · warning
Admin endpoints are only accessible from localhost
Error message
Admin endpoints are only accessible from localhost
What it means
This is the JSON 403 response body message sent by the requireLocalhost middleware when an admin endpoint is requested from a non-localhost client IP. It is a deliberate security guard: admin routes must only be reachable from the local machine, and the access-denied event is logged under the SECURITY logger with endpoint, IP, and method.
Solutions
- Run the requesting client on the same host and target 127.0.0.1 (or ::1) instead of a LAN/public IP.
- If remote access is required, put an authenticated local proxy/tunnel (e.g. SSH port forward) in front of the endpoint rather than exposing it.
- Check clientIp in the log to see why the request appears non-local (proxy X-Forwarded-For handling).
- Ensure the worker binds only to the loopback interface.
Example fix
// before curl http://192.168.1.10:3838/admin/stats // after curl http://127.0.0.1:3838/admin/stats
Defensive patterns
Strategy: validation
Validate before calling
const url = new URL(adminUrl);
if (!['127.0.0.1','localhost','[::1]'].includes(url.hostname)) {
throw new Error('Admin endpoints must be reached via localhost');
} Type guard
const isLocalhostUrl = (u: string) => ['127.0.0.1','localhost','::1'].includes(new URL(u).hostname);
Try / catch
const res = await fetch(adminUrl);
if (res.status === 403) {
// non-localhost access blocked; switch to 127.0.0.1 or set up an SSH tunnel
} Prevention
- Always target 127.0.0.1 for admin routes
- Never expose the worker port beyond loopback
- Use SSH tunnels or an authenticated proxy for remote admin access
When it happens
Trigger: Any HTTP request to an admin endpoint whose resolved clientIp is not a localhost address (127.0.0.1/::1), triggering the isLocalhost check to fail.
Common situations: Accessing the worker admin API from another machine on the network; requests forwarded through a proxy/load balancer so the apparent client IP is remote; misconfigured bind/port exposing the worker externally.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Access denied: " " resolves outside the workspace ( ). MCP…
- Could not restrict permissions on
- Forbidden
- is forbidden from tool use (claude-mem hard lockdown).
- Not found
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/6f70fd19683ffe60.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/worker/http/middleware.ts:88
}
next();
};
}
export function requireLocalhost(req: Request, res: Response, next: NextFunction): void {
const clientIp = req.ip || req.connection.remoteAddress || '';
const isLocalhost =
clientIp === '127.0.0.1' ||
clientIp === '::1' ||
clientIp === '::ffff:127.0.0.1' ||
clientIp === 'localhost';
if (!isLocalhost) {
logger.warn('SECURITY', 'Admin endpoint access denied - not localhost', {
endpoint: req.path,
clientIp,
method: req.method
});
res.status(403).json({
error: 'Forbidden',
message: 'Admin endpoints are only accessible from localhost'
});
return;
}
next();
}
// ---------------------------------------------------------------------------
// Observation TV remote read-only broadcast guard.
//
// The worker's HTTP surface has no request authentication; its only defence is
// the loopback bind. When the operator opens the bind (CLAUDE_MEM_WORKER_HOST)
// so a phone or a spare monitor can watch Observation TV, this guard is the
// whole security boundary: loopback requests are untouched, and every
// non-loopback request is default-denied except an exact-match allowlist ofView on GitHub (pinned to d8bc9755e7)