Skip to content

fix(sandbox): harden workspace_only host-FS allowlist - #87

Closed
adriannoes wants to merge 26 commits into
developmentfrom
cursor/sandbox-fs-allowlist-d519
Closed

adriannoes wants to merge 26 commits into
developmentfrom
cursor/sandbox-fs-allowlist-d519

Conversation

@adriannoes

@adriannoes adriannoes commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

Rewrite of Bugbot drafts #66 + 68a + #82 onto development.

sandbox_exec() does not pivot_root. workspace_only is the host-FS gate:

  • Walk the first existing ancestor when realpath fails (workspace/../../tmp/stolen).
  • Collapse . / .. lexically first so a missing directory before .. (/ws/nope/../../../tmp/stolen) cannot pin the walk at the workspace.
  • Scan quoted and embedded / and ~ on the full command (cat '/etc/passwd', python3 -c "open('/etc/passwd')").
  • Join embedded relative ../ to the workspace (python3 -c "open('../secret')").
  • Extract file: URL paths (file:/etc/passwd, file://localhost/etc/passwd, file://etc/passwd).
  • Expand $HOME / ${HOME} / $PWD / ${PWD} on the full command, including glued forms (cat$IFS/etc/passwd, cat"$HOME/.bashrc"). Other $ forms fail closed.
  • Reconstruct encoded leading slashes (\x2f, \57, \u002f) as / plus the following path body (python3 -c "open('\x2fetc/passwd')").
  • Fail closed on in-command HOME= / PWD= assignment, export, and unset instead of trusting process getenv (PWD=; cat $PWD/etc/passwd).
  • Do not cancel .. across a symlink (workspace/out/../etc/passwd with out → /).
  • Percent-decode file: URLs (%2e%2e, %2f) before the workspace check. https:// stays allowed.
  • Fail closed on quoted eval / sh -c HOME= / PWD= mutation (eval 'PWD=; cat $PWD/etc/passwd').
  • Join quote-split file: schemes (f'ile://localhost/etc/passwd', f"ile:/etc/passwd", 'f'+'ile://...') before the URL check. https:// stays allowed.
  • Reconstruct encoded . (\x2e, \56, \u002e) forming ../, and extra slash encodings (\u{2f}); fail-closed on \N{ without parsing Unicode names.
  • Scan ../ after :// (https://example.com/../../../../etc/passwd); real https:// without a .. walk stays allowed.
  • Reconstruct Perl \x{2f} / \x{2e} like \u{2f}.
  • Fail closed on env -i, env -u HOME|PWD, os.environ.pop/del of HOME/PWD, and HOME=/PWD= after a comma.
  • Fail closed on POSIX read HOME|PWD, GNU env --unset / --unset=, clustered env -iu, and Python os.unsetenv / os.putenv / os.environ.clear.
  • Reconstruct Perl \o{57} / \o{057} / \o{056} like \x{2f}.
  • Reconstruct hex/unicode scheme letters (\x66ile:, \u0066ile:) before the file: check. https:// stays allowed.
  • Reconstruct octal scheme bytes (\146ile:, \072) and fold shell identity escapes (f\ile:) before the file: check. https:// stays allowed.
  • Collapse POSIX \ + newline (optional CR) before HOME/PWD and file: scans (f\<newline>ile:, PW\<newline>D=).
  • Fail closed on printf -v HOME|PWD, os.environ["HOME"]=, and os.environ.update({"HOME":...}).

Residuals (not this PR):

Supersedes #66 and #82.

Does not start Landlock (#74 + #76). Operator merges.

adriannoes and others added 6 commits September 14, 2026 01:19
Lexical prefix matching treats workspace/../../tmp/newfile as inside
the workspace whenever realpath fails. Lock the missing-file ancestor
case before walking dirname like tools/file.c.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
realpath cannot canonicalize a missing destination, so a lexical
workspace prefix allowed workspace/../../tmp/stolen. Collapse ..
through existing dirs like tools/file.c. sandbox_exec does not
pivot_root, so this is the host FS gate.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Whitespace tokenization never saw cat '/etc/passwd' or a path inside
python3 -c. Namespaces do not chroot, so lock those host-FS bypasses
and keep relative/URL slashes allowed.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Whitespace tokens never saw cat '/etc/passwd' or python3 -c open().
Scan the full command for / and ~ fragments, strip one quote layer,
and fail closed on strdup OOM. Relative 3/4 and https:// stay allowed;
file:/// still blocks.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
has_path_chars only flagged / ~ ., so cat $HOME/.shellclaw/... skipped
the workspace gate. Lock HOME, ${HOME}, $PWD, ANSI-C $'\\x2f...', and
quoted \"$HOME/...\" before expanding those tokens.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
has_path_chars ignored tokens that only become absolute after /bin/sh
expands them. Expand HOME/PWD (and one quote layer), fail closed on
ANSI-C and other \$ forms, and do not treat \${HOME}/ as a new FS root.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Summary

ShellClaw is a C99 edge agent (Jetson / Raspberry Pi). The shell tool’s host-filesystem boundary is this allowlist: sandbox_exec() uses namespaces only and does not pivot_root. This PR hardens workspace_only in src/sandbox/allowlist.c in three real ways: walk the first existing ancestor instead of a lexical prefix when realpath fails, scan quoted/embedded / and ~ on the full command, and expand $HOME/$PWD (fail-closed on other $ forms) on whitespace tokens. strdup OOM is now fail-closed. The ancestor walk and quoted /etc/passwd cases work and are tested. The $ work only applies when a strtok token starts with $, so glued shell expansions still open host files. That is a merge blocker for a change that documents this scanner as the primary host-FS gate.

Must Fix

  1. Glued $IFS / ${IFS} bypasses the new / scanner. is_fs_absolute_path_start (allowlist.c:184-188) skips / after a path-body letter and after }. cat$IFS/etc/passwd and cat${IFS}/etc/passwd therefore return allow. Bash word-splits both to cat /etc/passwd. /etc/passwd is not in the substring blocklist.
  2. Glued $HOME / ANSI-C $... are not fail-closed. has_path_chars only looks at tok[0]. cat$'\x20/etc/passwd' expands to cat /etc/passwd and is allowed. python3 -c "open('$HOME/.shellclaw/auth_tokens.json')" and cat"$HOME/.bashrc" are allowed. The tests only cover spaced cat $HOME/... and cat $'\x2f...'.

Verified locally against d9d1a9a (allowlist_check_shell_command rc=0 on those strings; test_allowlist otherwise passed, linked with -lm because this image has no libcurl).

Should Fix

  • expand_tilde_fragment (allowlist.c:206-208) uses home="" when HOME is unset, so ~/x becomes /x. The token expander keeps ~/x.
  • docs/SECURITY.md still describes the old lexical/token checker. Task 7.1 residual should name glued $ / $IFS until they are closed.
  • $PWD/../outside.txt in test_workspace_only_blocks_home_env_expansion uses a mkdtemp workspace, not $PWD, so it does not prove .. collapse after expansion.
  • Leftover (void)workspace_only at allowlist.c:343-344. If dirname returns a pointer into path_copy, snprintf(path_copy, ..., dir) is overlap UB (same pattern as tools/file.c).

Nice to Have

  • Conservative false positive: awk '/foo/' notes.txt is blocked because /foo/ looks like a path. Fine if you want that, but operators will hit it.
  • Python open(chr(47)+'etc/passwd') has no / in the command string. Residual of not parsing Python; document it, do not turn this module into a language interpreter.
  • PR title is the branch name. A conventional fix(sandbox): ... title would match the commits.

Positive Highlights

  • Ancestor walk matches tools/file.c and the new test_dotdot_escape_nonexistent_destination actually exercises a missing workspace/../../tmp/... dest, not only a helper.
  • Fail-closed strdup OOM and the quoted/python3 -c open('/etc/passwd') scan close the exact holes called out on earlier PRs. Header comments now state honestly that namespaces are not a chroot.
Open in Web View Automation 

Sent by Cursor Automation: Adrianno’s personal code review

Comment thread src/sandbox/allowlist.c Outdated
Comment thread src/sandbox/allowlist.c
Comment thread tests/test_allowlist.c

@cursor cursor 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.

Stale comment

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

Whitespace $HOME tests never saw cat$IFS/etc/passwd, cat${IFS}/...,
ANSI-C cat$'\x20/...', python3 -c open('$HOME/...'), or cat"$HOME/...".
Lock those host-FS bypasses and expand $PWD to the process PWD before
asserting .. collapse.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Stale comment

Agentic security review of the sandbox workspace_only allowlist changes. Three additional HIGH containment gaps on the current head (ancestor walk with a missing component before .., file: URL variants, and embedded relative ../). Prior review threads on $IFS/} slash skipping and $HOME expansion only for whitespace tokens still apply and were not restated.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread src/sandbox/allowlist.c
Comment thread src/sandbox/allowlist.c
Comment thread src/sandbox/allowlist.c Outdated
adriannoes and others added 2 commits September 14, 2026 11:39
has_path_chars only ran when a strtok token started with $. Shell glues
$IFS, ANSI-C $'...', and "$HOME" onto the previous word. Expand HOME/PWD
(plus a following / suffix) anywhere in the command, fail closed on other
$ forms, and treat / after } as a new FS root unless it closes ${HOME} or
${PWD}. Unset HOME no longer maps ~/x to /x. Copy dirname into a second
buffer so the ancestor walk is not snprintf overlap.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Task 7.1 residual now names the full-command $ scan (including glued
$IFS and mid-token $HOME) and the leftovers: relative cd tokens,
Python chr(47), and awk '/foo/' false positives. Landlock is next.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
@adriannoes adriannoes changed the title Cursor/sandbox fs allowlist d519 fix(sandbox): harden workspace_only host-FS allowlist Sep 14, 2026
adriannoes and others added 3 commits September 14, 2026 11:44
The ancestor walk stopped at the workspace for /ws/nope/../../../tmp,
file:/ and file://localhost/ skipped the / scanner, and python3 -c
open('../secret') never started a path fragment. Lock those host-FS
bypasses before collapsing .. lexically.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
A missing directory before .. stopped the ancestor walk at the workspace.
Collapse . and .. first so /ws/nope/../../../tmp leaves the sandbox.
Extract file: URL paths (file:/, file://localhost/, file://etc/passwd)
and join embedded relative ../ to the workspace before the same check.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
cppcheck uninitvar on parts[i] when the input is only '/'. Initialize
the stack array and skip a NULL slot before strlen.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Stale comment

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor 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.

Stale comment

Agentic security review of the workspace_only host-FS allowlist. Four high-severity containment gaps remain on this head: interpreter slash escapes, $PWD/$HOME vs in-command assignment, lexical .. collapse across symlinks, and undecoded file: percent-encoding. Prior findings on glued $IFS, mid-token $HOME, missing-dir .. walks, and plain file: URLs look addressed.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread src/sandbox/allowlist.c
Comment thread src/sandbox/allowlist.c
Comment thread src/sandbox/allowlist.c Outdated
Comment thread src/sandbox/allowlist.c
adriannoes and others added 3 commits September 14, 2026 12:15
Lock four Security Agent HIGH residuals: interpreter \x2f/\57/\u002f
leading slashes, in-command HOME/PWD assignment, symlink-then-.., and
percent-encoded file: URLs. Assertions expect deny.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Reconstruct \x2f / \57 / \u002f as a leading slash plus path body.
Fail closed when the command assigns, exports, or unsets HOME/PWD.
realpath the original path before lexical collapse, and refuse to
cancel .. across a symlink. Percent-decode file: URLs before the
workspace check. Keep https:// allowed.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Move val and n into the octal branch so variableScope is clean.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Stale comment

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor 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.

Stale comment

Agentic security review of the workspace_only host-FS allowlist. Three residual HIGH bypasses remain in the new scanners: quoted HOME/PWD mutation vs process getenv, incomplete interpreter-escape reconstruction (encoded . and extra / forms), and quote-split file: URLs.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread src/sandbox/allowlist.c Outdated
Comment thread src/sandbox/allowlist.c Outdated
Comment thread src/sandbox/allowlist.c
adriannoes and others added 3 commits September 14, 2026 12:37
Lock three Security Agent HIGH residuals: eval/sh -c quoted HOME/PWD
mutation, quote-split file: schemes, and encoded . / \u{2f} / \N{.
Assertions expect deny.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Strip quotes and trivial quote-concat before HOME/PWD mutation and
file: scans so eval/sh -c and f'ile:// cannot skip the gate.
Reconstruct encoded . and / including \\u{2f}; fail-closed on \\N{.
Keep https:// allowed. Not a Python interpreter for chr(47)+.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Move lo into the \\x branch so variableScope is clean.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Stale comment

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor 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.

Stale comment

Agentic security review of HEAD 925a0dcb6f26ae8eeeebd7e0a82aaddcbc44affb. Prior threads on quoted PWD=/eval, encoded \\x2e/\\u{2f}/\\N{, and quote-split file: URLs are addressed. Three remaining HIGH issues: :// skipping embedded ../, Perl \\x{2f} not reconstructed, and HOME/PWD getenv vs env/os.environ/comma-nested assignment.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread src/sandbox/allowlist.c Outdated
Comment thread src/sandbox/allowlist.c
Comment thread src/sandbox/allowlist.c Outdated
Lock three Security Agent HIGH residuals: https://.../../ containment,
Perl braced hex, and HOME/PWD via env -i / os.environ.pop / comma.
Assertions expect deny.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Stale comment

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

Scan ../ after :// so URL-disguised walks are containment-checked;
real https:// fetches without a .. walk stay allowed. Reconstruct
Perl \x{2f}/\x{2e} like \u{2f}. Fail closed on env -i, env -u HOME|PWD,
os.environ.pop/del, and HOME=/PWD= after a comma. Not chr(47)+.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Stale comment

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor 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.

Stale comment

Comment thread src/sandbox/allowlist.c Outdated
Comment thread src/sandbox/allowlist.c
Comment thread src/sandbox/allowlist.c
adriannoes and others added 2 commits September 14, 2026 13:24
Lock three Security Agent HIGH residuals: POSIX read HOME/PWD,
GNU env --unset and clustered -iu, os.unsetenv/putenv/clear,
Perl braced octal, and \x66/\u0066 hiding file:. Assertions
expect deny. https:// stays allowed. Not chr(47)+.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Fail closed on POSIX read HOME/PWD, GNU env --unset and clustered
-iu, and os.unsetenv/putenv/environ.clear. Reconstruct Perl \o{57}
like \x{2f}. Decode \xNN/\u00NN so \x66ile: cannot hide file:.
Keep https:// allowed. Not chr(47)+.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Stale comment

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor 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.

Stale comment

Agentic security review of the workspace_only host-FS allowlist. Three HIGH residuals remain in the new scanners: incomplete file: scheme recovery (octal / identity-escape), HOME/PWD mutation forms that still trust process getenv(), and POSIX backslash-newline continuation that skips the new gates. Prior threads on read/\\o{}/\\x66ile: are addressed on this head.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread src/sandbox/allowlist.c
Comment thread src/sandbox/allowlist.c
Comment thread src/sandbox/allowlist.c
adriannoes and others added 2 commits September 14, 2026 13:46
Lock three Security Agent HIGH residuals: octal \146/\072 and
f\ile: scheme hiding, POSIX backslash-newline continuation, and
printf -v / os.environ[] / .update HOME/PWD. Assertions expect
deny. https:// stays allowed. Not chr(47)+.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Decode octal scheme bytes and fold shell identity escapes so
\146ile: / f\ile: hit the file: scan. Collapse POSIX backslash
+ newline before HOME/PWD and file: gates. Fail closed on
printf -v HOME|PWD, os.environ[] assign, and .update.
Keep https:// allowed. Not chr(47)+.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>

@cursor cursor 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.

Stale comment

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor 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.

Security review of the latest head found two remaining workspace_only host-FS bypasses. Earlier threads on file: octal/identity encoding, printf -v / os.environ[...] mutation, and \\+newline folding are addressed on this commit.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread src/sandbox/allowlist.c Outdated
set_reason(reason_buf, reason_cap, "command blocked: out of memory", "");
return 1;
}
mutated = command_mutates_home_or_pwd(unquoted);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Agentic Security Review
Severity: HIGH

The new HOME/PWD mutation gate still scans a heuristic keyword list on dup_unquoted() (no identity-escape fold) and then expands $PWD/$HOME/~ with this process getenv(). POSIX /bin/sh and bash still change those parameters through forms the scanner misses, so workspace_only allows the command while the executed shell opens a host path outside the workspace.

Impact: With sandbox_exec() chdiring into the workspace and no pivot_root, an auto-executed agent shell command can read or write host files such as /etc/passwd. Live allow (rc=0) cases include export PW\D=; cat $PWD/etc/passwd, \unset HOME; cat $HOME/etc/passwd, \env -i sh -c 'cat $HOME/etc/passwd', bash -c 'declare -n x=PWD; x=; cat $PWD/etc/passwd', and bash -c 'exec -c sh -c "cat $HOME/etc/passwd"'.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 2d0fc3e. Configure here.

Comment thread src/sandbox/allowlist.c Outdated
if (file_blocked)
return 1;
}
if (block_if_dollar_expansions_escape(cmd, workspace_root, reason_buf, reason_cap))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Agentic Security Review
Severity: HIGH

block_if_dollar_expansions_escape() only looks for a literal $ on the raw command. Hex/octal/unicode decoding is applied to file: recovery, not to $ (\x24 / \044 / \u0024). A Python/Perl string can therefore hide $HOME so the new expansion gate never runs, and is_fs_absolute_path_start() also skips the following / because it sits after an identifier (E/D).

Impact: Direct cat $HOME/.bashrc stays blocked, but python3 -c "import os; os.system('cat \\x24HOME/.bashrc')" is allowed (rc=0) and the interpreter expands the real home, which is typically outside ~/.shellclaw. That can expose host files such as ~/.bashrc, ~/.ssh/config, or ~/.git-credentials.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 2d0fc3e. Configure here.

adriannoes and others added 2 commits September 14, 2026 14:13
Assert deny for identity-escaped PWD/HOME keywords, bash
declare -n targeting PWD, exec -c, and \x24/\044/\u0024
before getenv. Keep https:// allowed. Not chr(47)+.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
Run the same hex/unicode/octal/identity decode as file: recovery
before the HOME/PWD keyword gate and the $ expansion scan so
PW\D=, \unset, \env -i, \x24, \044, and \u0024 cannot skip getenv.
Fail closed on declare -n targeting HOME|PWD and exec -c.
Keep https:// allowed. Not chr(47)+.

Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
@adriannoes adriannoes closed this Sep 14, 2026

@cursor cursor 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.

Left a non-blocking comment; this automation did not approve. Cursor Bugbot was not present; Cursor Security Agent stayed pending past the 8-minute wait, so human review is required. Requested adriannoes as reviewer per the approval instructions.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@adriannoes

Copy link
Copy Markdown
Collaborator Author

Superseded by #88 (cursor/sandbox-fs-landlock-d519 vs development).

The allowlist commits from this PR were replayed onto origin/development and folded with a rewritten Landlock + fail-closed namespace bound. Title left unchanged.

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.

1 participant