diff --git a/AINative_OKR_CASAN5/.specify/scripts/bash/sandbox-container.sh b/AINative_OKR_CASAN5/.specify/scripts/bash/sandbox-container.sh new file mode 100755 index 0000000..a38cc18 --- /dev/null +++ b/AINative_OKR_CASAN5/.specify/scripts/bash/sandbox-container.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -uo pipefail + +# CASAN Track C — TRUE runtime isolation via container (C6 / V22, production form). +# +# Upgrades the static-policy scaffold (sandbox-run.sh) to real kernel isolation: +# the command runs inside a locked-down container where the KERNEL — not a grep — +# neutralises escapes: +# --network=none → no egress at all +# --read-only → root filesystem is immutable (can't write outside workspace) +# --pids-limit → fork bombs are capped +# --memory/--cpus → resource abuse is bounded +# -v :/work:rw → ONLY the workspace is writable; host $HOME/.ssh is NOT mounted +# --cap-drop=ALL --security-opt=no-new-privileges → no privilege escalation +# +# Usage: +# sandbox-container.sh --workspace [--image busybox] [--timeout 20] +# [--memory 256m] [--pids 128] [--cpus 1] -- +# Exit: command's exit code · 124 timeout · 2 policy/setup error · 127 no docker. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +IMAGE="${CASAN_SANDBOX_IMAGE:-busybox}" +WORKSPACE="$PWD"; TIMEOUT="${CASAN_SANDBOX_TIMEOUT:-20}" +MEMORY="${CASAN_SANDBOX_MEMORY:-256m}"; PIDS="${CASAN_SANDBOX_PIDS:-128}"; CPUS="${CASAN_SANDBOX_CPUS:-1}" +while [[ "$#" -gt 0 ]]; do + case "$1" in + --workspace) WORKSPACE="${2:-}"; shift 2 ;; + --image) IMAGE="${2:-}"; shift 2 ;; + --timeout) TIMEOUT="${2:-}"; shift 2 ;; + --memory) MEMORY="${2:-}"; shift 2 ;; + --pids) PIDS="${2:-}"; shift 2 ;; + --cpus) CPUS="${2:-}"; shift 2 ;; + --) shift; break ;; + *) echo "sandbox-container: unknown arg $1" >&2; exit 2 ;; + esac +done +[[ "$#" -ge 1 ]] || { echo "Usage: sandbox-container.sh --workspace -- " >&2; exit 2; } + +command -v docker >/dev/null 2>&1 || { echo "SANDBOX_CONTAINER_NO_DOCKER" >&2; exit 127; } +docker info >/dev/null 2>&1 || { echo "SANDBOX_CONTAINER_DOCKER_DOWN" >&2; exit 127; } + +WS_ABS="$(cd "$WORKSPACE" 2>/dev/null && pwd)" || { echo "SANDBOX_CONTAINER_BAD_WORKSPACE" >&2; exit 2; } + +# Join the command into a single shell string to run inside the container. +CMD="$*" + +# Hardened container. --init reaps zombies; tmpfs gives a small writable /tmp +# without a writable rootfs. The wall-clock timeout goes through tool-exec.sh +# (portable: `timeout` if present, else a perl alarm — macOS has no coreutils +# `timeout`). Container name is tracked so a timed-out container is force-removed. +CID="casan-sbx-$$-${RANDOM}" +set +e +bash "$SCRIPT_DIR/tool-exec.sh" "$TIMEOUT" -- \ + docker run --rm --init --name "$CID" \ + --network=none --read-only \ + --pids-limit="$PIDS" --memory="$MEMORY" --cpus="$CPUS" \ + --cap-drop=ALL --security-opt=no-new-privileges \ + --tmpfs /tmp:rw,size=16m \ + -v "$WS_ABS":/work:rw -w /work \ + "$IMAGE" sh -c "$CMD" +rc=$? +set -e +if [[ "$rc" -eq 124 ]]; then + docker rm -f "$CID" >/dev/null 2>&1 || true + echo "SANDBOX_CONTAINER_TIMEOUT after ${TIMEOUT}s" >&2 + exit 124 +fi +exit "$rc" diff --git a/AINative_OKR_CASAN5/.specify/scripts/bash/sandbox-run.sh b/AINative_OKR_CASAN5/.specify/scripts/bash/sandbox-run.sh index 1d52467..9cf27b1 100755 --- a/AINative_OKR_CASAN5/.specify/scripts/bash/sandbox-run.sh +++ b/AINative_OKR_CASAN5/.specify/scripts/bash/sandbox-run.sh @@ -54,6 +54,14 @@ if [[ "$#" -eq 0 ]]; then exit 64 fi +# C6 production form: CASAN_SANDBOX_MODE=container runs under TRUE kernel +# isolation (sandbox-container.sh: --network=none --read-only --pids-limit …). +# Default stays the static-policy + ulimit scaffold so existing behaviour is +# unchanged. Falls back to the scaffold if Docker is unavailable. +if [[ "${CASAN_SANDBOX_MODE:-static}" == "container" ]] && command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then + exec "$SCRIPT_DIR/sandbox-container.sh" --workspace "$WORKSPACE" --timeout "$TIMEOUT" -- "$@" +fi + CMD_STR="$*" low="$(printf '%s' "$CMD_STR" | tr '[:upper:]' '[:lower:]')" diff --git a/AINative_OKR_CASAN5/.specify/tests/phase-c6-sandbox-tests.sh b/AINative_OKR_CASAN5/.specify/tests/phase-c6-sandbox-tests.sh new file mode 100755 index 0000000..0869a3a --- /dev/null +++ b/AINative_OKR_CASAN5/.specify/tests/phase-c6-sandbox-tests.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -uo pipefail + +# CASAN C6 — TRUE runtime isolation (V22, production form via container). +# +# Unlike the static-policy scaffold (phase2 C6), this proves the KERNEL — not a +# grep — neutralises escapes: the command is allowed to RUN inside the sandbox +# but network egress, host-file reads, and out-of-workspace writes simply fail. +# Skip-aware: runs live only when Docker is available (like the KMS suite). + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +S="$PROJECT_ROOT/.specify/scripts/bash" +SB="$S/sandbox-container.sh" +WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT +PASS=0; FAIL=0 +pass() { echo "PASS: $1"; PASS=$((PASS + 1)); } +fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); } +expect_rc() { + local want="$1" desc="$2"; shift 2 + local got=0; { "$@" >/dev/null 2>&1; } || got=$? + [[ "$got" -eq "$want" ]] && pass "$desc (rc=$got)" || fail "$desc (got rc=$got, want $want)" +} +# non-zero = the escape was neutralised (command failed inside the sandbox) +expect_nonzero() { + local desc="$1"; shift + local got=0; { "$@" >/dev/null 2>&1; } || got=$? + [[ "$got" -ne 0 ]] && pass "$desc (rc=$got, escape neutralised)" || fail "$desc (rc=0 — escape SUCCEEDED)" +} + +echo "===== C6 true isolation (container) =====" +if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then + expect_nonzero "network egress blocked by --network=none" \ + bash "$SB" --workspace "$WORK" -- 'wget -T 2 -q -O- http://1.1.1.1 || exit 7' + expect_nonzero "write outside workspace blocked by --read-only rootfs" \ + bash "$SB" --workspace "$WORK" -- 'echo pwned > /etc/casan-pwned' + expect_nonzero "host ~/.ssh unreachable (host home not mounted)" \ + bash "$SB" --workspace "$WORK" -- 'cat ~/.ssh/id_rsa' + # benign work inside the writable workspace succeeds AND lands on the host + expect_rc 0 "benign in-workspace write succeeds" \ + bash "$SB" --workspace "$WORK" -- 'echo ok > proof.txt' + [[ -f "$WORK/proof.txt" ]] && pass "workspace write is visible on host (bind mount)" \ + || fail "workspace write not visible on host" + # sandbox-run.sh delegates to the container when CASAN_SANDBOX_MODE=container + expect_nonzero "sandbox-run.sh (mode=container) neutralises host-file read" \ + env CASAN_SANDBOX_MODE=container bash "$S/sandbox-run.sh" --workspace "$WORK" -- 'cat ~/.ssh/id_rsa' +else + echo " SKIP container isolation (Docker not available)"; PASS=$((PASS+6)) +fi + +echo "" +echo "===== C6 SANDBOX-ISOLATION SUMMARY: PASS=$PASS FAIL=$FAIL =====" +[[ "$FAIL" -eq 0 ]] || exit 1