{"record":{"id":"c7a8d91e5ca3fa7a","repo":"JuliusBrussee/caveman","slug":"cave-claude-header-invalid","errorCode":"cave_claude_header_invalid","errorMessage":"cave_claude_header_invalid","messagePattern":"cave_claude_header_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/claude-runtime.ts","lineNumber":451,"sourceCode":"  for (const [name, value] of Object.entries(headers)) {\n    raw = mergeClaudeHeader(raw, name, value);\n  }\n  return raw ?? \"\";\n}\n\nfunction stripCaveHeaders(raw: string | undefined): string | undefined {\n  const kept = (raw ?? \"\").split(/\\r\\n|\\n|\\r/).filter((line) => {\n    if (!line.trim()) return false;\n    const colon = line.indexOf(\":\");\n    const name = (colon < 0 ? line : line.slice(0, colon)).trim().toLowerCase();\n    return !name.startsWith(\"x-cave-\");\n  });\n  return kept.length === 0 ? undefined : kept.join(\"\\n\");\n}\n\nfunction mergeClaudeHeader(raw: string | undefined, name: string, value: string): string {\n  if (/[:\\r\\n]/.test(name) || /[\\r\\n]/.test(value)) {\n    throw new Error(\"cave_claude_header_invalid\");\n  }\n  const target = name.toLowerCase();\n  const kept = (raw ?? \"\").split(/\\r\\n|\\n|\\r/).filter((line) => {\n    if (!line.trim()) return false;\n    const colon = line.indexOf(\":\");\n    return (colon < 0 ? line : line.slice(0, colon)).trim().toLowerCase() !== target;\n  });\n  kept.push(`${name}: ${value}`);\n  return kept.join(\"\\n\");\n}\n\nfunction resolveClaudeModel(definition: AgentDefinition, rootDir: string): string {\n  const configured = typeof definition.model === \"string\"\n    ? definition.model\n    : process.env.CAVE_MODEL ?? localModel(rootDir) ??\n      (process.env.ANTHROPIC_API_KEY ? \"anthropic/claude-haiku-4-5\" : undefined);\n  if (configured === undefined) throw new Error(\"cave_claude_model_required\");\n  const separator = configured.indexOf(\"/\");","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/claude-runtime.ts#L433-L469","documentation":"mergeClaudeHeader validates header names and values before merging into the Claude headers blob: a name containing ':', CR, or LF, or a value containing CR/LF, throws cave_claude_header_invalid to prevent header injection/malformed headers.","triggerScenarios":"Passing a header name with a colon or newline (e.g. \"X:Foo\" or \"X\\nFoo\"), or a value containing CRLF, into the Claude headers configuration (via claudeHeaders / agent definition headers).","commonSituations":"Building headers from untrusted/user input without sanitizing; template strings embedding newlines; copy-pasted headers that include \"Name: value\" as a single value.","solutions":["Sanitize name/value: strip or reject any of ':', '\\r', '\\n' before calling","Split 'Name: value' strings into separate name and value arguments","Trim and validate with the same regex the library uses: /[:\\r\\n]/ on name, /[\\r\\n]/ on value"],"exampleFix":"// before\nmergeClaudeHeader(raw, `X-${label}`, valueWithNewline);\n// after\nconst safeName = `X-${label.replace(/[:\\r\\n]/g, \"\")}`;\nconst safeValue = valueWithNewline.replace(/[\\r\\n]/g, \" \");\nmergeClaudeHeader(raw, safeName, safeValue);","handlingStrategy":"validation","validationCode":"const sanitizeHeader = ([name, value]) => [name.replace(/[:\\r\\n]/g, \"\"), value.replace(/[\\r\\n]/g, \" \")];\nObject.fromEntries(Object.entries(headers).map(sanitizeHeader));","typeGuard":"const isSafeHeader = ([n, v]) => !/[:\\r\\n]/.test(n) && ![\"\\r\", \"\\n\"].some(c => v.includes(c));","tryCatchPattern":"try { await runClaudeAgent(opts); } catch (e) { if (e.message === \"cave_claude_header_invalid\") { opts.headers = sanitizeHeaders(opts.headers); return runClaudeAgent(opts); } throw e; }","preventionTips":["Sanitize any header material derived from user input","Never embed 'Name: value' strings as a single value","Reject CR/LF at your config boundary"],"tags":["headers","injection","validation","claude-runtime"],"backgroundTag":"invalid-argument-format","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}