fix(sandbox): keep agent workspace off gateway state files - #79
Conversation
Default workspace_path was ~/.shellclaw, the same tree as pairing tokens, memory.db, and config.toml. With workspace_only on, inbound Discord/webchat/cron file tools could read or overwrite those files. Point the default at ~/.shellclaw/workspace and deny runtime state paths even when an operator keeps the old workspace root. Co-authored-by: esadrianno <esadrianno@gmail.com>
Keep the default workspace off ~/.shellclaw and the runtime-state denylist together with development's atomic writes, symlink rejection, and Jetson allowlist entries.
GCC -Werror=format-truncation rejected snprintf of a 256-byte directory plus /config.toml into another 256-byte buffer.
There was a problem hiding this comment.
Summary
This PR moves the default tool workspace from ~/.shellclaw to ~/.shellclaw/workspace, and it refuses pairing tokens, the pid file, the log, and .shellclaw/config.toml / .shellclaw/memory.db when a custom workspace still contains them. The default split is the right fix, and the file-tool test checks that token bytes are not returned.
The compatibility denylist is incomplete. I compiled allowlist.c against a throwaway driver: absolute config.toml is blocked, and the three cases below are allowed (allowlist_check_shell_command returns 0).
Must Fix
memory.db-walandmemory.db-shmare not reserved.src/core/memory.csetsPRAGMA journal_mode=WAL, so live memory and session pages sit in those sidecars.allowlist_path_is_runtime_state_filematches only the exact basenamememory.db. With the workspace pointed at a.shellclawdirectory,read_file/write_fileandcat <abs>/.shellclaw/memory.db-walare allowed. Matchmemory.dbandmemory.db-*when the parent directory is.shellclaw.- Sandboxed shell
chdirs intoworkspace_path(setup_child_processinsrc/sandbox/sandbox.c) but the new check only inspects the raw token.has_path_charsignores a token that does not start with/,~, or., and a bareconfig.tomlhas no slash, so the parent-directory test returns "not reserved".cat config.tomlandcat memory.dbare allowed, then the child reads the state files. Resolve the token against the workspace root before deciding.tests/test_allowlist.conly asserts absolutecat %spaths, so CI stays green.
Should Fix
ensure_workspace_directorytreats anyEEXISTas success. If~/.shellclaw/workspaceis a symlink (including one planted before this upgrade), laterrealpathuses the link target as the workspace root and the default split is gone.lstatthe final component and refuse a symlink or a non-directory.- The runtime-state loop sits under the
workspace_onlyearly return inallowlist_check_shell_command, and the unsandboxed fallback insrc/tools/shell.cnever calls it.sandbox_enabledstays 0 unless TOML sets it. Unsandboxedcat ~/.shellclaw/config.tomlstill runs. A global substring onconfig.tomlis too broad. Run the same path predicate on that fallback for real path tokens. test_runtime_state_files_rejected_inside_workspacereturns without failing whenmkdirfails, so a setup error looks like a pass.test_shell_blocked_auth_tokenspasses a NULL config, so it only hits the fallback substring.
Nice to Have
test_defaultsonly checks that the path ends in/workspace, not that the parent is.shellclaw.ensure_workspace_directorycreates only the immediate parent. The default~/.shellclaw/workspaceis fine. A deeper custom path logs a mkdir failure andtools_initstill returns 0. File access then fail-closes because the workspace does not resolve.- Every basename
auth_tokens.jsonis reserved, not only under.shellclaw. That also blocks an unrelated project file and any command that merely mentions the name. The header comment says this is intentional.
Positive Highlights
- Separating the tool root from the state directory fixes the default case without a new sandbox mechanism.
config.tomlandmemory.dbare reserved only when the parent is.shellclaw, andtest_allow_project_config_tomllocks the other case.- The file-tool regression checks that
secret-pairis absent from the tool result, not only that the call returned -1. - Example config, CHANGELOG, and the gateway oversize-file helper were updated for the new directory.
Sent by Cursor Automation: Adrianno鈥檚 personal code review
WAL sidecars next to memory.db were readable, and a bare cat config.toml ran after the sandbox chdir. Also refuse a symlink workspace and apply the same state-file check on the unsandboxed shell path.
|
Should Fix items that were not inline threads are also in e63a7ec:
Left as-is: a custom workspace whose parents do not exist still fail-closes when |
| snprintf(expanded, sizeof(expanded), "%s", tok); | ||
| check = expanded; | ||
| } | ||
| if (allowlist_path_is_runtime_state_file(check)) { |
There was a problem hiding this comment.
馃敀 Agentic Security Review
Severity: HIGH
The new runtime-state denylist inspects raw strtok tokens and exact basenames. A quoted or globbed path such as cat "~/.shellclaw/auth_tok*" never matches auth_tokens.json / config.toml, has_path_chars skips the token because it does not start with /, ~, or ., and the unsandboxed fallback still calls this check with cfg == NULL.
Impact: An inbound Discord/WebChat/cron prompt that causes shell can still dump pairing tokens or config.toml after this PR鈥檚 default workspace split.
Reviewed by Cursor Security Reviewer for commit e63a7ec. Configure here.




Summary
Default
workspace_pathwas~/.shellclaw, the same directory as gateway pairing tokens,memory.db, andconfig.toml. With defaultworkspace_only=1, inbound Discord / WebChat / cronfiletools treated those files as in-bounds.Bug
src/core/config.c(config_loaddefault),src/tools/file.c(path_within_workspace),src/sandbox/allowlist.cworkspace_path). Send a Discord/WebChat/cron prompt that causes the agent toread_file/write_fileauth_tokens.json, orcat ~/.shellclaw/auth_tokens.jsonvia shell.memory.dborconfig.toml. After PR fix(asap): wire provider and tools into POST /asap聽#53 lands, the same path applies to inboundPOST /asap.workspace_onlyboundary includes secrets.Root cause
workspace_onlyis a prefix check againstworkspace_path. The default path was the process state dir, so the security control that is on by default authorized tool access to pairing tokens.Fix
workspace_pathto~/.shellclaw/workspaceand create that directory at tool init.auth_tokens.json,shellclaw.pid,shellclaw.log, and.shellclaw/config.toml/.shellclaw/memory.dbeven if they sit inside a custom workspace.Validation
CI=true GATEWAY=1 make test(full suite, including new file/allowlist/config/shell cases)