{"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":"critical","filePath":"scripts/dashboard-web.js","lineNumber":29,"sourceCode":"\nconst 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');\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":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/dashboard-web.js#L11-L47","documentation":"The resolveDashboardHost() function in dashboard-web.js reads the ECC_DASHBOARD_HOST environment variable and validates it against LOOPBACK_HOSTNAMES, which is the set {'127.0.0.1', 'localhost', '[::1]', '::1'}. Any other value — including 0.0.0.0, LAN IPs, hostnames, or public addresses — is rejected. This is a deliberate security posture: the dashboard has no authentication and must never bind to a non-loopback interface.","triggerScenarios":"Setting ECC_DASHBOARD_HOST=0.0.0.0 to expose the dashboard on all interfaces; setting it to a LAN IP like 192.168.1.100 for remote access; setting it to a hostname; or setting it to a public IP. Any of these trigger the guard before the HTTP server is created.","commonSituations":"Users trying to access the dashboard from another machine on the network; Docker containers where the host expects 0.0.0.0 binding; misconfigured environment from a deployment template that defaults to 0.0.0.0; SSH tunnel setups that expect a specific bind address.","solutions":["Unset ECC_DASHBOARD_HOST to use the default 127.0.0.1, then access via localhost or an SSH tunnel","Set ECC_DASHBOARD_HOST to exactly '127.0.0.1', 'localhost', or '::1'","For remote access, use SSH port forwarding (ssh -L 3456:127.0.0.1:3456 user@host) rather than widening the bind address","Do NOT set ECC_DASHBOARD_HOST to 0.0.0.0 — the dashboard lacks authentication and exposing it is a security risk"],"exampleFix":"// before\nexport ECC_DASHBOARD_HOST=0.0.0.0\nnode scripts/dashboard-web.js\n// after (option 1: unset)\nunset ECC_DASHBOARD_HOST\nnode scripts/dashboard-web.js\n// after (option 2: explicit loopback)\nexport ECC_DASHBOARD_HOST=127.0.0.1\nnode scripts/dashboard-web.js","handlingStrategy":"validation","validationCode":"// Validate ECC_DASHBOARD_HOST before launching the server\nconst LOOPBACK = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);\nconst host = (process.env.ECC_DASHBOARD_HOST || '').trim().toLowerCase();\nif (host && !LOOPBACK.has(host)) {\n  console.error(`[ECC] ECC_DASHBOARD_HOST='${host}' is not loopback-only.`);\n  console.error('Allowed values: 127.0.0.1, localhost, ::1. Unset the variable to use the default (127.0.0.1).');\n  console.error('For remote access, use SSH port forwarding instead of widening the bind address.');\n  process.exit(1);\n}","typeGuard":"// Type guard for a valid loopback hostname\nconst LOOPBACK_HOSTNAMES = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);\nfunction isLoopbackHostname(value) {\n  return typeof value === 'string' && LOOPBACK_HOSTNAMES.has(value.trim().toLowerCase());\n}","tryCatchPattern":"try {\n  const host = resolveDashboardHost();\n  // start server...\n} catch (error) {\n  if (error.message.includes('ECC_DASHBOARD_HOST must be loopback-only')) {\n    console.error(error.message);\n    console.error('For remote access use: ssh -L 3456:127.0.0.1:3456 user@remote-host');\n    process.exit(1);\n  }\n  throw error;\n}","preventionTips":["Never set ECC_DASHBOARD_HOST to 0.0.0.0 or a public/LAN IP — the dashboard has no authentication","Unset ECC_DASHBOARD_HOST to use the safe default of 127.0.0.1","For remote access, use SSH port forwarding: ssh -L 3456:127.0.0.1:3456 user@host","In Docker, bind to 127.0.0.1 inside the container and map ports via -p 3456:3456"],"tags":["security","network","loopback","environment","dashboard"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}