system-ca
Reads trusted certificates from the OS certificate store on Windows, macOS, and Linux for use with Node's TLS APIs.
Repository Health
Technical Analysis
system-ca is a small TypeScript library that gives Node.js applications direct access to the operating system’s trusted certificate store on Windows, macOS, and Linux. It exposes both synchronous (systemCertsSync) and asynchronous (systemCertsAsync) functions that return an array of PEM-formatted X.509 certificates, ready to pass straight into the ca option of tls.connect() or any other API that accepts a certificate authority list.
Under the hood, system-ca adapts its strategy per platform: on Windows and macOS it uses native addons (via the optional win-export-certificate-and-key and macos-export-certificate-and-key packages) to pull certificates out of the OS certificate store, while on Linux and other Unix-like systems it reads PEM certificates directly from the typical system locations (/etc/ssl, /etc/pki), honoring SSL_CERT_FILE and SSL_CERT_DIR environment overrides the same way OpenSSL does. An optional includeNodeCertificates flag merges in Node’s own bundled root certificates alongside the system ones.
What You Get
- Synchronous and asynchronous APIs for reading the OS trust store
- PEM-formatted certificate arrays that drop straight into tls.connect()‘s ca option
- Native OS-store access on Windows and macOS via optional native-addon dependencies
- Unix certificate file/dir scanning that mirrors OpenSSL’s SSL_CERT_FILE/SSL_CERT_DIR conventions
Common Use Cases
- Trusting corporate or self-signed proxy certificates installed system-wide without bundling them into the app
- Making outbound HTTPS/TLS requests from a CLI tool or database driver work behind an enterprise TLS-inspecting firewall
- Supplementing Node’s default root certificates with the OS’s own trust store in restricted or air-gapped environments
Under The Hood
Architecture
The library is a single flat module: index.ts exposes systemCertsSync/systemCertsAsync, each branching on process.platform to call into platform-specific generator functions defined in impl.ts (unixSyncImpl/unixAsyncImpl read PEM files off disk; windowsSyncImpl/macosSyncImpl/their async counterparts lazily require() optional native-addon packages), collect results into a Set to dedupe, and optionally fold in Node’s own tls.rootCertificates via a shared maybeAddNodeCertificates helper. There is no dependency injection, no internal layering, and no abstraction beyond the platform switch itself — if the shape of the native-addon exports changed, both the sync and async code paths would need to be updated in lockstep since they are hand-mirrored rather than derived from one implementation.
Tech Stack
Written in TypeScript (^4.0.3, strict mode enabled in tsconfig.json) targeting ES2018/CommonJS, compiled with tsc and wrapped for dual CJS/ESM consumption via gen-esm-wrapper. It has no runtime framework dependencies, relying only on Node core (fs, path, tls) plus two optionalDependencies (win-export-certificate-and-key, macos-export-certificate-and-key) that are lazily require()’d so installs on unsupported platforms don’t fail. Linting uses eslint with the semistandard config plus @typescript-eslint and eslint-plugin-promise.
Code Quality
Tests use Mocha + Chai (src/index.spec.ts) with nyc for coverage and ts-node for direct TypeScript execution; the suite is thin (two integration-style tests) but genuinely validates behavior against the real OS certificate store by asserting a well-known Microsoft root CA’s serial number is present in the returned set, rather than mocking the filesystem. Error handling in the Unix path is explicit and deliberate: read failures are collected but only rethrown if no certificates were found at all, favoring partial success over hard failure. TypeScript strict mode plus a GitHub Actions CI workflow (nodejs.yml) and a CodeQL security-scanning workflow back the codebase, though there’s no unit-level coverage of impl.ts’s individual branches in isolation.
API Design
The public surface is deliberately minimal — two functions (sync/async) with one shared Options type — so integrating it requires almost no boilerplate: call systemCertsAsync() and pass the result to tls.connect()’s ca option. Shipped .d.ts types, a documented includeNodeCertificates flag, and platform differences hidden entirely behind the same function signature make this a low-friction, single-purpose utility rather than a general framework.