{"record":{"id":"cf8941270e6a4040","repo":"thedotmack/claude-mem","slug":"worker-bound-to-a-non-loopback-host-with-no-claude-mem-tv","errorCode":null,"errorMessage":"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","messagePattern":"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","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/worker-service.ts","lineNumber":486,"sourceCode":"    // Must run before startSupervisor(): its validateWorkerPidFile() removes\n    // the dead previous run's stale PID file, which crash detection needs.\n    this.detectPreviousShutdown();\n\n    await startSupervisor();\n\n    await this.server.listen(port, host);\n\n    if (this.tvToken) {\n      // Operators need to see, in the log, that a remote surface is open.\n      // Never log the token itself.\n      logger.info('SYSTEM', 'Observation TV remote broadcast enabled', {\n        host,\n        allowedPaths: ['/tv', '/tv.html', '/stream', 'GET /api/observations'],\n      });\n    } else if (host !== '127.0.0.1' && host !== '::1' && host !== '::ffff:127.0.0.1' && host !== 'localhost') {\n      // Warn, do not refuse to bind: docs/docker.md tells people to set\n      // CLAUDE_MEM_WORKER_HOST=0.0.0.0, and refusing would break that install.\n      logger.warn(\n        'SECURITY',\n        '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',\n        { host }\n      );\n    }\n\n    writePidFile({\n      pid: process.pid,\n      port,\n      startedAt: new Date().toISOString()\n    });\n\n    getSupervisor().registerProcess('worker', {\n      pid: process.pid,\n      type: 'worker',\n      startedAt: new Date().toISOString()\n    });\n","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/worker-service.ts#L468-L504","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set CLAUDE_MEM_TV_TOKEN to a strong random value in the worker environment.","Bind the worker to loopback (CLAUDE_MEM_WORKER_HOST=127.0.0.1) and reverse-proxy externally with auth.","If Docker requires 0.0.0.0, keep the port unpublished from the host network or restrict with firewall rules.","Never expose GET /api/settings publicly; if keys may have leaked, rotate provider API keys immediately."],"exampleFix":"// before\nCLAUDE_MEM_WORKER_HOST=0.0.0.0\n// after\nCLAUDE_MEM_WORKER_HOST=0.0.0.0\nCLAUDE_MEM_TV_TOKEN=$(openssl rand -hex 32)","handlingStrategy":"validation","validationCode":"const host = process.env.CLAUDE_MEM_WORKER_HOST ?? '127.0.0.1';\nif (host !== '127.0.0.1' && host !== 'localhost' && !process.env.CLAUDE_MEM_TV_TOKEN) {\n  throw new Error('Refusing non-loopback bind without CLAUDE_MEM_TV_TOKEN');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set CLAUDE_MEM_TV_TOKEN when binding beyond loopback","Default CLAUDE_MEM_WORKER_HOST to 127.0.0.1 in local dev","Review what GET /api/settings exposes before exposing the worker to a network","Rotate provider API keys if the worker was publicly reachable without a token"],"tags":["security","authentication","network-exposure"],"backgroundTag":"missing-credentials","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}