{"record":{"id":"ead8556c5a245188","repo":"caddyserver/caddy","slug":"s-array-index-out-of-bounds-s","errorCode":null,"errorMessage":"[%s] array index out of bounds: %s","messagePattern":"\\[(.+?)\\] array index out of bounds: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"admin.go","lineNumber":1221,"sourceCode":"traverseLoop:\n\tfor i, part := range parts {\n\t\tswitch v := ptr.(type) {\n\t\tcase map[string]any:\n\t\t\t// if the next part enters a slice, and the slice is our destination,\n\t\t\t// handle it specially (because appending to the slice copies the slice\n\t\t\t// header, which does not replace the original one like we want)\n\t\t\tif arr, ok := v[part].([]any); ok && i == len(parts)-2 {\n\t\t\t\tvar idx int\n\t\t\t\tif method != http.MethodPost {\n\t\t\t\t\tidxStr := parts[len(parts)-1]\n\t\t\t\t\tidx, err = parseCanonicalArrayIndex(idxStr)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"[%s] invalid array index '%s': %v\",\n\t\t\t\t\t\t\tpath, idxStr, err)\n\t\t\t\t\t}\n\n\t\t\t\t\tif idx < 0 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) {\n\t\t\t\t\t\treturn fmt.Errorf(\"[%s] array index out of bounds: %s\", path, idxStr)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch method {\n\t\t\t\tcase http.MethodGet:\n\t\t\t\t\terr = enc.Encode(arr[idx])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"encoding config: %v\", err)\n\t\t\t\t\t}\n\t\t\t\tcase http.MethodPost:\n\t\t\t\t\tif ellipses {\n\t\t\t\t\t\tvalArray, ok := val.([]any)\n\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"final element is not an array\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tv[part] = append(arr, valArray...)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tv[part] = append(arr, val)","sourceCodeStart":1203,"sourceCodeEnd":1239,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/admin.go#L1203-L1239","documentation":"Thrown when addressing an array element by index in the config where the index is outside the array: negative, or >= len(arr) for GET/POST-style access (PUT may use idx == len(arr) to append at the end, but never greater). The message includes the offending path and index.","triggerScenarios":"GET /config/apps/http/servers/myserver/routes/5 when only 2 routes exist; DELETE with index == len(arr); negative indices like .../routes/-1.","commonSituations":"Off-by-one errors assuming 1-based indexing (Caddy is 0-based); stale index after another client deleted an element; using PUT index len(arr)+1 expecting append.","solutions":["GET the parent array first and confirm its length; valid GET/DELETE/PATCH indices are 0..len-1","For PUT, len(arr) is valid and appends; anything larger is rejected","If elements were removed concurrently, re-fetch and recompute the index"],"exampleFix":"# before (only 2 routes exist)\ncurl -X DELETE http://localhost:2019/config/apps/http/servers/myserver/routes/3\n# after\ncurl -X DELETE http://localhost:2019/config/apps/http/servers/myserver/routes/1","handlingStrategy":"validation","validationCode":"parent=\"http://localhost:2019/config/apps/http/servers/myserver/routes\"\nn=$(curl -s \"$parent\" | jq 'length')\nidx=1\n[ \"$idx\" -ge 0 ] && [ \"$idx\" -lt \"$n\" ] && curl \"$parent/$idx\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always fetch array length before indexing","PUT may use index == length to append; GET/DELETE/PATCH may not"],"tags":["caddy","admin-api","config","array","bad-request"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}