{"record":{"id":"02ea67ee55bf53f2","repo":"oven-sh/bun","slug":"self-signed-cert-in-chain","errorCode":"SELF_SIGNED_CERT_IN_CHAIN","errorMessage":"SELF_SIGNED_CERT_IN_CHAIN","messagePattern":"SELF_SIGNED_CERT_IN_CHAIN","errorType":"error_code","errorClass":"CertError","httpStatus":null,"severity":"error","filePath":"src/http/error.rs","lineNumber":159,"sourceCode":"    #[error(\"CERT_HAS_EXPIRED\")]\n    CERT_HAS_EXPIRED,\n    #[error(\"CRL_NOT_YET_VALID\")]\n    CRL_NOT_YET_VALID,\n    #[error(\"CRL_HAS_EXPIRED\")]\n    CRL_HAS_EXPIRED,\n    #[error(\"ERROR_IN_CERT_NOT_BEFORE_FIELD\")]\n    ERROR_IN_CERT_NOT_BEFORE_FIELD,\n    #[error(\"ERROR_IN_CERT_NOT_AFTER_FIELD\")]\n    ERROR_IN_CERT_NOT_AFTER_FIELD,\n    #[error(\"ERROR_IN_CRL_LAST_UPDATE_FIELD\")]\n    ERROR_IN_CRL_LAST_UPDATE_FIELD,\n    #[error(\"ERROR_IN_CRL_NEXT_UPDATE_FIELD\")]\n    ERROR_IN_CRL_NEXT_UPDATE_FIELD,\n    #[error(\"OUT_OF_MEM\")]\n    OUT_OF_MEM,\n    #[error(\"DEPTH_ZERO_SELF_SIGNED_CERT\")]\n    DEPTH_ZERO_SELF_SIGNED_CERT,\n    #[error(\"SELF_SIGNED_CERT_IN_CHAIN\")]\n    SELF_SIGNED_CERT_IN_CHAIN,\n    #[error(\"UNABLE_TO_GET_ISSUER_CERT_LOCALLY\")]\n    UNABLE_TO_GET_ISSUER_CERT_LOCALLY,\n    #[error(\"UNABLE_TO_VERIFY_LEAF_SIGNATURE\")]\n    UNABLE_TO_VERIFY_LEAF_SIGNATURE,\n    #[error(\"CERT_CHAIN_TOO_LONG\")]\n    CERT_CHAIN_TOO_LONG,\n    #[error(\"CERT_REVOKED\")]\n    CERT_REVOKED,\n    #[error(\"INVALID_CA\")]\n    INVALID_CA,\n    #[error(\"PATH_LENGTH_EXCEEDED\")]\n    PATH_LENGTH_EXCEEDED,\n    #[error(\"INVALID_PURPOSE\")]\n    INVALID_PURPOSE,\n    #[error(\"CERT_UNTRUSTED\")]\n    CERT_UNTRUSTED,\n    #[error(\"CERT_REJECTED\")]","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/oven-sh/bun/blob/8c5296ac459e8252d3cd702f3fbcbb0c249d95d5/src/http/error.rs#L141-L177","documentation":"X509 verify result 19 (X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN): a self-signed certificate was found in the chain (typically the root, or a corporate intermediate) but it is not in the local trust store, so the chain cannot be anchored. Mapped via get_cert_error_from_no (src/http/lib.rs:1538) to CertError::SELF_SIGNED_CERT_IN_CHAIN.","triggerScenarios":"TLS handshake where the server sends its full chain including the self-signed root (unnecessary but common), and that root is absent from Bun's CA bundle — typical behind corporate TLS-inspecting proxies (Zscaler, Netskope, Blue Coat) or with private CAs.","commonSituations":"Corporate laptops behind SSL-inspection proxies whose root CA isn't installed for Bun, containers missing the company root, private service meshes whose root isn't distributed, or servers configured to send root.pem in the chain.","solutions":["Install the corporate/private root CA: export it and point Bun at it via NODE_EXTRA_CA_CERTS=/path/to/corp-root.pem, or pass fetch(url, { tls: { ca: corpRootPem } })","Fix the server to not send the self-signed root in the chain (leaf + intermediates only) — clients that already trust the root then verify fine","In containers, bake the company root into the image's CA store","Only as a last resort: rejectUnauthorized: false, understanding it disables all peer verification"],"exampleFix":"# before\nawait fetch(\"https://internal.corp/api\"); // SELF_SIGNED_CERT_IN_CHAIN\n\n# after\nNODE_EXTRA_CA_CERTS=/usr/local/share/corp-root.pem bun run app.ts","handlingStrategy":"try-catch","validationCode":"// fail fast at boot if a corporate root is configured but unreadable\nimport { readFileSync, existsSync } from \"node:fs\";\nconst corpCa = process.env.NODE_EXTRA_CA_CERTS;\nif (corpCa && !existsSync(corpCa)) throw new Error(`NODE_EXTRA_CA_CERTS points to missing file: ${corpCa}`);","typeGuard":"function isCertErrorCode(e: unknown, code = \"SELF_SIGNED_CERT_IN_CHAIN\"): e is Error & { code: string } {\n  return e instanceof Error && (e as any).code === code;\n}","tryCatchPattern":"try {\n  await fetch(\"https://internal.corp/api\");\n} catch (e) {\n  if (isCertErrorCode(e, \"SELF_SIGNED_CERT_IN_CHAIN\")) {\n    // corporate proxy root not trusted — load it explicitly instead of disabling verification\n    const res = await fetch(\"https://internal.corp/api\", {\n      tls: { ca: readFileSync(\"/usr/local/share/corp-root.pem\", \"utf8\") },\n    });\n  } else throw e;\n}","preventionTips":["Distribute the corporate root via NODE_EXTRA_CA_CERTS in your base image/CI templates","Don't disable verification to work around SSL-inspection proxies — trust the proxy root instead","Configure servers to omit the self-signed root from the served chain"],"tags":["tls","certificates","corporate-proxy","fetch","pkix"],"backgroundTag":null,"analyzedSha":"8c5296ac459e8252d3cd702f3fbcbb0c249d95d5","analyzedAt":"2026-08-16T08:01:58.794Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}