Mount .devcontainer/ read-only to prevent container escape on rebuild (#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>
This commit is contained in:
Dan Guido
2026-02-10 03:20:33 -05:00
committed by GitHub
parent 96e3ca270f
commit 90e3280503
2 changed files with 11 additions and 2 deletions
+2 -1
View File
@@ -46,7 +46,8 @@
"source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
"source=claude-code-config-${devcontainerId},target=/home/vscode/.claude,type=volume",
"source=claude-code-gh-${devcontainerId},target=/home/vscode/.config/gh,type=volume",
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly"
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly",
"source=${localWorkspaceFolder}/.devcontainer,target=/workspace/.devcontainer,type=bind,readonly"
],
"containerEnv": {
"NODE_OPTIONS": "--max-old-space-size=4096",
+9 -1
View File
@@ -82,6 +82,12 @@ get_workspace_folder() {
# Extract custom mounts from devcontainer.json to a temp file
# Returns the temp file path, or empty string if no custom mounts
#
# 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).
extract_mounts_to_file() {
local devcontainer_json="$1"
local temp_file
@@ -98,7 +104,9 @@ extract_mounts_to_file() {
(startswith("source=claude-code-bashhistory-") | not) and
(startswith("source=claude-code-config-") | not) and
(startswith("source=claude-code-gh-") | not) and
(startswith("source=${localEnv:HOME}/.gitconfig,") | not)
(startswith("source=${localEnv:HOME}/.gitconfig,") | not) and
# Security: read-only .devcontainer mount prevents container escape on rebuild
(startswith("source=${localWorkspaceFolder}/.devcontainer,") | not)
)
) | if length > 0 then . else empty end
' "$devcontainer_json" 2>/dev/null) || true