mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-30 01:51:20 +00:00
Multi-agent review (9 reviewers) found the devcontainer files carried comments + README from the abandoned read-only-symlink design, plus real behavioral gaps. Resolved all actionable findings (no deferrals). Documentation drift (the headline — stale comments described a security model opposite to what shipped): - README "Trust boundary" claimed a malicious dep "cannot write back … the read-only /host mount blocks the write." FALSE — the shareable dirs are RW-bound. Rewrote to document the bidirectional write-through, what stays one-way (credentials never flow back), and how to close it. - devcontainer.json mount group-1 comment described "selectively symlinks … read-only eliminates write-through" — replaced with the RW-bind reality. - Header "Windows-native is unsupported" -> supported (auto HOME setup). - containerEnv comment "credentials persist in host-bind-mounted dirs" -> they live in the named volumes. - hooks.json exclusion documented honestly as a partial mitigation, not a clean boundary (commands/agents/skills/rules are equally executing). - ~/.local "named volume" -> image directory. Behavioral fixes: - chown -R recursed into the RW host binds (could rewrite host ownership / EPERM-abort provisioning on non-UID-aligned Linux). Switched to `find -xdev` per dir so chown stays on the volume filesystem. - Cursor installer wrapped in `timeout 300` — its inner binary download isn't covered by curl --max-time and could hang docker build forever. - Removed dead CURSOR_VERSION ARG/ENV/build-arg (never consumed; "latest" implied a pin the installer can't honor). Documented why Cursor is unpinned. Extraction + tests (the two inline post-create.sh node heredocs were unlintable and untestable; the path regex had had bugs): - seed-claude-config.cjs — installMethod-strip seed, now with a non-object guard (a bare-value/array host .claude.json could otherwise slip the try/catch and silently re-trigger onboarding) and labeled write errors. - translate-plugin-registries.cjs — plugin-registry path translation with labeled errors. - translate-plugin-registries.test.cjs — 12 tests (Windows/POSIX paths, cross-CLI isolation, nested objects, non-object/empty-config guard). - post-create.sh calls the modules via $SCRIPT_DIR. CI: - .github/workflows/ci-devcontainer.yml — runs the unit tests + shell syntax checks + a `@devcontainers/cli build` smoke on .devcontainer/** changes. Conforms to the repo concurrency convention (validator passes). Documented (real gaps, fixes are honest docs since no correct auto-fix exists): user-scope MCP servers with absolute host command paths don't resolve in-container; user-scope config is copy-on-create so host edits need a rebuild; in-container plugin installs get shadowed by an empty host bind on rebuild (recovery noted); plugin installs are single-writer across checkouts; gh/docker RW-vs-ssh/aws/azure-RO rationale. Verified: fresh `@devcontainers/cli up` succeeds; installMethod stripped, registry translated to Linux paths, credentials node:node, 12/12 tests pass.
88 lines
4.2 KiB
Docker
88 lines
4.2 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# Base image: Microsoft's TypeScript+Node devcontainer image. Multi-arch
|
|
# (linux/amd64, linux/arm64), monthly security patching, ships the non-root
|
|
# `node` user (UID 1000), zsh + Oh My Zsh, eslint global, `gh` CLI.
|
|
FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
|
|
|
|
# Build args. Version defaults are NOT set here — devcontainer.json
|
|
# `build.args` is the single source of truth. Standalone `docker build
|
|
# .devcontainer/` (e.g., CI smoke) must pass each version via --build-arg
|
|
# or the build will fail loudly rather than silently drift from the
|
|
# devcontainer-canonical pin.
|
|
ARG CLAUDE_CODE_VERSION
|
|
ARG CODEX_VERSION
|
|
ARG TZ=UTC
|
|
ARG USERNAME=node
|
|
# NOTE: there is intentionally no CURSOR_VERSION ARG. The cursor.com/install
|
|
# script does not honour a version pin, so an ARG would be dead config that
|
|
# implies a guarantee the installer can't keep. Cursor is currently installed
|
|
# unpinned (see the install step below); pinning is tracked as a follow-up in
|
|
# README § "What's not included (yet)".
|
|
|
|
# Promote build-only ARGs into runtime ENV so shells and lifecycle scripts
|
|
# can read them. CLAUDE_CONFIG_DIR is intentionally NOT set here — the
|
|
# canonical value lives in devcontainer.json `containerEnv` (single source
|
|
# of truth; runtime-time wins anyway).
|
|
ENV CLAUDE_CODE_VERSION=${CLAUDE_CODE_VERSION} \
|
|
CODEX_VERSION=${CODEX_VERSION} \
|
|
TZ=${TZ} \
|
|
DEVCONTAINER=true \
|
|
NODE_OPTIONS=--max-old-space-size=4096 \
|
|
POWERLEVEL9K_DISABLE_GITSTATUS=true
|
|
|
|
# Native build toolchain required by gitnexus/postinstall: tree-sitter
|
|
# native bindings, vendored Dart/Proto/Swift grammars, @ladybugdb/core
|
|
# N-API addon. python3/make/g++ are non-negotiable; mirrors the apt block
|
|
# in the existing Dockerfile.cli / gitnexus/Dockerfile.test images.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
python3 make g++ git curl ca-certificates bash \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Pre-create + chown the named-volume mount points (~/.npm, ~/.local,
|
|
# /commandhistory) so empty volumes inherit `node:node` ownership on first
|
|
# mount. The three CLI config dirs (~/.claude, ~/.codex, ~/.cursor) are
|
|
# bind-mounted from the host — bind mounts fully shadow image-side
|
|
# ownership, so no chown is needed for those paths here.
|
|
RUN mkdir -p \
|
|
/home/${USERNAME}/.npm \
|
|
/home/${USERNAME}/.local/bin \
|
|
/commandhistory \
|
|
&& chown -R ${USERNAME}:${USERNAME} \
|
|
/home/${USERNAME}/.npm \
|
|
/home/${USERNAME}/.local \
|
|
/commandhistory
|
|
|
|
USER ${USERNAME}
|
|
|
|
# Install Claude Code and Codex CLI globally as the `node` user. The base
|
|
# image configures /usr/local/share/npm-global as the npm-global prefix
|
|
# with the `npm` group writable by `node`, so `npm install -g` works
|
|
# without sudo. Both versions are pinned via build args — bump in
|
|
# devcontainer.json and rebuild to upgrade.
|
|
RUN npm install -g \
|
|
@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} \
|
|
@openai/codex@${CODEX_VERSION}
|
|
|
|
# Cursor CLI install. The official cursor.com/install script does not
|
|
# expose version pinning or a checksum, so we download to a temp file,
|
|
# log the sha256 to build output (so drift across rebuilds shows up in
|
|
# CI logs), then execute. Trust assumption: cursor.com's TLS chain is
|
|
# reliable. Long-term hardening (tracked as a follow-up): pin a specific
|
|
# downloads.cursor.com/lab/<version>/<arch>/agent-cli-package.tar.gz URL
|
|
# with a hard sha256 verification and skip the install script entirely.
|
|
#
|
|
# `timeout 300` wraps the installer because the script makes its OWN network
|
|
# requests (it downloads the actual Cursor binary) that `curl --max-time`
|
|
# above does NOT cover — without it a slow/hung Cursor CDN would block the
|
|
# Docker build indefinitely with no watchdog.
|
|
RUN curl -fsS --retry 3 --max-time 60 -o /tmp/cursor-install.sh https://cursor.com/install \
|
|
&& echo "Cursor installer sha256:" \
|
|
&& sha256sum /tmp/cursor-install.sh \
|
|
&& timeout 300 bash /tmp/cursor-install.sh \
|
|
&& rm -f /tmp/cursor-install.sh
|
|
|
|
# ~/.local/bin (where Cursor's installer drops `agent` and `cursor-agent`
|
|
# symlinks) on PATH for interactive shells and lifecycle scripts.
|
|
ENV PATH=/home/${USERNAME}/.local/bin:${PATH}
|