hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Web Security Fundamentals with @hectoday/http

The Attacker's Mindset

  • Thinking Like an Attacker
  • Project Setup

Injection Attacks

  • SQL Injection
  • SQL Injection: Beyond the Basics
  • Command Injection
  • Header Injection

Cross-Site Scripting (XSS)

  • What Is XSS?
  • Output Encoding
  • Content Security Policy in Practice

Broken Access and Redirects

  • Insecure Direct Object References (IDOR)
  • Open Redirects
  • Server-Side Request Forgery (SSRF)

File and Data Handling

  • Path Traversal
  • Mass Assignment
  • Denial of Service via Input

Putting It All Together

  • Security Testing
  • The OWASP Top 10
  • Capstone: Hardened Notes API

Header Injection

The pattern repeats

SQL injection puts code into SQL. Command injection puts code into shell commands. Header injection puts code into HTTP headers. Same principle, different interpreter.

The vulnerable code

Imagine a route that sets a custom header based on user input — perhaps a language preference from a query parameter:

route.get("/notes", {
  resolve: (c) => {
    const user = authenticate(c.request);
    if (user instanceof Response) return user;

    const lang = new URL(c.request.url).searchParams.get("lang") ?? "en";

    const notes = db.prepare("SELECT * FROM notes WHERE user_id = ?").all(user.id);

    return new Response(JSON.stringify(notes), {
      headers: {
        "content-type": "application/json",
        "content-language": lang, // DELIBERATELY VULNERABLE
      },
    });
  },
});

The lang parameter is inserted directly into a response header.

The attack: CRLF injection

HTTP headers are separated by \r\n (carriage return + line feed, or CRLF). If the attacker can inject \r\n into a header value, they can add arbitrary headers to the response.

The attacker sets lang to: en\r\nSet-Cookie: session=attacker-session

The response becomes:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: en
Set-Cookie: session=attacker-session

[...]

The attacker just set a cookie on the victim’s browser. This can be used for session fixation (forcing the victim to use a session the attacker controls).

[!NOTE] Modern Node.js runtimes and the Web Response API reject headers containing \r or \n, which prevents the most basic form of this attack. But you should still validate header values because (1) not all runtimes protect you, (2) the protection may not cover all edge cases, and (3) defense in depth means not relying on a single layer.

The fix: validate header values

Never use raw user input as a header value. Validate it against an allowlist or strip control characters:

const ALLOWED_LANGUAGES = new Set(["en", "es", "fr", "de", "ja", "zh"]);

route.get("/notes", {
  resolve: (c) => {
    const user = authenticate(c.request);
    if (user instanceof Response) return user;

    const rawLang = new URL(c.request.url).searchParams.get("lang") ?? "en";
    const lang = ALLOWED_LANGUAGES.has(rawLang) ? rawLang : "en";

    const notes = db.prepare("SELECT * FROM notes WHERE user_id = ?").all(user.id);

    return new Response(JSON.stringify(notes), {
      headers: {
        "content-type": "application/json",
        "content-language": lang, // SAFE: validated against allowlist
      },
    });
  },
});

The allowlist approach is the safest: the value must be one of the predefined options. Anything else falls back to the default.

If an allowlist is not practical (the value is too dynamic), strip control characters:

function sanitizeHeaderValue(value: string): string {
  return value.replace(/[\r\n\x00]/g, "");
}

This removes carriage returns, line feeds, and null bytes — the characters used for header injection.

Exercises

Exercise 1: Add the language header route to your app. Try the CRLF injection with curl. Does your runtime block it? (Modern Node.js should throw an error.)

Exercise 2: Apply the allowlist fix. Try setting lang=xx and verify it falls back to en.

What character sequence enables header injection?

← Command Injection What Is XSS? →

© 2026 hectoday. All rights reserved.