Skip to content

chore: integrate Claude Code into devcontainer for autonomus development#3166

Open
shumkov wants to merge 12 commits intov3.1-devfrom
chore/devcontainer
Open

chore: integrate Claude Code into devcontainer for autonomus development#3166
shumkov wants to merge 12 commits intov3.1-devfrom
chore/devcontainer

Conversation

@shumkov
Copy link
Collaborator

@shumkov shumkov commented Mar 1, 2026

Issue being fixed or feature implemented

Integrate Claude Code into the Dev Container to enable autonomous AI-assisted
development with full sandbox isolation. This gives developers (and Claude itself) a
ready-to-go environment where Claude Code can build, test, and iterate on Dash Platform
code without manual setup or permission prompts.

What was done?

Claude Code integration

  • Added [ghcr.io/anthropics/devcontainer-features/claude-code](https://github.com/anth
    ropics/devcontainer-features) as a devcontainer feature
  • Host ~/.claude/ config (credentials, skills, plugins) is staged by init-host.sh
    and copied into a persistent Docker volume by post-create.sh, then the staged copy is
    cleaned up
  • bypassPermissions mode is forced in Claude settings so Claude Code runs autonomously
    without prompts
  • ANTHROPIC_API_KEY is forwarded from the host environment as a fallback auth method
  • Persistent named volume for ~/.claude/ survives container rebuilds (conversation
    history, config)

Optional network firewall (.devcontainer/init-firewall.sh)

  • iptables-based outbound firewall restricting Claude Code and other tools to
    whitelisted services only (Anthropic API, npm, crates.io, GitHub, Docker Hub, VS Code
    marketplace)
  • Disabled by default — documented how to enable for stricter sandboxing

Devcontainer modernization

  • Replaced static image reference (ghcr.io/dashpay/platform/devcontainer:0.1.0) with a
    local Dockerfile build
  • Added devcontainer features: Node.js 24, Docker-in-Docker, GitHub CLI, common-utils,
    jq-likes
  • Added VS Code extensions: Claude Code, rust-analyzer, ESLint, Prettier, GitLens,
    Docker, LLDB, TOML
  • Configured named Docker volumes for cargo cache, build target, and shell history
  • Added developer tools: ripgrep, fd-find, fzf, git-delta, vim, nano
  • Bumped wasm-bindgen-cli to 0.2.108, added wasm-pack

Git worktree support

  • init-host.sh resolves the main .git directory and mounts it into the container
  • post-create.sh creates symlinks so git operations work transparently from worktrees

Documentation

  • Added .devcontainer/README.md covering prerequisites, auth options (OAuth + API
    key), VS Code and CLI usage, firewall setup, persistent data, and troubleshooting
  • Updated root README.md with Dev Container as recommended setup method

How Has This Been Tested?

  • Built and opened the devcontainer in VS Code on macOS (arm64) from both main repo and
    a git worktree
  • Verified Claude Code launches with bypassPermissions mode and host credentials
  • Verified git operations (status, commit, push) work inside the container from a
    worktree
  • Verified Docker-in-Docker is functional for dashmate usage
  • Verified persistent volumes survive container rebuilds (cargo cache, Claude config,
    shell history)

Breaking Changes

None. The devcontainer previously pointed to a static image. This replaces it with a
local build — existing users will get the new configuration on next rebuild.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding
    section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features

    • New, parameterized dev container with expanded toolchain, wasm tooling, shell/editor defaults, persisted shell history, git worktree support, and post-create host-to-container configuration/ownership steps.
    • Optional strict outbound firewall initialization for locked-down networking.
  • Documentation

    • Added comprehensive dev container README, example env, and updated main README with setup and troubleshooting.
  • Chores

    • Removed legacy devcontainer build config and associated CI workflow.

@shumkov shumkov requested a review from QuantumExplorer as a code owner March 1, 2026 14:09
@github-actions github-actions bot added this to the v3.1.0 milestone Mar 1, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Redesigns the devcontainer to a build-based setup: expanded Dockerfile with architecture-aware tooling, new host and container init scripts for Git worktrees and Claude config, a post-create bootstrap (permissions, Claude merge), a strict outbound firewall script, plus README and env additions; CI prebuild workflow and prior build JSON removed.

Changes

Cohort / File(s) Summary
Devcontainer Dockerfile & build config
/.devcontainer/Dockerfile, /.devcontainer/devcontainer.json
Replaces image-based config with a parameterized build. Dockerfile adds many dev tools, rust/wasm tooling, architecture-aware installs (protoc, git-delta, cargo-binstall), user/permission setup, and environment variables. devcontainer.json switches to build args, extensions, features, mounts, and init/post-create commands.
Container init & runtime scripts
/.devcontainer/post-create.sh, /.devcontainer/init-firewall.sh
Adds post-create bootstrap to set up Git worktree symlink, safe Git settings, Cargo permissions, and merge staged Claude config; adds init-firewall.sh to restrict outbound access via iptables/ipset to resolved service IPs and CIDRs.
Host-side staging & init
/.devcontainer/init-host.sh, /.devcontainer/.env.example
Adds host init script that stages minimal Claude credentials/plugins/agents/skills, resolves main Git repository vs worktree, and writes a trimmed host config for mounting; provides .env.example for CLAUDE_AGENTS/CLAUDE_SKILLS.
Documentation & README
/.devcontainer/README.md, README.md
Adds a comprehensive devcontainer README and updates main README with devcontainer and manual setup instructions and version notes (Node, wasm-bindgen-cli).
Git ignore & cleanup
/.gitignore, /.devcontainer/.gitignore
Adds machine-specific ignores for staged host config and resolved git symlinks to avoid committing host artifacts.
Removed CI / prebuild artifacts
/.devcontainer/devcontainer-build.json, /.github/workflows/prebuild-devcontainers.yml
Removes prior devcontainer-build.json and the GitHub Actions workflow that prebuilt/pushed devcontainer images.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(200,200,255,0.5)
    participant Dev as "Developer Host"
    participant InitHost as "init-host.sh"
    participant Git as "Host .git / Worktree"
  end

  rect rgba(200,255,200,0.5)
    participant Build as "VS Code / devcontainer build"
    participant Container as "Dev Container"
    participant Post as "post-create.sh"
    participant Firewall as "init-firewall.sh"
  end

  rect rgba(255,200,200,0.5)
    participant Claude as "Anthropic/Claude Config"
    participant External as "External Services (GitHub, Registry, Crates, APIs)"
  end

  Dev->>InitHost: run init-host.sh (stage .claude, resolve .git)
  InitHost->>Git: read/resolve worktree or main .git
  InitHost-->>Dev: write .devcontainer/.claude-host-config & .main-git-resolved

  Dev->>Build: open devcontainer (build with Dockerfile)
  Build->>Container: create image with toolchain, mounts, env
  Container->>Post: run post-create.sh
  Post->>Claude: copy/merge staged Claude config into /home/vscode/.claude
  Post->>Git: create symlink to resolved host .git, mark workspace safe, set permissions

  Container->>Firewall: run init-firewall.sh
  Firewall->>External: resolve domains, fetch GitHub CIDRs, populate ipset
  Firewall->>External: allow only resolved IPs/CIDRs (DNS, SSH, required registries)
  Container-->>Dev: ready message (requires ANTHROPIC_API_KEY on host)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through Docker, with tools in my sack,

