Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
9a47693
Deliver fallback installer and path hints for non-VS Code use
claude May 25, 2026
d01e4e8
Add read-only --doctor diagnostics to setup-cert.sh
claude May 25, 2026
49c94b9
Document non-VS Code use and ship a worked example
claude May 25, 2026
f331b7f
Move cert generator and exporter into the shared package
claude May 25, 2026
849fe7d
Move the platform trust-store layer into the shared package
claude May 25, 2026
a55cf43
Add ddc — a host CLI for generating, inspecting, and trusting dev certs
claude May 25, 2026
c184b70
Add hostCertGenerator setting; promote backends into shared
claude May 26, 2026
41ddc01
Drop aspire from public backend surface
claude May 26, 2026
4809f8e
Harden runProcess against Windows cwd-first executable lookup
claude May 26, 2026
30a2c4e
Document the ddc CLI in the manual-setup example and a new CLI README
claude May 26, 2026
08fb2a1
Native backend: skip the platform store entirely on --no-trust
claude May 26, 2026
a7227a7
ddc bundle: warn on cross-dir paths; ddc doctor: per-OS tool parity
claude May 26, 2026
15f33c5
Rename CLI binary ddc → dcdc; ready @devcontainer-dev-certs/cli for npm
claude May 26, 2026
aa1294c
CLI README: drop the ddc-collision backstory
claude May 26, 2026
5275537
Wire the CLI into the existing release pipeline
claude May 26, 2026
1d1f8a1
Add RELEASING.md covering the normal flow and the one-time CLI bootstrap
claude May 26, 2026
b990484
Fix dotnet backend: --no-password is PFX-invalid; bypass dotnet export
claude May 26, 2026
e3f90dd
Wire Linux NSS reporter through every trust-step entry point
claude May 26, 2026
27c965e
dcdc generate / bundle: trust symmetry + sibling-file probe parity
claude May 26, 2026
54406f7
setup-cert.sh: honor DEVCONTAINER_DEV_CERTS_*_DIR env vars
claude May 26, 2026
376e6c6
macStore.removeCertificates: untrust before delete, with cert file
claude May 26, 2026
a140e40
windowsStore: re-probe pwsh on each call if not yet found
claude May 26, 2026
4a04052
Backend cleanup: drop PFX reparse; collapse certProvider's double nat…
claude Jun 12, 2026
8a19d45
Move findSiblingKey to shared; consolidate CLI defaults
claude Jun 12, 2026
d3f5e07
dcdc doctor: probe dotnet once and run check groups concurrently
claude Jun 12, 2026
9707fb0
Extract stubPlatform into a per-workspace test helper
claude Jun 12, 2026
8e25995
Empirically verify macOS dotnet dev-certs disk-cache compatibility
claude Jun 12, 2026
9619a5e
Merge remote-tracking branch 'origin/main' into claude/devcontainer-n…
claude Jun 12, 2026
546b03e
Fix two CI failures from the main merge
claude Jun 12, 2026
d33c914
Fix two more CI failures: install.sh shellcheck + macOS keychain HOME
claude Jun 12, 2026
9b35bd9
setup-cert.sh: fix two shellcheck findings
claude Jun 12, 2026
09115d8
Accept the one legacy PBE algorithm aspnetcore writes on macOS
claude Jun 12, 2026
d40d0b7
Try both empty-password conventions in legacy 3DES decrypt
claude Jun 12, 2026
29d3204
Address four Copilot review findings
claude Jun 12, 2026
a6747fe
Bump Node floor from 20 to 22 (Node 20 went EOL April 2026)
claude Jun 12, 2026
7b92c6b
Bump CLI version to 1.3.2-pre to match the rest of the repo
claude Jun 12, 2026
66eb591
Bump all components to 1.4.0-pre.1
claude Jun 12, 2026
d4861f3
nativeBackend.test.ts: consolidate the two shared imports
claude Jun 12, 2026
33032ba
dcdc generate / --help: clearer reuse + interaction semantics
claude Jun 13, 2026
93311f8
Drop dcdc CLI from PR scope; keep shared layer + macOS support
claude Jun 13, 2026
17def4a
Restore cross-platform optional deps in package-lock.json
claude Jun 13, 2026
6bf93f8
Fix OID inspector in macOS PFX format compatibility test
claude Jun 13, 2026
483cddd
Merge remote-tracking branch 'origin/main' into claude/devcontainer-n…
claude Jul 27, 2026
5e45df0
Normalize version to 1.4.0-pre
claude Jul 27, 2026
93eefd0
Test runProcess's safe-exec guard branch
claude Jul 28, 2026
45f8c13
Address nine Copilot review findings (all verified)
claude Jul 28, 2026
abf9d91
Linux isTrusted: require both authoritative trust surfaces
claude Jul 28, 2026
867adfa
Merge remote-tracking branch 'origin/main' into claude/devcontainer-n…
claude Jul 28, 2026
0f702f4
Revert re-added non-VS Code README sections; defer to #80's stance
claude Jul 28, 2026
efe6b57
Drop setup-cert.sh/schema/examples entries from Repository Layout
claude Jul 28, 2026
d075f26
Address five Copilot review findings (all verified)
claude Jul 28, 2026
6560f25
Address three findings from a local review session (all verified)
claude Jul 28, 2026
107c07a
Remove stray test helper accidentally committed
claude Jul 28, 2026
2f12e2f
Normalize all settings under the devcontainerDevCerts prefix
claude Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions .github/workflows/build-extensions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ on:
description: Use production build (minified, no sourcemaps)
type: boolean
default: false
upload-vsix:
description: Package and upload VSIX artifacts
upload-artifacts:
description: Package and upload release artifacts (VSIXes) for downstream publish jobs.
type: boolean
default: false

Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
run: npm run package -w src/vscode-workspace-extension

