Skip to content

Mount .devcontainer/ read-only to prevent container escape on rebuild#12

Closed
RyanJarv wants to merge 1 commit intotrailofbits:mainfrom
RyanJarv:main
Closed

Mount .devcontainer/ read-only to prevent container escape on rebuild#12
RyanJarv wants to merge 1 commit intotrailofbits:mainfrom
RyanJarv:main

Conversation

@RyanJarv
Copy link
Copy Markdown

@RyanJarv RyanJarv commented Feb 9, 2026

A process inside the container could modify .devcontainer/devcontainer.json to inject malicious mounts or initializeCommand entries that execute on the host during the next rebuild. Bind-mounting .devcontainer/ as read-only blocks this privilege escalation vector.

A process inside the container could modify .devcontainer/devcontainer.json
to inject malicious mounts or initializeCommand entries that execute on the
host during the next rebuild. Bind-mounting .devcontainer/ as read-only
blocks this privilege escalation vector.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Feb 9, 2026

CLA assistant check
All committers have signed the CLA.

@RyanJarv
Copy link
Copy Markdown
Author

RyanJarv commented Feb 9, 2026

@dguido fyi

Copy link
Copy Markdown
Member

@dguido dguido left a comment

Choose a reason for hiding this comment

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

Nice find — the threat model is solid and the fix is well-scoped. Three small suggestions:

SYS_ADMIN guard (suggestion 3): The read-only bind mount only holds if the container can't remount it read-write, which requires SYS_ADMIN. The current runArgs only grant NET_ADMIN/NET_RAW so this is fine today, but there's no guard against someone adding SYS_ADMIN later and silently breaking the protection. Consider adding a comment above extract_mounts_to_file() (lines 83-84) like:

# Security: .devcontainer/ is mounted read-only inside the container to prevent
# a compromised process from injecting malicious mounts or commands into
# devcontainer.json that execute on the host during rebuild. This protection
# requires that SYS_ADMIN is never added to runArgs (it would allow remounting
# read-write).

(Can't put it in devcontainer.json directly since install.sh processes it with jq, which doesn't handle JSONC comments.)

Comment thread install.sh
(startswith("source=claude-code-gh-") | not) and
(startswith("source=${localEnv:HOME}/.gitconfig,") | not)
(startswith("source=${localEnv:HOME}/.gitconfig,") | not) and
(contains("target=/workspace/.devcontainer,") | not)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggestions 1+2: contains() is slightly loose — it would also match a mount targeting e.g. /workspace/.devcontainer-backup. Since the source is a known literal in the JSON (${localWorkspaceFolder} isn't resolved until the devcontainer CLI processes it), startswith() on the source is more precise and consistent with the other filters. Added an inline comment explaining the security rationale.

Suggested change
(contains("target=/workspace/.devcontainer,") | not)
# Security: read-only .devcontainer mount prevents container escape on rebuild
(startswith("source=${localWorkspaceFolder}/.devcontainer,") | not)

dguido added a commit that referenced this pull request Feb 10, 2026
A process inside the container could modify .devcontainer/devcontainer.json
to inject malicious mounts or initializeCommand entries that execute on the
host during the next rebuild. Bind-mounting .devcontainer/ as read-only
blocks this privilege escalation vector.

Uses startswith() for the jq filter to be precise and consistent with
other mount filters, and documents the SYS_ADMIN guard requirement.

Based on PR #12 with review feedback from @dguido addressed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dguido added a commit that referenced this pull request Feb 10, 2026
…#13)

A process inside the container could modify .devcontainer/devcontainer.json
to inject malicious mounts or initializeCommand entries that execute on the
host during the next rebuild. Bind-mounting .devcontainer/ as read-only
blocks this privilege escalation vector.

Uses startswith() for the jq filter to be precise and consistent with
other mount filters, and documents the SYS_ADMIN guard requirement.

Based on PR #12 with review feedback from @dguido addressed.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@dguido
Copy link
Copy Markdown
Member

dguido commented Feb 10, 2026

Superseded by #13, which incorporates the changes from this PR with @dguido's review feedback applied (startswith() filter, inline security comment, SYS_ADMIN guard documentation).

Thanks again for finding this escape vector and putting together a clean fix — great catch.

@dguido dguido closed this Feb 10, 2026
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.

3 participants