{"record":{"id":"29a26bd7e5c51856","repo":"caddyserver/caddy","slug":"parsing-s-v","errorCode":null,"errorMessage":"parsing %s: %v","messagePattern":"parsing (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/templates/tplcontext.go","lineNumber":180,"sourceCode":"\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// funcHTTPInclude returns the body of a virtual (lightweight) request\n// to the given URI on the same server. Note that included bodies\n// are NOT escaped, so you should only include trusted resources.\n// If it is not trusted, be sure to use escaping functions yourself.\nfunc (c TemplateContext) funcHTTPInclude(uri string) (string, error) {\n\t// prevent virtual request loops by counting how many levels\n\t// deep we are; and if we get too deep, return an error\n\trecursionCount := 1\n\tif numStr := c.Req.Header.Get(recursionPreventionHeader); numStr != \"\" {\n\t\tnum, err := strconv.Atoi(numStr)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"parsing %s: %v\", recursionPreventionHeader, err)\n\t\t}\n\t\tif num >= 3 {\n\t\t\treturn \"\", fmt.Errorf(\"virtual request cycle\")\n\t\t}\n\t\trecursionCount = num + 1\n\t}\n\n\tbuf := bufPool.Get().(*bytes.Buffer)\n\tbuf.Reset()\n\tdefer bufPool.Put(buf)\n\n\tvirtReq, err := http.NewRequest(\"GET\", uri, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvirtReq.Host = c.Req.Host\n\tvirtReq.RemoteAddr = \"127.0.0.1:10000\" // https://github.com/caddyserver/caddy/issues/5835\n\tvirtReq.Header = c.Req.Header.Clone()","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/templates/tplcontext.go#L162-L198","documentation":"Returned by the templates module's httpInclude function when the internal recursion-prevention header (X-Caddy-Recursion-Prevention, or similar constant) is present on the incoming request but its value is not a valid integer. The header is normally set by Caddy itself to count virtual-request nesting depth.","triggerScenarios":"A client (or an upstream proxy that forwards arbitrary headers) sends the request with the recursion-prevention header set to a non-numeric value like 'abc' or '2.5', and a template then calls {{ httpInclude \"/path\" }}.","commonSituations":"Misconfigured reverse proxy forwarding internal Caddy headers from the outside, security scanners sending junk headers, or curl commands copied from debug traces that include internal headers.","solutions":["Find who sends the recursion header with a bogus value and stop it (do not forward that header from outside)","Strip the header at the trust boundary: request_header -X-Caddy-Recursion-Prevention (or via reverse_proxy headers)","Retry the request without the header to confirm the template works","If you control the template, guard httpInclude calls behind trusted-input checks"],"exampleFix":"# before\nlocalhost {\n    templates\n    reverse_proxy backend:8080  # backend echoes all headers back with junk value\n}\n\n# after\nlocalhost {\n    templates\n    @junk header X-Caddy-Recursion-Prevention *\n    request_header @junk -X-Caddy-Recursion-Prevention\n    reverse_proxy backend:8080\n}","handlingStrategy":"validation","validationCode":"# strip client-supplied recursion header at the edge\n@bad header X-Caddy-Recursion-Prevention *\nrequest_header @bad -X-Caddy-Recursion-Prevention","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not forward internal X-Caddy-* headers through proxies or load tests","Strip unknown client headers at the trust boundary","When replaying captured requests, remove internal headers first"],"tags":["caddy","templates","headers","recursion"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}