{"record":{"id":"8c13ba3fc9ebb22c","repo":"affaan-m/ECC","slug":"ecc-ecc-dashboard-host-must-be-loopback-only-12","errorCode":null,"errorMessage":"[ECC] ECC_DASHBOARD_HOST must be loopback-only (127.0.0.1, localhost, or ::1).","messagePattern":"\\[ECC\\] ECC_DASHBOARD_HOST must be loopback-only \\(127\\.0\\.0\\.1, localhost, or ::1\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/dashboard-web.js","lineNumber":30,"sourceCode":"const fs = require('fs');\nconst path = require('path');\nconst http = require('http');\nconst {\n  LOOPBACK_HOSTNAMES,\n  buildAllowedHostnames,\n  isAllowedHostHeader,\n  isAllowedOrigin,\n} = require('./lib/loopback-guard');\nconst { normalizeAgentTools } = require('./lib/agent-tools');\nconst { readHooksConfig } = require('./lib/hooks-config');\n\nconst DEFAULT_HOST = '127.0.0.1';\n\nfunction resolveDashboardHost(env = process.env) {\n  const configured = String(env.ECC_DASHBOARD_HOST || '').trim().toLowerCase();\n  if (!configured) return DEFAULT_HOST;\n  if (!LOOPBACK_HOSTNAMES.has(configured)) {\n    throw new Error(\n      '[ECC] ECC_DASHBOARD_HOST must be loopback-only ' +\n      '(127.0.0.1, localhost, or ::1).'\n    );\n  }\n  return configured === '[::1]' ? '::1' : configured;\n}\n\nfunction parsePort(v) {\n  const n = parseInt(String(v), 10);\n  if (isNaN(n) || n < 1 || n > 65535) { console.error('[ECC] Invalid port: ' + v + ' — using 3456'); return 3456; }\n  return n;\n}\nconst PORT = parsePort(process.argv[2] || process.env.ECC_DASHBOARD_PORT || '3456');\nconst HOST = resolveDashboardHost();\nconst ROOT = path.resolve(__dirname, '..');\n\nfunction readFrontmatter(p) {\n  try {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/dashboard-web.js#L12-L48","documentation":"resolveDashboardHost in scripts/dashboard-web.js enforces that the dashboard HTTP server binds only to loopback addresses. ECC_DASHBOARD_HOST set to anything other than 127.0.0.1, localhost, or ::1 throws, preventing accidental exposure of the dashboard to the network. This is a deliberate security-by-default measure.","triggerScenarios":"Setting ECC_DASHBOARD_HOST to a non-loopback value such as 0.0.0.0, a LAN IP (192.168.x.x), a hostname, or an uppercase/untrimmed variant that still fails the loopback set check.","commonSituations":"Trying to expose the dashboard in Docker or to a phone on the LAN by setting 0.0.0.0; CI setting the host to a container hostname; trailing spaces or capitalization quirks in env files.","solutions":["Set ECC_DASHBOARD_HOST to 127.0.0.1, localhost, or ::1 (default 127.0.0.1 is used when unset).","If remote access is needed, put a proper reverse proxy with auth in front of the loopback-bound server instead of rebinding.","Check .env / shell exports for stray whitespace or casing and normalize the value."],"exampleFix":"// before\nECC_DASHBOARD_HOST=0.0.0.0 node scripts/dashboard-web.js\n// after\nECC_DASHBOARD_HOST=127.0.0.1 node scripts/dashboard-web.js","handlingStrategy":"validation","validationCode":"const host = (process.env.ECC_DASHBOARD_HOST || '').trim().toLowerCase();\nif (host && !['127.0.0.1', 'localhost', '::1'].includes(host)) {\n  throw new Error(`ECC_DASHBOARD_HOST must be loopback, got: ${host}`);\n}","typeGuard":"function isLoopbackHost(v) { return ['127.0.0.1','localhost','::1','[::1]'].includes(String(v).trim().toLowerCase()); }","tryCatchPattern":"try {\n  startDashboard();\n} catch (e) {\n  if (e.message.includes('loopback-only')) {\n    console.error('Unset ECC_DASHBOARD_HOST or set it to 127.0.0.1 to use the dashboard locally.');\n    process.exit(1);\n  } else throw e;\n}","preventionTips":["Never set ECC_DASHBOARD_HOST to 0.0.0.0 or a LAN IP; use a reverse proxy for remote access.","Normalize env values (trim, lowercase) in deployment scripts.","Document the loopback-only policy in onboarding/runbooks."],"tags":["security","env-var","server-binding","loopback"],"backgroundTag":"invalid-env-var-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}