FlowiseAI/Flowise · error · Error

Invalid header "${key}": value must be a string

Error message

Invalid header "${key}": value must be a string

What it means

Thrown by validateCustomHeaders() when a header value is not a JavaScript string. The validator enforces that every value in the headers bag is a string before it is forwarded to an outbound HTTP request, preventing accidental serialization of numbers, booleans, objects, or arrays into header lines. This is a type-enforcement guard, not a network-level check.

Source

Thrown at packages/components/src/headerValidation.ts:66

    for (const [key, value] of entries) {
        if (typeof key !== 'string' || key.length === 0) {
            throw new Error('Invalid header: key must be a non-empty string')
        }
        if (key.length > MAX_KEY_LENGTH) {
            throw new Error(`Invalid header "${key}": key exceeds ${MAX_KEY_LENGTH} chars`)
        }
        if (!RFC7230_TOKEN.test(key)) {
            throw new Error(`Invalid header "${key}": key contains illegal characters`)
        }

        const lower = key.toLowerCase()
        if (DENIED_HEADER_NAMES.has(lower) || DENIED_HEADER_PREFIXES.some((p) => lower.startsWith(p))) {
            throw new Error(`Invalid header "${key}": this header name is not allowed`)
        }

        if (typeof value !== 'string') {
            throw new Error(`Invalid header "${key}": value must be a string`)
        }
        if (value.length > MAX_VALUE_LENGTH) {
            throw new Error(`Invalid header "${key}": value exceeds ${MAX_VALUE_LENGTH} chars`)
        }
        for (let i = 0; i < value.length; i++) {
            const code = value.charCodeAt(i)
            if (code === 0x0d || code === 0x0a || (code < 0x20 && code !== 0x09)) {
                throw new Error(`Invalid header "${key}": value contains illegal control characters`)
            }
        }
    }
}

/**
 * Returns a copy of `headers` with credential-bearing entries (Authorization, Cookie, X-Api-Key, …)
 * replaced by a placeholder string. Used at trust boundaries before a header bag is exposed to flow
 * templates, observers, or logs. Comparison is case-insensitive; non-sensitive headers pass through.
 */

View on GitHub (pinned to abe4a8601a)

Solutions

  1. Coerce every header value to a string before calling validateCustomHeaders: build the headers object with template literals or String().
  2. Audit the headers object at the call site and convert numeric/boolean values explicitly.
  3. If the value can legitimately be non-string, drop the header or stringify it intentionally rather than letting it through raw.

Example fix

// before
validateCustomHeaders({ 'X-Retry-Count': retries, 'X-Verbose': true })

// after
validateCustomHeaders({ 'X-Retry-Count': String(retries), 'X-Verbose': String(true) })
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate that every header value is a string before calling validateCustomHeaders
function safeHeaders(input: Record<string, unknown>): Record<string, string> {
  const out: Record<string, string> = {}
  for (const [k, v] of Object.entries(input)) {
    if (typeof v !== 'string') {
      throw new Error(`Header '${k}' must be a string, got ${typeof v}`)
    }
    out[k] = v
  }
  return out
}

const safe = safeHeaders(rawHeaders)
validateCustomHeaders(safe)

Type guard

function isStringHeaders(h: Record<string, unknown>): h is Record<string, string> {
  return Object.values(h).every((v) => typeof v === 'string')
}

Try / catch

try {
  validateCustomHeaders(headers)
} catch (e) {
  // surface a user-facing validation error, do not retry
  throw new BadRequestError(String(e))
}

Prevention

When it happens

Trigger: Calling validateCustomHeaders() with a record where any value is a number (e.g. { 'X-Retry-Count': 3 }), a boolean, null, undefined, an array, or an object. The check at line 65 (typeof value !== 'string') fires before any length or character validation.

Common situations: Config objects built from typed application state where numeric fields leak into headers without String(). JSON parsed from external config that contains numeric values. Passing request options straight from a form or API response where numbers are common (retry counts, timeouts, content lengths).

Related errors


AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12). Data as JSON: /api/errors/515519dceae1e0f7. Report an issue: GitHub.