Skip to content

sec (6/9): run user-defined scanners in an isolated working directory#30

Merged
jesse-merhi merged 3 commits into
mainfrom
sec/06-isolated-cwd
Jul 26, 2026
Merged

sec (6/9): run user-defined scanners in an isolated working directory#30
jesse-merhi merged 3 commits into
mainfrom
sec/06-isolated-cwd

Conversation

@jesse-merhi

@jesse-merhi jesse-merhi commented Jul 23, 2026

Copy link
Copy Markdown
Member

What changes

A user-defined scanner no longer runs with its working directory set to the
target's folder. On a host run it now runs in a fresh isolated temp directory
(clawscan-scanner-*, removed after the run); under Docker the workdir stays
empty so nothing target-adjacent is mounted.

Why it matters

Running with cwd = the target's directory hands the scanner writable access
to every file sitting next to the target. A malicious or buggy scanner could
modify or drop files beside the skill being scanned (and under Docker that
directory would be a writable bind mount). Giving the scanner a private throwaway
cwd removes that ambient write access; the target is still passed explicitly as
an argument.

Before / after (runnable)

Scanner that records its cwd and tries to write a neighbor file:

command: "echo {{target}} >/dev/null; pwd 1>&2; touch NEIGHBOR_LEAK; echo '{}'; exit 9"
Behavior
Before cwd = target dir; NEIGHBOR_LEAK lands next to the skill
After cwd = isolated temp dir; nothing left beside the target
$ clawscan ./skill --config cwd.yml --profile demo --sandbox off --json
# scanner error stderr shows its cwd:
exit status 9: /private/var/folders/.../T/clawscan-scanner-2472157201
# and next to the target:
marker next to target? no — ran in isolated temp dir, cleaned up

Verify

go test ./internal/runner/ -run 'IsolatedCwd' -count=1

TestUserDefinedScannerUsesIsolatedCwd asserts the host run's cwd is a fresh
temp dir (not the target dir) and that Docker runs use an empty workdir.

Copilot AI review requested due to automatic review settings July 23, 2026 12:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@jesse-merhi
jesse-merhi force-pushed the sec/05-windows-target-guard branch from 82956cd to 5ecd550 Compare July 26, 2026 02:35
@jesse-merhi
jesse-merhi force-pushed the sec/06-isolated-cwd branch from 494f412 to 1774bc0 Compare July 26, 2026 02:36
@jesse-merhi
jesse-merhi changed the base branch from sec/05-windows-target-guard to main July 26, 2026 02:36
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P1 Urgent regression or broken agent/channel workflow affecting real users now. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Jul 26, 2026
@clawsweeper

clawsweeper Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed July 26, 2026, 12:35 PM ET / 16:35 UTC.

ClawSweeper review

What this changes

This PR runs profile-defined scanners from a disposable host directory and stops deriving Docker’s working directory from the scan target, while preserving a read-only Docker mount for the target argument.

Merge readiness

⚠️ Ready for maintainer review - 3 items remain

Keep this PR open for maintainer review. The latest head appears to resolve the earlier Docker-read regression: it leaves Docker’s working directory unset while the shared runner mounts the absolute target read-only, and the new focused test checks that contract. The author is a repository member, so this cleanup workflow should not auto-close it.

Priority: P1
Reviewed head: 93450d72cc7a07022836ccd2087fca94b7870bce
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) The implementation is focused, the prior Docker-read concern is addressed by the latest mount-specific test, and the remaining step is maintainer confirmation of the intended sandbox interface.
Proof confidence 🐚 platinum hermit (4/6) Not applicable: This is a MEMBER-authored PR, so the external-contributor proof gate does not apply; the author nevertheless supplied a concrete reported Alpine CLI validation and focused regression tests.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Not applicable Not applicable: This is a MEMBER-authored PR, so the external-contributor proof gate does not apply; the author nevertheless supplied a concrete reported Alpine CLI validation and focused regression tests.
Evidence reviewed 4 items Focused implementation: At the PR head, the user-defined scanner creates a disposable clawscan-scanner-* directory for host execution and passes an empty working directory for Docker execution, removing the prior target-derived working-directory helper.
Docker target-access regression coverage: The latest commit adds focused coverage that inspects the Docker command arguments for a read-only mount of the target and no Docker -w argument, addressing the earlier review concern that Docker scanners must still read the target.
Shared-runner explanation: The author’s follow-up identifies the execution chain from the user-defined scanner through dockerCommandRunner.Run and dockerMounts, and reports an Alpine CLI run that read SKILL.md, could not write inside the target, emitted JSON, and exited successfully.
Findings None None.
Security None None.