- name: Upload VSIX artifacts
if: inputs.upload-vsix
if: inputs.upload-artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vsix
Expand Down Expand Up @@ -107,6 +107,56 @@ jobs:
- name: Validate PFX with .NET
run: npm test -w src/vscode-ui-extension -- tests/dotnetPfx.integration.test.ts

macos-dotnet-dev-certs-cache:
name: macOS dotnet dev-certs disk-cache compatibility
runs-on: macos-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "22"

- name: Install dependencies
run: npm ci

- name: Build UI Extension
run: npm run ${{ inputs.production && 'build:prod' || 'build' }} -w src/vscode-ui-extension

- name: Set up .NET 10 SDK
# Test invokes `dotnet dev-certs https` to produce the actual
# on-disk cache file aspnetcore's MacOSCertificateManager
# writes. We need a real SDK on PATH; the exact major version
# is logged so we can correlate the result with SDK behavior
# if aspnetcore changes its export defaults.
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: "10.0.x"

- name: Unlock login keychain for dotnet dev-certs
# `dotnet dev-certs https` saves the generated cert into
# CurrentUser\My, which on macOS is the login keychain. GitHub
# Actions macos runners ship with the login keychain locked
# by default — without an unlock, the save call fails with
# "There was an error saving the HTTPS developer certificate
# to the current user personal certificate store." and the
# disk cache we're trying to test never gets written.
#
# The empty password is the documented GH Actions convention
# for the runner user's login keychain.
run: |
security unlock-keychain -p '' "$HOME/Library/Keychains/login.keychain-db"
security set-keychain-settings "$HOME/Library/Keychains/login.keychain-db"

- name: Validate dotnet dev-certs disk cache is loadable
# If this fails, the dotnet backend on macOS cannot discover
# the cert dotnet just created — platform-store discovery
# depends on `parsePfx` accepting whatever
# `certificate.Export(X509ContentType.Pfx)` writes on macOS.
# The same test pins the on-disk PBE algorithm OID so we get
# a loud signal the day aspnetcore switches to PBES2 and the
# legacy 3DES handler in `pkcs12LegacyPbe.ts` can be removed.
run: npm test -w src/vscode-ui-extension -- tests/dotnetMacosCache.integration.test.ts

feature:
name: Validate Devcontainer Feature
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ jobs:
uses: ./.github/workflows/build-extensions.yml
with:
production: true
upload-vsix: true
upload-artifacts: true
2 changes: 1 addition & 1 deletion .github/workflows/release-feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
uses: ./.github/workflows/build-extensions.yml
with:
production: true
upload-vsix: true
upload-artifacts: true

