oven-sh/bun · critical

InvalidCA

Error message

InvalidCA

What it means

An inline CA certificate (passed as cert data rather than a file path — uws invalid_ca mapped at src/http/HTTPContext.rs:520; message 'the provided CA is invalid' at src/http/HTTPThread.rs:369-371) failed to parse. This covers CA material supplied through APIs that feed BunSocketContextOptions.ca (inline PEM strings in fetch/Bun.connect-style TLS options or install config `ca` entries).

Source

Thrown at src/http/InitError.rs:9

#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error, strum::IntoStaticStr)]
pub enum InitError {
    #[error("FailedToOpenSocket")]
    FailedToOpenSocket,
    #[error("LoadCAFile")]
    LoadCAFile,
    #[error("InvalidCAFile")]
    InvalidCAFile,
    #[error("InvalidCA")]
    InvalidCA,
    #[error("InvalidCRL")]
    InvalidCRL,
}

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Round-trip the inline CA through a parser before handing it to Bun: `Bun.file`-free check with `openssl x509 -noout -subject` via stdin, or in JS verify it starts with '-----BEGIN CERTIFICATE-----'.
  2. Fix newline handling: replace literal '\\n' sequences with '\n' and ensure LF endings.
  3. Prefer `cafile` with a verified PEM on disk over inline strings when possible — it gets clearer diagnostics (LoadCAFile vs InvalidCAFile).
  4. Make sure you pass the root/intermediate CA, not the server's leaf certificate.

Example fix

// before: secret stored single-line
const ca = process.env.CA_PEM; // "-----BEGIN CERTIFICATE-----\\nMIIF...\\n" with literal backslash-n
await fetch('https://internal', { tls: { ca } });
// after
const ca = process.env.CA_PEM!.replace(/\\n/g, '\n');
await fetch('https://internal', { tls: { ca } });
Defensive patterns

Strategy: validation

Validate before calling

function isPemCert(s) {
  return typeof s === 'string' &&
    s.startsWith('-----BEGIN CERTIFICATE-----') &&
    s.includes('-----END CERTIFICATE-----') &&
    !s.includes('PRIVATE KEY');
}
const ca = process.env.CA_PEM!.replace(/\\n/g, '\n');
if (!isPemCert(ca)) throw new Error('inline CA is not a PEM certificate');
await fetch('https://internal', { tls: { ca } });

Type guard

function isPemCert(value) {
  return typeof value === 'string' &&
    /^-----BEGIN CERTIFICATE-----[\s\S]+-----END CERTIFICATE-----/.test(value);
}

Prevention

When it happens

Trigger: Passing an inline PEM string that is truncated, has escaped newlines mangled by JSON/env interpolation, uses CRLF line endings from a Windows secret, or contains a non-CA end-entity certificate as the trust anchor.

Common situations: Secrets managers storing the PEM as a single-line string with literal '\n' that never get converted to real newlines; env-var CA data base64'd once too many/few times; corporate MITM proxies documented to hand out leaf certs instead of the root.

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/e6c1a0ad7df73247. Report an issue: GitHub.