denoland/deno · error · NodeTypeError

ERR_INVALID_CHAR

ERR_INVALID_CHAR

Error message

Invalid character in trailer content ["${field}"]

What it means

While assembling trailers, addTrailers runs checkInvalidHeaderChar on each value; a value containing characters illegal in a header field value (control characters such as \r, \n, \t, other C0 controls) throws ERR_INVALID_CHAR for 'trailer content'. The check exists to prevent header/trailer injection through unsanitized values, since the trailer is serialized verbatim after the terminal chunk.

Source

Thrown at ext/node/polyfills/_http_outgoing.ts:546

    value: function addTrailers(headers: any) {
      this._trailer = "";
      const keys = ObjectKeys(headers);
      const isArray = ArrayIsArray(headers);
      let field, value;
      for (let i = 0, l = keys.length; i < l; i++) {
        if (isArray) {
          field = headers[keys[i]][0];
          value = headers[keys[i]][1];
        } else {
          field = keys[i];
          value = headers[field];
        }
        if (typeof field !== "string" || !field || !checkIsHttpToken(field)) {
          throw new ERR_INVALID_HTTP_TOKEN("Trailer name", field);
        }
        if (checkInvalidHeaderChar(value)) {
          debug('Trailer "%s" contains invalid characters', field);
          throw new ERR_INVALID_CHAR("trailer content", field);
        }
        this._trailer += field + ": " + value + "\r\n";
      }
    },
    writable: true,
    enumerable: true,
    configurable: true,
  },
  end: {
    __proto__: null,
    value: function end(chunk: any, encoding: any, callback: any) {
      if (typeof chunk === "function") {
        callback = chunk;
        chunk = null;
        encoding = null;
      } else if (typeof encoding === "function") {
        callback = encoding;
        encoding = null;

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Sanitize values: strip/encode control characters before passing to addTrailers
  2. Trim newline-terminated command output used as trailer values
  3. Encode arbitrary data (base64/hex) so only printable ASCII remains

Example fix

// before
res.addTrailers({ 'x-checksum': execSync('sha256sum f | cut -d" " -f1') }); // has trailing \n

// after
const sum = execSync('sha256sum f | cut -d" " -f1').toString().trim();
res.addTrailers({ 'x-checksum': sum });
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeTrailerValue(v) {
  return String(v).replace(/[\r\n\t\x00-\x1f\x7f]/g, '');
}
res.addTrailers({ 'x-checksum': sanitizeTrailerValue(sum) });

Type guard

const isCleanTrailerValue = (v) => typeof v === 'string' && !/[\r\n\t\x00-\x1f\x7f]/.test(v);

Try / catch

try { res.addTrailers(t); } catch (e) { if (e.code === 'ERR_INVALID_CHAR') { /* strip control chars and retry once */ } else throw e; }

Prevention

When it happens

Trigger: addTrailers({ 'x-checksum': hash + '\n' }) (newline-terminated data); values sourced from user input, logs, or external APIs containing control characters; binary buffers stringified with control bytes.

Common situations: Checksums or metadata computed by tools that append newlines (wc, openssl dg2 outputs); proxying upstream values into trailers without sanitization; injecting request data into trailer values creating a smuggling vector.

Understand the failure class

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/f28eda1d966d50f5. Report an issue: GitHub.