{"record":{"id":"af9c0b2c2a258227","repo":"JuliusBrussee/caveman","slug":"name-must-not-contain-a-newline","errorCode":null,"errorMessage":"${name} must not contain a newline","messagePattern":"(.+?) must not contain a newline","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":8412,"sourceCode":"// auth header, then append exactly one current value when one is supplied.\nfunction mergeAnthropicCustomHeader(raw: string | undefined, name: string, value: string | undefined): string {\n  const target = name.toLowerCase();\n  const kept = (raw ?? \"\")\n    .split(/\\r\\n|\\n|\\r/)\n    .filter((line) => {\n      if (!line.trim()) return false;\n      const colon = line.indexOf(\":\");\n      const headerName = (colon < 0 ? line : line.slice(0, colon)).trim().toLowerCase();\n      return headerName !== target;\n    });\n  if (value !== undefined) kept.push(`${name}: ${value}`);\n  return kept.join(\"\\n\");\n}\n\nfunction bedrockCredentialEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {\n  const raw = env[name];\n  if (typeof raw !== \"string\" || !raw.trim()) return undefined;\n  if (/[\\r\\n]/.test(raw)) throw new Error(`${name} must not contain a newline`);\n  return raw.trim();\n}\n\nfunction bedrockUpstreamCredentialFromEnv(env: NodeJS.ProcessEnv): string | undefined {\n  const bearer = bedrockCredentialEnvValue(env, \"AWS_BEARER_TOKEN_BEDROCK\");\n  if (bearer) return bearer;\n  const accessKey = bedrockCredentialEnvValue(env, \"AWS_ACCESS_KEY_ID\");\n  const secretKey = bedrockCredentialEnvValue(env, \"AWS_SECRET_ACCESS_KEY\");\n  const sessionToken = bedrockCredentialEnvValue(env, \"AWS_SESSION_TOKEN\");\n  if (!accessKey && !secretKey && !sessionToken) return undefined;\n  if (!accessKey || !secretKey) {\n    throw new Error(\"AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set together\");\n  }\n  return [accessKey, secretKey, ...(sessionToken ? [sessionToken] : [])].join(\":\");\n}\n\n// applyClaudeBedrockWrap selects Claude Code's native Bedrock or opt-in Mantle\n// transport only when the operator explicitly requests it. The default Claude","sourceCodeStart":8394,"sourceCodeEnd":8430,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/2f49f0e1a352aa810e70056b7930aeb0b3d219b4/packages/cli/src/index.ts#L8394-L8430","documentation":"In the managed Bedrock wrap path (CAVEMAN_WRAP_PROVIDER=bedrock with Claude), AWS credential env values are merged into Claude Code's ANTHROPIC_CUSTOM_HEADERS (x-cave-upstream-key). Because that variable is newline-separated 'Name: Value' lines, a credential containing \\r or \\n would allow header injection into requests, so bedrockCredentialEnvValue rejects any of AWS_BEARER_TOKEN_BEDROCK, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or AWS_SESSION_TOKEN whose value contains a newline. Empty/whitespace values are treated as unset, not errors.","triggerScenarios":"Running `caveman wrap claude` (or `caveman claude`) with CAVEMAN_WRAP_PROVIDER=bedrock in managed mode when one of the AWS credential variables above holds a multi-line string — typically a YAML block scalar in CI, a .env file with embedded line breaks, or a pasted PEM-style token.","commonSituations":"GitHub Actions/GitLab CI 'env: |' block scalars accidentally folding a key across lines; a shell export with an unterminated quote swallowing a newline; secrets managers returning values with trailing CRLF on Windows.","solutions":["Re-export the offending variable as a single line: `export AWS_ACCESS_KEY_ID=$(printf '%s' \"$AWS_ACCESS_KEY_ID\" | tr -d '\\r\\n')` and retry","Fix the source: in CI YAML use a single-line secret reference (`AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}`) instead of a block scalar; in .env keep the value on one line","Strip CRLF when loading from files: `export AWS_BEARER_TOKEN_BEDROCK=$(tr -d '\\r\\n' < token.txt)`","If the multi-line value is legitimate (e.g. a PEM), it is not a valid header credential — use a single-line token or key pair instead"],"exampleFix":"# before: block scalar injects a newline (CI yaml)\nenv:\n  AWS_SECRET_ACCESS_KEY: |\n    abc123\n\n# after: single-line secret reference\nenv:\n  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}","handlingStrategy":"type-guard","validationCode":"const isSingleLine = (v) => typeof v === 'string' && v.length > 0 && !/[\\r\\n]/.test(v);\nfor (const name of ['AWS_BEARER_TOKEN_BEDROCK', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN']) {\n  if (process.env[name] !== undefined && !isSingleLine(process.env[name])) {\n    throw new Error(`${name} contains a newline — fix it before wrap`);\n  }\n}","typeGuard":"const isSingleLineSecret = (v: unknown): v is string =>\n  typeof v === 'string' && v.length > 0 && !/[\\r\\n]/.test(v);","tryCatchPattern":"catch (err) { if (/must not contain a newline/.test(err.message)) { /* strip CR/LF from the named env var and retry once */ } else throw err; }","preventionTips":["Reference CI secrets inline (`VAR: ${{ secrets.X }}`), never as YAML block scalars","Normalize on load: `export K=$(tr -d '\\r\\n' < value)` for anything pasted or stored in files","Treat multi-line PEMs as a signal you grabbed the wrong credential format"],"tags":["aws","env-var","header-injection","input-validation","bedrock"],"backgroundTag":"header-injection","analyzedSha":"2f49f0e1a352aa810e70056b7930aeb0b3d219b4","analyzedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-08-21T10:36:37.832Z"}