Skip to content

feat: place caller-provided files into the sandbox workspace (extra_files, --workspace-file) - #1085

Merged
bearsyankees merged 6 commits into
mainfrom
feat/sandbox-extra-files
Aug 14, 2026
Merged

feat: place caller-provided files into the sandbox workspace (extra_files, --workspace-file)#1085
bearsyankees merged 6 commits into
mainfrom
feat/sandbox-extra-files

Conversation

@yoni-at-strix

@yoni-at-strix yoni-at-strix commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Until now the only way into the sandbox filesystem was a whole directory: local_sourcesLocalDir manifest entries (copy backends) or bind mounts (Docker). There was no way to place a single file that is not part of a target tree, so callers had to write into the cloned target directory (dirtying what the agent inspects, and writable by the agent) or push the file after session start (extra round trip, races startup).

This adds one backend-agnostic path for that, and exposes it on the CLI.

Engine API. run_strix_scan(..., extra_files=[{"workspace_path": "/workspace/<rel>", "content": bytes | str}]), forwarded to session_manager.create_or_reuse. Both backends materialize the same declaration:

  • manifest backends: a single-file File(content=…) entry that rides the existing one-tar upload — no extra transport;
  • bind-mount backends: the content is staged under the run's state dir and mounted read_only=True at the same path.

Paths must be under /workspace, with no traversal and no control characters. An entry is skipped with a warning when its path collides with a local source tree (exact match, nested under a source root, or an ancestor of one) or with an already-placed extra file, so an extra file can never replace a target's LocalDir/mount, shadow it, or produce two mounts for one path.

CLI. --workspace-file PATH[:DEST], repeatable. DEST is a path inside /workspace and defaults to the file name:

strix -t ./my-project --workspace-file ./wordlist.txt
strix -t https://app.com --workspace-file ./openapi.yaml:specs/openapi.yaml

Specs are resolved at parse time (file exists and is readable, destination inside /workspace, no duplicate destinations, 10 MB total cap), persisted in run.json so --resume places the same files again, and read into extra_files at launch. The resolved paths are listed in the root task under Files Provided By The User:, explicitly marked as data rather than instructions or scope, so the agent knows where to read them without the contents claiming authority.

Docs: --workspace-file in the CLI reference plus a "Workspace files" section in usage/instructions covering destinations, the read-only guarantee, the collision and size rules, and a warning against placing secrets there.

Link to Devin session: https://app.devin.ai/sessions/ea01d0ad34dc4dd28200d258ebd2198e
Requested by: @yoni-at-strix

@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds caller-provided, read-only workspace files across the engine API, CLI, manifest, and bind-mount sandbox paths.

  • Adds extra_files support and backend-specific materialization.
  • Adds --workspace-file PATH[:DEST], resume persistence, validation, and prompt rendering.
  • Rejects workspace files that overlap source trees or earlier extra-file destinations.
  • Adds documentation and coverage for parsing, delivery, collisions, and path validation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; both previously reported collision issues are prevented on the current paths.

Important Files Changed

Filename Overview
strix/runtime/session_manager.py Adds validated manifest and read-only bind-mount materialization; the fixes correctly prevent source-tree and duplicate-destination collisions.
strix/interface/utils.py Resolves and validates workspace-file specifications, rejecting duplicate, traversing, malformed, and unreadable inputs before loading content.
strix/interface/cli_args.py Adds argument parsing and resume restoration with revalidation of persisted workspace-file declarations.
strix/core/runner.py Threads caller-provided extra files into sandbox session creation.
strix/core/inputs.py Lists valid workspace-file paths in the root task while explicitly separating them from instructions and scan scope.
strix/interface/cli.py Includes workspace files in scan configuration and loads their bytes for non-interactive launches.
strix/interface/tui/runtime.py Mirrors workspace-file configuration and delivery through the interactive TUI launch path.
strix/interface/scan_setup.py Persists resolved workspace-file declarations so resumed runs can restore them.
tests/test_session_entries.py Covers backend parity, invalid entries, source-tree overlap, duplicate destinations, and read-only bind mounts.
tests/test_workspace_files.py Covers CLI destination validation, duplicate rejection, content loading, and safe task rendering.

