{"record":{"id":"7071a4a13e1ec21e","repo":"affaan-m/ECC","slug":"unsupported-plan-canvas-request-path-url-pathna","errorCode":null,"errorMessage":"unsupported plan-canvas request path: ${url.pathname}","messagePattern":"unsupported plan-canvas request path: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/plan-canvas.js","lineNumber":110,"sourceCode":"\nfunction validatePort(port) {\n  const value = Number(port);\n  if (!Number.isInteger(value) || value < 0 || value > 65535) {\n    throw new Error(`invalid plan-canvas server port: ${port}`);\n  }\n  return value;\n}\n\nfunction validateRequestPath(requestPath) {\n  if (typeof requestPath !== 'string' || !requestPath.startsWith('/')) {\n    throw new Error('plan-canvas request path must be root-relative');\n  }\n  const url = new URL(requestPath, `http://${DEFAULT_HOST}`);\n  if (url.hostname !== DEFAULT_HOST) {\n    throw new Error('plan-canvas request path must stay on the loopback server');\n  }\n  if (!SAFE_REQUEST_PATHS.has(url.pathname) && !SESSION_REPLY_PATH.test(url.pathname)) {\n    throw new Error(`unsupported plan-canvas request path: ${url.pathname}`);\n  }\n  return `${url.pathname}${url.search}`;\n}\n\nfunction requestOptions(port, method, requestPath, headers) {\n  return {\n    host: DEFAULT_HOST,\n    port: validatePort(port),\n    method,\n    path: validateRequestPath(requestPath),\n    agent: false,\n    headers\n  };\n}\n\nfunction request(port, method, requestPath, body = null) {\n  return new Promise((resolve, reject) => {\n    const payload = body === null ? null : JSON.stringify(body);","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/plan-canvas.js#L92-L128","documentation":"Thrown by validateRequestPath in scripts/plan-canvas.js when the parsed pathname is not in the SAFE_REQUEST_PATHS allowlist and does not match the SESSION_REPLY_PATH regex. This is a path-allowlist guard: only known endpoints (/, /health, /shutdown, /api/await, /api/sessions, /api/end, and /api/session/<12-hex-key>/(reply|typing)) may be requested by the CLI.","triggerScenarios":"An internal call to a path the server does not expose, or a typo in a pathname constant within plan-canvas.js. The session reply/typing paths require exactly 12 lowercase hex characters in the key segment, followed by /reply or /typing.","commonSituations":"A new endpoint added to the server but not to SAFE_REQUEST_PATHS; a refactored path string that drops a segment; an invalid session key length in the path that fails the regex.","solutions":["If adding a new server endpoint, also add its pathname to SAFE_REQUEST_PATHS or extend SESSION_REPLY_PATH.","For session-scoped paths, ensure the key is a 12-character lowercase hex string and the suffix is /reply or /typing.","End users: this indicates a CLI bug — update ECC or report the issue."],"exampleFix":"// before (internal call, path not allowlisted)\nrequest(port, 'GET', '/api/unknown');\n// after\nrequest(port, 'GET', '/api/sessions');","handlingStrategy":"validation","validationCode":"const SAFE_PATHS = new Set(['/', '/health', '/shutdown', '/api/await', '/api/sessions', '/api/end']);\nconst SESSION_PATH = /^\\/api\\/session\\/[a-f0-9]{12}\\/(reply|typing)$/;\nfunction assertAllowedPath(pathname) {\n  if (!SAFE_PATHS.has(pathname) && !SESSION_PATH.test(pathname)) {\n    throw new Error(`Refusing unsupported request path: ${pathname}`);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["When adding a server endpoint, update SAFE_REQUEST_PATHS in the same change.","Keep session-keyed paths to the documented /api/session/<key>/(reply|typing) shape.","End users hitting this should treat it as a CLI bug and report it."],"tags":["plan-canvas","network","security","allowlist","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}