Mount CONTAINER_WORKSPACE read-only by default - #103
Open
anticomputer wants to merge 1 commit into
Open
anticomputer wants to merge 1 commit into
anticomputer wants to merge 1 commit into
Conversation
The workspace bind mount is writable, so a command run via container_shell_exec can modify the host's copy of the tree under audit. That tree is usually the source being analyzed, and callers commonly collect it afterwards as an artifact. A writable mount therefore makes the collected artifact agent-influenced rather than a faithful copy of the input, which is the wrong default for a tool whose whole purpose is examining untrusted code. The shell is also the one surface an injected prompt can reach, so the capability is worth narrowing even though nothing exercises it today. Adds CONTAINER_WORKSPACE_MODE, defaulting to "ro", with "rw" as an explicit opt-in for any taskflow that genuinely needs to write. This follows the CONTAINER_NETWORK precedent in this module exactly: secure by default, opt-in to widen, and an empty or unrecognized value falls back to the safe value so the default cannot be silently weakened by a blank variable. Only a literal "rw" (case-insensitive, whitespace-trimmed) opts in. The mode is also folded into the persistent-container key material, for the same reason the network mode already is: a run configured for "ro" must not reuse a container that was created with a writable workspace. Evidence that read-only is a safe default rather than a guess: across a full audit run of the six taskflows that use this toolbox, all 144 container_shell_exec calls were reads (tree, cat, ls, grep, rg, wc, git log, sed -n). None wrote to /workspace, and no taskflow prompt instructs a write. Tests: read-only default and rw opt-in at the docker run layer, env parsing including blank/unrecognized/injection-shaped values, and persistent-name separation between modes. Verified the new assertions fail when the mount mode is removed, so they detect the regression rather than merely passing. Note test_start_container_success changed: it asserts membership in the argument list, which is exact-element rather than substring, so it needed the new suffix.
anticomputer
requested review from
JarLob,
Kwstubbs,
kevinbackhouse,
p- and
sylwia-budzynska
as code owners
September 18, 2026 15:27
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The writable opt-in is not propagated through bundled toolboxes, and persistent-name changes can orphan existing containers.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (3)
What changed in this PR
This PR makes /workspace mounts read-only by default while allowing explicit writable mode configuration.
Changes:
- Adds workspace-mode parsing and Docker mount flags.
- Includes mount mode in persistent-container naming.
- Adds parsing, mount, and persistence tests.
| File | Description |
|---|---|
src/seclab_taskflows/mcp_servers/container_shell.py |
Implements read-only workspace mounts and mode-aware persistence. |
tests/test_container_shell.py |
Tests defaults, opt-in behavior, parsing, and name separation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+13
to
+15
| modify the host's copy of the source under audit. Set it to ``rw`` only if a | ||
| taskflow genuinely needs to write into the workspace. An empty, unset, or | ||
| unrecognized value falls back to ``ro``, so the default cannot be silently |
Comment on lines
+126
to
+129
| key_material = ( | ||
| f"{CONTAINER_IMAGE}:{CONTAINER_WORKSPACE}" | ||
| f":net={CONTAINER_NETWORK}:ws={CONTAINER_WORKSPACE_MODE}" | ||
| ) |
| finally: | ||
| _restore_env_and_reload("CONTAINER_WORKSPACE_MODE", original) | ||
|
|
||
| @pytest.mark.parametrize("value", ["", " ", "\t", "bogus", "readwrite", "ro"]) |
kevinbackhouse
approved these changes
Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
container_shellbind-mountsCONTAINER_WORKSPACEat/workspacewritable, so anything run throughcontainer_shell_execcan modify the host's copy of the tree under audit.This adds
CONTAINER_WORKSPACE_MODE, defaulting toro, withrwas an explicit opt-in.Why this is worth changing
The workspace is normally the source being analyzed, and consumers commonly collect it afterwards as an artifact. In our pipeline it lands in the audit bundle as
workspaces/data/repo_under_test/with 90-day retention, and human triagers read it when reviewing findings.A writable mount makes that artifact agent-influenced rather than a faithful copy of the input. That is the wrong default for a tool whose entire purpose is examining untrusted code, and the shell is precisely the surface an injected prompt would reach. Narrowing it costs nothing today and removes a whole class of "is this artifact actually what upstream shipped?" doubt later.
To be clear about severity: this is not a sandbox escape, and I am not reporting it as a vulnerability. It is a default that grants more than anything needs.
Evidence that
rois safe as a default, not just as an optionI checked rather than assumed. Across a full audit run of the six taskflows that use this toolbox, all 144
container_shell_execcalls were reads:Not one write: no redirect into
/workspace, notouch, noctags, no build step. I also grepped the taskflow prompts for write/build verbs; the only hits (write to,make) are prose describing the audited application's behaviour, not instructions to the agent.If a downstream taskflow does need writes,
CONTAINER_WORKSPACE_MODE=rwrestores the old behaviour exactly.Design
Follows the existing
CONTAINER_NETWORKprecedent in this module, deliberately:rw(case-insensitive, whitespace-trimmed) opts inAlso folds the mode into
_persistent_name()key material, for the same reason the network mode already is: a run configured forromust not reuse a persistent container created with a writable workspace. That bug would otherwise be invisible and intermittent.Note the existing colon guard on
CONTAINER_WORKSPACEstill applies, so the mode cannot be smuggled through the path.Tests
62 passed. New coverage:rwopt-in at thedocker runlayerbogus,readwrite,ro,rw,RW,rw, and an injection-shapedrw; rm -rf /(falls back toro)roandrwI verified the new assertions fail when the mount mode is removed (3 failed / 59 passed), so they detect the regression rather than merely passing alongside it.
One existing test changed:
test_start_container_successasserts membership in the argument list, which is exact-element rather than substring, so it needed the:rosuffix.Compatibility
Behaviour change for any consumer that writes to
/workspace. I believe that is nobody today, but if you would rather not change the default, flipping it to opt-out is a one-line edit to the constant and I am happy to do that instead.