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

  1. Run the requesting client on the same host and target 127.0.0.1 (or ::1) instead of a LAN/public IP.
  2. 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.
  3. Check clientIp in the log to see why the request appears non-local (proxy X-Forwarded-For handling).
  4. 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

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


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 of

View on GitHub (pinned to d8bc9755e7)