Skip to content

fix: enforce deny_read, deny_exec, and config protection inside sandbox#5

Merged
machado144 merged 2 commits intomainfrom
fix/sandbox-security-enforcement
Feb 13, 2026
Merged

fix: enforce deny_read, deny_exec, and config protection inside sandbox#5
machado144 merged 2 commits intomainfrom
fix/sandbox-security-enforcement

Conversation

@machado144
Copy link
Contributor

Summary

Real-world security testing revealed that none of the sandbox policies were actually enforced inside the namespace — deny_read, deny_exec, and config protection all failed silently while the target command ran normally.

  • Root cause: buildMountOverrides() joined all mount commands with &&. Any single mount failure cascaded to skip ALL subsequent security setup, but the target command still executed (separated by ; from the last for-loop)
  • deny_read fix: Each mount is now independent (newline-separated with || true), paths are quoted, and mount --make-rprivate / is called at sandbox entry for compatibility
  • deny_exec fix: New buildExecDenyOverrides() kernel-enforces blocks via mount --bind overlays on Linux and Seatbelt (deny process-exec) on macOS
  • Config protection: ~/.aigate/ is hidden via tmpfs (Linux) / Seatbelt deny (macOS)
  • Tilde expansion: resolvePatterns() now expands ~/ prefixes; default config updated with ~/.ssh/, ~/.kube/config, ~/.npmrc, etc.

Test plan

  • All 70+ unit tests pass (make test)
  • Build succeeds (make build-local)
  • Manual: ./aigate init --force && ./aigate run -- sh -c 'curl --version' → blocked
  • Manual: ./aigate run -- sh -c 'cat ~/.aigate/config.yaml' → denied
  • Manual: ./aigate run -- sh -c 'cat ~/.kube/config' → denied
  • Manual: ./aigate run -- sh -c 'cat .env' → denied (if .env exists in workdir)

Real-world testing revealed three critical security gaps:
- ~/.kube/config and ~/.npmrc were fully readable (leaked credentials)
- All deny_exec commands ran normally inside the sandbox
- ~/.aigate/config.yaml was readable from inside the sandbox

Root cause: buildMountOverrides() joined all mounts with && — any single
mount failure cascaded to skip ALL subsequent security setup (mount
overrides, exec deny, config protection), while the target command still
ran because it was separated by ; from the last for-loop.

Fixes:
- Add mount --make-rprivate / at sandbox entry (ensures bind mounts work
  in all environments, not just util-linux 2.27+)
- Rewrite buildMountOverrides() with independent mounts (each on its own
  line with || true, no cascading && chains)
- Rewrite buildExecDenyOverrides() as standalone commands (not chained to
  mount overrides)
- Add tilde expansion (~/) in resolvePatterns() for home-dir paths
- Update default config: ~/.ssh/, ~/.aws/, ~/.gcloud/, ~/.kube/config,
  ~/.npmrc, ~/.pypirc use ~/ prefix; add kubectl delete/exec examples
- Protect ~/.aigate/ config dir: tmpfs on Linux, Seatbelt deny on macOS
- Add Seatbelt (deny process-exec) rules for deny_exec on macOS
- Quote all paths in mount commands for space safety
- Use exec for target command in runUnshare (matches buildNetFilterScript)
Resource limits are defined in the config but not yet enforced.
Update docs to reflect this rather than implying they work.
@machado144 machado144 merged commit d3aaa34 into main Feb 13, 2026
2 checks passed
murilopmachado pushed a commit that referenced this pull request Mar 5, 2026
…ox (#5)

Real-world security testing revealed that none of the sandbox policies were
actually enforced inside the namespace — deny_read, deny_exec, and config
protection all failed silently while the target command ran normally.

Root cause: buildMountOverrides() joined all mount commands with && — any
single mount failure cascaded to skip ALL subsequent security setup, but the
target command still executed (separated by ; from the last for-loop).

Fixes:
- Add mount --make-rprivate / at sandbox entry for bind mount compatibility
- Rewrite buildMountOverrides() with independent mounts (no cascading &&)
- Add buildExecDenyOverrides() for kernel-level deny_exec enforcement
- Add tilde expansion (~/) in resolvePatterns() for home-dir paths
- Protect ~/.aigate/ config dir: tmpfs on Linux, Seatbelt deny on macOS
- Add Seatbelt (deny process-exec) rules for deny_exec on macOS
- Quote all paths in mount commands for space safety
- Update default config with ~/ prefixes and subcommand examples
- Mark resource_limits as coming soon in docs
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