Reviews (5): Last reviewed commit: "drop the workspace-file size limit" | Re-trigger Greptile

Comment thread strix/runtime/session_manager.py Outdated
@devin-ai-integration

Copy link
Copy Markdown
Contributor

@greptile

@bearsyankees

Copy link
Copy Markdown
Collaborator

@strix-security

@devin-ai-integration devin-ai-integration Bot changed the title feat/workspace-file-provisioning feat: place caller-provided files into the sandbox workspace (extra_files, --workspace-file) Aug 14, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

@greptile

@bearsyankees

Copy link
Copy Markdown
Collaborator

@strix

@strix-security

strix-security Bot commented Aug 14, 2026

Copy link
Copy Markdown

Strix Security Review

All previously reported security findings have been resolved.

1 resolved finding
Review summary

Reviewed the security-relevant changes for the new extra_files / --workspace-file flow across CLI parsing, resume-state restoration, root-task rendering, and backend-specific sandbox materialization. The updated code now consistently validates destination paths under /workspace, revalidates persisted workspace-file metadata on --resume, and prevents prompt-line injection via control characters in rendered file paths. The session-manager changes also correctly reject collisions with source-tree mounts and previously placed extra files on both manifest and bind-mount backends. No exploitable security vulnerabilities were identified in the changed code.

Updated for 2bcd0d2.


Reviewed by Strix
Re-run review · Configure security review settings

Comment thread strix/runtime/session_manager.py

@strix-security strix-security Bot 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.

Strix flagged 2 new security findings below. See the pinned summary comment for the full PR status.

Comment thread strix/interface/utils.py
Comment on lines +395 to +401
if not getattr(args, "workspace_files", None):
args.workspace_files = [
workspace_file
for workspace_file in state.get("workspace_files") or []
if isinstance(workspace_file, dict)
and Path(str(workspace_file.get("source_path", ""))).is_file()
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Revalidate persisted workspace-file metadata on resume

Suggested change
if not getattr(args, "workspace_files", None):
args.workspace_files = [
workspace_file
for workspace_file in state.get("workspace_files") or []
if isinstance(workspace_file, dict)
and Path(str(workspace_file.get("source_path", ""))).is_file()
]
if not getattr(args, "workspace_files", None):
restored_specs = []
for workspace_file in state.get("workspace_files") or []:
if not isinstance(workspace_file, dict):
continue
source_path = Path(str(workspace_file.get("source_path", "")))
if not source_path.is_file():
continue
workspace_path = str(workspace_file.get("workspace_path", ""))
if not workspace_path.startswith("/workspace/"):
continue
restored_specs.append(f"{source_path}:{workspace_path.removeprefix('/workspace/')}")
try:
args.workspace_files = resolve_workspace_files(restored_specs)
except ValueError as exc:
parser.error(f"--resume {args.resume}: invalid workspace file metadata: {exc}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Applied in 9c5307d: --resume now rebuilds the persisted declarations into PATH:DEST specs and runs them back through resolve_workspace_files, so a resumed run revalidates the destination, duplicates, and total size exactly like a fresh --workspace-file instead of only checking that the source still exists. An edited run.json pointing outside /workspace fails the resume with --resume <run>: invalid workspace file: …; a file deleted between runs is still dropped rather than fatal. Tests: test_resume_revalidates_persisted_workspace_files and test_resume_rejects_an_edited_workspace_file_path.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

@greptile

@devin-ai-integration

Copy link
Copy Markdown
Contributor

@strix-security

@devin-ai-integration

Copy link
Copy Markdown
Contributor

@greptile

@devin-ai-integration

Copy link
Copy Markdown
Contributor

@strix-security

@bearsyankees
bearsyankees merged commit 8551339 into main Aug 14, 2026
3 checks passed
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