joewalnes/websocketd · error
no CGI script named in path %q
Error message
no CGI script named in path %q
What it means
resolveCgiPath cleans the incoming URL path rooted at '/' and rejects it when the result is just '/', meaning no script filename was named. Without this check the handler would have no target file to execute, so it fails the request with this error before any filesystem join happens.
Source
Thrown at libwebsocketd/http.go:197
// resolveCgiPath maps a request URL path to a file inside cgiDir, refusing
// any path that would escape the directory.
//
// req.URL.Path arrives already percent-decoded, and this handler is not
// mounted behind a ServeMux that would normalize it, so "../" segments and
// (on Windows) "..\" segments reach us verbatim. Naively joining such a
// path lets a request name any file on the host, which cgi.Handler would
// then execute — an unauthenticated RCE. We normalize the request path
// ourselves and require the result to stay within cgiDir. checkPathBoundary
// (applied by the caller) additionally defends against symlinks that point
// out of the directory.
func resolveCgiPath(cgiDir, urlPath string) (string, error) {
// Normalize in slash space, then map to the OS separator. path.Clean
// collapses "." and ".." lexically; a rooted clean path can never
// retain a leading "..", so anything that tried to climb out is folded
// back to the root and lands inside cgiDir.
clean := path.Clean("/" + filepath.ToSlash(urlPath))
if clean == "/" {
return "", fmt.Errorf("no CGI script named in path %q", urlPath)
}
filePath := filepath.Join(cgiDir, filepath.FromSlash(clean))
// Belt and suspenders: confirm the lexical result really is contained.
// The rooted clean above should already guarantee this on every OS
// (including Windows, where ToSlash folds "..\" into "../" before the
// clean sees it), so this check is not load-bearing today — it is here
// to fail closed if the normalization above is ever weakened.
if err := containsPath(cgiDir, filePath); err != nil {
return "", err
}
return filePath, nil
}
// containsPath reports an error unless child is dir itself or lies beneath it,
// comparing lexically (no filesystem access, no symlink resolution).
func containsPath(dir, child string) error {
rel, err := filepath.Rel(dir, child)View on GitHub (pinned to 7a8683dc7f)
Solutions
- Request an actual script path, e.g. http://host:8080/script.cgi instead of the root
- Fix the client/base URL so the script filename is included in the path
- If a root health check is needed, point it at a real endpoint or a non-CGI route
Example fix
// before
fetch('http://host:8080/')
// after
fetch('http://host:8080/handler.cgi') Defensive patterns
Strategy: validation
Validate before calling
const url = new URL(endpoint)
if (url.pathname === '/' || url.pathname === '') {
throw new Error('CGI request must name a script path, e.g. /handler.cgi')
} Type guard
function namesScript(u) {
const p = new URL(u).pathname.replace(/\/+$/, '')
return p.length > 0
} Try / catch
try {
const res = await fetch(endpoint)
} catch (e) {
if (e.message.includes('no CGI script named in path')) {
endpoint = new URL('/handler.cgi', base).toString()
}
} Prevention
- Always include the script filename in CGI endpoint URLs
- Validate constructed URLs in client config (pathname must be non-root)
- Configure health checks to target a real script endpoint, not '/'
When it happens
Trigger: GET http://host:8080/ (root) on a CGI-enabled server; a URL that collapses to root after cleaning, like /./ or /../; an empty path portion in the request.
Common situations: Browsers or uptime checks hitting the server root instead of a script endpoint; clients with a misconfigured base URL that omit the script name; link generators producing trailing-only paths.
Related errors
AI-assisted analysis of joewalnes/websocketd@7a8683dc7f (2026-09-03).
Data as JSON: /api/errors/2e566c1863e24675.
Report an issue: GitHub.