{"record":{"id":"24696366d5ea7ecd","repo":"joewalnes/websocketd","slug":"could-not-resolve-script-for-path-q","errorCode":null,"errorMessage":"could not resolve script for path %q","messagePattern":"could not resolve script for path %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libwebsocketd/handler.go","lineNumber":183,"sourceCode":"\t\t}\n\n\t\t// Verify the resolved path stays within the script directory.\n\t\t// This prevents symlink attacks where a link inside ScriptDir\n\t\t// points to an arbitrary file outside it.\n\t\tif err := checkPathBoundary(urlInfo.FilePath, config.ScriptDir); err != nil {\n\t\t\treturn nil, ErrScriptNotFound\n\t\t}\n\n\t\t// no extra args\n\t\tif isLastPart {\n\t\t\treturn urlInfo, nil\n\t\t}\n\n\t\t// build path info from extra parts of url\n\t\turlInfo.PathInfo = \"/\" + strings.Join(parts[i+1:], \"/\")\n\t\treturn urlInfo, nil\n\t}\n\treturn nil, fmt.Errorf(\"could not resolve script for path %q\", path)\n}\n\n// checkPathBoundary resolves symlinks and verifies the real path is within the\n// allowed directory. Returns an error if the path escapes the boundary.\nfunc checkPathBoundary(path, boundary string) error {\n\trealPath, err := filepath.EvalSymlinks(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\trealBoundary, err := filepath.EvalSymlinks(boundary)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Ensure the resolved path starts with the resolved boundary\n\tif !strings.HasPrefix(realPath, realBoundary+string(filepath.Separator)) && realPath != realBoundary {\n\t\treturn fmt.Errorf(\"path %q escapes boundary %q\", realPath, realBoundary)\n\t}\n\treturn nil","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/joewalnes/websocketd/blob/7a8683dc7f9778dc615945aaed2a8dc77290227b/libwebsocketd/handler.go#L165-L201","documentation":"GetURLInfo maps an incoming WebSocket/HTTP URL path to a script under the configured base directory (or an explicit script path). When every resolution strategy fails to produce an existing script for the requested path, it returns this error, which the handler turns into a failed request rather than a process spawn.","triggerScenarios":"Requesting /echo/x when baseDir only contains count.sh; URL path with extra segments mapping to a nonexistent file; --dir mode where the client URL's first segment names no file in the directory; requesting an empty/no path where nothing can be resolved.","commonSituations":"Client built with a wrong endpoint URL after renaming scripts; case-sensitivity mismatch on Linux (Echo.sh vs echo.sh); missing file extension the resolver expects; deploying scripts to a different directory than the one passed to --dir.","solutions":["Verify the script exists under the configured dir with the exact name in the URL (`ls <dir>`), respecting case and extensions","Fix the client URL's path to match a real script file","Check you launched with the intended --dir (or explicit script) so resolution searches the right directory"],"exampleFix":"// client\n// before\nnew WebSocket('ws://host:8080/echos/')\n// after\nnew WebSocket('ws://host:8080/echo/')   // scripts/echo exists on disk","handlingStrategy":"try-catch","validationCode":"// client side: confirm the script exists for the URL path\nconst script = 'echo'\nconst res = await fetch(`http://host:8080/${script}/`, {method:'HEAD'})\nif (!res.ok) throw new Error(`no script resolved for /${script}/`)","typeGuard":null,"tryCatchPattern":"try {\n  const ws = new WebSocket('ws://host:8080/echo/')\n} catch (e) {\n  // 'could not resolve script for path' — check --dir contents and URL casing/extensions\n}","preventionTips":["Keep URL first segment equal to the script filename on disk (mind case sensitivity)","List the --dir contents and compare with client endpoints during deploys","Include file extensions the resolver expects","Add a smoke test that opens each expected endpoint after deploy"],"tags":["http","routing","path-resolution"],"backgroundTag":"script-path-not-resolved","analyzedSha":"7a8683dc7f9778dc615945aaed2a8dc77290227b","analyzedAt":"2026-09-03T13:52:22.309Z","contentChangedAt":"2026-09-03T13:52:22.309Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}