mihomo-party-org/clash-party · error

Forbidden header

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}`.

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.

Appendix: 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)