mihomo-party-org/clash-party · error

Forbidden header: ${k}

Error message

Forbidden header: ${k}

What it means

validateHeaders() rejects requests containing headers in the FORBIDDEN_HEADERS set (matched case-insensitively). These headers (typically hop-by-hop or identity/spoofable ones like host, content-length, connection, cookie controls) must be controlled by the HTTP client itself, so user-supplied values are rejected with `Forbidden header: ${k}`.

Source

Thrown at src/main/resolve/plugin/http-client.ts:38

  headers: http.IncomingHttpHeaders
  body: string
}

const FORBIDDEN_HEADERS = new Set(['host', 'content-length', 'connection', 'transfer-encoding'])
const MAX_HEADERS = 32
const MAX_HEADER_NAME_LEN = 128
const MAX_HEADER_VALUE_LEN = 4096
const MAX_HEADER_BYTES = 16 * 1024

function validateHeaders(input: Record<string, string>): Record<string, string> {
  const entries = Object.entries(input)
  if (entries.length > MAX_HEADERS) throw new Error('Request headers too large')

  const headers: Record<string, string> = {}
  let total = 0
  for (const [k, v] of entries) {
    if (FORBIDDEN_HEADERS.has(k.toLowerCase())) {
      throw new Error(`Forbidden header: ${k}`)
    }
    const nameBytes = Buffer.byteLength(k, 'utf-8')
    const valueBytes = Buffer.byteLength(v, 'utf-8')
    if (nameBytes > MAX_HEADER_NAME_LEN || valueBytes > MAX_HEADER_VALUE_LEN) {
      throw new Error('Request headers too large')
    }
    total += nameBytes + valueBytes
    headers[k] = v
  }
  if (total > MAX_HEADER_BYTES) throw new Error('Request headers too large')
  return headers
}

export function requestOnce(urlStr: string, opts: PluginRequestOptions): Promise<PluginResponse> {
  return new Promise((resolve, reject) => {
    let url: URL
    try {
      url = new URL(urlStr)

View on GitHub (pinned to 911e090537)

Solutions

  1. Remove the forbidden header(s) from the headers object before calling.
  2. If forwarding inbound headers, filter against the forbidden set (case-insensitive) first.
  3. Let the client set transport-level headers (host, content-length, connection) itself; only pass application-level headers.

Example fix

// before
await request(url, { headers: { host: 'gw.example', 'content-type': 'application/json' } })
// after
await request(url, { headers: { 'content-type': 'application/json' } })
Defensive patterns

Strategy: validation

Validate before calling

const FORBIDDEN = new Set(['host','content-length','connection','transfer-encoding','expect','keep-alive'])
const safeHeaders = Object.fromEntries(
  Object.entries(headers).filter(([k]) => !FORBIDDEN.has(k.toLowerCase()))
)

Try / catch

try {
  return await request(url, { headers })
} catch (e) {
  const m = (e as Error).message.match(/^Forbidden header: (.+)$/)
  if (m) {
    delete headers[m[1]]
    return request(url, { headers })
  } else throw e
}

Prevention

When it happens

Trigger: Passing any header whose lowercase name is in FORBIDDEN_HEADERS — e.g. { 'Host': 'x', 'content-length': '5', 'Connection': 'keep-alive' } — in the headers argument consumed by requestOnce.

Common situations: Blindly forwarding headers from an inbound request (host, connection, content-length are common), manually setting content-length after the client already computes it, or copying headers from a cURL example that includes forbidden ones.

Understand the failure class

Related errors


AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30). Data as JSON: /api/errors/95080d6d431506ca. Report an issue: GitHub.