Skip to content

feat: Claude --chrome support, build stage refactor, bridge tmpdir fix#227

Merged
lroolle merged 2 commits intomainfrom
docs/custom-images
Mar 25, 2026
Merged

feat: Claude --chrome support, build stage refactor, bridge tmpdir fix#227
lroolle merged 2 commits intomainfrom
docs/custom-images

Conversation

@lroolle
Copy link
Member

@lroolle lroolle commented Mar 25, 2026

Summary

  • Add Claude --chrome integration: mount host Chrome profile Extensions and bridge socket into container
  • Refactor Dockerfile into agent-base + final stages; extract shared install-agent-tooling.sh
  • Fix bridge dir resolution to match Node.js os.tmpdir() order (macOS /var/folders/ vs /tmp)
  • Remove dead entrypoint fallback env vars (DEVA_CHROME_HOST_SOCKET_PATH, DEVA_CHROME_HOST_TMPDIR)
  • Add build-core / build-rust-image Makefile targets
  • CI smoke tests for chrome mount assembly and entrypoint symlink
  • Update docs: custom-images guide, troubleshooting for --chrome

Copilot AI review requested due to automatic review settings March 25, 2026 06:33
lroolle added 2 commits March 24, 2026 23:34
- normalize embedded tags from env and config for image resolution
- split persistent containers by resolved image and preserve installer fallback tags
- Extract agent-base stage for stable core image layer
- Add shared install-agent-tooling.sh for main and rust images
- Mount Chrome profile Extensions/ dirs for extension detection
- Setup MCP browser bridge symlink in container entrypoint
- Add CI smoke tests for Chrome mount and bridge setup
- Update custom-images and troubleshooting docs
@lroolle lroolle force-pushed the docs/custom-images branch from cf16070 to 4047ddd Compare March 25, 2026 06:34
@lroolle lroolle merged commit 502e1a8 into main Mar 25, 2026
3 of 5 checks passed
@lroolle lroolle deleted the docs/custom-images branch March 25, 2026 06:34
@claude-yolo
Copy link
Contributor

claude-yolo bot commented Mar 25, 2026

Claude finished @lroolle's task in 2m 46s —— View job


Security Audit of PR #227 - CRITICAL VULNERABILITIES FOUND

Status: ⛔ DO NOT DEPLOY TO PRODUCTION - Multiple container escape vectors identified

🚨 BLOCKING SECURITY ISSUES

1. Directory Traversal via Chrome Profile Name (deva.sh:449-450, 469)

profile_name="$(basename "$profile_path")"  # Insufficient validation
profile_target="/home/deva/.config/google-chrome/$profile_name/Extensions"
  • Attack: DEVA_CHROME_PROFILE_PATH='/host/path/to/../../etc'profile_name='etc' → mounts /host/path/to/Extensions as /home/deva/.config/google-chrome/etc/Extensions
  • Impact: Arbitrary host filesystem access within container

2. Host User Injection (deva.sh:552-555)

host_user="$(configured_env_value DEVA_CHROME_HOST_USER || true)"
# Used in: bridge_dir="$host_tmpdir/claude-mcp-browser-bridge-$host_user"
  • Attack: DEVA_CHROME_HOST_USER='../../../var/log/audit' manipulates bridge path
  • Impact: Bridge socket created in sensitive host directories

3. Bridge Directory Race Condition (docker-entrypoint.sh:279-282)

rm -f "$container_socket_path" 2>/dev/null || true
ln -snf "$host_bridge_dir" "$container_socket_path"
  • Attack: Attacker creates malicious symlink between rm and ln operations
  • Impact: Symlink attack, potential host file overwrite

4. Supply Chain Attack Vector (install-agent-tooling.sh:25-26)

curl -fsSL "https://raw.githubusercontent.com/lroolle/atlas-cli/${ATLAS_CLI_VERSION}/install.sh" | bash
  • Issue: No signature/checksum validation of external script
  • Attack: Compromise of lroolle/atlas-cli repository
  • Impact: Code execution during container build