How this fits together

Profile-defined scanners are configured as shell commands and executed by ClawScan’s user-defined scanner adapter. That adapter sends a target to the shared host-or-Docker command runner, whose output becomes raw scanner evidence in the run artifact.

flowchart LR
  A[Profile scanner command] --> B[User-defined scanner adapter]
  C[Skill or URL target] --> B
  B --> D[Shared command runner]
  D --> E[Host temporary workspace]
  D --> F[Docker read-only target mount]
  E --> G[Raw scanner JSON]
  F --> G
  G --> H[Run artifact]
Loading

Decision needed

Question Recommendation
Does the maintained Docker runner contract intentionally support user-defined scanners reading the target only through its read-only bind mount while receiving no target-derived working directory? Accept the read-only mount contract: Merge the focused isolation change after confirming the shared runner’s target mount is the supported Docker scanner interface.

Why: The source and new test indicate that this is the intended safe boundary, but accepting the sandbox contract for all supported target forms is a maintainer-owned compatibility decision.

Before merge

  • Resolve merge risk (P1) - This changes the default Docker sandbox execution contract, so maintainers should confirm the focused Docker mount test covers the supported target forms before merge.
  • Complete next step (P2) - A repository-member PR with a security-boundary and Docker compatibility contract needs explicit maintainer acceptance, not automated cleanup or repair.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Focused surface 2 files affected; 112 additions, 15 deletions The patch is limited to scanner workspace behavior and its regression coverage.
Regression coverage 2 workspace/mount tests added The tests cover host workspace cleanup and the Docker no-workdir/read-only-target contract.

Root-cause cluster

Relationship: canonical
Canonical: #30
Summary: This PR is the focused workspace-isolation hardening follow-up for the merged user-defined scanner feature.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge-risk options

Maintainer options:

  1. Confirm the shared mount contract (recommended)
    Review the focused Docker test and shared runner behavior to confirm target reads remain supported without a target-derived workdir.
  2. Pause for sandbox-interface direction
    Hold the PR if maintainers do not want profile-defined scanner commands to rely on the runner’s read-only target mount as their Docker interface.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Verify the Docker target mount remains read-only and container-visible for all supported target forms, then refresh the focused regression test if any form is missing.

Technical review

Best possible solution:

Retain the isolated host workspace and empty Docker working directory, with the target supplied only through the shared read-only Docker mount; merge after a maintainer confirms the focused coverage reflects the supported target forms.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: a profile-defined scanner can attempt to read the passed target and write beside it under both host and Docker execution. The focused tests and the contributor’s described Alpine CLI run cover the expected read-success/write-denial outcome, although this read-only review environment could not execute the commands independently.

Is this the best way to solve the issue?

Yes, with maintainer confirmation of the shared Docker mount contract. Avoiding a target-derived working directory removes ambient write access while retaining explicit read access through the runner’s read-only mount.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 316cd87b69d1.

Labels

Label changes:

  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Not applicable: This is a MEMBER-authored PR, so the external-contributor proof gate does not apply; the author nevertheless supplied a concrete reported Alpine CLI validation and focused regression tests.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.

