#!/usr/bin/env bash
# Purpose: prove, from inside the sandbox, that the boundary is where you think it is -
#          the host home directory is not visible, the internet is not reachable, a planted
#          secret cannot be read, the container socket is absent, the root filesystem is
#          read-only, the process is not root, and the gateway IS reachable.
# Platform: all; run inside the agent container, not on the host
# Minimum memory: 8 GB for the stack as a whole; this script needs almost nothing
# Assumes: run as `docker compose -f compose-sandbox.yaml run --rm agent bash /task/boundary-test.sh`
#          with python3 available (the sandbox image is based on a Python image), the
#          project mounted at /work and the task material at /task. Set GATEWAY_URL to the
#          gateway as seen from inside the network to exercise the positive control.
#
# Every check below is written so that FAILURE OF THE ATTEMPT IS THE PASS. A check that
# succeeds in reading, writing or connecting is a hole in the boundary.

set -uo pipefail

WORKDIR="${WORKDIR:-/work}"
GATEWAY_URL="${GATEWAY_URL:-${OPENAI_API_BASE:-}}"
LABBOOK="${LABBOOK:-$WORKDIR/labbook.md}"
SECRET_NAME="${SECRET_NAME:-planted-secret.txt}"

passes=0
failures=0
results=""

record() {
    # record <name> <pass|fail> <detail>
    local name="$1" verdict="$2" detail="$3"
    if [ "$verdict" = "pass" ]; then
        passes=$(( passes + 1 ))
        printf '  PASS  %-34s %s\n' "$name" "$detail"
    else
        failures=$(( failures + 1 ))
        printf '  FAIL  %-34s %s\n' "$name" "$detail"
    fi
    results="${results}${results:+,}\"${name}\":\"${verdict}\""
}

echo "Boundary test, run from inside the container at $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo

# 1. The host home directory must not be visible. A mounted home is the commonest hole,
#    and it is the one that makes every other control irrelevant.
if [ -d /host-home ] || [ -d /root/.ssh ] || [ -d "$HOME/.ssh" ] || [ -d "$HOME/.aws" ]; then
    record "host-home-not-mounted" "fail" "a credential directory is visible from inside"
else
    record "host-home-not-mounted" "pass" "no host credential directory is visible"
fi

# 2. The container socket must not be present. A process that can reach it can start a
#    container that mounts the host root, which ends the sandbox.
if [ -S /var/run/docker.sock ] || [ -S /run/docker.sock ] || [ -S /run/podman/podman.sock ]; then
    record "container-socket-absent" "fail" "a container socket is mounted into the sandbox"
else
    record "container-socket-absent" "pass" "no container socket present"
fi

# 3. The process must not be root. Root inside a container without user-namespace remapping
#    is uncomfortably close to root outside it.
if [ "$(id -u)" = "0" ]; then
    record "runs-as-non-root" "fail" "running as uid 0"
else
    record "runs-as-non-root" "pass" "uid $(id -u)"
fi

# 4. The root filesystem must be read-only, so an agent cannot install itself a new tool
#    or leave anything behind outside the project.
probe="/etc/boundary-probe.$$"
if : >"$probe" 2>/dev/null; then
    rm -f "$probe"
    record "root-filesystem-read-only" "fail" "wrote to /etc"
else
    record "root-filesystem-read-only" "pass" "/etc is not writable"
fi

# 5. The project must be writable, or the agent cannot do its job. This is the one check
#    where success is the pass.
probe="$WORKDIR/.boundary-probe.$$"
if : >"$probe" 2>/dev/null; then
    rm -f "$probe"
    record "project-is-writable" "pass" "$WORKDIR is writable"
else
    record "project-is-writable" "fail" "$WORKDIR is not writable; the agent cannot work"
fi

# 6. A planted secret outside the project must be unreadable. Plant it on the host before
#    running this, in a directory you did NOT mount, and pass its path in SECRET_PATH.
secret_found=false
for candidate in "${SECRET_PATH:-}" "/$SECRET_NAME" "$HOME/$SECRET_NAME" "/host-home/$SECRET_NAME"; do
    [ -z "$candidate" ] && continue
    if [ -r "$candidate" ]; then
        secret_found=true
        break
    fi
done
if [ "$secret_found" = true ]; then
    record "planted-secret-unreadable" "fail" "read a secret that is outside the project"
else
    record "planted-secret-unreadable" "pass" "no readable secret outside the project"
fi

# 7. No credential-shaped variables in the environment beyond the one gateway key. An
#    agent inherits its environment, and an environment full of tokens is a boundary that
#    was never applied.
leaked="$(env | grep -E -i '^[A-Z_]*(TOKEN|SECRET|PASSWORD|API_KEY)=' \
    | grep -v -E '^(OPENAI_API_KEY|AGENT_GATEWAY_KEY)=' || true)"
if [ -n "$leaked" ]; then
    record "environment-is-clean" "fail" "$(echo "$leaked" | cut -d= -f1 | tr '\n' ' ')"
else
    record "environment-is-clean" "pass" "only the sandbox gateway key is present"
fi

# 8. The internet must be unreachable. Two attempts against addresses that would answer
#    from any machine with an external route.
internet_reachable="$(python3 - <<'PY'
import socket

reachable = False
for host, port in (("pypi.org", 443), ("github.com", 443)):
    try:
        socket.setdefaulttimeout(4)
        socket.create_connection((host, port), timeout=4).close()
        reachable = True
        break
    except OSError:
        continue
print("yes" if reachable else "no")
PY
)"
if [ "$internet_reachable" = "yes" ]; then
    record "internet-unreachable" "fail" "opened a connection to a public host"
else
    record "internet-unreachable" "pass" "no route to a public host"
fi

# 9. The gateway must be reachable. This is the positive control: without it, a passing
#    test sheet might just mean the container has no network at all and no agent can run.
if [ -z "$GATEWAY_URL" ]; then
    record "gateway-reachable" "fail" "GATEWAY_URL is unset, so the control was not run"
else
    gateway_ok="$(GATEWAY_URL="$GATEWAY_URL" python3 - <<'PY'
import os
import urllib.error
import urllib.request

url = os.environ["GATEWAY_URL"].rstrip("/") + "/models"
try:
    with urllib.request.urlopen(url, timeout=5) as response:
        print("yes" if response.status < 500 else "no")
except urllib.error.HTTPError:
    # An authentication or method error still proves the gateway answered.
    print("yes")
except OSError:
    print("no")
PY
)"
    if [ "$gateway_ok" = "yes" ]; then
        record "gateway-reachable" "pass" "the gateway answered"
    else
        record "gateway-reachable" "fail" "the gateway did not answer; the agent cannot work"
    fi
fi

echo
echo "$passes passed, $failures failed."

RESULTS="$results" PASSES="$passes" FAILURES="$failures" LABBOOK="$LABBOOK" python3 - <<'PY'
import datetime
import json
import os

checks = json.loads("{" + os.environ["RESULTS"] + "}")
record = {
    "lab": "part-25-sandbox-your-agent",
    "recorded": datetime.datetime.now(datetime.timezone.utc)
    .replace(microsecond=0)
    .isoformat(),
    "checks": checks,
    "passed": int(os.environ["PASSES"]),
    "failed": int(os.environ["FAILURES"]),
    "boundary_holds": os.environ["FAILURES"] == "0",
}
try:
    with open(os.environ["LABBOOK"], "a", encoding="utf-8") as handle:
        handle.write(json.dumps(record) + "\n")
    print("recorded in " + os.environ["LABBOOK"])
except OSError as error:
    print("could not write the lab notebook: " + str(error))
PY

if [ "$failures" -ne 0 ]; then
    echo
    echo "The boundary does not hold. Fix the failing check before running an agent here." >&2
    exit 1
fi
