Skip to content

Fixes the 400 Bad Request error when launching agents in directories outside ~/, such as /Volumes/workplace on macOS. #110

Merged
haofeif merged 3 commits into
mainfrom
fix/relax-working-directory-validation
Mar 12, 2026
Merged

Fixes the 400 Bad Request error when launching agents in directories outside ~/, such as /Volumes/workplace on macOS. #110
haofeif merged 3 commits into
mainfrom
fix/relax-working-directory-validation

Conversation

@haofeif

@haofeif haofeif commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Reported by: User on macOS with workspace at /Volumes/workplace/PODCollectionEngine — worked before v1.1.1, broke after the working directory validation was added.

Problem

The previous validation required all working directories to be under the user's home directory (~/). This blocked legitimate use cases:

  • macOS external volumes (/Volumes/workplace/...)
  • Corporate dev desktops with non-home project paths (/opt/projects/...)
  • NFS mounts and custom workspace locations

Fix

Changed the approach from "only allow ~/" to "block known-dangerous system paths". The existing _BLOCKED_DIRECTORIES blocklist becomes the primary guard instead of the home-directory containment
check.

Now allowed:

  • /Volumes/workplace/PODCollectionEngine
  • /opt/projects/my-app
  • Any real directory not in the blocklist

Still blocked:

  • /, /etc, /var, /tmp, /dev, /proc, /sys, /root, /boot, /bin, /sbin, /usr/bin, /usr/sbin, /lib, /lib64
  • macOS equivalents: /private/etc, /private/var, /private/tmp

Changes

