GitNexus/.github/workflows/ci-devcontainer.yml
Gergo Magyar bfdd183c95 fix(devcontainer): resolve adversarial review findings (pins, RO mounts, tests)
Resolves the blocking + actionable findings from the PR #1875 review:

- Pin base image by digest as bare name@digest [#1]. The :tag@digest form
  trips the @devcontainers/cli image-name parser (which builds this image
  in CI and in VS Code "Reopen in Container"); bare name@digest is the
  parser-compatible form. Verified by a full local build.
- Pin Cursor by version + per-arch sha256 and fetch the artifact directly
  instead of executing cursor.com/install; fail-closed on mismatch [#2].
- Mount ~/.config/gh and ~/.docker read-only so a compromised dep can't
  rewrite the host GitHub token / Docker credHelper [#4].
- Pin @devcontainers/cli@0.87.0 in the CI smoke [#5].
- chown via find -xdev in install-deps.sh (symlink-safe; matches
  post-create.sh) [#6].
- Add filesystem-I/O tests (translate/readHostConfig/seed main/ensurePaths)
  and refactor ensure-host-config-dirs to be unit-testable [#7].
- Stop pre-creating settings.json/config.toml on the host; only the real
  single-file bind source (.claude.json) is touched [#10].
- Add a prominent top-of-README security callout for the RW write-through
  trade-off and reframe the deferred egress firewall as the key missing
  compensating control [#3, #9].

Full devcontainer build verified locally (digest pull + pinned Cursor
download/extract/symlink). 24/24 config-transform tests pass.
2026-05-29 07:03:15 +01:00

73 lines
3.1 KiB
YAML

name: Devcontainer Smoke
# Smoke-tests the .devcontainer/ on changes to it: unit-tests the pure
# host->container config transforms (plugin-registry path translation +
# the $HOME/.claude.json machine-field strip) and builds the devcontainer
# image via the canonical @devcontainers/cli path (which reads build.args
# from devcontainer.json, enforcing the "single source of truth" pin).
on:
push:
branches: [main]
paths:
- '.devcontainer/**'
- '.github/workflows/ci-devcontainer.yml'
pull_request:
paths:
- '.devcontainer/**'
- '.github/workflows/ci-devcontainer.yml'
permissions:
contents: read
# Concurrency convention: see CONTRIBUTING.md → "GitHub Actions — Concurrency Convention".
# Branch/tag scope; cancel superseded PR runs, never cancel push-to-main runs.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
config-transforms:
name: Config-transform unit tests
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
# persist-credentials: false — read-only job (tests + syntax checks),
# never pushes; keeps GITHUB_TOKEN out of .git/config (zizmor artipacked).
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
- name: Unit-test the host->container config transforms
run: node --test .devcontainer/translate-plugin-registries.test.cjs
- name: Syntax-check the lifecycle shell scripts
run: |
bash -n .devcontainer/install-deps.sh
bash -n .devcontainer/post-create.sh
build:
name: Build devcontainer image
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
# persist-credentials: false — read-only build smoke, never pushes;
# keeps GITHUB_TOKEN out of .git/config (zizmor artipacked).
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
# Builds the image exactly as a developer's "Reopen in Container" would:
# @devcontainers/cli parses devcontainer.json (jsonc), resolves build.args
# (the CLAUDE_CODE_VERSION / CODEX_VERSION pins), and runs the Dockerfile.
# This is the smoke that catches Dockerfile regressions + drift from the
# canonical version pins. Lifecycle hooks (post-create.sh) are not run
# here — they need the host config mounts, which CI has none of.
# Version-pinned: bare `npx --yes @devcontainers/cli` resolves @latest at
# run time, so a breaking or malicious publish could change CI behavior
# (or how devcontainer.json is interpreted) with no diff. Bump
# deliberately alongside the Dockerfile/devcontainer.json pins.
- name: Build devcontainer via @devcontainers/cli
run: npx --yes @devcontainers/cli@0.87.0 build --workspace-folder .