{"record":{"id":"1ec4f8b663d1a39a","repo":"oven-sh/bun","slug":"invalid-extension","errorCode":"INVALID_EXTENSION","errorMessage":"INVALID_EXTENSION","messagePattern":"INVALID_EXTENSION","errorType":"error_code","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/http/error.rs","lineNumber":203,"sourceCode":"    #[error(\"KEYUSAGE_NO_CERTSIGN\")]\n    KEYUSAGE_NO_CERTSIGN,\n    #[error(\"UNABLE_TO_GET_CRL_ISSUER\")]\n    UNABLE_TO_GET_CRL_ISSUER,\n    #[error(\"UNHANDLED_CRITICAL_EXTENSION\")]\n    UNHANDLED_CRITICAL_EXTENSION,\n    #[error(\"KEYUSAGE_NO_CRL_SIGN\")]\n    KEYUSAGE_NO_CRL_SIGN,\n    #[error(\"UNHANDLED_CRITICAL_CRL_EXTENSION\")]\n    UNHANDLED_CRITICAL_CRL_EXTENSION,\n    #[error(\"INVALID_NON_CA\")]\n    INVALID_NON_CA,\n    #[error(\"PROXY_PATH_LENGTH_EXCEEDED\")]\n    PROXY_PATH_LENGTH_EXCEEDED,\n    #[error(\"KEYUSAGE_NO_DIGITAL_SIGNATURE\")]\n    KEYUSAGE_NO_DIGITAL_SIGNATURE,\n    #[error(\"PROXY_CERTIFICATES_NOT_ALLOWED\")]\n    PROXY_CERTIFICATES_NOT_ALLOWED,\n    #[error(\"INVALID_EXTENSION\")]\n    INVALID_EXTENSION,\n    #[error(\"INVALID_POLICY_EXTENSION\")]\n    INVALID_POLICY_EXTENSION,\n    #[error(\"NO_EXPLICIT_POLICY\")]\n    NO_EXPLICIT_POLICY,\n    #[error(\"DIFFERENT_CRL_SCOPE\")]\n    DIFFERENT_CRL_SCOPE,\n    #[error(\"UNSUPPORTED_EXTENSION_FEATURE\")]\n    UNSUPPORTED_EXTENSION_FEATURE,\n    #[error(\"UNNESTED_RESOURCE\")]\n    UNNESTED_RESOURCE,\n    #[error(\"PERMITTED_VIOLATION\")]\n    PERMITTED_VIOLATION,\n    #[error(\"EXCLUDED_VIOLATION\")]\n    EXCLUDED_VIOLATION,\n    #[error(\"SUBTREE_MINMAX\")]\n    SUBTREE_MINMAX,\n    #[error(\"APPLICATION_VERIFICATION\")]","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/oven-sh/bun/blob/8c5296ac459e8252d3cd702f3fbcbb0c249d95d5/src/http/error.rs#L185-L221","documentation":"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\".","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (extfile duplicates basicConstraints -> INVALID_EXTENSION)\n[req]\ndistinguished_name = dn\nx509_extensions = v3\n[v3]\nbasicConstraints = critical, CA:FALSE\nbasicConstraints = CA:TRUE\nsubjectAltName = DNS:example.com\n\n# after (each extension appears once)\n[v3]\nbasicConstraints = critical, CA:FALSE\nsubjectAltName = DNS:example.com","handlingStrategy":"try-catch","validationCode":"// Pre-flight: fetch and lint the peer chain before relying on it\nimport tls from \"node:tls\";\nimport { execFileSync } from \"node:child_process\";\nexport function lintPeerCert(host, port = 443) {\n  const pem = execFileSync(\"openssl\", [\"s_client\", \"-connect\", `${host}:${port}`, \"-showcerts\", \"-servername\", host], { input: \"\" }).toString();\n  const out = execFileSync(\"openssl\", [\"x509\", \"-noout\", \"-text\"], { input: pem }).toString();\n  const seen = new Set();\n  for (const m of out.matchAll(/X509v3 ([^:\\n]+):/g)) {\n    if (seen.has(m[1])) throw new Error(`duplicate extension: ${m[1]}`);\n    seen.add(m[1]);\n  }\n}","typeGuard":"export function isInvalidExtension(e): e is Error & { code: \"INVALID_EXTENSION\" } {\n  return e instanceof Error && (e as any).code === \"INVALID_EXTENSION\";\n}","tryCatchPattern":"try {\n  const res = await fetch(\"https://api.example.com/v1\");\n} catch (e) {\n  if (e instanceof Error && e.code === \"INVALID_EXTENSION\") {\n    // server cert has a malformed/duplicated extension - server-side fix required\n    await alertCertOwner(\"api.example.com\", e);\n  } else throw e;\n}","preventionTips":["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"],"tags":["tls","x509","certificate","fetch","security","asn1"],"backgroundTag":null,"analyzedSha":"8c5296ac459e8252d3cd702f3fbcbb0c249d95d5","analyzedAt":"2026-08-16T08:01:58.794Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}