Skip to content

ishwar170695/certsync

Repository files navigation

Go CI

certsync

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.

Why

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.


Install

go install github.com/ishwar170695/certsync@latest

Or download a binary from Releases (Windows, macOS, Linux).


Usage

Step 1 — certsync init

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.


Step 2 — certsync up

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.


How it works

init

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..."]
    }
  ]
}

up

  1. Reads ~/.certsync/profiles.json
  2. Shows the TUI selection menu
  3. Writes a devcontainer Feature to ~/.certsync/ca-inject/:
    • devcontainer-feature.json — declares a bind mount for the PEM bundle
    • install.sh — copies the bundle into the container trust store
    • bundle.pem — the selected certificates in PEM format
  4. Reads the project's devcontainer.json (supports JSONC comments), merges in the Feature, and writes .certsync-overlay.jsonc at the project root
  5. Calls devcontainer up --workspace-folder <project> --config .certsync-overlay.jsonc
  6. On success: deletes the overlay file (Docker has already processed it)
  7. On failure: leaves the overlay file in place for inspection

Generated overlay structure

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".

// Auto-generated by certsync — do not edit. Delete this file to discard CA injection.
{
  "image": "ubuntu:22.04",
  "features": {
    "/home/you/.certsync/ca-inject": {},
    "ghcr.io/devcontainers/features/go:1": {}
  },
  "overrideFeatureInstallOrder": [
    "/home/you/.certsync/ca-inject",
    "ghcr.io/devcontainers/features/go:1"
  ]
}

The certsync feature is always first in overrideFeatureInstallOrder so CAs are trusted before any other feature makes network requests.

install.sh — distro detection

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
fi

If 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.


File layout

~/.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

Limitations (v1)

  • 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.sh detects update-ca-certificates and update-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 init when you start seeing certificate errors again, then certsync up to rebuild.

Future / unattended mode

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.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages