thedotmack/claude-mem · error

Worker bound to a non-loopback host with no…

Error message

Worker bound to a non-loopback host with no CLAUDE_MEM_TV_TOKEN — the full worker API, including provider API keys via GET /api/settings, is reachable from the network

What it means

When the worker starts bound to a non-loopback host while CLAUDE_MEM_TV_TOKEN is unset, everything except the TV paths is exposed unauthenticated on the network — including GET /api/settings, which returns provider API keys. The worker deliberately warns instead of refusing so documented docker setups (CLAUDE_MEM_WORKER_HOST=0.0.0.0) keep working.

Solutions

  1. Set CLAUDE_MEM_TV_TOKEN to a strong random value in the worker environment.
  2. Bind the worker to loopback (CLAUDE_MEM_WORKER_HOST=127.0.0.1) and reverse-proxy externally with auth.
  3. If Docker requires 0.0.0.0, keep the port unpublished from the host network or restrict with firewall rules.
  4. Never expose GET /api/settings publicly; if keys may have leaked, rotate provider API keys immediately.

Example fix

// before
CLAUDE_MEM_WORKER_HOST=0.0.0.0
// after
CLAUDE_MEM_WORKER_HOST=0.0.0.0
CLAUDE_MEM_TV_TOKEN=$(openssl rand -hex 32)
Defensive patterns

Strategy: validation

Validate before calling

const host = process.env.CLAUDE_MEM_WORKER_HOST ?? '127.0.0.1';
if (host !== '127.0.0.1' && host !== 'localhost' && !process.env.CLAUDE_MEM_TV_TOKEN) {
  throw new Error('Refusing non-loopback bind without CLAUDE_MEM_TV_TOKEN');
}

Prevention

When it happens

Trigger: start (called by main) with host set to 0.0.0.0, a LAN IP, a Docker/bridge address, etc., while no CLAUDE_MEM_TV_TOKEN is present in the environment.

Common situations: Docker installs following docs/docker.md with CLAUDE_MEM_WORKER_HOST=0.0.0.0; running the worker on a shared server; exposing the port through a router/port-forward without a token.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/cf8941270e6a4040. Report an issue: GitHub.

Appendix: source

Thrown at src/services/worker-service.ts:486

    // Must run before startSupervisor(): its validateWorkerPidFile() removes
    // the dead previous run's stale PID file, which crash detection needs.
    this.detectPreviousShutdown();

    await startSupervisor();

    await this.server.listen(port, host);

    if (this.tvToken) {
      // Operators need to see, in the log, that a remote surface is open.
      // Never log the token itself.
      logger.info('SYSTEM', 'Observation TV remote broadcast enabled', {
        host,
        allowedPaths: ['/tv', '/tv.html', '/stream', 'GET /api/observations'],
      });
    } else if (host !== '127.0.0.1' && host !== '::1' && host !== '::ffff:127.0.0.1' && host !== 'localhost') {
      // Warn, do not refuse to bind: docs/docker.md tells people to set
      // CLAUDE_MEM_WORKER_HOST=0.0.0.0, and refusing would break that install.
      logger.warn(
        'SECURITY',
        'Worker bound to a non-loopback host with no CLAUDE_MEM_TV_TOKEN — the full worker API, including provider API keys via GET /api/settings, is reachable from the network',
        { host }
      );
    }

    writePidFile({
      pid: process.pid,
      port,
      startedAt: new Date().toISOString()
    });

    getSupervisor().registerProcess('worker', {
      pid: process.pid,
      type: 'worker',
      startedAt: new Date().toISOString()
    });

View on GitHub (pinned to d8bc9755e7)