Git links and Claude crumbs tucked neatly in stack.
I fenced outbound paths with a careful paw,
Set Rust and WASM so builds never stall.
Come code in my burrow — light, safe, and ready to hack.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title addresses the main objective (integrating Claude Code into devcontainer) but contains a typo ('autonomus' instead of 'autonomous') that slightly diminishes clarity.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/devcontainer
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the tone of the review comments and chat replies.

Configure the tone_instructions setting to customize the tone of the review comments and chat replies. For example, you can set the tone to Act like a strict teacher, Act like a pirate and more.

@shumkov shumkov self-assigned this Mar 1, 2026
@shumkov shumkov moved this to In review / testing in Platform team Mar 1, 2026
@shumkov shumkov changed the title chore: integrate Claude Code into devcontainer chore: integrate Claude Code into devcontainer for autonomus development Mar 1, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
.devcontainer/init-firewall.sh (2)

92-92: Consider using conntrack instead of deprecated state module.

The --state option is deprecated in favor of -m conntrack --ctstate. This is a minor compatibility note for newer iptables versions.

Suggested update
-iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
+iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/init-firewall.sh at line 92, Replace the deprecated state
match in the iptables rule: update the rule that currently uses "-m state
--state ESTABLISHED,RELATED -j ACCEPT" (found in the init-firewall script) to
use the conntrack match instead by switching to "-m conntrack --ctstate
ESTABLISHED,RELATED -j ACCEPT" so it uses the modern conntrack module while
preserving the same behavior.

62-68: Consider validating GitHub CIDR ranges before adding to iptables.

The GitHub meta API response is used directly in iptables rules without validation. A compromised or malformed response could inject unexpected rules. While the 2>/dev/null || true on line 112 suppresses errors, explicit validation would be safer.

Suggested validation
 # GitHub (dynamic IP ranges - added as CIDR rules below since ipset doesn't support /16 etc.)
