Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 15 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,23 @@
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/anthropics/devcontainer-features/claude-code:1": {}
},
// The history bind below names a *file*, and Docker creates a missing bind source
// itself, as a directory on the host, where it outlives the container.
"initializeCommand": "touch ${localEnv:HOME}/.zsh_history",
// Narrowed from a mount of the whole host home at /WSL_USER, which handed this
// container read-write reach over the host's SSH keys, credentials and every other
// repository on the machine in order to reach one history file. (RSRMID-3052)
//
// Be aware that path currently feeds nothing here: /WSL_USER/.zsh_history is read by
// the devbase Feature, and this frame does not list it. The "migrate onto the shared
// devbase Feature" commit added .dockerignore and env-info.conf but never the Feature
// itself, so this repository has no history persistence to break — and the
// env-info.conf beside this file is read by nobody. Completing that migration is a
// separate change; this one only stops the mount being wider than it needs to be.
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,readonly",
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached",
"source=${localEnv:HOME}${localEnv:USERPROFILE},target=/WSL_USER,type=bind,consistency=cached",
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly",
"source=${localEnv:HOME}/.zsh_history,target=/WSL_USER/.zsh_history,type=bind,consistency=cached",
"source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind,consistency=cached"
],
"remoteUser": "vscode",
Expand Down
11 changes: 8 additions & 3 deletions docs/agents/project-policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,15 @@ The shared CI workflow currently maps `secrets.RTLDEV_MW_CI_USER_CNR` → `env.C

### `.devcontainer/devcontainer.json` host mounts — accepted, not fixed

`mounts` binds three host paths into the container beyond the pre-existing `~/.gitconfig`:
`mounts` binds four host paths into the container, each naming one path and granting the least it can — read-write is the exception and needs a reason:

- `${localEnv:HOME}/.ssh` (readonly) — lets the container's `git` use the host's own SSH keys for clone/push against private repos, since the container has none of its own.
- `${localEnv:HOME}/.gitconfig` (readonly) — the container reads that identity, it does not maintain it. Readonly costs nothing: the shared devcontainer Feature writes credential and signing config with `git config --local`, never `--global`, precisely so the host's file stays untouched. What it stops is a stray `git config --global` inside a container editing the host's identity.
- `${localEnv:HOME}/.zsh_history` → `/WSL_USER/.zsh_history` (read-write, because history is written) — the path the `devbase` Feature reads to make shell history survive a rebuild. **This frame does not list that Feature**, so nothing here currently reads it; see the note below.
- `${localEnv:HOME}/.claude` — shares this Claude Code CLI's local config/session state between host and container, so an agent session started on the host and one started inside the container see the same auth and history.
- `${localEnv:HOME}${localEnv:USERPROFILE}` → `/WSL_USER` — a WSL-specific idiom: `HOME` is empty on native WSL2 and `USERPROFILE` is empty outside Windows/WSL, so exactly one of the two is non-empty per platform and the concatenation resolves to whichever one applies, mounting the Windows-side user profile (reachable from WSL) into the container. On a non-WSL host this duplicates the plain `$HOME` mount instead.

Both risks a reviewer would flag are real, not overlooked: a contributor without `~/.ssh` or `~/.claude` on the host gets a container that fails to start rather than a graceful skip (Docker bind mounts require the source to exist), and any of these three paths — private SSH keys included — is reachable by anything that runs inside the container, including a compromised extension or a malicious `postCreateCommand`/dependency script. Accepted anyway for this repo's actual contributor base (Team Internet engineers, on the org's own machines, already running Claude Code and authenticating over SSH day to day) rather than reworked into something more portable — revisit if the contributor base broadens beyond that assumption, or if a specific incident traces back to one of these mounts.
This list used to mount the **whole host home** at `/WSL_USER`, through the `${localEnv:HOME}${localEnv:USERPROFILE}` concatenation, in order to reach one file inside it. RSRMID-3052 replaced it with the single-file bind above, fleet-wide: reaching one history file is not worth putting every container within read-write reach of the host's SSH keys, credentials and every other repository on the machine. In the workspace repository the same pattern had made the readonly flag on its GitHub tokens decorative — the same inodes were writable by the second path. A single-file bind needs the host file to exist before the bind, so `initializeCommand` touches it; otherwise Docker creates a directory in its place, on the host, where it outlives the container.

Both risks a reviewer would flag are still real, not overlooked: a contributor without `~/.ssh` or `~/.claude` on the host gets a container that fails to start rather than a graceful skip (Docker bind mounts require the source to exist), and each of these paths — private SSH keys included — is reachable by anything that runs inside the container, including a compromised extension or a malicious `postCreateCommand`/dependency script. That second risk is now scoped to four named paths rather than the entire home directory, which is the part RSRMID-3052 actually changed. The rest is accepted for this repo's actual contributor base (Team Internet engineers, on the org's own machines, already running Claude Code and authenticating over SSH day to day) rather than reworked into something more portable — revisit if the contributor base broadens beyond that assumption, or if a specific incident traces back to one of these mounts.

**The `devbase` migration here is incomplete.** The commit titled `build(devcontainer): migrate onto the shared devbase Feature` added `.dockerignore` and `.devcontainer/env-info.conf` and nothing else — `features` still lists only `github-cli` and `claude-code`, and `devcontainer-lock.json` has no `devbase` entry. So this repository gets none of that Feature's behaviour: no history persistence (hence the bullet above feeding nothing), no team zsh prompt, no `gh` credential helper from it, and the `env-info.conf` beside the frame is read by nobody. Verified by reading the frame, the lock and that commit's diff — not inferred from the commit subject, which is what makes this worth writing down. Completing it is its own change.