Label justifications:

  • P1: This security hardening changes the normal Docker execution path for user-defined scanners and must preserve real scans.
  • merge-risk: 🚨 compatibility: Existing Docker scanner commands depend on the target remaining readable at a container-visible path.
  • merge-risk: 🚨 security-boundary: The patch changes filesystem isolation for arbitrary user-defined scanner commands.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Not applicable: This is a MEMBER-authored PR, so the external-contributor proof gate does not apply; the author nevertheless supplied a concrete reported Alpine CLI validation and focused regression tests.

Evidence

What I checked:

  • Focused implementation: At the PR head, the user-defined scanner creates a disposable clawscan-scanner-* directory for host execution and passes an empty working directory for Docker execution, removing the prior target-derived working-directory helper. (internal/runner/user_defined_scanner.go:87, 93450d72cc7a)
  • Docker target-access regression coverage: The latest commit adds focused coverage that inspects the Docker command arguments for a read-only mount of the target and no Docker -w argument, addressing the earlier review concern that Docker scanners must still read the target. (internal/runner/runner_test.go:944, 93450d72cc7a)
  • Shared-runner explanation: The author’s follow-up identifies the execution chain from the user-defined scanner through dockerCommandRunner.Run and dockerMounts, and reports an Alpine CLI run that read SKILL.md, could not write inside the target, emitted JSON, and exited successfully. (internal/runner/user_defined_scanner.go:107, 93450d72cc7a)
  • Feature provenance: The merged BYOS feature was introduced by the related pull request, and this PR is a focused hardening follow-up rather than a parallel scanner implementation. (internal/runner/user_defined_scanner.go:87, 656e558b4ba5)

Likely related people:

  • jesse-merhi: The provided history identifies this person as the author of merged BYOS support and all three commits in this isolated-working-directory follow-up. (role: introduced the user-defined scanner feature and recent security hardening contributor; confidence: high; commits: 656e558b4ba5, 23fac1b4b95f, 5f06f1e51f39; files: internal/runner/user_defined_scanner.go, internal/runner/runner_test.go)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Confirm that the shared read-only Docker mount is the supported interface for each target form user-defined scanners may receive.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (3 earlier review cycles)
  • reviewed 2026-07-26T02:39:36.292Z sha 1774bc0 :: found issues before merge. :: [P1] Keep the scan target mounted for Docker scanners
  • reviewed 2026-07-26T07:04:01.731Z sha 5f06f1e :: found issues before merge. :: [P1] Mount the scan target read-only for Docker scanners
  • reviewed 2026-07-26T09:29:03.729Z sha 93450d7 :: found issues before merge. :: [P1] Mount the scan target read-only for Docker scanners

The scanner used to run with a working directory derived from the target:
the target's parent directory, or the target itself when it was a directory.
That handed the scanner writable access to files sitting next to the target,
and under Docker that parent became a writable bind mount. Run host scanners
in a fresh os.MkdirTemp directory (removed after the run) and keep the Docker
cwd empty so nothing target-derived is mounted; the container workdir is
already isolated. Removes the now-unused userDefinedScannerCWD helper.
@jesse-merhi
jesse-merhi force-pushed the sec/06-isolated-cwd branch from 1774bc0 to 5f06f1e Compare July 26, 2026 07:00
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. label Jul 26, 2026
@jesse-merhi

Copy link
Copy Markdown
Member Author

The current P1 appears to overlook the shared Docker runner behavior. Passing an empty cwd removes only the writable working-directory mount; dockerCommandRunner.Run still sends the absolute target argument through dockerMounts, producing a read-only bind mount at the identical container path.

I also verified the complete behavior with a real Alpine container through the ClawScan CLI: the user-defined scanner successfully read SKILL.md, was denied a write inside the target, returned valid JSON, and completed with exit code 0. The focused unit test additionally verifies that the generated Docker arguments contain the read-only target mount and no -w argument. Please trace user_defined_scanner.go -> dockerCommandRunner.Run -> dockerMounts when reassessing the finding.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 26, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 26, 2026
@jesse-merhi
jesse-merhi merged commit 2d4298a into main Jul 26, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants