denoland/deno · error · TypeError

ERR_INVALID_CHAR

ERR_INVALID_CHAR

Error message

Invalid character in header content ["Link"]

What it means

ERR_INVALID_CHAR thrown while composing an Early Hints response: the link hint is normalized by validateLinkHeaderValue(), then re-checked with checkInvalidHeaderChar(). If the resulting value still contains CR, LF, or other characters illegal in a header, the write is refused with 'Invalid character in header content ["Link"]' — this is the response-splitting defense for the Link header.

Solutions

  1. Strip CR/LF and control characters from every dynamic segment before building the Link value
  2. Restrict dynamic parts to an allowlist pattern such as /^[\w.-]+$/ for filenames
  3. Encode or reject unsafe characters instead of passing them through

Example fix

// before
res.writeEarlyHints({ link: `</assets/${req.query.css}>; rel=preload` }); // css contains %0d%0a

// after
const css = String(req.query.css).replace(/[\r\n\u0000-\u001f]/g, '');
res.writeEarlyHints({ link: `</assets/${css}>; rel=preload` });
Defensive patterns

Strategy: validation

Validate before calling

const sanitizeHeaderValue = (s) =>
  String(s).replace(/[\r\n\u0000-\u001f\u007f]/g, '');

Type guard

const isSafeHeaderValue = (s) =>
  !/[\r\n\u0000-\u001f\u007f]/.test(String(s));

Try / catch

try {
  res.writeEarlyHints({ link });
} catch (e) {
  if (e.code === 'ERR_INVALID_CHAR') return; // skip unsafe hint, keep serving
  throw e;
}

Prevention

When it happens

Trigger: res.writeEarlyHints({ link: `</assets/${req.query.file}>; rel=preload` }) where the interpolated value contains %0d%0a (decoded to CR LF), a raw newline from config, or other control bytes.

Common situations: Interpolating request parameters or config values into preload hints; Link values copied from multi-line YAML strings; penetration tests probing header injection through Early Hints.

Understand the failure class

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/b0eb802bf3b17fb6. Report an issue: GitHub.

Appendix: source

Thrown at ext/node/polyfills/_http_server.js:473

  hints,
  cb,
) {
  let head = "HTTP/1.1 103 Early Hints\r\n";

  validateObject(hints, "hints");

  if (hints.link === null || hints.link === undefined) {
    return;
  }

  const link = validateLinkHeaderValue(hints.link);

  if (link.length === 0) {
    return;
  }

  if (checkInvalidHeaderChar(link)) {
    throw new ERR_INVALID_CHAR("header content", "Link");
  }

  head += "Link: " + link + "\r\n";

  const keys = ObjectKeys(hints);
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    if (key !== "link") {
      validateHeaderName(key);
      const value = hints[key];
      validateHeaderValue(key, value);
      head += key + ": " + value + "\r\n";
    }
  }

  head += "\r\n";

  this._writeRaw(head, "ascii", cb);

View on GitHub (pinned to 9ad36f7a2c)