publish-feature:
name: Publish feature to GHCR
Expand Down
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ The two extensions communicate using VS Code's cross-host `executeCommand()` rou

All configurable surfaces in one place. Four categories: the Dev Container feature options (set in `devcontainer.json`), the host VS Code settings (set on your local machine), the workspace VS Code settings (set inside the Dev Container — and inherited by SSH/WSL remotes), and the commands both extensions contribute. The prose sections that follow this reference cover the workflows these knobs participate in.

**Two settings prefixes are in use.** `devcontainerDevCerts.*` (camelCase) is the newer convention for settings that describe cert *content* the extensions share across the host/container boundary; `devcontainer-dev-certs.*` (hyphenated) is the older convention for extension behavior knobs. Both stay supported; we don't rename them to avoid churning user settings.
**One settings prefix.** Every setting lives under `devcontainerDevCerts.*`, so they all group together in the Settings UI. Three settings (`autoProvision`, `autoInject`, `warnOnStaleDevCerts`) historically used a hyphenated `devcontainer-dev-certs.*` prefix — those legacy names are deprecated but still honored whenever the new name is unset, so existing configuration keeps working without changes.

**One name, two scopes.** `generateDotNetCert` exists both as a *feature option* (container-side: "pull the host's dev cert into this container") and as a *host VS Code setting* `devcontainerDevCerts.generateDotNetCert` (host-side: "generate and trust a managed dev cert on this machine at all"). They are separate knobs at different layers — turning off the host setting means no managed dev cert exists anywhere; turning off the feature option means this one container doesn't receive it. Check which table you're reading.

Expand Down Expand Up @@ -184,8 +184,9 @@ Set in your host VS Code's user or workspace settings. Provided by the **Dev Con