🔥 CONTAINER ESCAPE PATH

Critical combination enabling full host compromise:

  1. Passwordless sudo (Dockerfile:182-184): deva user has NOPASSWD: ALL
  2. Arbitrary host mounts (deva.sh:469): Chrome integration bypasses mount restrictions
  3. Path injection (deva.sh:521-523): DEVA_HOST_CHROME_BRIDGE_DIR can target any host path

Attack scenario:

export DEVA_HOST_CHROME_BRIDGE_DIR='/etc'
export DEVA_CHROME_PROFILE_PATH='/host/secrets'
deva.sh claude -- --chrome
# Inside container: sudo mount --bind /deva-host-chrome-bridge/shadow /etc/shadow

Result: Full host privilege escalation

🛡️ IMMEDIATE FIXES REQUIRED

1. Add strict path validation:

validate_chrome_profile_name() {
    case "$1" in 
        Default|Profile\ [0-9]*) return 0 ;;
        *) echo "Invalid profile name: $1" >&2; return 1 ;;
    esac
}

2. Atomic symlink creation:

temp_link="$container_socket_path.tmp.$$"
ln -snf "$host_bridge_dir" "$temp_link"
mv "$temp_link" "$container_socket_path"

3. Supply chain integrity:

expected_sha256="abc123..."
curl -fsSL "https://raw.githubusercontent.com/lroolle/atlas-cli/${ATLAS_CLI_VERSION}/install.sh" | \
    tee >(sha256sum -c <(echo "$expected_sha256  -")) | bash

4. Remove passwordless sudo or isolate Chrome functionality in separate unprivileged container

5. Implement mount allowlist validation:

validate_mount_path() {
    local normalized="$1"
    case "$normalized" in
        /home/*/.*|/tmp/*|/var/tmp/*) return 0 ;;
        *) echo "Mount path not allowed: $normalized" >&2; return 1 ;;
    esac
}

🧪 MISSING SECURITY TESTS

Critical test gaps:

  • ❌ Path traversal in Chrome profile names (../../../etc)
  • ❌ Shell injection in environment variables ($(rm -rf /))
  • ❌ Bridge symlink race condition testing
  • ❌ Mount boundary validation (ensure /etc cannot be mounted)
  • ❌ User privilege escalation scenarios
  • ❌ Supply chain integrity verification

🔍 ADDITIONAL FINDINGS

6. Unsafe basename usage (deva.sh:500): profile_name="$(basename "$candidate")" without validation
7. Missing input sanitization throughout Chrome integration functions
8. Bridge permissions (docker-entrypoint.sh:282): chown -h may fail silently
9. Error handling: Many operations use || true masking security failures

⚠️ DEPLOYMENT RECOMMENDATION

DO NOT MERGE until all blocking issues are resolved. This PR introduces multiple high-severity container escape vectors that compromise the core security model.

Priority fixes:

  1. Remove passwordless sudo (CRITICAL)
  2. Add Chrome path validation (CRITICAL)
  3. Fix bridge race condition (HIGH)
  4. Add supply chain validation (HIGH)
  5. Implement comprehensive security tests (HIGH)

@lroolle lroolle mentioned this pull request Mar 25, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Claude --chrome host integration and refactors the Docker build into reusable stages so downstream images can avoid rebuilding volatile agent layers, while also tightening Docker image ref/tag handling in the launcher and installer.

Changes:

  • Add Claude --chrome support by mounting host Chrome Extensions/ trees plus a host “bridge” dir and creating the expected tmpdir symlink inside the container.
  • Refactor Dockerfiles into a stable agent-base stage + final stage, extracting shared npm/atlas installs into scripts/install-agent-tooling.sh.
  • Improve Docker image ref handling (tag/digest, embedded tags) and add build targets + CI smoke tests for the new behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
scripts/install-agent-tooling.sh New shared script to install agent CLIs + atlas-cli in a single place for multiple images.
install.sh Refactors installer image reference resolution (tag/digest handling, fallback behavior).
docs/troubleshooting.md Adds troubleshooting guidance for Claude --chrome integration and bridge behavior.
docs/custom-images.md Updates recommended build workflow to use new Makefile targets and staged builds.
docker-entrypoint.sh Adds tmpdir detection + bridge symlink creation in container startup for --chrome.
deva.sh Adds chrome mount/bridge assembly, image ref normalization, and container shape hashing including image.
Makefile Adds build-core/build-rust-image targets and updates default tool version fallbacks.
Dockerfile Splits into agent-base and final, moving volatile agent installs into the final stage.
Dockerfile.rust Uses shared tooling install script and new build-arg defaults/labels for agent tools.
.github/workflows/ci.yml Adds CI smoke tests and Makefile-based build steps for core/rust + chrome integration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mkdir -p "$container_tmpdir"

local container_socket_path="$container_tmpdir/claude-mcp-browser-bridge-$DEVA_USER"
rm -f "$container_socket_path" 2>/dev/null || true
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

rm -f "$container_socket_path" will not remove an existing directory. If Claude (or a previous run) created $container_tmpdir/claude-mcp-browser-bridge-$DEVA_USER as a directory, the subsequent ln -snf will create the symlink inside that directory instead of replacing it, and Chrome bridge discovery will fail. Consider removing/replacing the path robustly (e.g., handle the directory case explicitly, or use a removal that covers directories) before creating the symlink.

Suggested change
rm -f "$container_socket_path" 2>/dev/null || true
rm -rf "$container_socket_path" 2>/dev/null || true

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +88
.PHONY: build-core
build-core:
@echo "🔨 Building stable core image..."
docker build -f $(DOCKERFILE) --target agent-base --build-arg COPILOT_API_VERSION=$(COPILOT_API_VERSION) -t $(CORE_IMAGE) .
@echo "✅ Core build completed: $(CORE_IMAGE)"
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

Makefile passes COPILOT_API_VERSION as a build arg (including in build-core), but the Dockerfile checks out the Copilot API repo via COPILOT_API_COMMIT (which defaults to master). As a result, builds are not actually pinned to the SHA in COPILOT_API_VERSION, and the label can misrepresent what was built. Consider passing COPILOT_API_COMMIT=$(COPILOT_API_VERSION) (or renaming/aligning the args) so the checked-out commit matches the version you record.

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cf1607044f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +11 to +14
ARG CLAUDE_CODE_VERSION=2.1.81
ARG CODEX_VERSION=0.116.0
ARG GEMINI_CLI_VERSION=0.35.0
ARG ATLAS_CLI_VERSION=v0.1.4

Choose a reason for hiding this comment

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

P1 Badge Thread rust CLI version args through Dockerfile.rust builds

This stage now reinstalls Claude/Codex/Gemini from hardcoded defaults, so any build path that does not pass explicit CLAUDE_CODE_VERSION/CODEX_VERSION/GEMINI_CLI_VERSION will silently diverge from the resolved release versions. I checked .github/workflows/release.yml: the build-and-push-rust job only passes BASE_IMAGE, so rust releases can lag or downgrade tool versions relative to the base image/job metadata. Please wire the resolved version args into every rust build path (or stop reinstalling these tools in the rust stage).

Useful? React with 👍 / 👎.

Comment on lines +280 to +281
rm -f "$container_socket_path" 2>/dev/null || true
ln -snf "$host_bridge_dir" "$container_socket_path"

Choose a reason for hiding this comment

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

P2 Badge Replace existing bridge directory before creating symlink

Using rm -f here does not remove directories, so if /tmp/claude-mcp-browser-bridge-<user> already exists as a directory, ln -snf will place a link inside that directory instead of replacing the path Claude probes. In that state, --chrome still fails to find the mounted host bridge. Use a directory-safe replacement (rm -rf or ln -sfnT) so the path is guaranteed to become the symlink target.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants