Resolving macOS Sandbox Restrictions: Migrating Dev Workflows to EC2 for Reliable File Access
What Was Done
During a recent development session, we encountered a fundamental limitation in how macOS handles file system access through the sandbox: certain directories under /Users/cb/Documents/—specifically those containing JADA operational repos and handoff files—return Operation not permitted errors even when running commands locally. Rather than fighting the sandbox, we implemented a principled workaround: we established a direct pattern for all development work to flow through EC2 (specifically the Lightsail instance at ip-172-26-6-34), reserving local CLI access for triage and lightweight operations only.
This post documents the exact issue, why it occurs, and the architectural pattern we're adopting to prevent future friction.
The Root Cause: macOS TCC and Sandbox Restrictions
The immediate symptom was straightforward: attempting to read files from /Users/cb/Documents/repos/agent_handoffs/projects/jada-charter-system-2026-05-30.md returned nothing, and attempts to list that directory returned Operation not permitted. This is not a file permissions issue—it's a macOS Transparency, Consent, and Control (TCC) sandbox restriction.
When a process (in this case, the shell or a command-line tool) tries to access files in protected system directories on modern macOS, the kernel checks the TCC database. Even though the files are owned by the user and have proper Unix permissions, TCC can block access based on:
- The requesting process's code signature
- Whether the user has explicitly granted consent for that process to access Documents or Desktop
- Whether the access is happening through a sandboxed vs. unsandboxed context
The Documents/ directory is particularly sensitive because it contains user data. Once TCC denies access, retrying the same command produces the same denial—there's no workaround short of:
- Disabling SIP (System Integrity Protection), which is strongly discouraged
- Granting full disk access to the terminal, which requires a reboot and involves security tradeoffs
- Moving the working directories outside of protected paths
- Accessing files through a remote session (SSH), which bypasses the local TCC check
Why We Chose Remote-First for Dev Work
Our standing rule, documented in feedback_dev_on_ec2_only.md, already stated: "ALL DEV ON EC2 — local CLI is triage only." This session validated that principle. Here's why remote-first makes sense for JADA operations:
- Consistency: EC2 and Lightsail environments are not subject to macOS sandbox restrictions. A command that fails locally will run identically on the remote box.
- Reproducibility: Dev work on EC2 can be logged, audited, and reproduced without OS-specific workarounds.
- CI/CD alignment: Our actual deployment pipelines run on Linux. Testing locally on macOS introduces false positives and false negatives.
- Team accessibility: Other team members can SSH to the same EC2 instance and continue work without OS-specific setup.
The tradeoff is a small SSH latency tax, which is negligible for typical dev workflows.
Implementing the Pattern: Local Triage, Remote Development
We've formalized a two-tier access pattern:
Local (macOS) operations—read-only, lightweight:
# List available handoff files
ls -la ~/Documents/repos/jada-ops/handoffs/
# Check file size without opening
stat ~/Documents/repos/jada-ops/proposals/dylan-july4-opus.html
# Probe connectivity and SSH key status
ssh -o ConnectTimeout=5 ubuntu@34.239.233.28 "echo 'SSH ready'"
Remote (EC2/Lightsail) operations—all read-write development:
# Read handoff files from EC2
ssh ubuntu@34.239.233.28 "cat /home/ubuntu/jada-ops/handoffs/charter-system-2026-05-30.md"
# Copy files from EC2 to local temp for review
scp ubuntu@34.239.233.28:/home/ubuntu/proposals/dylan-july4-opus.html /tmp/local-review.html
# Execute dev scripts remotely
ssh ubuntu@34.239.233.28 "cd /home/ubuntu/jada-ops && ./scripts/validate-proposals.sh"
For interactive work, we establish SSH sessions:
ssh ubuntu@34.239.233.28
# Now in remote shell
cd ~/jada-ops/projects
cat jada-charter-system-2026-05-30.md
Infrastructure Snapshot
The JADA operational infrastructure currently spans:
- EC2 instance:
34.239.233.28(primary dev/handoff repo) - Lightsail instance:
ip-172-26-6-34(secondary/failover, searched during diagnosis) - Local Mac:
/Users/cb/jada-ops-work/(working copy for proposals, synced viascp) - iCloud Drive:
~/Library/Mobile Documents/com~apple~CloudDocs/jada-ops/proposals/(read-only reference, avoids TCC issues for Finder-sync'd content)
SSH key configuration lives in ~/.ssh/config with pre-configured host aliases for both instances, enabling fast SSH without typing full addresses.
Key Decisions and Their Rationale
Decision: Accept SSH latency over fighting macOS sandbox.
The alternative—granting full disk access to the terminal app—would weaken system security for marginal speed gain. SSH latency for a 50-line handoff read is under 500ms, negligible in dev cycles.
Decision: Mirror working directories locally only for specific workflows.
We copy proposal files and review docs to ~/jada-ops-work/proposals/ using scp after they're finalized on EC2. This keeps the local working directory lean and avoids sync conflicts, since local changes don't flow back to EC2 automatically.
Decision: Use EC2 as the single source of truth for handoffs.
The file jada-charter-system-2026-05-30.md lives on EC2. If it needs to be read locally, it's fetched fresh via SSH. This prevents stale copies and merge conflicts.
What's Next
Short-term, we're documenting SSH access patterns in a new runbook so team members can replicate this setup. Medium-term, we're considering:
- Automating proposal syncs from EC2 to local working directories using a scheduled
scpor rsync wrapper - Setting up SSH agent forwarding to allow operations on the EC2 box that may need to reach S3 or other AWS resources
- Evaluating whether Lightsail is still necessary, given EC2 is