| Setting | Default | Description |
|---------|---------|-------------|
| `devcontainer-dev-certs.autoProvision` | `true` | Allow certificate provisioning when the workspace extension requests one. On first use, a consent prompt explains what will happen before any certificates are generated. Set to `false` to disable provisioning entirely (host-generation AND acceptance of container-pushed certs). |
| `devcontainerDevCerts.autoProvision` | `true` | Allow certificate provisioning when the workspace extension requests one. On first use, a consent prompt explains what will happen before any certificates are generated. Set to `false` to disable provisioning entirely (host-generation AND acceptance of container-pushed certs). |
| `devcontainerDevCerts.generateDotNetCert` | `true` | Auto-generate the ASP.NET / Aspire compatible HTTPS dev cert and trust it in the host OS store. When `false`, user-managed certificates (if any) are still synced, but no managed dev cert lives on the host — this also implicitly disables acceptance of container-pushed certs (a container push would land one in the same trust store the user opted out of). |
| `devcontainerDevCerts.hostCertGenerator` | `"auto"` | Which backend generates the host dev cert: `native` (the extension's bundled cert primitives — no SDK required), `dotnet` (shell out to `dotnet dev-certs https`; requires the dotnet SDK on PATH), or `auto` (prefer `dotnet` on macOS when the CLI is available — the Apple-signed binary gives a smoother keychain-trust prompt — and `native` everywhere else). Only affects fresh provisioning of the auto-generated dev cert; an existing cert in the OS store is reused regardless of this setting, and user-managed certificates are unaffected. |
| `devcontainerDevCerts.userCertificates` | `[]` | Host-managed certificates to sync from the host into Dev Containers. See "[User-managed certificates](#user-managed-certificates)" for the per-entry field table. User-managed certs are never added to the host OS trust store. |
| `devcontainerDevCerts.installUserCertsToDotNetStore` | `false` | When `true`, also copies each key-bearing entry from `userCertificates` into the container's .NET X509Store. CA-only entries (no private key) are skipped — there's no PFX to write. **Security note:** the on-disk PFX there is passwordless (Linux's `StoreName.My` enumeration can't accept per-file passwords), so opting in strips your user cert's password on the in-container copy. Per-entry exemption via `excludeFromDotNetStore: true`. The auto-generated dotnet-dev cert is always installed to the store regardless. |
| `devcontainerDevCerts.defaultKestrelCertificate` | `""` | Name of a `userCertificates` entry to use as the default Kestrel certificate inside Dev Containers. When set, the remote extension writes that cert's PFX to `~/.aspnet/dev-certs/https/kestrel-default.pfx` and exports `ASPNETCORE_Kestrel__Certificates__Default__Path`/`__Password` to processes launched from VS Code (terminals, debug, tasks). Leave empty to opt out — Kestrel will still discover the auto-generated dev cert via X509Store. See "[Default Kestrel certificate (opt-in)](#default-kestrel-certificate-opt-in)" for scope and caveats. |
Expand All @@ -197,8 +198,8 @@ Set in your workspace settings or user settings inside the Dev Container / remot

| Setting | Default | Description |
|---------|---------|-------------|
| `devcontainer-dev-certs.autoInject` | `true` | Automatically inject and configure the dev cert when a remote session starts. Set to `false` to require manual invocation via the "Dev Certs: Inject Certificate into Remote" command. |
| `devcontainer-dev-certs.warnOnStaleDevCerts` | `true` | Show a warning when multiple dev certificates are detected in this Dev Container's .NET certificate stores after install — alongside the extension-managed certificate. Pairs with the "Dev Certs: Clean Up Other Dev Certificates in Dev Container" command. |
| `devcontainerDevCerts.autoInject` | `true` | Automatically inject and configure the dev cert when a remote session starts. Set to `false` to require manual invocation via the "Dev Certs: Inject Certificate into Remote" command. |
| `devcontainerDevCerts.warnOnStaleDevCerts` | `true` | Show a warning when multiple dev certificates are detected in this Dev Container's .NET certificate stores after install — alongside the extension-managed certificate. Pairs with the "Dev Certs: Clean Up Other Dev Certificates in Dev Container" command. |

### Commands

Expand Down Expand Up @@ -366,7 +367,7 @@ To enable, set the feature option on the container:
}
```

The host side is gated by the same VS Code settings that gate the normal host-generation flow: `devcontainerDevCerts.generateDotNetCert` (default `true`) and `devcontainer-dev-certs.autoProvision` (default `true`). If you've disabled either — because you don't want any extension-managed dev cert on your host — a container's push is rejected with the same intent. No separate "accept container certs" toggle: the user-level question "is this host willing to trust a managed ASP.NET dev cert?" has one answer, regardless of where the cert came from.
The host side is gated by the same VS Code settings that gate the normal host-generation flow: `devcontainerDevCerts.generateDotNetCert` (default `true`) and `devcontainerDevCerts.autoProvision` (default `true`). If you've disabled either — because you don't want any extension-managed dev cert on your host — a container's push is rejected with the same intent. No separate "accept container certs" toggle: the user-level question "is this host willing to trust a managed ASP.NET dev cert?" has one answer, regardless of where the cert came from.

With `syncContainerCert` enabled:

Expand Down Expand Up @@ -406,8 +407,8 @@ Failure notifications from the remote extension usually name which side to check
Work through these in order:

1. **Is the host extension installed?** The remote extension shows an **Install Host Extension** prompt when it can't reach the host side. If you dismissed it, install `dnegstad.devcontainer-dev-certs-host` on your local VS Code and reload.
2. **Is auto-injection on?** If `devcontainer-dev-certs.autoInject` is `false`, run **Dev Certs: Inject Certificate into Remote** from the Command Palette.
3. **Is provisioning on?** If `devcontainer-dev-certs.autoProvision` or `devcontainerDevCerts.generateDotNetCert` is `false` on the host, no managed dev cert is generated or served.
2. **Is auto-injection on?** If `devcontainerDevCerts.autoInject` is `false`, run **Dev Certs: Inject Certificate into Remote** from the Command Palette.
3. **Is provisioning on?** If `devcontainerDevCerts.autoProvision` or `devcontainerDevCerts.generateDotNetCert` is `false` on the host, no managed dev cert is generated or served.
4. **Did the feature actually install?** Run `echo $SSL_CERT_DIR` in a container terminal. The first entry should be the trust directory under your home directory, expanded — e.g. `/home/vscode/.aspnet/dev-certs/trust`. If the variable is empty, the feature didn't run; rebuild the container.

Running **Dev Certs: Inject Certificate into Remote** is the standard way to retry any of this without rebuilding.
Expand Down Expand Up @@ -498,23 +499,26 @@ src/
src/
cert/ Cert primitives: classify, load, PFX build/parse,
OID + SAN + key constants, validation rules
paths.ts .NET store and OpenSSL trust directory paths
certName.ts userCertificates[].name pattern and guard
logger.ts Output channel logging

vscode-ui-extension/ VS Code host extension (extensionKind: ui)
src/
cert/ Host-side certificate work
generator.ts X.509 certificate generation (matches ASP.NET CertificateManager)
exporter.ts PFX and PEM export
manager.ts Orchestrates generate/trust/export/check
(loader/pfx/properties/types here are re-export
shims over the canonical copies in shared/)
pkcs12LegacyPbe.ts Narrow legacy 3DES PBE decoder for aspnetcore's
macOS disk cache (see its removal checklist)
platform/ OS-specific cert store implementations
windowsStore.ts Windows cert store via PowerShell
macStore.ts macOS keychain via security CLI
linuxStore.ts Linux X509Store + OpenSSL trust directory
nssTrust.ts Firefox/Chromium NSS trust via certutil
processUtil.ts PATH-safe process spawning (Windows cwd-hijack defense)
backends/ hostCertGenerator backends: native (bundled
primitives) and dotnet (dev-certs pass-through)
paths.ts .NET store and OpenSSL trust directory paths
certName.ts userCertificates[].name pattern and guard
logger.ts Pluggable logging (loggerVscode.ts binds the output channel)

vscode-ui-extension/ VS Code host extension (extensionKind: ui)
src/
cert/, platform/ Re-export shims over the canonical copies in shared/
certProvider.ts Serves cert material to the workspace extension
containerCertAccept.ts Validates and trusts container-pushed certs

Expand All @@ -538,6 +542,7 @@ test/
sample-project/ Test project template (hydrated into .out/ for testing)
hydrate-sample-project.mjs Assembles a runnable test project from the template + feature
dotnet-pfx-roundtrip/ .NET PFX-load harness driven by the UI extension's tests
fixtures/ Binary test fixtures (e.g. a legacy 3DES PKCS#12)
install-sh.test.mjs Behavioral test that actually runs install.sh
validate-feature.mjs Consistency checks across feature JSON, install.sh, package.json
generate-test-cert.mjs Test certificate fixture generator
Expand Down
24 changes: 24 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Releasing

This repository ships three components together on a single release trigger:

- The devcontainer feature (`ghcr.io/dnegstad/devcontainer-dev-certs/devcontainer-dev-certs`)
- The host VS Code extension (`dnegstad.devcontainer-dev-certs-host`)
- The remote VS Code extension (`dnegstad.devcontainer-dev-certs-remote`)

All three share one repo-wide version. A release is one GitHub Release; everything downstream runs from `.github/workflows/release-feature.yml`.

## Normal release procedure

1. Verify every component is at the version you intend to release. `bump-version.yml` keeps them in lockstep automatically after the previous release, so this usually means "merge the open `chore/bump-X.Y.Z-pre` PR and then promote `X.Y.Z-pre` to `X.Y.Z`" via a separate bump PR.
2. Create a GitHub Release with tag `vX.Y.Z` (must match the component versions exactly — `validate-release` will fail the workflow otherwise).
3. Publish the GitHub Release. The `release: [released]` trigger starts the pipeline.

The workflow:

- Validates that the tag matches every component's `package.json` / `devcontainer-feature.json` version.
- Builds and tests everything via `build-extensions.yml` in production mode, producing VSIX files as workflow artifacts.
- Publishes the devcontainer feature to GHCR with build provenance.
- Attests VSIX provenance via `actions/attest-build-provenance` and attaches both VSIXes to the GitHub Release.

No manual publish commands. The GitHub Release is the trigger; CI does the rest.
Loading
Loading