unclecode/crawl4ai · error · ValueError
control characters in value for header {name!r}
Error message
control characters in value for header {name!r} What it means
SetHeadersParams' validator rejects header values containing \r, \n, or \x00. This is a classic CRLF/header-injection guard: a newline in a value would let a crafted config smuggle extra headers or split responses. Raised while validating a set_headers hook spec.
Source
Thrown at deploy/docker/hook_registry.py:78
class AddCookiesParams(BaseModel):
cookies: List[_Cookie] = Field(..., min_length=1, max_length=_MAX_COOKIES)
class SetHeadersParams(BaseModel):
headers: Dict[str, str]
@field_validator("headers")
@classmethod
def _check(cls, v):
if len(v) > _MAX_HEADERS:
raise ValueError(f"too many headers (max {_MAX_HEADERS})")
for name, value in v.items():
if not _HEADER_NAME_RE.match(name):
raise ValueError(f"invalid header name {name!r}")
if any(c in value for c in "\r\n\x00"):
raise ValueError(f"control characters in value for header {name!r}")
return v
class ScrollToBottomParams(BaseModel):
max_steps: int = Field(10, ge=1, le=_MAX_SCROLL_STEPS)
delay_ms: int = Field(500, ge=0, le=_MAX_SCROLL_DELAY_MS)
class WaitForTimeoutParams(BaseModel):
timeout_ms: int = Field(..., ge=0, le=_MAX_WAIT_MS)
# ───────────────────────── server-authored hook factories ─────────────────────────
def _factory_block_resources(p: BlockResourcesParams):
types = set(p.resource_types)
async def hook(page, **kwargs):
context = kwargs.get("context")View on GitHub (pinned to 7e80152142)
Solutions
- Strip/replace newlines: value.strip().replace('\r', '').replace('\n', '').
- Encode multi-value headers per RFC: 'Accept-Encoding: gzip, deflate, br' (comma-separated, single line).
- If a NUL appears, the value is binary — pass it base64-encoded or not at all.
Example fix
# before
{"headers": {"Cookie": "a=1\nb=2\n"}}
# after
{"headers": {"Cookie": "a=1; b=2"}} Defensive patterns
Strategy: validation
Validate before calling
def clean_header_value(v: str) -> str:
return v.replace("\r", "").replace("\n", "").replace("\x00", "").strip()
def safe_headers(headers: dict) -> dict:
return {k: clean_header_value(v) for k, v in headers.items()} Type guard
def is_crlf_free(value) -> bool:
return isinstance(value, str) and not any(c in value for c in "\r\n\x00") Try / catch
try:
hooks = build_declarative_hooks(specs)
except HookValidationError as e:
if "control characters" in str(e):
specs = sanitize_header_values(specs) # strip \r\n\x00, rebuild
else:
raise Prevention
- Strip newlines from any value pasted from files or env vars.
- Use comma/semicolon joining for multi-value headers, never '\n'.
- Unit-test generated webhook/cookie/header configs for CRLF before submit.
When it happens
Trigger: Passing a multi-line value (e.g. a pasted cookie block or a value with a trailing \n), or embedding CR/LF/NUL in any set_headers value.
Common situations: Pasting a value from a file that keeps a trailing newline; building values with '\n'.join(...) for multi-value headers instead of the comma convention; JSON configs where an escaped \n slips into a string.
Related errors
- invalid header name {name!r}
- LLMConfig.api_token may not reference an environment variabl
- Hook '{hook_name}' must be a callable function, got {type(ho
- Rejected request: {e}
- unsupported resource_types {bad}; allowed: {sorted(_ALLOWED_
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/93d87aa227a2b2d3.
Report an issue: GitHub.