Add CRLF injection protection for header values

Implement security validation to prevent HTTP header injection attacks:
- Reject header values containing \r or \n characters
- Add comprehensive test coverage for CRLF protection
- Replace multiline YAML test with proper rejection test

Security improvements:
- Validates header values to prevent header injection
- Clear warning messages when values are rejected
- Four new test cases covering LF, CR, CRLF, and multiline scenarios

This addresses a critical security concern where malicious headers
could be injected via newline characters in header values.

All 84 tests passing.
This commit is contained in:
Yonatan Golick
2026-01-18 12:19:43 +02:00
parent f77380037b
commit 15ae50ae2f
4 changed files with 63 additions and 5 deletions

View File

@@ -283,7 +283,52 @@ valid123: value5`
})
})
it('handles multiline YAML values', () => {
it('rejects header values with newline characters (LF)', () => {
const jsonInput = '{"X-Custom-Header": "value\\nwith\\nnewline", "header1": "safe-value"}'
const result = parseCustomHeaders(jsonInput)
// Only the safe header should be accepted
expect(result).toEqual({
header1: 'safe-value',
})
expect(core.warning).toHaveBeenCalledWith(
'Skipping header "X-Custom-Header" because its value contains newline characters, which are not allowed in HTTP header values.',
)
})
it('rejects header values with carriage return characters (CR)', () => {
const jsonInput = '{"X-Injected": "value\\rwith\\rcarriage", "X-Safe": "safe-value"}'
const result = parseCustomHeaders(jsonInput)
// Only the safe header should be accepted
expect(result).toEqual({
'X-Safe': 'safe-value',
})
expect(core.warning).toHaveBeenCalledWith(
'Skipping header "X-Injected" because its value contains newline characters, which are not allowed in HTTP header values.',
)
})
it('rejects header values with CRLF sequences', () => {
const jsonInput = '{"X-Attack": "value\\r\\nInjected-Header: malicious", "X-Valid": "normal"}'
const result = parseCustomHeaders(jsonInput)
// Only the valid header should be accepted
expect(result).toEqual({
'X-Valid': 'normal',
})
expect(core.warning).toHaveBeenCalledWith(
'Skipping header "X-Attack" because its value contains newline characters, which are not allowed in HTTP header values.',
)
})
it('rejects multiline YAML values for security', () => {
const yamlInput = `header1: value1
header2: |
multiline
@@ -292,8 +337,14 @@ header2: |
const result = parseCustomHeaders(yamlInput)
expect(result.header1).toBe('value1')
expect(result.header2).toContain('multiline')
// header2 should be rejected because it contains newlines
expect(result).toEqual({
header1: 'value1',
})
expect(core.warning).toHaveBeenCalledWith(
'Skipping header "header2" because its value contains newline characters, which are not allowed in HTTP header values.',
)
})
it('handles complex real-world Azure APIM example', () => {

5
dist/index.js generated vendored
View File

@@ -61361,6 +61361,11 @@ function validateAndMaskHeaders(headers) {
}
// Convert value to string
const stringValue = String(value);
// Validate header value to prevent CRLF/header injection
if (stringValue.includes('\r') || stringValue.includes('\n')) {
coreExports.warning(`Skipping header "${name}" because its value contains newline characters, which are not allowed in HTTP header values.`);
continue;
}
validHeaders[name] = stringValue;
// Mask sensitive headers in logs
const lowerName = name.toLowerCase();

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -132,7 +132,9 @@ function validateAndMaskHeaders(headers: Record<string, unknown>): Record<string
// Validate header value to prevent CRLF/header injection
if (stringValue.includes('\r') || stringValue.includes('\n')) {
core.warning(`Skipping header "${name}" because its value contains newline characters, which are not allowed in HTTP header values.`)
core.warning(
`Skipping header "${name}" because its value contains newline characters, which are not allowed in HTTP header values.`,
)
continue
}
validHeaders[name] = stringValue