oven-sh/bun · error · TypeError
INVALID_EXTENSION
INVALID_EXTENSION
Error message
INVALID_EXTENSION
What it means
CertError::INVALID_EXTENSION is Bun's mapping of BoringSSL X509_V_ERR_INVALID_EXTENSION (verify code 41). It fires during TLS chain validation when a certificate presented by the peer contains an X.509 extension that is malformed, duplicated, or internally inconsistent. Bun converts SSL_get_verify_result() into Error::Cert(CertError::INVALID_EXTENSION) (src/http/lib.rs get_cert_error_from_no) and fetch() rejects with a SystemError whose code is "INVALID_EXTENSION" and message "invalid or inconsistent certificate extension".
Source
Thrown at src/http/error.rs:203
#[error("KEYUSAGE_NO_CERTSIGN")]
KEYUSAGE_NO_CERTSIGN,
#[error("UNABLE_TO_GET_CRL_ISSUER")]
UNABLE_TO_GET_CRL_ISSUER,
#[error("UNHANDLED_CRITICAL_EXTENSION")]
UNHANDLED_CRITICAL_EXTENSION,
#[error("KEYUSAGE_NO_CRL_SIGN")]
KEYUSAGE_NO_CRL_SIGN,
#[error("UNHANDLED_CRITICAL_CRL_EXTENSION")]
UNHANDLED_CRITICAL_CRL_EXTENSION,
#[error("INVALID_NON_CA")]
INVALID_NON_CA,
#[error("PROXY_PATH_LENGTH_EXCEEDED")]
PROXY_PATH_LENGTH_EXCEEDED,
#[error("KEYUSAGE_NO_DIGITAL_SIGNATURE")]
KEYUSAGE_NO_DIGITAL_SIGNATURE,
#[error("PROXY_CERTIFICATES_NOT_ALLOWED")]
PROXY_CERTIFICATES_NOT_ALLOWED,
#[error("INVALID_EXTENSION")]
INVALID_EXTENSION,
#[error("INVALID_POLICY_EXTENSION")]
INVALID_POLICY_EXTENSION,
#[error("NO_EXPLICIT_POLICY")]
NO_EXPLICIT_POLICY,
#[error("DIFFERENT_CRL_SCOPE")]
DIFFERENT_CRL_SCOPE,
#[error("UNSUPPORTED_EXTENSION_FEATURE")]
UNSUPPORTED_EXTENSION_FEATURE,
#[error("UNNESTED_RESOURCE")]
UNNESTED_RESOURCE,
#[error("PERMITTED_VIOLATION")]
PERMITTED_VIOLATION,
#[error("EXCLUDED_VIOLATION")]
EXCLUDED_VIOLATION,
#[error("SUBTREE_MINMAX")]
SUBTREE_MINMAX,
#[error("APPLICATION_VERIFICATION")]View on GitHub (pinned to 8c5296ac45)
Solutions
- Dump the chain and find the broken extension: openssl s_client -connect host:443 -showcerts | openssl x509 -noout -text and look for repeated OIDs or malformed values
- Regenerate the offending cert (usually the leaf) with a clean extension config; verify with openssl verify -CAfile ca.pem cert.pem before deploying
- If a middlebox (proxy/LB) mangles the cert, fix or trust that middlebox properly instead of bypassing validation
- As a throwaway diagnostic only, retry with tls: { rejectUnauthorized: false } to confirm the cert is the cause; never ship this
Example fix
# before (extfile duplicates basicConstraints -> INVALID_EXTENSION) [req] distinguished_name = dn x509_extensions = v3 [v3] basicConstraints = critical, CA:FALSE basicConstraints = CA:TRUE subjectAltName = DNS:example.com # after (each extension appears once) [v3] basicConstraints = critical, CA:FALSE subjectAltName = DNS:example.com
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: fetch and lint the peer chain before relying on it
import tls from "node:tls";
import { execFileSync } from "node:child_process";
export function lintPeerCert(host, port = 443) {
const pem = execFileSync("openssl", ["s_client", "-connect", `${host}:${port}`, "-showcerts", "-servername", host], { input: "" }).toString();
const out = execFileSync("openssl", ["x509", "-noout", "-text"], { input: pem }).toString();
const seen = new Set();
for (const m of out.matchAll(/X509v3 ([^:\n]+):/g)) {
if (seen.has(m[1])) throw new Error(`duplicate extension: ${m[1]}`);
seen.add(m[1]);
}
} Type guard
export function isInvalidExtension(e): e is Error & { code: "INVALID_EXTENSION" } {
return e instanceof Error && (e as any).code === "INVALID_EXTENSION";
} Try / catch
try {
const res = await fetch("https://api.example.com/v1");
} catch (e) {
if (e instanceof Error && e.code === "INVALID_EXTENSION") {
// server cert has a malformed/duplicated extension - server-side fix required
await alertCertOwner("api.example.com", e);
} else throw e;
} Prevention
- Lint issued certificates (duplicate-extension + ASN.1 parse check) in CI before deploying them
- Generate certs with mainstream tooling (openssl/step-ca) instead of hand-built ASN.1
- Keep a runbook mapping each X509 code to the owning CA/team so failures route fast
- Pin middleboxes out of test paths when debugging so the cert you inspect is the cert the runtime sees
When it happens
Trigger: fetch("https://host") or any TLS connect where the server's leaf/intermediate cert has: the same extension OID present twice (X.509 forbids duplicates), a critical extension with an empty/undecodable payload, or an extension whose DER does not parse. The handshake is aborted before any HTTP bytes flow.
Common situations: Certificates produced by hand-rolled ASN.1 scripts or misconfigured CFSSL/openssl -extfile templates; TLS-intercepting corporate proxies or load balancers that re-sign certs and botch extensions; stale test certs accepted by older OpenSSL but rejected by BoringSSL's stricter path builder.
Related errors
- INVALID_POLICY_EXTENSION
- SUBTREE_MINMAX
- UNSUPPORTED_CONSTRAINT_TYPE
- UNSUPPORTED_NAME_SYNTAX
- CERT_REVOKED
AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16).
Data as JSON: /api/errors/1ec4f8b663d1a39a.
Report an issue: GitHub.