Injects your host machine's CA certificates — including corporate MITM proxy CAs — into Dev Containers, so tools inside the container (curl, npm, pip, go get) trust the same roots your host does.
Corporate networks intercept TLS with a custom root CA. Your host trusts it because IT enrolled it in the system store. Dev Containers don't — they start from a stock image that has never seen that CA. Every HTTPS call from inside the container fails with certificate signed by unknown authority or CERTIFICATE_VERIFY_FAILED.
certsync solves this without hardcoding certificates in Dockerfiles or committing them to repos.
go install github.com/ishwar170695/certsync@latestOr download a binary from Releases (Windows, macOS, Linux).
Run once per machine. Scans the host certificate stores, groups certificates into trust profiles, and writes them to ~/.certsync/profiles.json.
$ certsync init
CertSync - Initializing host profiles
Scanning host certificate stores...
Found 75 unique CA certificates. Grouped into 7 profiles.
✔ Successfully wrote profiles to C:\Users\you\.certsync\profiles.json
Profiles detected on a typical corporate machine:
| Profile | Type | Description |
|---|---|---|
| Zscaler Proxy | ⚠ MITM | Corporate TLS inspection proxy |
| Google Trust Services LLC Root CA | System | OS vendor roots |
| Microsoft Corporation Root CA | System | OS vendor roots |
| System Default | Standard | All other trusted CAs |
The scan result includes a scanned_at timestamp. If the database is older than 30 days when you run certsync up, you'll see a warning — MITM proxy certificates rotate.
Run from inside a project directory that has a devcontainer.json. Loads the saved profiles, shows an interactive selection menu, then builds and starts the Dev Container with the chosen CAs injected.
$ certsync up
Loaded 7 profiles (75 total certificates) from ~/.certsync/profiles.json
Which profiles should containers trust? (↑↓/JK to move, Space to toggle, Enter to confirm)
▸ [✔] Zscaler Proxy (2 certs — likely MITM proxy)
[✔] System Default (41 certs — standard CAs)
[ ] Google Trust Services... (1 cert — system roots)
[ ] Microsoft Corporation... (12 certs — system roots)
Bundling 43 certificates...
Found devcontainer config: /projects/myapp/.devcontainer/devcontainer.json
Wrote overlay: /projects/myapp/.certsync-overlay.jsonc
Starting devcontainer...
[devcontainer CLI output follows]
✔ devcontainer is up with injected CA certificates.
MITM proxies and System Default are pre-checked. Use Space to adjust, Enter to confirm.
Reads from the host certificate store (Windows: CertOpenSystemStore; macOS: security find-certificate; Linux: /etc/ssl/certs), deduplicates, and classifies each CA with a heuristic scoring system:
- Known vendor strings in CN/O (Zscaler, Palo Alto, Netskope, …)
- Internal naming patterns (
corp,proxy,inspect, …) - Self-signed root with validity < 10 years
- RSA key < 2048 bits (unusual for public roots)
- No CRL or OCSP endpoints
Any CA scoring ≥ 2 is flagged as a likely MITM proxy and placed in its own profile.
Results are written to ~/.certsync/profiles.json:
{
"version": 1,
"scanned_at": "2025-06-01T10:00:00Z",
"profiles": [
{
"name": "Zscaler Proxy",
"description": "likely MITM proxy",
"is_mitm": true,
"certs": ["-----BEGIN CERTIFICATE-----\n..."]
}
]
}- Reads
~/.certsync/profiles.json - Shows the TUI selection menu
- Writes a devcontainer Feature to
~/.certsync/ca-inject/:devcontainer-feature.json— declares a bind mount for the PEM bundleinstall.sh— copies the bundle into the container trust storebundle.pem— the selected certificates in PEM format
- Reads the project's
devcontainer.json(supports JSONC comments), merges in the Feature, and writes.certsync-overlay.jsoncat the project root - Calls
devcontainer up --workspace-folder <project> --config .certsync-overlay.jsonc - On success: deletes the overlay file (Docker has already processed it)
- On failure: leaves the overlay file in place for inspection
Note
Since Go maps marshal non-deterministically, the keys inside "features" may render in a different visual order. This does not affect execution order, which is strictly governed by "overrideFeatureInstallOrder".
The certsync feature is always first in overrideFeatureInstallOrder so CAs are trusted before any other feature makes network requests.
if command -v update-ca-certificates > /dev/null 2>&1; then
cp /tmp/certsync-bundle.pem /usr/local/share/ca-certificates/certsync.crt
update-ca-certificates
CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
elif command -v update-ca-trust > /dev/null 2>&1; then
cp /tmp/certsync-bundle.pem /etc/pki/ca-trust/source/anchors/certsync.crt
update-ca-trust
CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt
else
echo "certsync: WARNING: no CA update command found" >&2
CA_BUNDLE=""
fi
if [ -n "$CA_BUNDLE" ]; then
echo "NODE_EXTRA_CA_CERTS=$CA_BUNDLE" >> /etc/environment
echo "SSL_CERT_FILE=$CA_BUNDLE" >> /etc/environment
echo "REQUESTS_CA_BUNDLE=$CA_BUNDLE" >> /etc/environment
fiIf a valid CA bundle path is found, it is propagated to /etc/environment so tools/runtimes that do not automatically query the system trust store (like Node.js, Python requests, or Go binaries respecting SSL_CERT_FILE) will trust the injected CA certificates.
~/.certsync/
├── profiles.json # host cert scan results (written by init)
└── ca-inject/ # devcontainer Feature (written by up, persistent)
├── devcontainer-feature.json
├── install.sh
└── bundle.pem
<project>/
├── .devcontainer/
│ └── devcontainer.json # your original, untouched
└── .certsync-overlay.jsonc # ephemeral — deleted on success
- Local Docker only. The feature path is an absolute host path. Remote SSH containers and WSL2 contexts where the devcontainer CLI runs with a different filesystem view are not yet supported.
- Debian/RHEL base images.
install.shdetectsupdate-ca-certificatesandupdate-ca-trust. Other distros will get a warning but the container will still start. - Re-run on cert rotation. MITM proxy CAs rotate. Run
certsync initwhen you start seeing certificate errors again, thencertsync upto rebuild.
To skip the TUI and use all MITM-flagged profiles automatically (e.g. in postCreateCommand):
// devcontainer.json
{
"postCreateCommand": "certsync up --all-mitm" // not yet implemented
}This flag isn't implemented in v1. The interactive menu is the current interface.