#!/usr/bin/env bash set -euo pipefail ROOT_DIR=$( cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 2>&1 pwd -P ) SLOPTRAP_BIN="$ROOT_DIR/sloptrap" TEST_ROOT="$ROOT_DIR/tests" if [[ ! -x $SLOPTRAP_BIN ]]; then printf 'error: sloptrap launcher not found at %s\n' "$SLOPTRAP_BIN" >&2 exit 1 fi failures=() can_run_script_pty() { if ! command -v script >/dev/null 2>&1; then return 1 fi if ! script -q -c "true" /dev/null >/dev/null 2>&1; then return 1 fi return 0 } run_shellcheck() { printf '==> shellcheck\n' if ! command -v shellcheck >/dev/null 2>&1; then record_failure "shellcheck: shellcheck binary not found in PATH" return fi if ! shellcheck "$SLOPTRAP_BIN"; then record_failure "shellcheck: lint errors detected" fi } setup_stub_env() { STUB_BIN=$(mktemp -d) STUB_HOME=$(mktemp -d) STUB_LOG=$(mktemp) cat >"$STUB_BIN/podman" <<'EOF' #!/usr/bin/env bash set -euo pipefail if [[ -z ${FAKE_PODMAN_LOG:-} ]]; then printf 'FAKE_PODMAN_LOG is not set\n' >&2 exit 1 fi verify_secret_mounts() { local -a args=("$@") if [[ -z ${SECRET_MASK_EXPECTED_TARGET:-} ]]; then return 0 fi local saw=0 local idx=0 while (( idx < ${#args[@]} )); do local arg=${args[$idx]} if [[ $arg == "--mount" ]]; then ((idx++)) if (( idx >= ${#args[@]} )); then printf 'podman stub: --mount missing spec\n' >&2 return 1 fi local spec=${args[$idx]} local target="" local source="" IFS=',' read -r -a parts <<< "$spec" for part in "${parts[@]}"; do case $part in target=*) target=${part#target=} ;; source=*) source=${part#source=} ;; esac done if [[ -n ${SECRET_MASK_EXPECTED_TARGET:-} && $target == "$SECRET_MASK_EXPECTED_TARGET" ]]; then saw=1 if [[ -z $source || ! -f $source ]]; then printf 'podman stub: masked source missing for %s\n' "$target" >&2 return 1 fi if [[ -s $source ]]; then printf 'podman stub: masked source leaked contents (%s)\n' "$source" >&2 return 1 fi fi fi ((idx++)) done if [[ $saw -eq 0 ]]; then printf 'podman stub: target %s not mounted\n' "${SECRET_MASK_EXPECTED_TARGET:-}" >&2 return 1 fi return 0 } maybe_create_helper_pidfile() { local -a args=("$@") local codex_source="" local helper_dir="" local idx=0 while (( idx < ${#args[@]} )); do local arg=${args[$idx]} case "$arg" in -v) idx=$((idx + 1)) if (( idx < ${#args[@]} )); then local spec=${args[$idx]} case "$spec" in *:/codex|*:/codex:* ) codex_source=${spec%%:/codex*} ;; esac fi ;; -e) idx=$((idx + 1)) if (( idx < ${#args[@]} )); then local envspec=${args[$idx]} case "$envspec" in SLOPTRAP_HELPER_DIR=*) helper_dir=${envspec#SLOPTRAP_HELPER_DIR=} ;; esac fi ;; esac idx=$((idx + 1)) done if [[ -z $codex_source || $helper_dir != /codex/* ]]; then return 0 fi local helper_host=${codex_source}/${helper_dir#/codex/} mkdir -p "$helper_host" printf '12345\n' >"$helper_host/helperd.pid" } if [[ ${1-} == "image" && ${2-} == "inspect" && ${FAKE_PODMAN_INSPECT_FAIL:-0} == 1 ]]; then if [[ " $* " == *" --format "* ]]; then printf 'fake-image-id\n' exit 0 fi echo "FAKE PODMAN (fail): $*" >>"$FAKE_PODMAN_LOG" exit 1 fi if [[ ${SECRET_MASK_VERIFY:-0} == 1 && ${1-} == "run" ]]; then if ! verify_secret_mounts "$@"; then echo "FAKE PODMAN (secret-check failed): $*" >>"$FAKE_PODMAN_LOG" exit 1 fi fi if [[ ${1-} == "run" ]]; then maybe_create_helper_pidfile "$@" fi echo "FAKE PODMAN: $*" >>"$FAKE_PODMAN_LOG" exit 0 EOF chmod +x "$STUB_BIN/podman" cp "$STUB_BIN/podman" "$STUB_BIN/docker" chmod +x "$STUB_BIN/docker" cat >"$STUB_BIN/curl" <<'EOF' #!/usr/bin/env bash set -euo pipefail if [[ ${1-} == "-fsSL" ]]; then cat <<'JSON' {"assets":[{"name":"codex-x86_64-unknown-linux-gnu.tar.gz","digest":"sha256:feedface"}]} JSON exit 0 fi if [[ ${1-} == "-Lso" ]]; then if [[ $# -lt 3 ]]; then echo "curl stub expected output path" >&2 exit 1 fi output=$2 : >"$output" exit 0 fi printf 'curl stub encountered unsupported args: %s\n' "$*" >&2 exit 1 EOF chmod +x "$STUB_BIN/curl" cat >"$STUB_BIN/jq" <<'EOF' #!/usr/bin/env bash set -euo pipefail if [[ ${1-} == "-n" ]]; then shift exec /usr/bin/jq -n "$@" fi while [[ $# -gt 0 ]]; do case "$1" in -r) shift continue ;; *) break ;; esac done cat >/dev/null printf 'sha256:feedface\n' EOF chmod +x "$STUB_BIN/jq" cat >"$STUB_BIN/sha256sum" <<'EOF' #!/usr/bin/env bash set -euo pipefail if [[ ${1-} == "-c" ]]; then shift if [[ ${1-} == "-" ]]; then shift cat >/dev/null exit 0 fi fi if [[ -x /usr/bin/sha256sum ]]; then exec /usr/bin/sha256sum "$@" fi printf 'sha256sum stub encountered unsupported args: %s\n' "$*" >&2 exit 1 EOF chmod +x "$STUB_BIN/sha256sum" cat >"$STUB_BIN/tar" <<'EOF' #!/usr/bin/env bash set -euo pipefail dest="" new_name="codex" while [[ $# -gt 0 ]]; do case "$1" in -C) shift if [[ $# -eq 0 ]]; then echo "tar stub missing directory for -C" >&2 exit 1 fi dest=$1 ;; --transform=*) transform=${1#--transform=} transform=${transform#s/} rest=${transform#*/} new_name=${rest%%/*} ;; esac shift done [[ -n $dest ]] || dest="." mkdir -p "$dest" cat >"$dest/$new_name" <<'BIN' #!/usr/bin/env bash echo "fake codex" BIN chmod +x "$dest/$new_name" EOF chmod +x "$STUB_BIN/tar" } teardown_stub_env() { rm -rf "${STUB_BIN:-}" "${STUB_HOME:-}" rm -f "${STUB_LOG:-}" } extract_embedded_helper() { local helper=$1 local output=$2 if ! awk -v helper="$helper" ' $0 ~ "^[[:space:]]*" helper "\\)$" { state=1; next } state == 1 && /cat <<'\''EOF'\''$/ { state=2; next } state == 2 && /^EOF$/ { exit } state == 2 { print } ' "$SLOPTRAP_BIN" >"$output"; then return 1 fi chmod +x "$output" } wait_for_path() { local path=$1 local attempts=${2:-100} while (( attempts > 0 )); do if [[ -e $path ]]; then return 0 fi sleep 0.1 ((attempts-=1)) done return 1 } record_failure() { failures+=("$1") } run_mount_injection() { local scenario_dir="$TEST_ROOT/mount_injection" printf '==> mount_injection\n' setup_stub_env rm -rf "$scenario_dir/.sloptrap-ignores" if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "mount_injection: sloptrap exited non-zero" teardown_stub_env return fi if ! grep -q -- "--mount type=bind" "$STUB_LOG"; then record_failure "mount_injection: bind mount invocation missing" fi if grep -q -- "attack,source=/etc/passwd" "$STUB_LOG"; then record_failure "mount_injection: unescaped mount key detected" fi if ! grep -q -- "attack\\\\,source\\\\=/etc/passwd" "$STUB_LOG"; then record_failure "mount_injection: escaped mount key missing" fi if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then record_failure "mount_injection: image build path did not trigger" fi teardown_stub_env } run_root_target() { local scenario_dir="$TEST_ROOT/root_target" printf '==> root_target\n' if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "root_target: expected rejection for project-root mask" return fi } run_symlink_escape() { local scenario_dir="$TEST_ROOT/symlink_escape" printf '==> symlink_escape\n' local secret_path="$ROOT_DIR/secrets.txt" touch "$secret_path" if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "symlink_escape: expected failure for symlink escape" rm -f "$secret_path" return fi rm -f "$secret_path" } run_manifest_injection() { local scenario_dir="$TEST_ROOT/manifest_injection" printf '==> manifest_injection\n' if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "manifest_injection: expected rejection of bad make override" return fi } run_helper_symlink() { local scenario_dir="$TEST_ROOT/helper_symlink" printf '==> helper_symlink\n' if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "helper_symlink: expected rejection when helper directory is a symlink" fi if "$SLOPTRAP_BIN" "$scenario_dir" clean /dev/null 2>&1; then record_failure "helper_symlink: expected rejection for clean when helper directory is a symlink" fi } run_secret_mask() { local scenario_dir="$TEST_ROOT/secret_mask" printf '==> secret_mask\n' setup_stub_env local custom_workdir="/alt-workspace" if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" \ FAKE_PODMAN_INSPECT_FAIL=1 SECRET_MASK_VERIFY=1 \ SECRET_MASK_EXPECTED_TARGET="${custom_workdir}/secret.txt" \ SLOPTRAP_WORKDIR="$custom_workdir" \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "secret_mask: masking check failed" teardown_stub_env return fi teardown_stub_env } run_resume_target() { local scenario_dir="$TEST_ROOT/resume_target" printf '==> resume_target\n' setup_stub_env local session_id="019a81b7-32d2-7622-8639-6698c6579625" if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" \ "$SLOPTRAP_BIN" "$scenario_dir" resume "$session_id" /dev/null 2>&1; then record_failure "resume_target: sloptrap exited non-zero" teardown_stub_env return fi if ! grep -q -- "resume $session_id" "$STUB_LOG"; then record_failure "resume_target: resume invocation missing" fi teardown_stub_env } run_runtime_context_prompt() { local scenario_dir="$TEST_ROOT/host_network_repo" printf '==> runtime_context_prompt\n' setup_stub_env if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "runtime_context_prompt: sloptrap exited non-zero" teardown_stub_env return fi local login_line run_line login_line=$(grep "FAKE PODMAN: run " "$STUB_LOG" | head -n 1 || true) run_line=$(grep "FAKE PODMAN: run " "$STUB_LOG" | tail -n 1 || true) if [[ -z $run_line || $run_line != *"You are running inside sloptrap"* ]]; then record_failure "runtime_context_prompt: startup prompt missing from fresh run" fi if ! grep -q -- "name=host-network-repo" "$STUB_LOG" \ || ! grep -q -- "network_mode=host" "$STUB_LOG"; then record_failure "runtime_context_prompt: runtime summary missing manifest state" fi if [[ -n $login_line && $login_line == *"You are running inside sloptrap"* ]]; then record_failure "runtime_context_prompt: login flow should not receive startup prompt" fi teardown_stub_env } run_sh_reexec() { local scenario_dir="$TEST_ROOT/host_network_repo" printf '==> sh_reexec\n' setup_stub_env if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ sh "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "sh_reexec: sloptrap exited non-zero when launched via sh" teardown_stub_env return fi if ! grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then record_failure "sh_reexec: startup prompt missing after sh re-exec" fi teardown_stub_env } run_resume_omits_runtime_context() { local scenario_dir="$TEST_ROOT/host_network_repo" local session_id="019a81b7-32d2-7622-8639-6698c6579625" printf '==> resume_omits_runtime_context\n' setup_stub_env if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" resume "$session_id" /dev/null 2>&1; then record_failure "resume_omits_runtime_context: sloptrap exited non-zero" teardown_stub_env return fi if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then record_failure "resume_omits_runtime_context: resume should not receive startup prompt" fi if ! grep -q -- "--sandbox danger-full-access --ask-for-approval never resume $session_id" "$STUB_LOG"; then record_failure "resume_omits_runtime_context: resume invocation missing" fi teardown_stub_env } run_shell_target_uses_entrypoint() { local scenario_dir="$TEST_ROOT/opencode_localhost" printf '==> shell_target_uses_entrypoint\n' setup_stub_env if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" shell /dev/null 2>&1; then record_failure "shell_target_uses_entrypoint: shell target failed" teardown_stub_env return fi if ! grep -q -- "--entrypoint /bin/bash" "$STUB_LOG"; then record_failure "shell_target_uses_entrypoint: missing entrypoint override" fi if grep -q -- "/codex/auth.json" "$STUB_LOG"; then record_failure "shell_target_uses_entrypoint: codex auth mount should not be present for opencode shell" fi teardown_stub_env } run_auth_file_mount() { local scenario_dir scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P) printf '==> auth_file_mount\n' setup_stub_env mkdir -p "$STUB_HOME/.codex" printf '{"access_token":"test"}\n' >"$STUB_HOME/.codex/auth.json" if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "auth_file_mount: sloptrap exited non-zero" teardown_stub_env return fi if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z,ro" "$STUB_LOG"; then record_failure "auth_file_mount: auth file should be mounted read-only for normal runs" fi if ! grep -q -- "-v ${STUB_HOME}/.codex/sloptrap/state/" "$STUB_LOG"; then record_failure "auth_file_mount: missing project state bind mount" fi teardown_stub_env } run_runtime_hardening_flags() { local scenario_dir scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P) printf '==> runtime_hardening_flags\n' setup_stub_env mkdir -p "$STUB_HOME/.codex" printf '{"access_token":"test"}\n' >"$STUB_HOME/.codex/auth.json" if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "runtime_hardening_flags: sloptrap exited non-zero" teardown_stub_env return fi if ! grep -q -- "--cap-drop ALL" "$STUB_LOG"; then record_failure "runtime_hardening_flags: cap-drop flag missing" fi if ! grep -q -- "--read-only" "$STUB_LOG"; then record_failure "runtime_hardening_flags: read-only rootfs flag missing" fi teardown_stub_env } run_codex_home_override() { local scenario_dir codex_root scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P) printf '==> codex_home_override\n' setup_stub_env codex_root="$STUB_HOME/codex-root" mkdir -p "$codex_root" printf '{"access_token":"test"}\n' >"$codex_root/auth.json" if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" CODEX_HOME="$codex_root" SLOPTRAP_PREFER_CODEX_HOME=1 \ FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "codex_home_override: sloptrap exited non-zero" teardown_stub_env return fi if ! grep -q -- "-v ${codex_root}/auth.json:/codex/auth.json:Z,ro" "$STUB_LOG"; then record_failure "codex_home_override: CODEX_HOME auth file should be mounted read-only for normal runs" fi if ! grep -q -- "-v ${codex_root}/sloptrap/state/" "$STUB_LOG"; then record_failure "codex_home_override: missing CODEX_HOME project state bind mount" fi if grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then record_failure "codex_home_override: should not fall back to HOME/.codex when CODEX_HOME is set" fi local first_run first_run=$(grep "FAKE PODMAN: run " "$STUB_LOG" | head -n 1 || true) if [[ -z $first_run || $first_run == *" login" ]]; then record_failure "codex_home_override: existing CODEX_HOME auth should avoid login target" fi teardown_stub_env } run_project_state_isolation() { local scenario_a scenario_b scenario_a=$(cd "$TEST_ROOT/resume_target" && pwd -P) scenario_b=$(cd "$TEST_ROOT/secret_mask" && pwd -P) printf '==> project_state_isolation\n' setup_stub_env mkdir -p "$STUB_HOME/.codex" printf '{"access_token":"test"}\n' >"$STUB_HOME/.codex/auth.json" if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_a" /dev/null 2>&1; then record_failure "project_state_isolation: first project run failed" teardown_stub_env return fi if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" \ "$SLOPTRAP_BIN" "$scenario_b" /dev/null 2>&1; then record_failure "project_state_isolation: second project run failed" teardown_stub_env return fi local -a codex_mounts=() mapfile -t codex_mounts < <( { grep "FAKE PODMAN: run " "$STUB_LOG" \ | grep -oE -- "-v [^ ]+:/codex:Z" \ | sed -e 's/^-v //' -e 's/:\/codex:Z$//' } || true ) if [[ ${#codex_mounts[@]} -lt 2 ]]; then record_failure "project_state_isolation: missing /codex state mounts" teardown_stub_env return fi if [[ ${codex_mounts[0]} == "${codex_mounts[1]}" ]]; then record_failure "project_state_isolation: projects reused same Codex state mount" fi if [[ ${codex_mounts[0]} != */.codex/sloptrap/state/* || ${codex_mounts[1]} != */.codex/sloptrap/state/* ]]; then record_failure "project_state_isolation: state mounts did not use sloptrap namespace" fi teardown_stub_env } run_auto_login_empty_auth() { local scenario_dir scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P) printf '==> auto_login_empty_auth\n' setup_stub_env mkdir -p "$STUB_HOME/.codex" : >"$STUB_HOME/.codex/auth.json" if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "auto_login_empty_auth: sloptrap exited non-zero" teardown_stub_env return fi local first_run first_run=$(grep "FAKE PODMAN: run " "$STUB_LOG" | head -n 1 || true) if [[ -z $first_run || $first_run != *" login" ]]; then record_failure "auto_login_empty_auth: expected login before primary run" fi if [[ -z $first_run || $first_run != *"-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z "* ]]; then record_failure "auto_login_empty_auth: login target should keep auth file writable" fi if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z,ro" "$STUB_LOG"; then record_failure "auto_login_empty_auth: post-login runtime should remount auth file read-only" fi teardown_stub_env } run_codex_symlink_home() { local scenario_dir scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P) printf '==> codex_symlink_home\n' local tmp_home tmp_home=$(mktemp -d) ln -s /etc "$tmp_home/.codex" if HOME="$tmp_home" "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "codex_symlink_home: expected rejection when ~/.codex is a symlink" fi rm -rf "$tmp_home" } run_root_directory_project() { printf '==> root_directory_project\n' local tmp_home tmp_home=$(mktemp -d) if HOME="$tmp_home" "$SLOPTRAP_BIN" --dry-run / /dev/null 2>&1; then record_failure "root_directory_project: expected rejection for '/' project root" fi rm -rf "$tmp_home" } run_invalid_manifest_name() { local scenario_dir="$TEST_ROOT/invalid_manifest_name" printf '==> invalid_manifest_name\n' if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "invalid_manifest_name: expected rejection for illegal name" fi } run_invalid_manifest_sandbox() { local scenario_dir="$TEST_ROOT/invalid_manifest_sandbox" printf '==> invalid_manifest_sandbox\n' if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "invalid_manifest_sandbox: expected rejection for sandbox mode" fi } run_invalid_manifest_packages() { local scenario_dir="$TEST_ROOT/invalid_manifest_packages" printf '==> invalid_manifest_packages\n' if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "invalid_manifest_packages: expected rejection for bad packages" fi } run_invalid_manifest_agent() { printf '==> invalid_manifest_agent\n' local scenario_dir scenario_dir=$(mktemp -d) cat >"$scenario_dir/.sloptrap" <<'EOF' name=invalid-agent agent=bogus EOF if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "invalid_manifest_agent: expected rejection for invalid agent" fi rm -rf "$scenario_dir" } run_invalid_agent_env_override() { local scenario_dir="$TEST_ROOT/opencode_print_config" printf '==> invalid_agent_env_override\n' if SLOPTRAP_AGENT=bogus "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "invalid_agent_env_override: expected rejection for invalid SLOPTRAP_AGENT" fi } run_removed_runtime_override_envs() { local scenario_dir="$TEST_ROOT/resume_target" printf '==> removed_runtime_override_envs\n' if SLOPTRAP_SECURITY_OPTS_EXTRA='--privileged' "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "removed_runtime_override_envs: expected rejection for SLOPTRAP_SECURITY_OPTS_EXTRA" fi if SLOPTRAP_ROOTFS_READONLY=0 "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "removed_runtime_override_envs: expected rejection for SLOPTRAP_ROOTFS_READONLY" fi if SLOPTRAP_NETWORK_NAME=host "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then record_failure "removed_runtime_override_envs: expected rejection for SLOPTRAP_NETWORK_NAME" fi } run_removed_build_override_envs() { local scenario_dir="$TEST_ROOT/resume_target" printf '==> removed_build_override_envs\n' if SLOPTRAP_DOCKERFILE_PATH=/etc/passwd "$SLOPTRAP_BIN" --dry-run "$scenario_dir" build /dev/null 2>&1; then record_failure "removed_build_override_envs: expected rejection for SLOPTRAP_DOCKERFILE_PATH" fi if SLOPTRAP_CODEX_URL=https://example.invalid/codex.tgz "$SLOPTRAP_BIN" --dry-run "$scenario_dir" build /dev/null 2>&1; then record_failure "removed_build_override_envs: expected rejection for SLOPTRAP_CODEX_URL" fi if SLOPTRAP_CODEX_ARCHIVE=codex-custom "$SLOPTRAP_BIN" --dry-run "$scenario_dir" build /dev/null 2>&1; then record_failure "removed_build_override_envs: expected rejection for SLOPTRAP_CODEX_ARCHIVE" fi if SLOPTRAP_CODEX_BIN=custom-codex "$SLOPTRAP_BIN" --dry-run "$scenario_dir" build /dev/null 2>&1; then record_failure "removed_build_override_envs: expected rejection for SLOPTRAP_CODEX_BIN" fi } run_wizard_create_manifest() { local scenario_dir="$TEST_ROOT/wizard_empty" printf '==> wizard_create_manifest\n' if ! can_run_script_pty; then printf 'skipping wizard_create_manifest: script PTY support not available\n' return fi rm -f "$scenario_dir/.sloptrap" # Wizard now has: name, packages_extra, agent (codex), allow_host_network # Use empty for name (default), empty for packages_extra, empty for agent (uses default), false for allow_host_network if ! printf '\n\n\nfalse\n\n' | "$SLOPTRAP_BIN" "$scenario_dir" wizard >/dev/null 2>&1; then record_failure "wizard_create_manifest: wizard failed" return fi if [[ ! -f $scenario_dir/.sloptrap ]]; then record_failure "wizard_create_manifest: manifest not created" return fi if ! grep -qx "name=wizard_empty" "$scenario_dir/.sloptrap"; then record_failure "wizard_create_manifest: name default mismatch" fi if ! grep -qx "packages_extra=" "$scenario_dir/.sloptrap"; then record_failure "wizard_create_manifest: packages_extra mismatch" fi if ! grep -qx "agent=codex" "$scenario_dir/.sloptrap"; then record_failure "wizard_create_manifest: agent mismatch" fi if ! grep -qx "allow_host_network=false" "$scenario_dir/.sloptrap"; then record_failure "wizard_create_manifest: allow_host_network mismatch" fi } run_wizard_existing_defaults() { local scenario_dir="$TEST_ROOT/wizard_existing" printf '==> wizard_existing_defaults\n' if ! can_run_script_pty; then printf 'skipping wizard_existing_defaults: script PTY support not available\n' return fi # Create initial manifest with custom-wizard name cat > "$scenario_dir/.sloptrap" </dev/null 2>&1; then record_failure "wizard_existing_defaults: wizard failed" return fi if ! grep -qx "name=custom-wizard" "$scenario_dir/.sloptrap"; then record_failure "wizard_existing_defaults: name not preserved" fi if ! grep -qx "packages_extra=make git" "$scenario_dir/.sloptrap"; then record_failure "wizard_existing_defaults: packages_extra not preserved" fi if ! grep -qx "agent=codex" "$scenario_dir/.sloptrap"; then record_failure "wizard_existing_defaults: agent not preserved" fi if ! grep -qx "allow_host_network=true" "$scenario_dir/.sloptrap"; then record_failure "wizard_existing_defaults: allow_host_network not preserved" fi } run_wizard_build_trigger() { local scenario_dir="$TEST_ROOT/wizard_build" printf '==> wizard_build_trigger\n' if ! can_run_script_pty; then printf 'skipping wizard_build_trigger: script PTY support not available\n' return fi setup_stub_env rm -f "$scenario_dir/.sloptrap" # Wizard now has: name, packages_extra, agent (codex), allow_host_network # Use empty for name (default), empty for packages_extra, empty for agent (uses default), false for allow_host_network if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ printf '\n\n\nfalse\n\n' | "$SLOPTRAP_BIN" "$scenario_dir" wizard >/dev/null 2>&1; then record_failure "wizard_build_trigger: wizard failed" teardown_stub_env return fi if [[ ! -f $scenario_dir/.sloptrap ]]; then record_failure "wizard_build_trigger: manifest not created" teardown_stub_env return fi # Run build to trigger image build if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" build >/dev/null 2>&1; then record_failure "wizard_build_trigger: build failed" teardown_stub_env return fi if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then record_failure "wizard_build_trigger: build not invoked" fi } run_opencode_build_downloads_release_cli() { local scenario_dir="$TEST_ROOT/opencode_build" printf '==> opencode_build_downloads_release_cli\n' setup_stub_env mkdir -p "$scenario_dir" cat > "$scenario_dir/.sloptrap" <<'EOF' name=opencode-build packages_extra=htop agent=opencode allow_host_network=false EOF if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" build >/dev/null 2>&1; then record_failure "opencode_build_downloads_release_cli: build failed" teardown_stub_env return fi if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then record_failure "opencode_build_downloads_release_cli: build not invoked" fi if ! grep -q -- "--build-arg OPENCODE_BIN=opencode" "$STUB_LOG"; then record_failure "opencode_build_downloads_release_cli: default OPENCODE_BIN build arg missing" fi if ! grep -q -- "--build-arg EXTRA_PACKAGES=htop" "$STUB_LOG"; then record_failure "opencode_build_downloads_release_cli: EXTRA_PACKAGES build arg missing" fi teardown_stub_env } run_opencode_localhost_rewrite() { local scenario_dir="$TEST_ROOT/opencode_localhost" printf '==> opencode_localhost_rewrite\n' setup_stub_env mkdir -p "$scenario_dir" cat > "$scenario_dir/.sloptrap" <<'EOF' name=opencode-localhost packages_extra= agent=opencode opencode_server=http://localhost:8080 allow_host_network=false EOF cat > "$STUB_BIN/slirp4netns" <<'EOF' #!/usr/bin/env bash exit 0 EOF chmod +x "$STUB_BIN/slirp4netns" if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "opencode_localhost_rewrite: run failed" teardown_stub_env return fi local config_path config_path=$(find "$STUB_HOME" -path '*/config/opencode/opencode.json' | head -n 1 || true) if [[ -z $config_path || ! -f $config_path ]]; then record_failure "opencode_localhost_rewrite: opencode config not written" teardown_stub_env return fi if ! grep -q -- "--network slirp4netns:allow_host_loopback=true" "$STUB_LOG"; then record_failure "opencode_localhost_rewrite: slirp host loopback not enabled" fi if ! grep -q -- "--add-host sloptrap.host:host-gateway" "$STUB_LOG"; then record_failure "opencode_localhost_rewrite: host alias not injected" fi if ! grep -q -- "OPENCODE_CONFIG=/codex/config/opencode/opencode.json" "$STUB_LOG"; then record_failure "opencode_localhost_rewrite: opencode config path not exported" fi if grep -q -- "/codex/auth.json" "$STUB_LOG"; then record_failure "opencode_localhost_rewrite: codex auth mount should not be present for opencode" fi if ! grep -q -- '"baseURL": "http://sloptrap.host:8080/v1"' "$config_path"; then record_failure "opencode_localhost_rewrite: localhost server not rewritten in config" fi if [[ $(jq -r '.provider["llama.cpp"].name' "$config_path") != "llama-server (local)" ]]; then record_failure "opencode_localhost_rewrite: provider name not merged into config" fi if [[ $(jq -r '.model' "$config_path") != "llama.cpp/bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0 - 262144" ]]; then record_failure "opencode_localhost_rewrite: opencode model not written to config" fi if [[ $(jq -r '.enabled_providers[0]' "$config_path") != "llama.cpp" ]]; then record_failure "opencode_localhost_rewrite: enabled_providers not merged into config" fi if [[ $(jq -r '.provider["llama.cpp"].models["bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0 - 262144"].limit.context' "$config_path") != "262144" ]]; then record_failure "opencode_localhost_rewrite: opencode context not written to config" fi if grep -q -- "--server " "$STUB_LOG"; then record_failure "opencode_localhost_rewrite: deprecated --server flag should not be passed" fi if grep -q -- "--sandbox workspace-write" "$STUB_LOG"; then record_failure "opencode_localhost_rewrite: codex sandbox args leaked into opencode run" fi if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then record_failure "opencode_localhost_rewrite: codex-style startup prompt should not be passed to opencode" fi teardown_stub_env } run_opencode_print_config_runtime_flags() { local scenario_dir="$TEST_ROOT/opencode_print_config" printf '==> opencode_print_config_runtime_flags\n' setup_stub_env mkdir -p "$scenario_dir" cat > "$scenario_dir/.sloptrap" <<'EOF' name=opencode-print-config packages_extra= agent=opencode allow_host_network=false EOF local output if ! output=$(env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" --print-config "$scenario_dir" 2>/dev/null); then record_failure "opencode_print_config_runtime_flags: print-config failed" teardown_stub_env return fi if ! grep -q 'runtime_flags=' <<<"$output"; then record_failure "opencode_print_config_runtime_flags: runtime_flags line missing for opencode" fi if ! grep -q 'opencode_config=' <<<"$output"; then record_failure "opencode_print_config_runtime_flags: opencode config path missing" fi if grep -q -- '--sandbox danger-full-access --ask-for-approval never' <<<"$output"; then record_failure "opencode_print_config_runtime_flags: codex runtime flags leaked into opencode config" fi teardown_stub_env } run_opencode_env_overrides() { local scenario_dir="$TEST_ROOT/opencode_print_config" printf '==> opencode_env_overrides\n' setup_stub_env mkdir -p "$scenario_dir" cat > "$scenario_dir/.sloptrap" <<'EOF' name=opencode-print-config packages_extra= agent=opencode opencode_server=http://manifest:8080 opencode_model=manifest-model opencode_context=128K allow_host_network=false EOF local output local plain_output if ! output=$(env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ SLOPTRAP_OPENCODE_SERVER=http://env:8080 SLOPTRAP_OPENCODE_MODEL=env-model SLOPTRAP_OPENCODE_CONTEXT=64K \ "$SLOPTRAP_BIN" --print-config "$scenario_dir" 2>/dev/null); then record_failure "opencode_env_overrides: print-config failed" teardown_stub_env return fi plain_output=$(printf '%s' "$output" | sed -E $'s/\x1B\\[[0-9;]*m//g') if ! grep -q 'opencode_server=http://env:8080' <<<"$plain_output"; then record_failure "opencode_env_overrides: server env override missing" fi if ! grep -q 'opencode_model=env-model' <<<"$plain_output"; then record_failure "opencode_env_overrides: model env override missing" fi if ! grep -q 'opencode_context=64K' <<<"$plain_output"; then record_failure "opencode_env_overrides: context env override missing" fi teardown_stub_env } run_opencode_config_symlink_rejected() { local scenario_dir="$TEST_ROOT/opencode_localhost" printf '==> opencode_config_symlink_rejected\n' setup_stub_env mkdir -p "$scenario_dir" cat > "$scenario_dir/.sloptrap" <<'EOF' name=opencode-localhost packages_extra= agent=opencode allow_host_network=false EOF local state_key state_key=$(printf '%s' "$scenario_dir" | sha256sum | awk '{print $1}') local config_dir="$STUB_HOME/.codex/sloptrap/state/$state_key/config/opencode" mkdir -p "$config_dir" ln -s "$STUB_HOME/target.json" "$config_dir/opencode.json" if env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "opencode_config_symlink_rejected: expected rejection for symlinked config" fi teardown_stub_env } run_opencode_login_rejected() { local scenario_dir="$TEST_ROOT/opencode_localhost" printf '==> opencode_login_rejected\n' setup_stub_env if env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ "$SLOPTRAP_BIN" "$scenario_dir" login /dev/null 2>&1; then record_failure "opencode_login_rejected: expected login rejection for opencode" fi teardown_stub_env } run_symlink_escape run_manifest_injection run_helper_symlink run_secret_mask run_resume_target run_runtime_context_prompt run_sh_reexec run_resume_omits_runtime_context run_shell_target_uses_entrypoint run_auth_file_mount run_runtime_hardening_flags run_codex_home_override run_project_state_isolation run_auto_login_empty_auth run_codex_symlink_home run_root_directory_project run_wizard_create_manifest run_wizard_existing_defaults run_wizard_build_trigger run_invalid_manifest_agent run_invalid_agent_env_override run_removed_runtime_override_envs run_removed_build_override_envs run_opencode_build_downloads_release_cli run_opencode_localhost_rewrite run_opencode_print_config_runtime_flags run_opencode_env_overrides run_opencode_config_symlink_rejected run_opencode_login_rejected if [[ ${#failures[@]} -gt 0 ]]; then printf '\nTest failures:\n' for entry in "${failures[@]}"; do printf ' - %s\n' "$entry" done exit 1 fi printf '\nAll regression checks passed.\n'