angular/angular-cli · error · Error

Header "${headerName}" with value "${headerValue}" contains

Error message

Header "${headerName}" with value "${headerValue}" contains characters that are not allowed.

What it means

After parsing the forwarded-host header value, verifyHostAllowed rejects values that contain more than a bare hostname: a non-empty path, query, hash, or credentials in 'http://<value>' indicate the header is not a plain host and could be an injection attempt, so the request is rejected.

Source

Thrown at packages/angular/ssr/src/utils/validation.ts:146

 *
 * @param headerName - The name of the header to validate (e.g., 'host', 'x-forwarded-host').
 * @param headerValue - The value of the header to validate.
 * @param allowedHosts - A set of allowed hostnames.
 * @throws Error if the header value is invalid or the hostname is not in the allowlist.
 */
function verifyHostAllowed(
  headerName: string,
  headerValue: string,
  allowedHosts: ReadonlySet<string>,
): void {
  const url = `http://${headerValue}`;
  if (!URL.canParse(url)) {
    throw new Error(`Header "${headerName}" contains an invalid value and cannot be parsed.`);
  }

  const { hostname, pathname, search, hash, username, password } = new URL(url);
  if (pathname !== '/' || search || hash || username || password) {
    throw new Error(
      `Header "${headerName}" with value "${headerValue}" contains characters that are not allowed.`,
    );
  }

  if (!isHostAllowed(hostname, allowedHosts)) {
    throw new Error(`Header "${headerName}" with value "${headerValue}" is not allowed.`);
  }
}

/**
 * Checks if the hostname is allowed.
 * @param hostname - The hostname to check.
 * @param allowedHosts - A set of allowed hostnames.
 * @returns `true` if the hostname is allowed, `false` otherwise.
 */
function isHostAllowed(hostname: string, allowedHosts: ReadonlySet<string>): boolean {
  if (allowedHosts.has('*') || allowedHosts.has(hostname)) {
    return true;

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Configure the upstream proxy to emit only the bare hostname in X-Forwarded-Host (e.g. nginx: proxy_set_header X-Forwarded-Host $host;).
  2. Strip path/query/credentials from the header value before forwarding.
  3. Identify the requesting client from logs and block/fix the source of the malformed header.

Example fix

// before (nginx)
proxy_set_header X-Forwarded-Host $request_uri;
// after
proxy_set_header X-Forwarded-Host $host;
Defensive patterns

Strategy: validation

Validate before calling

const fwh = request.headers.get('x-forwarded-host');
if (fwh) {
  const u = new URL(`http://${fwh}`);
  if (u.pathname !== '/' || u.search || u.hash || u.username || u.password) {
    return new Response('Bad Request', { status: 400 });
  }
}

Type guard

function isBareHostValue(value: string): boolean {
  const u = new URL(`http://${value}`);
  return u.pathname === '/' && !u.search && !u.hash && !u.username && !u.password;
}

Try / catch

try {
  await render(request);
} catch (e) {
  if (e instanceof Error && e.message.includes('contains characters that are not allowed.')) {
    return new Response('Bad Request: invalid forwarded host', { status: 400 });
  } throw e;
}

Prevention

When it happens

Trigger: X-Forwarded-Host (or similar) values like 'example.com/evil', 'example.com?q=1', 'user:pass@example.com', or 'example.com#frag' — anything where pathname !== '/', or search/hash/username/password are non-empty.

Common situations: Proxies forwarding the full original URL in X-Forwarded-Host; attackers probing for host-header injection; misconfigured middleware copying the entire request URL into the header.

Related errors


AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30). Data as JSON: /api/errors/5b936667f2b1f7d5. Report an issue: GitHub.