File Change
src/cli_agent_orchestrator/clients/tmux.py Replace home-dir containment check with blocklist-only validation; add macOS /private/* paths to blocklist
test/providers/test_tmux_working_directory.py Replace "outside home" rejection test with new tests for allowed external paths, blocked system paths, and symlink-to-blocked detection
README.md Update security policy description
docs/working-directory.md Update allowed/blocked directory documentation

Test plan

  • 625 unit tests pass (5 new tests added)
  • /Volumes/workplace/... paths now accepted
  • /opt/projects/... paths now accepted
  • /etc, /var, /tmp, /root, / still rejected
  • Symlinks resolving to blocked paths still rejected

haofeif and others added 3 commits March 11, 2026 23:10
The previous validation required all working directories to be under the
user's home directory (~/).  This broke users on macOS with workspaces on
external volumes (e.g. /Volumes/workplace) and corporate dev desktops
with non-home project paths.

Change the approach from "only allow ~/" to "block known-dangerous
system paths".  The existing _BLOCKED_DIRECTORIES blocklist (/, /etc,
/var, /tmp, /dev, /proc, /sys, /root, /boot, /bin, /sbin, /usr/bin,
/usr/sbin, /lib, /lib64) becomes the primary guard.  Paths outside ~/
like /Volumes/workplace or /opt/projects are now allowed.

Also adds /private/etc, /private/var, /private/tmp to the blocklist for
macOS where /etc is a symlink to /private/etc.

Fixes: 400 Bad Request when launching agents in /Volumes/workplace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haofeif haofeif added the bug Something isn't working label Mar 11, 2026
@haofeif haofeif requested a review from a team March 11, 2026 12:16
@gutosantos82

Copy link
Copy Markdown
Contributor

Independent Review & Test Results

Reviewed and tested locally on Amazon Linux 2 (Python 3.12.3, tmux next-3.6).

Summary

Clean fix for the regression where working directory validation rejected paths outside ~/, breaking macOS external volumes (/Volumes/workplace) and corporate dev paths (/opt/projects). Replaces the "allowlist ~/ only" approach with a "blocklist dangerous system paths" approach — simpler code, fewer steps, more practical.

Test Results

Test Result
Unit tests (625 total, 20 working-dir specific) ✅ All passed
E2E handoff (Kiro CLI) ✅ Passed
Direct validation: CWD ✅ Allowed
Direct validation: /tmp, /etc, / ✅ Blocked
black --check ✅ Clean
isort --check-only ✅ Clean

Review Notes

  • The fix removes complexity rather than adding it (~35 lines → ~10 lines)
  • macOS /private/* paths correctly added to blocklist
  • Good test coverage for allowed paths, blocked paths, and symlink-to-blocked detection

Minor note: The blocklist only checks exact matches (real_path in _BLOCKED_DIRECTORIES), so subdirectories of blocked paths like /etc/nginx or /var/log are technically allowed. This is intentional (needed for /var/folders on macOS) and documented, but worth being aware of. Also, the old code had CodeQL-specific startswith comments for taint analysis — if CodeQL scanning is enabled, watch for new alerts after merge since the check changed to in on a frozenset.

LGTM 👍

@haofeif haofeif merged commit d22ebde into main Mar 12, 2026
15 checks passed
@haofeif haofeif deleted the fix/relax-working-directory-validation branch March 13, 2026 11:52
haofeif added a commit that referenced this pull request Mar 16, 2026
Add startswith("/") guard after realpath() to satisfy CodeQL's
py/path-injection two-state taint model. CodeQL recognizes
str.startswith() as a SafeAccessCheck that clears NormalizedUnchecked
taint. The guard is always true after realpath() but explicitly
rejects relative paths and satisfies the static analysis requirement.

Regression was introduced in d22ebde (#110) which relaxed the home
directory containment check to allow paths outside ~/. This removed
the startswith(home_dir) guard that CodeQL relied on.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
haofeif added a commit that referenced this pull request Mar 16, 2026
Add startswith("/") guard after realpath() to satisfy CodeQL's
py/path-injection two-state taint model (code-scanning alert #5).

CodeQL recognizes str.startswith() as a SafeAccessCheck that clears
NormalizedUnchecked taint state. The guard is always true after
realpath() but explicitly rejects relative paths and satisfies the
static analysis requirement.

Regression was introduced in d22ebde (#110) which removed the
startswith(home_dir) guard to allow paths outside ~/. This fix
restores CodeQL compliance without re-restricting allowed paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fanhongy pushed a commit that referenced this pull request Mar 16, 2026
)

Add startswith("/") guard after realpath() to satisfy CodeQL's
py/path-injection two-state taint model (code-scanning alert #5).

CodeQL recognizes str.startswith() as a SafeAccessCheck that clears
NormalizedUnchecked taint state. The guard is always true after
realpath() but explicitly rejects relative paths and satisfies the
static analysis requirement.

Regression was introduced in d22ebde (#110) which removed the
startswith(home_dir) guard to allow paths outside ~/. This fix
restores CodeQL compliance without re-restricting allowed paths.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
haofeif added a commit that referenced this pull request Mar 25, 2026
…120)

* fix(claude_code): handle bypass permissions prompt on startup (#119)

Claude Code v2.1.41+ shows a "Bypass Permissions mode" confirmation
dialog on every launch with --dangerously-skip-permissions unless
skipDangerousModePermissionPrompt is persisted in ~/.claude/settings.json.
This blocks CAO initialization with a 30-second timeout.

Two-layer fix:
- Preventive: write skipDangerousModePermissionPrompt: true to
  ~/.claude/settings.json before launching Claude Code
- Defensive: detect "Yes, I accept" in tmux buffer and send Down+Enter
  as a fallback if the settings-based fix doesn't take effect

Also:
- Rename _handle_trust_prompt → _handle_startup_prompts to reflect it
  now handles both bypass permissions and workspace trust prompts
- Use continue (not return) after accepting bypass prompt so a
  subsequent trust prompt is still handled
- Exclude bypass prompt from WAITING_USER_ANSWER status detection

Closes #119

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: apply black formatting to test file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): add CodeQL SafeAccessCheck guard for path injection (#5)

Add startswith("/") guard after realpath() to satisfy CodeQL's
py/path-injection two-state taint model. CodeQL recognizes
str.startswith() as a SafeAccessCheck that clears NormalizedUnchecked
taint. The guard is always true after realpath() but explicitly
rejects relative paths and satisfies the static analysis requirement.

Regression was introduced in d22ebde (#110) which relaxed the home
directory containment check to allow paths outside ~/. This removed
the startswith(home_dir) guard that CodeQL relied on.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: apply black formatting to test assertions

* chore: remove test artifacts accidentally included in merge

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants