{"record":{"id":"68b235c0559b3d92","repo":"caddyserver/caddy","slug":"missing-ca-in-path","errorCode":null,"errorMessage":"missing CA in path","messagePattern":"missing CA in path","errorType":"http","errorClass":"caddy.APIError","httpStatus":400,"severity":"info","filePath":"modules/caddypki/adminapi.go","lineNumber":180,"sourceCode":"\t\t}\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/pem-certificate-chain\")\n\t_, err = w.Write(interCert) //nolint:gosec // false positive... no XSS in a PEM for cryin' out loud\n\tif err == nil {\n\t\t_, _ = w.Write(rootCert) //nolint:gosec // false positive... no XSS in a PEM for cryin' out loud\n\t}\n\n\treturn nil\n}\n\nfunc (a *adminAPI) getCAFromAPIRequestPath(r *http.Request) (*CA, error) {\n\t// Grab the CA ID from the request path, it should be the 4th segment (/pki/ca/<ca>)\n\tid := strings.Split(r.URL.Path, \"/\")[3]\n\tif id == \"\" {\n\t\treturn nil, caddy.APIError{\n\t\t\tHTTPStatus: http.StatusBadRequest,\n\t\t\tErr:        fmt.Errorf(\"missing CA in path\"),\n\t\t}\n\t}\n\n\t// Find the CA by ID, if PKI is configured\n\tvar ca *CA\n\tvar ok bool\n\tif a.pkiApp != nil {\n\t\tca, ok = a.pkiApp.CAs[id]\n\t}\n\n\t// If we didn't find the CA, and PKI is not configured\n\t// then we'll either error out if the CA ID is not the\n\t// default. If the CA ID is the default, then we'll\n\t// provision it, because the user probably aims to\n\t// change their config to enable PKI immediately after\n\t// if they actually requested the local CA ID.\n\tif !ok {\n\t\tif id != DefaultCAID {","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddypki/adminapi.go#L162-L198","documentation":"A 400 caddy.APIError from getCAFromAPIRequestPath when the CA id segment of the path is empty. The function splits r.URL.Path on '/' and takes index 3 (for /pki/ca/&lt;id&gt;: ['', 'pki', 'ca', '&lt;id&gt;']); an empty segment means the request path was /pki/ca/ (trailing slash) or shorter-but-routed-here. The router's own checks normally reject such paths as 404, so seeing this 400 usually means the request arrived via an unusual path shape.","triggerScenarios":"GET /pki/ca/ (trailing slash with empty id — normally a 404 earlier, but any path whose 4th slash-separated segment is empty when handled), or scripts that build the URL by concatenation and produce a double slash like /pki//ca// or /pki/ca//certificates.","commonSituations":"Template-built URLs where the id variable is empty (unset environment variable in a provisioning script); trailing-slash normalization differences between proxies in front of the admin endpoint; manual curl typos.","solutions":["Always include a concrete CA id: /pki/ca/local for the default CA","Check shell variable expansion: curl \"http://localhost:2019/pki/ca/${CA_ID}\" with CA_ID unset yields exactly this class of path","Guard scripts by defaulting the id: ${CA_ID:-local}"],"exampleFix":"# before\nCA_ID=\ncurl \"http://localhost:2019/pki/ca/$CA_ID\"\n\n# after\nCA_ID=${CA_ID:-local}\ncurl \"http://localhost:2019/pki/ca/$CA_ID\"","handlingStrategy":"validation","validationCode":"// Never build a PKI path with an empty id:\nfunc pkiURL(base, id string) (string, error) {\n    id = strings.TrimSpace(id)\n    if id == \"\" { return \"\", errors.New(\"CA id required\") }\n    return base + \"/pki/ca/\" + id, nil\n}","typeGuard":null,"tryCatchPattern":"// Treat 400 with 'missing CA in path' as a client bug; fail fast:\nif resp.StatusCode == 400 && strings.Contains(string(body), \"missing CA\") {\n    return errors.New(\"URL template produced an empty CA id\")\n}","preventionTips":["Default script variables: ${CA_ID:-local}","Lint generated URLs for empty segments/double slashes before requests"],"tags":["pki","admin-api","http-400","url"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}