mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-05 02:43:32 +00:00
fix(devcontainer): Node-based initializeCommand; bind-mount .ssh + .config/git
Two fixes bundled:
1. The previous commit's OS-keyed `initializeCommand` object was based
on a misread of the Dev Containers spec. The object form on command
properties is **named parallel tasks**, not OS dispatch — VS Code ran
all three keys in parallel via cmd.exe on Windows, the POSIX branches
failed, and container creation aborted before Docker was invoked.
Restore the single-string Node-based form:
`node .devcontainer/ensure-host-config-dirs.cjs`. Node works
identically in cmd.exe on Windows and bash/zsh on Linux/macOS/WSL,
and `os.homedir()` respects $HOME on POSIX and %USERPROFILE% on
Windows. The script is idempotent (mkdirSync recursive is a no-op
for existing dirs; touch is gated on .gitconfig existence).
Document Node ≥18 on the host as the only host-side prerequisite
beyond Docker Desktop and the VS Code Dev Containers extension.
Anyone running Claude Code on the host already has it.
2. Extend the host-bind mount surface with `~/.ssh` and `~/.config/git`,
both read-only:
- `~/.ssh` lets commit signing + push over SSH remotes work inside
the container without copying private keys. Read-only mount means
container code can read keys but can't modify or delete them.
(Threat: a malicious dep can still read private keys from inside
the container; the read-only mount narrows write-side blast
radius, not read-side. Documented in the trust-boundary section.)
- `~/.config/git` covers XDG-style git config (`~/.config/git/config`,
`~/.config/git/ignore`, `~/.config/git/attributes`) for users who
keep settings there instead of `~/.gitconfig`. Read-only, same as
`~/.gitconfig`.
Update the CLI-state-sharing table and trust-boundary paragraph to
reflect the expanded surface.
Re-adds .devcontainer/ensure-host-config-dirs.cjs (deleted before the
OS-keyed attempt).
This commit is contained in:
parent
32cb7dc28a
commit
f592c804ed
3 changed files with 56 additions and 18 deletions
|
|
@ -6,9 +6,10 @@ A cross-platform Dev Container that pre-installs Claude Code, OpenAI Codex CLI,
|
|||
|
||||
1. Install [Docker Desktop](https://docs.docker.com/desktop/) (Windows/macOS) or Docker Engine (Linux).
|
||||
2. Install [VS Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
|
||||
3. Open the repo in VS Code → Command Palette → **Dev Containers: Reopen in Container**.
|
||||
4. Wait for the first build (~3–6 minutes) and `postCreateCommand` to finish installing workspace dependencies.
|
||||
5. Authenticate the three CLIs once — see [First-time CLI authentication](#first-time-cli-authentication) below.
|
||||
3. Install [Node.js](https://nodejs.org/) on the **host** (Node 18+). This is the only host-side toolchain dependency beyond Docker and VS Code — the devcontainer's `initializeCommand` runs `node .devcontainer/ensure-host-config-dirs.cjs` to set up the bind-mount source directories before container create. If you already use Claude Code or another Node-based CLI on the host, you're already set.
|
||||
4. Open the repo in VS Code → Command Palette → **Dev Containers: Reopen in Container**.
|
||||
5. Wait for the first build (~3–6 minutes) and `postCreateCommand` to finish installing workspace dependencies.
|
||||
6. Authenticate the three CLIs once — see [First-time CLI authentication](#first-time-cli-authentication) below.
|
||||
|
||||
## Windows 11 — WSL2 is strongly recommended
|
||||
|
||||
|
|
@ -58,13 +59,16 @@ The following directories inside the container are **bind-mounted directly from
|
|||
| `~/.codex` | `$HOME/.codex` | read-write |
|
||||
| `~/.cursor` | `$HOME/.cursor` | read-write |
|
||||
| `~/.gitconfig` | `$HOME/.gitconfig` | **read-only** |
|
||||
| `~/.config/git` | `$HOME/.config/git` | **read-only** |
|
||||
| `~/.ssh` | `$HOME/.ssh` | **read-only** |
|
||||
| `~/.config/gh` | `$HOME/.config/gh` | read-write |
|
||||
|
||||
That means:
|
||||
|
||||
- **Authentication is shared.** If you're already logged in on the host (`claude login`, `codex login`, `cursor-agent login`, `gh auth login`), you're already logged in inside the container. No second login step.
|
||||
- **Plugins, skills, agents, memory, and settings sync both ways.** Install a plugin from inside the container and it shows up on the host; add a custom agent on the host and the container sees it immediately. The auto-memory store at `~/.claude/projects/<workspace>/memory/` is the same file tree from both sides.
|
||||
- **Git identity comes from the host.** Commits from inside the container use your host's `user.name` / `user.email`. The mount is read-only so container-side `git config --global` doesn't leak to your host config — set those values from the host shell.
|
||||
- **Git identity comes from the host.** Commits from inside the container use your host's `user.name` / `user.email` from `~/.gitconfig` and any XDG-style config under `~/.config/git/`. The mounts are read-only so container-side `git config --global` doesn't leak to host config — set those values from the host shell.
|
||||
- **SSH keys flow through (read-only).** Push over SSH remotes and SSH commit signing work inside the container using your host keys. The mount is read-only so container code can't exfiltrate or modify private keys — agent-perspective, this means you get git operations but the keys stay vendor-side.
|
||||
- **`gh` auth is shared.** `gh pr create`, `gh pr checks`, `gh issue create` work inside the container without re-authenticating.
|
||||
- **No per-workspace duplication.** All your devcontainers across all your projects see the same host CLI state, just like all your host shells do.
|
||||
|
||||
|
|
@ -72,7 +76,7 @@ The bind mount source directories are guaranteed to exist by the `initializeComm
|
|||
|
||||
### Trust boundary, concretely
|
||||
|
||||
Host and container share a single trust boundary by design — fine for personal-dev, but the consequence is concrete: any malicious npm package or `postinstall` script in the workspace dep tree, running inside the container with these bind mounts active, has direct read access to your OAuth refresh tokens for all three CLIs, your `gh` token, and `~/.claude/projects/<workspace>/memory/MEMORY.md` (which may contain user-stored secrets if you've used the `/remember` skill). The egress firewall is deferred (see "What's not included (yet)" below) so a compromised package would also have unrestricted network to exfiltrate.
|
||||
Host and container share a single trust boundary by design — fine for personal-dev, but the consequence is concrete: any malicious npm package or `postinstall` script in the workspace dep tree, running inside the container with these bind mounts active, has direct read access to your OAuth refresh tokens for all three CLIs, your `gh` token, **your SSH private keys under `~/.ssh/`**, and `~/.claude/projects/<workspace>/memory/MEMORY.md` (which may contain user-stored secrets if you've used the `/remember` skill). Read-only mounts on `~/.ssh`, `~/.gitconfig`, and `~/.config/git` prevent container code from modifying or deleting them, but they're still readable. The egress firewall is deferred (see "What's not included (yet)" below) so a compromised package would also have unrestricted network to exfiltrate.
|
||||
|
||||
**If a workspace dep is ever found compromised**, rotate credentials at the vendor side — local file deletion is insufficient because tokens may have already left:
|
||||
|
||||
|
|
|
|||
|
|
@ -19,19 +19,16 @@
|
|||
}
|
||||
},
|
||||
|
||||
// Runs on the HOST shell before the container is created. Guarantees the
|
||||
// bind-mount source directories below exist so Docker doesn't reject the
|
||||
// mount when a CLI has never been used on this host. OS-keyed because
|
||||
// VS Code runs the host shell in its native form (POSIX on Linux/macOS,
|
||||
// cmd.exe on Windows — which means `mkdir -p` + `$HOME` won't work on
|
||||
// Win32). All three branches are idempotent. WSL is covered by the
|
||||
// linux branch because VS Code's WSL extension runs initializeCommand
|
||||
// in the WSL shell.
|
||||
"initializeCommand": {
|
||||
"linux": "mkdir -p $HOME/.claude $HOME/.codex $HOME/.cursor $HOME/.config/gh && touch $HOME/.gitconfig",
|
||||
"darwin": "mkdir -p $HOME/.claude $HOME/.codex $HOME/.cursor $HOME/.config/gh && touch $HOME/.gitconfig",
|
||||
"win32": "powershell -NoProfile -Command \"$d=$env:USERPROFILE; foreach ($p in '.claude','.codex','.cursor','.config\\gh') { $f=Join-Path $d $p; if (-not (Test-Path $f)) { New-Item -ItemType Directory -Force -Path $f | Out-Null } }; if (-not (Test-Path (Join-Path $d '.gitconfig'))) { New-Item -ItemType File -Path (Join-Path $d '.gitconfig') | Out-Null }\""
|
||||
},
|
||||
// Runs on the HOST (not the container) before container create. The
|
||||
// single-string form is the spec-canonical shape for cross-platform
|
||||
// command-property dispatch; the object form is "named parallel tasks",
|
||||
// not OS dispatch. We use Node so the same command works in cmd.exe on
|
||||
// Windows and bash/zsh on Linux/macOS/WSL — the script reads `os.homedir()`
|
||||
// (which respects $HOME on POSIX and %USERPROFILE% on Windows) and creates
|
||||
// the host-side bind mount sources idempotently. Host prerequisite: Node
|
||||
// on PATH (the only host-side toolchain dependency beyond Docker Desktop
|
||||
// and the VS Code Dev Containers extension).
|
||||
"initializeCommand": "node .devcontainer/ensure-host-config-dirs.cjs",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||
|
|
@ -70,6 +67,8 @@
|
|||
"source=${localEnv:HOME}/.codex,target=/home/node/.codex,type=bind",
|
||||
"source=${localEnv:HOME}/.cursor,target=/home/node/.cursor,type=bind",
|
||||
"source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,readonly",
|
||||
"source=${localEnv:HOME}/.config/git,target=/home/node/.config/git,type=bind,readonly",
|
||||
"source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly",
|
||||
"source=${localEnv:HOME}/.config/gh,target=/home/node/.config/gh,type=bind",
|
||||
"source=commandhistory-${devcontainerId},target=/commandhistory,type=volume",
|
||||
"source=npm-cache-${devcontainerId},target=/home/node/.npm,type=volume",
|
||||
|
|
|
|||
35
.devcontainer/ensure-host-config-dirs.cjs
Normal file
35
.devcontainer/ensure-host-config-dirs.cjs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Runs on the HOST (not inside the container) before the dev container is
|
||||
// created, via devcontainer.json `initializeCommand`. Guarantees the bind
|
||||
// mount sources declared in devcontainer.json exist on the host so Docker
|
||||
// doesn't reject the mount when a CLI has never been used.
|
||||
//
|
||||
// Cross-platform via Node's `os.homedir()` (which reads $HOME on POSIX and
|
||||
// %USERPROFILE% on Windows) and `fs.mkdirSync({recursive: true})`. Idempotent
|
||||
// — `recursive: true` is a no-op when a directory already exists, and the
|
||||
// `.gitconfig` touch is gated on file existence.
|
||||
//
|
||||
// Host prerequisite: Node.js on PATH. This is the only documented host
|
||||
// requirement beyond Docker Desktop and the VS Code Dev Containers
|
||||
// extension — everything else runs inside the container.
|
||||
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
const home = os.homedir();
|
||||
|
||||
for (const dir of [
|
||||
".claude",
|
||||
".codex",
|
||||
".cursor",
|
||||
".ssh",
|
||||
path.join(".config", "gh"),
|
||||
path.join(".config", "git"),
|
||||
]) {
|
||||
fs.mkdirSync(path.join(home, dir), { recursive: true });
|
||||
}
|
||||
|
||||
const gitconfig = path.join(home, ".gitconfig");
|
||||
if (!fs.existsSync(gitconfig)) {
|
||||
fs.closeSync(fs.openSync(gitconfig, "a"));
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue