Files
ai-sandbox/tests/unit/02_config.sh
T
ducoterra 03ab9c0cb1 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
2026-10-27 15:11:29 -04:00

79 lines
2.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# tests/unit/02_config.sh -- config files, allowlists and precedence.
source "$TESTS/lib.sh"
CFG="$WORK/cfg"
REPO="$WORK/repo"
mkdir -p "$CFG" "$REPO/.ai-sandbox"
export AISB_CONFIG_DIR="$CFG"
cd "$REPO" || exit 1
t_name "allow files are merged"
cat > "$CFG/allow" <<'EOF'
# user-wide allowlist
api.openai.com
registry.npmjs.org # trailing comment
EOF
cat > "$REPO/.ai-sandbox/allow" <<'EOF'
pypi.org
EOF
sb --print-policy --net allow
assert_rc 0 "print-policy runs"
assert_contains "api.openai.com" "user allowlist entry present"
assert_contains "registry.npmjs.org" "user entry with trailing comment present"
assert_contains "pypi.org" "repo allowlist entry present"
t_name "dedup"
sb --print-policy --net allow --allow pypi.org --allow pypi.org
ALLOW_LINE="$(printf '%s' "$OUT" | grep '^allow=')"
N="$(printf '%s' "$ALLOW_LINE" | grep -o 'pypi.org' | wc -l | tr -d ' ')"
assert_eq "1" "$N" "duplicate allow entries collapse into one"
t_name "key = value config"
cat > "$CFG/config" <<'EOF'
net = none
harness = none
cpus = 7
memory = 1536m
pids = 77
selinux = :Z
EOF
sb --print-policy
assert_contains "net=none" "config file sets net"
assert_contains "harness=none" "config file sets harness"
assert_contains "cpus:7" "config file sets cpus"
assert_contains "memory:1536m" "config file sets memory"
assert_contains "pids:77" "config file sets pids"
assert_contains "selinux=:Z" "config file sets selinux"
t_name "precedence: flag > env > config file"
sb --print-policy --net allow
assert_contains "net=allow" "flag beats config file"
sb_env AISB_NET=full --print-policy
assert_contains "net=full" "AISB_* env beats config file"
sb_env AISB_NET=full --print-policy --net none
assert_contains "net=none" "flag beats AISB_* env"
t_name "AISB_ALLOW / AISB_ENV lists"
sb_env AISB_ALLOW=a.example.com,b.example.com:8443 --print-policy --net none
assert_contains "a.example.com" "AISB_ALLOW entry 1"
assert_contains "b.example.com:8443" "AISB_ALLOW entry 2 with port"
sb_env AISB_ENV=HOME_X,TMPDIR --print-policy --net none
assert_contains "env_names=HOME_X TMPDIR" "AISB_ENV populates env names"
assert_contains "env_missing=HOME_X" "unset names are reported by --print-policy"
t_name "repo config wins per-directory"
cat > "$REPO/.ai-sandbox/config" <<'EOF'
pids = 42
EOF
sb --print-policy --net none
assert_contains "pids:42" "repo config overrides user config"
t_name "unknown keys warn but do not break"
echo 'bogus_key = 1' >> "$CFG/config"
sb --print-policy --net none
assert_contains "unknown key: bogus_key" "unknown key warns"
assert_rc 0 "unknown key is not fatal"