%0D is a carriage return
%0D is a carriage return — on its own mostly harmless, but paired with a line feed it can split an HTTP response in two if it reaches a header unescaped.
| Character | (Carriage Return) |
|---|---|
| Name | Carriage Return |
| Encoded | %0D |
| Reserved | No |
Why it breaks things
A carriage return, U+000D, tells an old typewriter to return to line start. Alone it does little, but paired with a line feed, CRLF, %0D%0A is the exact sequence ending one line and starting the next in an HTTP message, headers included.
That pairing makes CR dangerous. If a server decodes a URL value into a response header — a redirect target in a Location header is the classic case — an attacker who controls it can supply %0D%0A followed by an extra header line. This is CRLF injection, or HTTP response splitting: the client reads the break as the real end of the header, and whatever follows is read as something new.
Percent-encoding does not prevent this. The danger appears only after a server decodes the value into a header unchecked. The fix is server-side validation, rejecting CR or LF in a header — Node's http module already does this, throwing ERR_INVALID_CHAR on a raw CR or LF.
Real examples
Without encoding
https://example.com/redirect?to=/home[CR][LF]Set-Cookie: session=fake
With encoding
https://example.com/redirect?to=/home%0D%0ASet-Cookie:%20session=fake
This is the CRLF injection shape: if a server decodes to and writes it straight into a Location header without checking for CR or LF, %0D%0A becomes a real line break in the response, and the attacker's Set-Cookie line is read as a genuine header. Encoding it is what lets the payload travel inside the URL — the defence is validating the value before it reaches a header, not the encoding itself.
Without encoding
https://example.com/notes?entry=Item one[CR]Item two
With encoding
https://example.com/notes?entry=Item%20one%0DItem%20two
A lone carriage return, no line feed attached. On its own it is mostly just an invisible character — a raw CR left in an assembled URL string is deleted during WHATWG parsing rather than encoded, the same as a tab or a line feed. Its real danger only appears paired with %0A and reflected into a header unescaped, as in the redirect example above.
Decode something
Result
Breakdown
| Part | Value | Copy |
|---|
History
Nothing yet.
History stays in this browser. It is never sent to our server.
Common questions
- What is CRLF injection?
- It is an attack where a value carrying an encoded carriage return and line feed, %0D%0A, is decoded by a server and written into an HTTP header without checking it. The CRLF is then read by the client as a real line break, letting an attacker inject an extra header or split the response into two.
- Does percent-encoding a URL prevent CRLF injection?
- No. Encoding only controls how the character travels inside the URL. The vulnerability appears after a server decodes the value and writes it into a header without validating it — that is where the fix has to be.
- Why is CR paired with LF specifically dangerous?
- CRLF is the exact sequence HTTP uses to end one header line and start the next. Implementations do not all agree on a bare CR or a bare LF alone: strict parsers require the full pair, but plenty of lenient ones accept a bare LF by itself as a line terminator. That disagreement between implementations is itself a known request-smuggling vector, since a proxy and the server behind it can end up parsing the same request differently.
- Do modern servers block this automatically?
- Many do now. Node's http module, for example, throws an error rather than let a raw CR or LF be written into a header value. Older or custom code that builds headers by string concatenation may not have this protection.