add ai-sandbox: rootless podman sandbox for agentic development
A single bash script that runs a coding harness (pi by default) inside a rootless Ubuntu 24.04 container with only the current directory mounted at /project, an explicit egress allowlist and an explicit environment: nothing is passed in that was not named on the command line or in a config file. Network enforcement here is empirical rather than assumed (tests/spike_network.sh, docs/networking.md). The original design -- a sidecar proxy on an internal network -- does not work rootless on this host, so allow mode runs pasta with --map-host-loopback, installs an inet aisb ruleset inside the container that drops every egress path except the gate's, and relays through a squid gate bound to the host's loopback that resolves names itself and refuses anything unlisted. The entrypoint checks that the default-drop rule really exists before setpriv drops to uid 1000 with an empty capability set, and exits 77/78 rather than run unenforced. The Containerfile, the entrypoint, the squid config generator and the ruleset renderer all live in bin/ai-sandbox, and the image is tagged by content hash, so a policy change can never silently reuse a stale image. Tests are pure bash: 161 assertions start no container and touch no network, 256 run real ones, including proof that a raw socket, a stripped proxy env and DNS all stay blocked while an allowlisted host works over both http and CONNECT. LLM-Generated: true
This commit is contained in:
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# tests/unit/01_args.sh -- argument handling.
|
||||
source "$TESTS/lib.sh"
|
||||
|
||||
t_name "help / version"
|
||||
sb --help; assert_rc 0 "--help exits 0"; assert_contains "NETWORK" "--help documents the network section"
|
||||
sb --version; assert_rc 0 "--version exits 0"
|
||||
sb version; assert_contains "ai-sandbox" "version subcommand prints the name"
|
||||
|
||||
t_name "flag -> harness pass-through"
|
||||
A="--allow example.com" # allow mode is the default and fails closed without this
|
||||
sb --dry-run $A --model sonnet; assert_rc 0 "unknown flag is accepted"; assert_contains "pi --model sonnet" "unknown flag and the rest are handed to pi"
|
||||
sb --dry-run $A file.txt --more; assert_contains "pi file.txt --more" "positional starts the command"
|
||||
sb --dry-run $A -- echo hi; assert_contains "echo hi" "after -- the command runs exactly as given"
|
||||
assert_absent "pi echo hi" "after -- the harness is replaced, not prefixed"
|
||||
assert_contains "--network pasta:--map-host-loopback" "and a --net after -- is not mistaken for a sandbox flag"
|
||||
sb --dry-run $A --shell; assert_contains "bash -li" "--shell runs bash"
|
||||
sb --dry-run $A --harness codex; assert_contains "codex" "--harness codex selects the command"
|
||||
sb --dry-run $A --harness none; assert_contains "bash -li" "--harness none drops to a shell"
|
||||
sb --dry-run --net none -- echo hi; assert_contains "echo hi" "--net= form before the command still parses"
|
||||
|
||||
t_name "strict args"
|
||||
sb --strict-args --model sonnet; assert_rc 1 "strict mode rejects unknown flags"
|
||||
assert_contains "unknown option" "strict mode says 'unknown option'"
|
||||
sb --strict-args --al foo; assert_contains "did you mean --allow" "strict mode suggests a near miss"
|
||||
|
||||
t_name "value validation"
|
||||
sb --net bogus --dry-run; assert_rc 1 "bad --net rejected"; assert_contains "--net must be none, allow or full" "names the valid values"
|
||||
sb --pids abc --dry-run; assert_rc 1 "bad --pids rejected"
|
||||
sb --cpus x4 --dry-run; assert_rc 1 "bad --cpus rejected"
|
||||
sb --memory huge --dry-run; assert_rc 1 "bad --memory rejected"
|
||||
sb --allow; assert_rc 1 "--allow with no value"
|
||||
sb --allow-file /nope/missing; assert_rc 1 "--allow-file must exist"
|
||||
sb --mount /nope/missing --dry-run; assert_rc 1 "--mount path must exist"
|
||||
|
||||
t_name "fail-closed network policy"
|
||||
sb --dry-run; assert_rc 1 "allow mode with no allowlist refuses to start"
|
||||
assert_contains "Refusing to start wide open" "explains that it refuses to be wide open"
|
||||
sb --dry-run --net none; assert_rc 0 "--net none needs no allowlist"; assert_contains "--network none" "--net none maps to podman --network none"
|
||||
sb --dry-run --net full; assert_rc 0 "--net full is allowed (explicit opt-in)"
|
||||
assert_contains "unrestricted internet" "--net full warns"
|
||||
sb --dry-run --allow example.com; assert_rc 0 "one allow entry is enough"
|
||||
Reference in New Issue
Block a user