-GITHUB_IPS=$(curl -s https://api.github.com/meta 2>/dev/null | jq -r '.web[], .api[], .git[], .actions[]' 2>/dev/null || true)
+GITHUB_IPS=$(curl -sf --max-time 10 https://api.github.com/meta 2>/dev/null | jq -r '.web[], .api[], .git[], .actions[]' 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$' || true)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/init-firewall.sh around lines 62 - 68, The script currently
assigns GITHUB_IPS from the GitHub meta API and feeds those values into firewall
rules; instead validate each entry before applying rules: after GITHUB_IPS is
populated, iterate its items and verify each is a valid IPv4/IPv6 address or
CIDR (use a strict regex or syscalls like ipcalc/ip in the shell) and reject
anything that doesn't match, log rejected/malformed entries, and only pass
sanitized CIDR strings to resolve_and_allow or the iptables commands; implement
this validation where GITHUB_IPS is set and before any use of
resolve_and_allow/iptables so resolve_and_allow and iptables only ever receive
trusted, validated CIDR/IP strings.
.devcontainer/post-create.sh (1)

44-49: Redundant dotfile copy loop — same issue as in init-host.sh.

The cp -a "$HOST_CONFIG"/. "$CLAUDE_DIR"/ on line 44 already copies hidden files. The subsequent loop duplicates this.

Suggested simplification
 if [ -d "$HOST_CONFIG" ] && [ "$(ls -A "$HOST_CONFIG" 2>/dev/null)" ]; then
     echo "Copying Claude config from host..."
     cp -a "$HOST_CONFIG"/. "$CLAUDE_DIR"/ 2>/dev/null || true
-    for item in "$HOST_CONFIG"/.*; do
-        basename="$(basename "$item")"
-        [ "$basename" = "." ] || [ "$basename" = ".." ] && continue
-        cp -a "$item" "$CLAUDE_DIR/$basename" 2>/dev/null || true
-    done
     chmod 600 "$CLAUDE_DIR/.credentials.json" 2>/dev/null || true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/post-create.sh around lines 44 - 49, The duplicate copy is
caused by the initial cp -a "$HOST_CONFIG"/. "$CLAUDE_DIR"/ which already copies
hidden files, followed by the for loop (for item in "$HOST_CONFIG"/.*) that
re-copies dotfiles; remove the entire loop (the for ... do ... done block) and
keep the single cp -a "$HOST_CONFIG"/. "$CLAUDE_DIR"/ command so hidden files
are copied once and behavior matches the init-host.sh approach.
.devcontainer/init-host.sh (1)

14-22: Redundant dotfile copy loop — cp -a dir/. already copies hidden files.

The command on line 16 (cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/) already copies all contents including dotfiles. The loop on lines 18-22 duplicates this work.

Suggested simplification
 if [ -d "$HOME/.claude" ] && [ "$(ls -A "$HOME/.claude" 2>/dev/null)" ]; then
     mkdir -p "$CLAUDE_STAGING"
     cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/ 2>/dev/null || true
-    # Also copy dotfiles
-    for item in "$HOME/.claude"/.*; do
-        base="$(basename "$item")"
-        [ "$base" = "." ] || [ "$base" = ".." ] && continue
-        cp -a "$item" "$CLAUDE_STAGING/$base" 2>/dev/null || true
-    done
     # Also copy ~/.claude.json (onboarding state, outside ~/.claude/)
     [ -f "$HOME/.claude.json" ] && cp -a "$HOME/.claude.json" "$CLAUDE_STAGING/.claude.json.root" 2>/dev/null || true
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/init-host.sh around lines 14 - 22, The dotfile copy loop is
redundant because the existing cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/
already copies hidden files; remove the entire for-loop that iterates over
"$HOME/.claude"/.* (the block that computes base and cp -a "$item"
"$CLAUDE_STAGING/$base") and keep the mkdir -p "$CLAUDE_STAGING" and the single
cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/ 2>/dev/null || true call so
CLAUDE_STAGING population and existing error suppression remain intact.
.devcontainer/Dockerfile (2)

1-2: Consider pinning the base image version for reproducibility.

The :ubuntu tag will pull whatever the latest Ubuntu-based devcontainer image is at build time. For more reproducible builds, consider pinning to a specific version (e.g., :ubuntu-24.04 or using a digest).

That said, for a devcontainer where staying current may be desirable, this is an acceptable trade-off.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/Dockerfile around lines 1 - 2, The Dockerfile uses an unpinned
base image "FROM mcr.microsoft.com/devcontainers/base:ubuntu" which makes builds
non-reproducible; update that FROM line to reference a specific, pinned tag or
digest (for example "mcr.microsoft.com/devcontainers/base:ubuntu-24.04" or a
sha256 digest) so builds are deterministic, and document the chosen pin in the
Dockerfile comment; locate the FROM instruction in the Dockerfile and replace
the ":ubuntu" tag with the chosen version or digest.

64-71: TOML parsing is fragile and may break with different formatting.

The current parsing assumes channel = "version" with spaces around =. TOML allows channel="version" (no spaces), which would cause awk '{print $3}' to fail.

Consider a more robust extraction:

♻️ More robust TOML parsing
 COPY --chown=vscode:vscode rust-toolchain.toml /tmp/rust-toolchain.toml
-RUN TOOLCHAIN_VERSION="$(grep channel /tmp/rust-toolchain.toml | awk '{print $3}' | tr -d '"')" && \
+RUN TOOLCHAIN_VERSION="$(grep -oP 'channel\s*=\s*"\K[^"]+' /tmp/rust-toolchain.toml)" && \
     curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \

Alternatively, using sed:

TOOLCHAIN_VERSION="$(sed -n 's/^channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' /tmp/rust-toolchain.toml)"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/Dockerfile around lines 64 - 71, The current RUN that sets
TOOLCHAIN_VERSION from rust-toolchain.toml using grep/awk is fragile (it depends
on spacing); change the extraction used in the RUN command to a robust
regex-based parser (e.g., sed) that captures the value of channel regardless of
spaces or quoting, so TOOLCHAIN_VERSION is correctly set from
rust-toolchain.toml before invoking the rustup installer; keep the rest of the
RUN flow (invoking sh.rustup.rs with --default-toolchain and --target
wasm32-unknown-unknown and removing /tmp/rust-toolchain.toml) unchanged, and
reference the variables and files exactly as used: TOOLCHAIN_VERSION,
rust-toolchain.toml, and the existing RUN invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.devcontainer/.claude-config-resolved:
- Line 1: Remove the machine-specific file .devcontainer/.claude-config-resolved
from the repository and stop tracking it: add an appropriate ignore pattern
(e.g., .devcontainer/.claude-config-resolved or a broader
.devcontainer/*.resolved) to .gitignore, remove the tracked file from git (git
rm --cached or git rm as appropriate), and commit the change so the personal
path (/Users/ivanshumkov/.claude) is not stored in the repo; ensure the
.devcontainer/.claude-config-resolved entry is present in .gitignore to prevent
future commits of that generated, user-specific file.

In @.devcontainer/README.md:
- Around line 108-114: The README statement saying the host's `~/.claude/`
directory is "mounted read-only" is inaccurate because the scripts init-host.sh
and post-create.sh actually copy the config into a staging area and then into a
persistent Docker volume; update the text in .devcontainer/README.md to say the
config is copied into a persistent Docker volume (not mounted), and keep/clarify
the notes that post-create.sh forces bypassPermissions and skips the safety
confirmation prompt and that host-specific path references are copied as-is and
may log harmless warnings in-container.

---

Nitpick comments:
In @.devcontainer/Dockerfile:
- Around line 1-2: The Dockerfile uses an unpinned base image "FROM
mcr.microsoft.com/devcontainers/base:ubuntu" which makes builds
non-reproducible; update that FROM line to reference a specific, pinned tag or
digest (for example "mcr.microsoft.com/devcontainers/base:ubuntu-24.04" or a
sha256 digest) so builds are deterministic, and document the chosen pin in the
Dockerfile comment; locate the FROM instruction in the Dockerfile and replace
the ":ubuntu" tag with the chosen version or digest.
- Around line 64-71: The current RUN that sets TOOLCHAIN_VERSION from
rust-toolchain.toml using grep/awk is fragile (it depends on spacing); change
the extraction used in the RUN command to a robust regex-based parser (e.g.,
sed) that captures the value of channel regardless of spaces or quoting, so
TOOLCHAIN_VERSION is correctly set from rust-toolchain.toml before invoking the
rustup installer; keep the rest of the RUN flow (invoking sh.rustup.rs with
--default-toolchain and --target wasm32-unknown-unknown and removing
/tmp/rust-toolchain.toml) unchanged, and reference the variables and files
exactly as used: TOOLCHAIN_VERSION, rust-toolchain.toml, and the existing RUN
invocation.

In @.devcontainer/init-firewall.sh:
- Line 92: Replace the deprecated state match in the iptables rule: update the
rule that currently uses "-m state --state ESTABLISHED,RELATED -j ACCEPT" (found
in the init-firewall script) to use the conntrack match instead by switching to
"-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT" so it uses the modern
conntrack module while preserving the same behavior.
- Around line 62-68: The script currently assigns GITHUB_IPS from the GitHub
meta API and feeds those values into firewall rules; instead validate each entry
before applying rules: after GITHUB_IPS is populated, iterate its items and
verify each is a valid IPv4/IPv6 address or CIDR (use a strict regex or syscalls
like ipcalc/ip in the shell) and reject anything that doesn't match, log
rejected/malformed entries, and only pass sanitized CIDR strings to
resolve_and_allow or the iptables commands; implement this validation where
GITHUB_IPS is set and before any use of resolve_and_allow/iptables so
resolve_and_allow and iptables only ever receive trusted, validated CIDR/IP
strings.

In @.devcontainer/init-host.sh:
- Around line 14-22: The dotfile copy loop is redundant because the existing cp
-a "$HOME/.claude"/. "$CLAUDE_STAGING"/ already copies hidden files; remove the
entire for-loop that iterates over "$HOME/.claude"/.* (the block that computes
base and cp -a "$item" "$CLAUDE_STAGING/$base") and keep the mkdir -p
"$CLAUDE_STAGING" and the single cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/
2>/dev/null || true call so CLAUDE_STAGING population and existing error
suppression remain intact.

In @.devcontainer/post-create.sh:
- Around line 44-49: The duplicate copy is caused by the initial cp -a
"$HOST_CONFIG"/. "$CLAUDE_DIR"/ which already copies hidden files, followed by
the for loop (for item in "$HOST_CONFIG"/.*) that re-copies dotfiles; remove the
entire loop (the for ... do ... done block) and keep the single cp -a
"$HOST_CONFIG"/. "$CLAUDE_DIR"/ command so hidden files are copied once and
behavior matches the init-host.sh approach.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78184f6 and 82fff6a.

📒 Files selected for processing (9)
  • .devcontainer/.claude-config-resolved
  • .devcontainer/Dockerfile
  • .devcontainer/README.md
  • .devcontainer/devcontainer.json
  • .devcontainer/init-firewall.sh
  • .devcontainer/init-host.sh
  • .devcontainer/post-create.sh
  • .gitignore
  • README.md

@@ -0,0 +1 @@
/Users/ivanshumkov/.claude No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This file should not be committed — it contains a machine-specific path and exposes a developer's username.

This file contains a hardcoded personal path (/Users/ivanshumkov/.claude) which:

  1. Exposes personal information (username)
  2. Is machine-specific and won't work for other developers
  3. Appears to be generated during the init process

This file should be removed from the commit and added to .gitignore:

Proposed .gitignore addition
 # Devcontainer host-resolved symlink (machine-specific)
 .devcontainer/.main-git-resolved
 .devcontainer/.claude-host-config
+.devcontainer/.claude-config-resolved

Then remove this file from the repository:

git rm .devcontainer/.claude-config-resolved
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/.claude-config-resolved at line 1, Remove the machine-specific
file .devcontainer/.claude-config-resolved from the repository and stop tracking
it: add an appropriate ignore pattern (e.g.,
.devcontainer/.claude-config-resolved or a broader .devcontainer/*.resolved) to
.gitignore, remove the tracked file from git (git rm --cached or git rm as
appropriate), and commit the change so the personal path
(/Users/ivanshumkov/.claude) is not stored in the repo; ensure the
.devcontainer/.claude-config-resolved entry is present in .gitignore to prevent
future commits of that generated, user-specific file.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.devcontainer/devcontainer.json (1)

74-77: Consider pinning the Git feature version for reproducible builds (Line 75).

Using "latest" for the version can cause non-deterministic environment drift across rebuilds. While supported by the feature (default is "os-provided"), pinning to a specific version ensures consistent environments and allows intentional updates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/devcontainer.json around lines 74 - 77, The devcontainer
feature entry "ghcr.io/devcontainers/features/git:1" currently sets "version":
"latest", which can cause non-deterministic builds; update the "version"
property for that feature to a fixed, explicit tag (e.g., a specific semver or
release tag) instead of "latest" — change the value of the "version" key under
"ghcr.io/devcontainers/features/git:1" to a pinned version string so rebuilds
are reproducible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.devcontainer/devcontainer.json:
- Around line 74-77: The devcontainer feature entry
"ghcr.io/devcontainers/features/git:1" currently sets "version": "latest", which
can cause non-deterministic builds; update the "version" property for that
feature to a fixed, explicit tag (e.g., a specific semver or release tag)
instead of "latest" — change the value of the "version" key under
"ghcr.io/devcontainers/features/git:1" to a pinned version string so rebuilds
are reproducible.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82fff6a and 044c89d.

📒 Files selected for processing (3)
  • .devcontainer/devcontainer-build.json
  • .devcontainer/devcontainer.json
  • .github/workflows/prebuild-devcontainers.yml
💤 Files with no reviewable changes (2)
  • .devcontainer/devcontainer-build.json
  • .github/workflows/prebuild-devcontainers.yml

@lklimek lklimek self-requested a review March 2, 2026 12:14
shumkov and others added 4 commits March 4, 2026 22:06
…iner

Copy enabledPlugins from host settings.json automatically (just IDs, no
secrets). Add .env/.env.example config for users to list specific agents
and skills to copy from ~/.claude/. Harden init-host.sh to only stage
credentials and explicitly listed extras instead of copying the entire
~/.claude/ directory. Remove unused bash history volume.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…h option

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add CLAUDE_CODE_OAUTH_TOKEN to containerEnv so it's automatically
forwarded from the host environment, same as ANTHROPIC_API_KEY.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@shumkov shumkov had a problem deploying to test-suite-approval March 6, 2026 04:53 — with GitHub Actions Error
@shumkov shumkov had a problem deploying to test-suite-approval March 6, 2026 04:53 — with GitHub Actions Error
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
.devcontainer/init-host.sh (1)

13-22: ⚠️ Potential issue | 🟠 Major

Move Claude auth staging out of the workspace tree.

.devcontainer/devcontainer.json Lines 3-10 set build.context to .., so this staging directory sits inside the Docker build context. Docker uses .dockerignore—not .gitignore—to exclude paths from that context, and the legacy builder sends the full context to the daemon. Keeping host auth files under .devcontainer/ is still the wrong secret boundary even if the Dockerfile never copies them. Please stage these files outside the workspace and mount that host-only path explicitly instead. (containers.dev)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/init-host.sh around lines 13 - 22, The staging directory for
Claude auth is currently set to
CLAUDE_STAGING=".devcontainer/.claude-host-config" which places secrets inside
the workspace/Docker build context; change CLAUDE_STAGING to a host-only path
outside the repo (for example under "$HOME/.claude-host-config" or another
directory outside the repository root), update the mkdir/cp logic to create and
copy credentials into that new path (references: CLAUDE_STAGING variable, the cp
lines that copy "$HOME/.claude/.credentials.json" and "$HOME/.claude.json"), and
ensure whatever devcontainer/docker config mounts that host-only path into the
container instead of relying on files under .devcontainer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.devcontainer/init-host.sh:
- Around line 61-78: The script can leave a stale directory at RESOLVED so
subsequent ln -sfn calls create a symlink inside that directory instead of
replacing it; update the branches that call ln -sfn (the ones using MAIN_GIT and
"$(pwd)/.git") to remove any existing $RESOLVED first (e.g. test for existence
and rm -rf "$RESOLVED") before creating the symlink, and preserve the mkdir -p
"$RESOLVED" fallback branch for the no-git case so the mount still has a
directory when needed.

---

Duplicate comments:
In @.devcontainer/init-host.sh:
- Around line 13-22: The staging directory for Claude auth is currently set to
CLAUDE_STAGING=".devcontainer/.claude-host-config" which places secrets inside
the workspace/Docker build context; change CLAUDE_STAGING to a host-only path
outside the repo (for example under "$HOME/.claude-host-config" or another
directory outside the repository root), update the mkdir/cp logic to create and
copy credentials into that new path (references: CLAUDE_STAGING variable, the cp
lines that copy "$HOME/.claude/.credentials.json" and "$HOME/.claude.json"), and
ensure whatever devcontainer/docker config mounts that host-only path into the
container instead of relying on files under .devcontainer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60159fb0-0b2b-478d-bbe6-de1856d474a4

📥 Commits

Reviewing files that changed from the base of the PR and between 044c89d and b5c8168.

📒 Files selected for processing (8)
  • .devcontainer/.env.example
  • .devcontainer/.gitignore
  • .devcontainer/README.md
  • .devcontainer/devcontainer.json
  • .devcontainer/init-host.sh
  • .devcontainer/post-create.sh
  • .gitignore
  • README.md
✅ Files skipped from review due to trivial changes (1)
  • .devcontainer/.env.example
🚧 Files skipped from review as they are similar to previous changes (4)
  • .devcontainer/post-create.sh
  • README.md
  • .devcontainer/README.md
  • .gitignore

cp -a "$HOME/.claude.json" "$CLAUDE_STAGING/.claude.json.root" 2>/dev/null || true

# Plugins: always copy (just IDs, no secrets)
if [ -f "$HOME/.claude/settings.json" ]; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure you want plugin-dev plugin inside the devcontainer. It's polluting context. I'd prefer using dedicated $PROJECT/.claude/settings.json instead.

It's minor issue. You can ignore.

done
fi

if [ -n "$CLAUDE_SKILLS" ]; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above.

It's minor issue.

…dd GH_TOKEN

- Remove .claude-config-resolved from git (contained personal path) and
  add it to .gitignore to prevent future commits
- Fix ln -sfn symlink bug: rm -rf $RESOLVED before ln -s to avoid
  creating symlink inside stale directory on subsequent runs
- Pass GH_TOKEN/GITHUB_TOKEN from host env so gh CLI works in container

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
.devcontainer/init-host.sh (2)

25-28: jq dependency on host machine may not be satisfied.

The script uses jq to extract enabledPlugins from settings.json, but jq may not be installed on all host machines. The 2>/dev/null || true fallback handles failures gracefully, but the feature silently fails without feedback.

Consider either:

  1. Adding a check/warning when jq is unavailable
  2. Documenting jq as a host prerequisite in the README
Optional: Add availability check
     # Plugins: always copy (just IDs, no secrets)
     if [ -f "$HOME/.claude/settings.json" ]; then
+        if command -v jq >/dev/null 2>&1; then
             jq '{enabledPlugins: .enabledPlugins}' "$HOME/.claude/settings.json" \
                 > "$CLAUDE_STAGING/enabled-plugins.json" 2>/dev/null || true
+        else
+            echo "Note: 'jq' not found on host; skipping plugin sync"
+        fi
     fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/init-host.sh around lines 25 - 28, The script uses jq to
extract enabledPlugins but silently ignores failures; add an availability check
and warning before using jq: test for jq (e.g., "command -v jq" or "which jq")
and if missing emit a clear stderr warning (using echo or logger) informing the
user that jq is required to populate $CLAUDE_STAGING/enabled-plugins.json and
that the feature will be skipped, then proceed without running jq; update the
.devcontainer/init-host.sh logic around the jq invocation (referencing
"$HOME/.claude/settings.json", jq, and "$CLAUDE_STAGING/enabled-plugins.json")
to perform the check and warning, or alternatively add a README prerequisite
entry documenting jq as required for host setup.

33-34: Add ShellCheck directive for the dynamic source.

ShellCheck reports SC1090 because it cannot statically analyze the sourced file. While the code is correct (the path is known at runtime), adding a directive improves maintainability and silences the warning in CI.

Proposed fix
     ENV_FILE=".devcontainer/.env"
+    # shellcheck source=/dev/null
     [ -f "$ENV_FILE" ] && source "$ENV_FILE"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/init-host.sh around lines 33 - 34, Add a ShellCheck directive
to silence SC1090 for the dynamic source: before the line that defines ENV_FILE
or immediately above the source command (the line that reads [ -f "$ENV_FILE" ]
&& source "$ENV_FILE"), add a comment like "# shellcheck
source=.devcontainer/.env" so ShellCheck knows the intended file; keep the
ENV_FILE variable and the conditional source logic unchanged.
.devcontainer/devcontainer.json (1)

82-82: Community feature with version 0 may be unstable.

The Starship feature ghcr.io/schlich/devcontainer-features/starship:0 uses major version 0, which typically indicates pre-1.0 software that may introduce breaking changes. This is a community-maintained feature rather than an official devcontainer feature.

Consider pinning to a specific minor version if stability is important, or document that this is optional/cosmetic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/devcontainer.json at line 82, The devcontainer is referencing
a community Starship feature pinned to major version 0
("ghcr.io/schlich/devcontainer-features/starship:0"), which may be unstable;
update the devcontainer.json entry to pin a specific stable minor/patch tag
(e.g., replace ":0" with a specific semver like ":0.7.5" or the latest non-0.x
tag if available) or add a comment/documentation line next to the
"ghcr.io/schlich/devcontainer-features/starship:0" entry to mark it as
optional/cosmetic so consumers know it's not guaranteed stable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.devcontainer/devcontainer.json:
- Line 82: The devcontainer is referencing a community Starship feature pinned
to major version 0 ("ghcr.io/schlich/devcontainer-features/starship:0"), which
may be unstable; update the devcontainer.json entry to pin a specific stable
minor/patch tag (e.g., replace ":0" with a specific semver like ":0.7.5" or the
latest non-0.x tag if available) or add a comment/documentation line next to the
"ghcr.io/schlich/devcontainer-features/starship:0" entry to mark it as
optional/cosmetic so consumers know it's not guaranteed stable.

In @.devcontainer/init-host.sh:
- Around line 25-28: The script uses jq to extract enabledPlugins but silently
ignores failures; add an availability check and warning before using jq: test
for jq (e.g., "command -v jq" or "which jq") and if missing emit a clear stderr
warning (using echo or logger) informing the user that jq is required to
populate $CLAUDE_STAGING/enabled-plugins.json and that the feature will be
skipped, then proceed without running jq; update the .devcontainer/init-host.sh
logic around the jq invocation (referencing "$HOME/.claude/settings.json", jq,
and "$CLAUDE_STAGING/enabled-plugins.json") to perform the check and warning, or
alternatively add a README prerequisite entry documenting jq as required for
host setup.
- Around line 33-34: Add a ShellCheck directive to silence SC1090 for the
dynamic source: before the line that defines ENV_FILE or immediately above the
source command (the line that reads [ -f "$ENV_FILE" ] && source "$ENV_FILE"),
add a comment like "# shellcheck source=.devcontainer/.env" so ShellCheck knows
the intended file; keep the ENV_FILE variable and the conditional source logic
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f313d63-1b60-4b9c-b10c-403269a3305d

📥 Commits

Reviewing files that changed from the base of the PR and between b5c8168 and 79d8e13.

📒 Files selected for processing (4)
  • .devcontainer/.gitignore
  • .devcontainer/devcontainer.json
  • .devcontainer/init-host.sh
  • .gitignore

~/.claude.json contains sensitive data: email, account UUID, session IDs,
project paths with token usage stats, and feature flag assignments. It
should not be copied into the devcontainer where Claude runs in bypass mode.

Authentication already works via .credentials.json alone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@shumkov shumkov had a problem deploying to test-suite-approval March 12, 2026 05:05 — with GitHub Actions Error
@shumkov shumkov had a problem deploying to test-suite-approval March 12, 2026 05:05 — with GitHub Actions Error
…lugin sync

- Remove automatic syncing of all host enabledPlugins — plugins should be
  defined per-project in the repo's .claude/settings.json
- Keep opt-in CLAUDE_AGENTS and CLAUDE_SKILLS in .env for users who want
  to bring specific personal agents/skills into the container
- Credentials (.credentials.json) are still always copied from the host

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.devcontainer/init-host.sh:
- Around line 29-48: Don't source ENV_FILE on the host; instead treat
CLAUDE_AGENTS and CLAUDE_SKILLS as input strings only and validate each name
before using it—remove the "source $ENV_FILE" behavior and ensure parsing uses
CLAUDE_AGENTS/CLAUDE_SKILLS as already-exported env vars; for each item in
AGENT_LIST/SKILL_LIST trim whitespace, reject any name containing "/" or ".."
(and ideally allow only a safe charset like [A-Za-z0-9._-]), and derive the file
path using a safe basename-like step when constructing src (the variables
AGENT_LIST, SKILL_LIST, src, and the cp usage) so path-traversal is impossible;
if a name fails validation, skip it and log or ignore rather than copying.

In @.devcontainer/post-create.sh:
- Around line 56-72: The script currently overwrites SETTINGS_FILE
("$CLAUDE_DIR/settings.json") with a minimal SETTINGS payload, losing any
existing user settings; change it to read the existing settings.json if present
and use jq to merge/override only the required keys
("permissions.defaultMode":"bypassPermissions" and
"skipDangerousModePermissionPrompt":true) into the existing JSON (or into an
empty object when missing), then write the merged result back to SETTINGS_FILE
(replacing the current cat > usage). Keep the subsequent merge of
HOST_CONFIG/enabled-plugins.json as-is but operate on the merged SETTINGS_FILE
so enabled-plugins and all other preexisting settings are preserved; continue to
use TMP and atomic mv for safe writes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6bb34e39-9545-40ae-84a4-e25dda7814f9

📥 Commits

Reviewing files that changed from the base of the PR and between 79d8e13 and abaf2fe.

📒 Files selected for processing (2)
  • .devcontainer/init-host.sh
  • .devcontainer/post-create.sh

Comment on lines +56 to +72
# Write a clean settings.json with bypassPermissions (no host settings leak)
SETTINGS_FILE="$CLAUDE_DIR/settings.json"
cat > "$SETTINGS_FILE" <<'SETTINGS'
{
"permissions": {
"defaultMode": "bypassPermissions"
},
"skipDangerousModePermissionPrompt": true
}
SETTINGS

# Merge host's enabledPlugins into settings (plugin IDs only, no secrets)
if [ -f "$HOST_CONFIG/enabled-plugins.json" ]; then
TMP=$(mktemp)
jq -s '.[0] * .[1]' "$SETTINGS_FILE" "$HOST_CONFIG/enabled-plugins.json" \
> "$TMP" 2>/dev/null && mv "$TMP" "$SETTINGS_FILE" || true
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve existing Claude settings when forcing bypassPermissions.

Lines 58-65 recreate settings.json from scratch, and Lines 68-72 only merge enabledPlugins back. Any Claude settings already stored in the persistent volume are lost the next time this script runs. Merge the required permission flags into the existing file instead of replacing it.

♻️ Proposed fix
 # Write a clean settings.json with bypassPermissions (no host settings leak)
 SETTINGS_FILE="$CLAUDE_DIR/settings.json"
-cat > "$SETTINGS_FILE" <<'SETTINGS'
-{
-  "permissions": {
-    "defaultMode": "bypassPermissions"
-  },
-  "skipDangerousModePermissionPrompt": true
-}
-SETTINGS
+BASE_SETTINGS=$(mktemp)
+if [ -f "$SETTINGS_FILE" ]; then
+    cp -a "$SETTINGS_FILE" "$BASE_SETTINGS"
+else
+    printf '{}\n' > "$BASE_SETTINGS"
+fi
+
+TMP=$(mktemp)
+jq '.permissions.defaultMode = "bypassPermissions"
+    | .skipDangerousModePermissionPrompt = true' \
+    "$BASE_SETTINGS" > "$TMP" && mv "$TMP" "$SETTINGS_FILE"
+rm -f "$BASE_SETTINGS"
 
 # Merge host's enabledPlugins into settings (plugin IDs only, no secrets)
 if [ -f "$HOST_CONFIG/enabled-plugins.json" ]; then
     TMP=$(mktemp)
     jq -s '.[0] * .[1]' "$SETTINGS_FILE" "$HOST_CONFIG/enabled-plugins.json" \
         > "$TMP" 2>/dev/null && mv "$TMP" "$SETTINGS_FILE" || true
 fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Write a clean settings.json with bypassPermissions (no host settings leak)
SETTINGS_FILE="$CLAUDE_DIR/settings.json"
cat > "$SETTINGS_FILE" <<'SETTINGS'
{
"permissions": {
"defaultMode": "bypassPermissions"
},
"skipDangerousModePermissionPrompt": true
}
SETTINGS
# Merge host's enabledPlugins into settings (plugin IDs only, no secrets)
if [ -f "$HOST_CONFIG/enabled-plugins.json" ]; then
TMP=$(mktemp)
jq -s '.[0] * .[1]' "$SETTINGS_FILE" "$HOST_CONFIG/enabled-plugins.json" \
> "$TMP" 2>/dev/null && mv "$TMP" "$SETTINGS_FILE" || true
fi
# Write a clean settings.json with bypassPermissions (no host settings leak)
SETTINGS_FILE="$CLAUDE_DIR/settings.json"
BASE_SETTINGS=$(mktemp)
if [ -f "$SETTINGS_FILE" ]; then
cp -a "$SETTINGS_FILE" "$BASE_SETTINGS"
else
printf '{}\n' > "$BASE_SETTINGS"
fi
TMP=$(mktemp)
jq '.permissions.defaultMode = "bypassPermissions"
| .skipDangerousModePermissionPrompt = true' \
"$BASE_SETTINGS" > "$TMP" && mv "$TMP" "$SETTINGS_FILE"
rm -f "$BASE_SETTINGS"
# Merge host's enabledPlugins into settings (plugin IDs only, no secrets)
if [ -f "$HOST_CONFIG/enabled-plugins.json" ]; then
TMP=$(mktemp)
jq -s '.[0] * .[1]' "$SETTINGS_FILE" "$HOST_CONFIG/enabled-plugins.json" \
> "$TMP" 2>/dev/null && mv "$TMP" "$SETTINGS_FILE" || true
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.devcontainer/post-create.sh around lines 56 - 72, The script currently
overwrites SETTINGS_FILE ("$CLAUDE_DIR/settings.json") with a minimal SETTINGS
payload, losing any existing user settings; change it to read the existing
settings.json if present and use jq to merge/override only the required keys
("permissions.defaultMode":"bypassPermissions" and
"skipDangerousModePermissionPrompt":true) into the existing JSON (or into an
empty object when missing), then write the merged result back to SETTINGS_FILE
(replacing the current cat > usage). Keep the subsequent merge of
HOST_CONFIG/enabled-plugins.json as-is but operate on the merged SETTINGS_FILE
so enabled-plugins and all other preexisting settings are preserved; continue to
use TMP and atomic mv for safe writes.

…nfig model

- Plugins are no longer auto-synced from host; document settings.local.json
  approach instead (gitignored by Claude Code, no unknown field risk)
- Agents/skills remain opt-in via CLAUDE_AGENTS/CLAUDE_SKILLS in .env
- Update security model section and .env.example accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RUN if [[ "$TARGETARCH" == "arm64" ]] ; then export PROTOC_ARCH=aarch_64; else export PROTOC_ARCH=x86_64; fi; \
curl -Ls https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip \
RUN if [ "$TARGETARCH" = "arm64" ]; then export PROTOC_ARCH=aarch_64; else export PROTOC_ARCH=x86_64; fi; \
curl -Ls "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip" \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just call scripts/setup-ai-agent-environment.sh ?

GITHUB_IPS=$(curl -s https://api.github.com/meta 2>/dev/null | jq -r '.web[], .api[], .git[], .actions[]' 2>/dev/null || true)
resolve_and_allow "github.com"
resolve_and_allow "api.github.com"
resolve_and_allow "raw.githubusercontent.com"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectively, it's wildcard for all the open source code in the world ;-).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For DET, I have (but I'm not saying it will work):

Image

iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT

# Allow SSH
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why ssh? for github? Will it work if the devcontainer doesn't have ssh key?

For GH_TOKEN / GITHUB_TOKEN to work, I had to use https, like:

        run: git config --global url."https://github.com/".insteadOf "git@github.com:"

There is also gh auth setup-git

Not sure you need these, just mentioning.

fi

# Write a clean settings.json with bypassPermissions
cat > "$CLAUDE_DIR/settings.json" <<'SETTINGS'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, there is settings.local.json or sth like that.

- **Agents/skills** are only copied if explicitly listed in `.devcontainer/.env` — nothing personal leaks in by default.
- **A clean `settings.json`** is generated inside the container with `bypassPermissions` — your host's permission allowlists, MCP server configs, and hooks are not copied.
- **No shell history** is persisted or shared with the container.
- **The `.git` directory** is mounted read-write (required for commits/pushes). This is the main trust boundary — Claude can push code.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@lklimek lklimek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — 6 MEDIUM findings

3 specialist agents (security, project consistency, shell/Docker quality) reviewed 12 files, 699 additions, 169 deletions. After deduplication: 0 CRITICAL, 0 HIGH, 6 MEDIUM, 30 LOW, 5 INFO findings.

Overall this is well-architected — the container-as-sandbox model for bypassPermissions is sound, credential handling is careful, and the security model is transparently documented. The 5 inline comments below cover the actionable MEDIUM findings tied to specific code locations.

PROJ-002 (MEDIUM): PR description inaccuracy

The PR description states:

Host ~/.claude/ config (credentials, skills, plugins) is staged by init-host.sh

But init-host.sh explicitly does not copy plugins. The README correctly states "Plugins are not copied from your host." The PR description should say "credentials, and optionally agents/skills" to match the actual implementation.

What's done well

  • No secrets in Docker layers — all sensitive data at runtime only
  • Transparent security model — README identifies .git as the trust boundary
  • Version consistency — wasm-bindgen-cli 0.2.108 matches across Dockerfile, README, and Cargo.lock
  • Git worktree support — elegant two-script host/container path resolution
  • Opt-in personal config — agents/skills require explicit .env listing

Also noted (typo)

PR title: "autonomus" → "autonomous"

Full report (41 findings): available on request.

🤖 Co-authored by Claudius the Magnificent AI Agent

CLAUDE_AGENTS=""
CLAUDE_SKILLS=""
ENV_FILE=".devcontainer/.env"
[ -f "$ENV_FILE" ] && source "$ENV_FILE"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEC-001 (MEDIUM): source executes arbitrary shell code, not just variable assignments. This script runs on the host machine (via initializeCommand) before the container exists — the container sandbox provides zero protection here.

If .devcontainer/.env is malformed or tampered with (compromised workspace, social engineering), this enables full code execution with the developer's host privileges. Three review agents flagged this independently.

Suggestion: Replace with a safe key-value parser:

if [ -f "$ENV_FILE" ]; then
    while IFS='=' read -r key value; do
        case "$key" in
            CLAUDE_AGENTS) CLAUDE_AGENTS="$value" ;;
            CLAUDE_SKILLS) CLAUDE_SKILLS="$value" ;;
        esac
    done < <(grep -E '^(CLAUDE_AGENTS|CLAUDE_SKILLS)=' "$ENV_FILE")
fi

This only extracts the two expected variables and ignores everything else.

🤖 Claudius the Magnificent AI Code Review

Comment on lines +12 to +18
CLAUDE_STAGING=".devcontainer/.claude-host-config"
rm -rf "$CLAUDE_STAGING"
if [ -d "$HOME/.claude" ]; then
mkdir -p "$CLAUDE_STAGING"
# Credentials (OAuth tokens) — required for authentication
[ -f "$HOME/.claude/.credentials.json" ] && \
cp -a "$HOME/.claude/.credentials.json" "$CLAUDE_STAGING/.credentials.json" 2>/dev/null || true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEC-002 (MEDIUM): OAuth credentials staged here persist in the workspace if container creation fails before post-create.sh runs cleanup (line 80). Neither script has an EXIT/ERR trap for cleanup.

The .gitignore prevents accidental commits, but stale credentials in the workspace can be picked up by file search tools, backups, or other processes.

Suggestion: Add a trap at the top of this script and also in post-create.sh:

# init-host.sh — after CLAUDE_STAGING is set:
trap 'rm -rf "$CLAUDE_STAGING"' ERR

# post-create.sh — near the top, after HOST_CONFIG is set:
trap 'rm -rf "$HOST_CONFIG"' EXIT

Also restrict permissions immediately after copy:

cp -a "$HOME/.claude/.credentials.json" "$CLAUDE_STAGING/.credentials.json" 2>/dev/null || true
chmod 600 "$CLAUDE_STAGING/.credentials.json" 2>/dev/null || true

🤖 Claudius the Magnificent AI Code Review

Comment on lines +172 to +178
```jsonc
"runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"],
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
"waitFor": "postStartCommand"
```

You'll also need to add `iptables ipset iproute2 dnsutils` to the `apt-get install` in the Dockerfile and uncomment the firewall COPY/sudoers block. See `init-firewall.sh` for the domain whitelist.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PROJ-001 (MEDIUM): These instructions reference "uncomment the firewall COPY/sudoers block" but no such block exists in the current Dockerfile. A developer following this will hit a dead end.

Suggestion: Either:

  1. Add the commented-out COPY and sudoers lines to the Dockerfile so developers can uncomment them, or
  2. Rewrite the instructions to be fully self-contained — explain exactly which lines to add and where:
You'll also need to:
1. Add `iptables ipset iproute2 dnsutils` to the `apt-get install` in the Dockerfile
2. Add `COPY init-firewall.sh /usr/local/bin/init-firewall.sh` to the Dockerfile
3. Add a sudoers entry: `RUN echo "vscode ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/firewall`

🤖 Claudius the Magnificent AI Code Review

Comment on lines +56 to +59
"source": "${localWorkspaceFolder}/.devcontainer/.main-git-resolved",
"target": "/workspace/.host-main-git",
"type": "bind"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PROJ-007 (MEDIUM): This bind mount requires .main-git-resolved to exist on the host, which is created by init-host.sh (the initializeCommand). If a developer uses a tool that doesn't support initializeCommand (raw docker build, some CI systems, GitHub Codespaces), this mount fails with an opaque Docker error like invalid mount config.

Suggestion:

  1. Document in the README that initializeCommand support is required (VS Code Dev Containers and the devcontainer CLI both support it; Codespaces may not).
  2. Consider adding a manual fallback instruction for non-standard clients:
    # If your tool doesn't support initializeCommand, run manually first:
    bash .devcontainer/init-host.sh
    

🤖 Claudius the Magnificent AI Code Review

Comment on lines +53 to +56
# Persist bash/zsh history
RUN mkdir -p /commandhistory && \
touch /commandhistory/.bash_history /commandhistory/.zsh_history && \
chown -R vscode:vscode /commandhistory
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PROJ-008 (MEDIUM): This creates /commandhistory with bash/zsh history files, but devcontainer.json has no corresponding volume mount for this directory. Without a persistent volume, shell history is lost on every container rebuild — making these lines dead code.

The README also states "No shell history is persisted or shared with the container" (Security Model section), which contradicts this Dockerfile setup.

Suggestion: Either:

  1. Add a history volume in devcontainer.json and configure HISTFILE:

    {
      "source": "devcontainer-platform-shell-history-${devcontainerId}",
      "target": "/commandhistory",
      "type": "volume"
    }

    Then set ENV HISTFILE=/commandhistory/.bash_history in the Dockerfile and update the README accordingly.

  2. Or remove these 4 lines if history persistence is intentionally not desired, keeping the README accurate.

🤖 Claudius the Magnificent AI Code Review

- Replace `source .env` with safe key-value parser (SEC-001: prevents
  arbitrary shell code execution from a tampered .env file)
- Add `gh auth setup-git` in post-create when GH_TOKEN/GITHUB_TOKEN is
  set, enabling HTTPS git push/pull without SSH keys
- Update README: document GH_TOKEN as the preferred GitHub auth method,
  fix firewall setup instructions (provide explicit Dockerfile changes
  instead of referencing a nonexistent commented-out block)
- Remove unused /commandhistory setup from Dockerfile (no volume mount
  backs it; README correctly states history is not persisted)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@shumkov shumkov requested a deployment to test-suite-approval March 13, 2026 12:42 — with GitHub Actions Waiting
@shumkov shumkov requested a deployment to test-suite-approval March 13, 2026 12:42 — with GitHub Actions Waiting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review / testing

Development

Successfully merging this pull request may close these issues.

2 participants