Cleanup capabilities

This commit is contained in:
Samuel Aubertin
2026-03-10 16:51:17 +01:00
parent b080f06613
commit 87d1577546
10 changed files with 466 additions and 165 deletions

View File

@@ -1,4 +1,4 @@
name=skz-sloptrap name=skz-sloptrap
packages_extra=bash make shellcheck jq podman iproute2 strace packages_extra=bash make shellcheck jq podman iproute2 strace
capabilities=apt-install packet-capture capabilities=
allow_host_network=false allow_host_network=false

View File

@@ -19,3 +19,4 @@ Do not remove existing instructions unless they are outdated or wrong.
`shellcheck sloptrap` `shellcheck sloptrap`
`bash tests/run_tests.sh` (you can also run them separately) `bash tests/run_tests.sh` (you can also run them separately)
- When running tests from inside sloptrap, inherited `CODEX_HOME=/codex` plus `SLOPTRAP_PREFER_CODEX_HOME=1` can leak into host-style child launches; ignore that preference when `HOME` has been redirected elsewhere and the runtime hints still point into the inherited `/codex` tree. - When running tests from inside sloptrap, inherited `CODEX_HOME=/codex` plus `SLOPTRAP_PREFER_CODEX_HOME=1` can leak into host-style child launches; ignore that preference when `HOME` has been redirected elsewhere and the runtime hints still point into the inherited `/codex` tree.
- Capability-enabled runs are Podman-only. `packet-capture` uses a dedicated helper container/pod, and host-network capture must prompt for an explicit acknowledgement on every runtime launch.

View File

@@ -62,13 +62,13 @@ Supported keys when the manifest is present:
| --- | --- | --- | | --- | --- | --- |
| `name` | project directory name | Must match `^[A-Za-z0-9_.-]+$`. Used for image/container naming. | | `name` | project directory name | Must match `^[A-Za-z0-9_.-]+$`. Used for image/container naming. |
| `packages_extra` | *empty* | Additional Debian packages installed during `docker/podman build`. Tokens must be alphanumeric plus `+.-`. | | `packages_extra` | *empty* | Additional Debian packages installed during `docker/podman build`. Tokens must be alphanumeric plus `+.-`. |
| `capabilities` | *empty* | Optional privileged features. Supported values are `apt-install` and `packet-capture`. | | `capabilities` | *empty* | Optional privileged features. Supported values are `apt-install` and `packet-capture`. Capability-enabled runs require Podman. When `packet-capture` is combined with `allow_host_network=true`, sloptrap shows a runtime warning with concrete consequences and requires an interactive acknowledgement on every run. |
| `allow_host_network` | `false` | `true` opts into `--network host`; keep `false` unless the project absolutely requires direct access to host-local services. | | `allow_host_network` | `false` | `true` opts into `--network host`; keep `false` unless the project absolutely requires direct access to host-local services. |
Values containing `$`, `` ` ``, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start. Values containing `$`, `` ` ``, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start.
sloptrap always runs Codex with `--sandbox danger-full-access --ask-for-approval never`. `codex_args` is deprecated and rejected if present. sloptrap always runs Codex with `--sandbox danger-full-access --ask-for-approval never`. `codex_args` is deprecated and rejected if present.
Capability trust is local state, not part of the repository. Builds for manifests that request capabilities require either an interactive trust confirmation or `--trust-capabilities`. Once the current manifest is trusted, its requested capabilities are enabled automatically for that project configuration. Capability trust is local state, not part of the repository. Builds for manifests that request capabilities require either an interactive trust confirmation or `--trust-capabilities`. Once the current manifest is trusted, its requested capabilities are enabled automatically for that project configuration. The `allow_host_network=true` plus `packet-capture` combination still requires a separate interactive acknowledgement each time a runtime container is launched.
### `.sloptrapignore` ### `.sloptrapignore`
@@ -130,24 +130,24 @@ The launcher executes targets sequentially, so `./sloptrap repo build run` perfo
When the current manifest's capabilities are trusted and enabled, the container includes helper commands: When the current manifest's capabilities are trusted and enabled, the container includes helper commands:
- `slop-apt install <package...>` for session-scoped package installation. - `slop-apt install <package...>` for session-scoped package installation.
- `slopcap capture --interface <iface> [--filter <expr>] [--output <path>] [--stdout]` for packet capture. - `slopcap capture --interface <iface> [--filter <expr>] [--output <path>] [--stdout]` for non-promiscuous packet capture through a dedicated helper container. Host-network captures require an explicit acknowledgement each run.
## Execution Environment ## Execution Environment
- Container engine: Podman or Docker with identical command lines. Podman uses `--userns=keep-id`; Docker receives the equivalent `--user UID:GID` for standard runs. - Container engine: Podman or Docker for standard runs. Capability-enabled runs require Podman. Podman uses `--userns=keep-id`; Docker receives the equivalent `--user UID:GID` for standard runs.
- Filesystem view: the project directory mounts at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` mounts at `/codex`; `${HOME}/.codex/auth.json` mounts at `/codex/auth.json`. - Filesystem view: the project directory mounts at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` mounts at `/codex`; `${HOME}/.codex/auth.json` mounts at `/codex/auth.json`.
- Ignore filter: `.sloptrapignore` entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to Codex. - Ignore filter: `.sloptrapignore` entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to Codex.
- Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`. - Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`. When `packet-capture` is enabled, sloptrap starts a separate capture helper container in the same Podman pod so the main Codex container does not receive `NET_RAW`.
- Process context: standard runs drop capabilities, set `no-new-privileges`, use a read-only root filesystem, and keep scratch paths (`/tmp`, `/run`, `/run/lock`) on tmpfs. Capability-enabled runs may selectively add the runtime options required for the requested capability. - Process context: standard runs drop capabilities, set `no-new-privileges`, use a read-only root filesystem, and keep scratch paths (`/tmp`, `/run`, `/run/lock`) on tmpfs. Capability-enabled runs may selectively add the runtime options required for the requested capability.
- Codex configuration: runtime flags are fixed to `--sandbox danger-full-access --ask-for-approval never`. Persistent Codex state is project-scoped under `${HOME}/.codex/sloptrap/state/`, while credentials are shared via `${HOME}/.codex/auth.json`. - Codex configuration: runtime flags are fixed to `--sandbox danger-full-access --ask-for-approval never`. Persistent Codex state is project-scoped under `${HOME}/.codex/sloptrap/state/`, while credentials are shared via `${HOME}/.codex/auth.json` and mounted read-only except during the `login` target.
## Threat Model and Limits ## Threat Model and Limits
- **Outbound disclosure**: prompts and referenced data travel from the container to the configured LLM endpoint. Any file content within `/workspace` or environment data exposed to the process can appear in that traffic. - **Outbound disclosure**: prompts and referenced data travel from the container to the configured LLM endpoint. Any file content within `/workspace` or environment data exposed to the process can appear in that traffic.
- **Shared storage**: `/workspace`, project-scoped `/codex`, and `/codex/auth.json` are host mounts. Files written to these locations become visible on the host and to the LLM provider through prompts. - **Shared storage**: `/workspace`, project-scoped `/codex`, and `/codex/auth.json` are host mounts. Files written to these locations become visible on the host and to the LLM provider through prompts.
- **Environment surface**: the container receives a minimal fixed environment (HOME/XDG paths, `CODEX_HOME`). The manifest no longer allows injecting additional environment variables. - **Environment surface**: the container receives a minimal fixed environment (HOME/XDG paths, `CODEX_HOME`). The manifest no longer allows injecting additional environment variables.
- **Process isolation**: standard runs keep a read-only root filesystem and no extra Linux capabilities. Capability-enabled runs deliberately relax specific runtime controls for the enabled feature, so they should be treated as a stronger trust decision than a default session. - **Process isolation**: standard runs keep a read-only root filesystem and no extra Linux capabilities. Capability-enabled runs deliberately relax specific runtime controls for the enabled feature, so they should be treated as a stronger trust decision than a default session. `packet-capture` now runs in a dedicated helper container so the main Codex container does not hold raw-socket capability.
- **Networking stance**: traffic is unrestricted once it leaves the container. sloptrap does not enforce an allowlist or DNS policy. Host networking is opt-in per manifest; if you require an offline or firewalled workflow, sloptrap is not an appropriate launcher. - **Networking stance**: traffic is unrestricted once it leaves the container. sloptrap does not enforce an allowlist or DNS policy. Host networking is opt-in per manifest; when it is combined with `packet-capture`, sloptrap warns and requires an explicit acknowledgement for each runtime launch because the capture helper will have raw packet access in the host namespace and may observe plaintext traffic or inject spoofed packets. If you require an offline or firewalled workflow, sloptrap is not an appropriate launcher.
- **Persistence**: Codex history and logs accumulate per project under `${HOME}/.codex/sloptrap/state/`. Sensitive prompts recorded on disk remain on the host after the session. Because `.git/` is ignored inside the container, any historical secrets in Git objects stay outside the LLM context unless explicitly surfaced in the working tree. - **Persistence**: Codex history and logs accumulate per project under `${HOME}/.codex/sloptrap/state/`. Sensitive prompts recorded on disk remain on the host after the session. Because `.git/` is ignored inside the container, any historical secrets in Git objects stay outside the LLM context unless explicitly surfaced in the working tree.
- **Codex cache hygiene**: per-project state mounts remain writable by the container and hold prompts/history/state, while `${HOME}/.codex/auth.json` holds shared credentials. Rotate credentials regularly and protect both locations. - **Codex cache hygiene**: per-project state mounts remain writable by the container and hold prompts/history/state, while `${HOME}/.codex/auth.json` holds shared credentials. Rotate credentials regularly and protect both locations.
- **Secret scanning**: sloptrap does not perform secret discovery or redaction; any credentials present in the project remain available to Codex and the upstream provider. - **Secret scanning**: sloptrap does not perform secret discovery or redaction; any credentials present in the project remain available to Codex and the upstream provider.

362
sloptrap
View File

@@ -351,6 +351,7 @@ write_embedded_helper() {
cat <<'EOF' cat <<'EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
helper_pid="" helper_pid=""
helperd_bin=${SLOPTRAP_HELPERD_BIN:-/usr/local/bin/sloptrap-helperd} helperd_bin=${SLOPTRAP_HELPERD_BIN:-/usr/local/bin/sloptrap-helperd}
@@ -394,6 +395,7 @@ EOF
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
umask 077 umask 077
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper} helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper}
queue_dir="$helper_dir/queue" queue_dir="$helper_dir/queue"
@@ -482,15 +484,15 @@ release_request_dir() {
local owner_gid=$3 local owner_gid=$3
local path local path
[[ $owner_uid =~ ^[0-9]+$ && $owner_gid =~ ^[0-9]+$ ]] || return 0 [[ $owner_uid =~ ^[0-9]+$ && $owner_gid =~ ^[0-9]+$ ]] || return 0
for path in "$request_dir" "$request_dir/status" "$request_dir/stdout" "$request_dir/stderr"; do
[[ -e $path && ! -L $path ]] || continue
chown "$owner_uid:$owner_gid" "$path" 2>/dev/null || true
done
chmod 700 "$request_dir" 2>/dev/null || true chmod 700 "$request_dir" 2>/dev/null || true
for path in "$request_dir/status" "$request_dir/stdout" "$request_dir/stderr"; do for path in "$request_dir/status" "$request_dir/stdout" "$request_dir/stderr"; do
[[ -e $path && ! -L $path ]] || continue [[ -e $path && ! -L $path ]] || continue
chmod 600 "$path" 2>/dev/null || true chmod 600 "$path" 2>/dev/null || true
done done
for path in "$request_dir" "$request_dir/status" "$request_dir/stdout" "$request_dir/stderr"; do
[[ -e $path && ! -L $path ]] || continue
chown "$owner_uid:$owner_gid" "$path" 2>/dev/null || true
done
} }
init_request_outputs() { init_request_outputs() {
@@ -537,6 +539,7 @@ write_status() {
run_apt_install() { run_apt_install() {
local request_dir=$1 local request_dir=$1
local apt_get_bin
has_capability "apt-install" || { has_capability "apt-install" || {
printf 'capability apt-install is not active\n' >"$request_dir/stderr" printf 'capability apt-install is not active\n' >"$request_dir/stderr"
write_status "$request_dir" 126 write_status "$request_dir" 126
@@ -566,8 +569,18 @@ run_apt_install() {
return return
fi fi
done done
if apt-get update >"$request_dir/stdout" 2>"$request_dir/stderr" \ apt_get_bin=${SLOPTRAP_APT_GET_BIN:-}
&& apt-get install -y --no-install-recommends "${packages[@]}" >>"$request_dir/stdout" 2>>"$request_dir/stderr"; then if [[ -z $apt_get_bin ]]; then
apt_get_bin=$(command -v apt-get 2>/dev/null || true)
fi
if [[ -z $apt_get_bin || ! -x $apt_get_bin ]]; then
printf 'apt-get is not available in this image\n' >"$request_dir/stderr"
write_status "$request_dir" 127
log_action "apt-install" "packages=missing-tool" 127
return
fi
if "$apt_get_bin" update >"$request_dir/stdout" 2>"$request_dir/stderr" \
&& "$apt_get_bin" install -y --no-install-recommends "${packages[@]}" >>"$request_dir/stdout" 2>>"$request_dir/stderr"; then
write_status "$request_dir" 0 write_status "$request_dir" 0
log_action "apt-install" "packages=${packages[*]}" 0 log_action "apt-install" "packages=${packages[*]}" 0
return return
@@ -578,6 +591,7 @@ run_apt_install() {
run_packet_capture() { run_packet_capture() {
local request_dir=$1 local request_dir=$1
local tcpdump_bin
has_capability "packet-capture" || { has_capability "packet-capture" || {
printf 'capability packet-capture is not active\n' >"$request_dir/stderr" printf 'capability packet-capture is not active\n' >"$request_dir/stderr"
write_status "$request_dir" 126 write_status "$request_dir" 126
@@ -609,7 +623,17 @@ run_packet_capture() {
log_action "packet-capture" "interface=$iface stdout=invalid" 2 log_action "packet-capture" "interface=$iface stdout=invalid" 2
return return
fi fi
local -a cmd=(tcpdump -i "$iface") tcpdump_bin=${SLOPTRAP_TCPDUMP_BIN:-}
if [[ -z $tcpdump_bin ]]; then
tcpdump_bin=$(command -v tcpdump 2>/dev/null || true)
fi
if [[ -z $tcpdump_bin || ! -x $tcpdump_bin ]]; then
printf 'tcpdump is not available in this image\n' >"$request_dir/stderr"
write_status "$request_dir" 127
log_action "packet-capture" "interface=$iface tool=missing" 127
return
fi
local -a cmd=("$tcpdump_bin" -p -i "$iface")
if [[ -s $output_file ]]; then if [[ -s $output_file ]]; then
local capture_path local capture_path
capture_path=$(read_request_value "$output_file" || true) capture_path=$(read_request_value "$output_file" || true)
@@ -704,6 +728,7 @@ EOF
cat <<'EOF' cat <<'EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper} helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper}
queue_dir="$helper_dir/queue" queue_dir="$helper_dir/queue"
@@ -721,50 +746,8 @@ ensure_helper_ready() {
if [[ -w $queue_dir ]] && helper_running; then if [[ -w $queue_dir ]] && helper_running; then
return 0 return 0
fi fi
if [[ -z ${SLOPTRAP_ACTIVE_CAPABILITIES:-} ]]; then printf 'slop-apt: capability helper is unavailable; start a fresh sloptrap session with apt-install enabled\n' >&2
printf 'slop-apt: capability helper is not available in this session\n' >&2
exit 1 exit 1
fi
if ! command -v setpriv >/dev/null 2>&1; then
printf 'slop-apt: setpriv is required to bootstrap the capability helper\n' >&2
exit 1
fi
setpriv --reuid 0 --regid 0 --clear-groups -- env \
SLOPTRAP_HELPER_DIR="$helper_dir" \
SLOPTRAP_ACTIVE_CAPABILITIES="${SLOPTRAP_ACTIVE_CAPABILITIES:-}" \
SLOPTRAP_AUDIT_LOG="${SLOPTRAP_AUDIT_LOG:-/codex/state/capabilities.log}" \
SLOPTRAP_CAPTURE_DIR="${SLOPTRAP_CAPTURE_DIR:-/codex/state/captures}" \
SLOPTRAP_WORKDIR="${SLOPTRAP_WORKDIR:-/workspace}" \
SLOPTRAP_HOST_UID="${SLOPTRAP_HOST_UID:-$(id -u)}" \
SLOPTRAP_HOST_GID="${SLOPTRAP_HOST_GID:-$(id -g)}" \
bash -c '
set -euo pipefail
helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper}
queue_dir="$helper_dir/queue"
pidfile="$helper_dir/helperd.pid"
helper_bin=$(command -v sloptrap-helperd)
[[ -n $helper_bin ]] || exit 1
mkdir -p "$queue_dir"
chmod 711 "$helper_dir"
chmod 1733 "$queue_dir"
if [[ -r $pidfile ]]; then
pid=$(<"$pidfile")
if [[ -n $pid ]] && kill -0 "$pid" 2>/dev/null; then
exit 0
fi
fi
"$helper_bin" >/dev/null 2>&1 &
for ((i=0; i<30; i+=1)); do
if [[ -r $pidfile ]]; then
pid=$(<"$pidfile")
if [[ -n $pid ]] && kill -0 "$pid" 2>/dev/null; then
exit 0
fi
fi
sleep 0.1
done
exit 1
'
} }
if [[ ${1-} != "install" ]]; then if [[ ${1-} != "install" ]]; then
@@ -811,12 +794,14 @@ EOF
cat <<'EOF' cat <<'EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper} helper_dir=${SLOPTRAP_CAPTURE_HELPER_DIR:-/codex/state/capture-helper}
queue_dir="$helper_dir/queue" queue_dir="$helper_dir/queue"
default_output=${SLOPTRAP_CAPTURE_DIR:-/codex/state/captures} default_output=${SLOPTRAP_CAPTURE_DIR:-/codex/state/captures}
workspace_root=${SLOPTRAP_WORKDIR:-/workspace} workspace_root=${SLOPTRAP_WORKDIR:-/workspace}
pidfile="$helper_dir/helperd.pid" pidfile="$helper_dir/helperd.pid"
packet_capture_enabled=${SLOPTRAP_PACKET_CAPTURE_ENABLED:-0}
mkdir -p "$default_output" mkdir -p "$default_output"
helper_running() { helper_running() {
@@ -828,53 +813,15 @@ helper_running() {
} }
ensure_helper_ready() { ensure_helper_ready() {
if [[ $packet_capture_enabled != "1" ]]; then
printf 'slopcap: packet capture is not enabled in this session\n' >&2
exit 1
fi
if [[ -w $queue_dir ]] && helper_running; then if [[ -w $queue_dir ]] && helper_running; then
return 0 return 0
fi fi
if [[ -z ${SLOPTRAP_ACTIVE_CAPABILITIES:-} ]]; then printf 'slopcap: capture helper is unavailable; start a fresh sloptrap session with packet-capture enabled\n' >&2
printf 'slopcap: capability helper is not available in this session\n' >&2
exit 1 exit 1
fi
if ! command -v setpriv >/dev/null 2>&1; then
printf 'slopcap: setpriv is required to bootstrap the capability helper\n' >&2
exit 1
fi
setpriv --reuid 0 --regid 0 --clear-groups -- env \
SLOPTRAP_HELPER_DIR="$helper_dir" \
SLOPTRAP_ACTIVE_CAPABILITIES="${SLOPTRAP_ACTIVE_CAPABILITIES:-}" \
SLOPTRAP_AUDIT_LOG="${SLOPTRAP_AUDIT_LOG:-/codex/state/capabilities.log}" \
SLOPTRAP_CAPTURE_DIR="${SLOPTRAP_CAPTURE_DIR:-/codex/state/captures}" \
SLOPTRAP_WORKDIR="${SLOPTRAP_WORKDIR:-/workspace}" \
SLOPTRAP_HOST_UID="${SLOPTRAP_HOST_UID:-$(id -u)}" \
SLOPTRAP_HOST_GID="${SLOPTRAP_HOST_GID:-$(id -g)}" \
bash -c '
set -euo pipefail
helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper}
queue_dir="$helper_dir/queue"
pidfile="$helper_dir/helperd.pid"
helper_bin=$(command -v sloptrap-helperd)
[[ -n $helper_bin ]] || exit 1
mkdir -p "$queue_dir"
chmod 711 "$helper_dir"
chmod 1733 "$queue_dir"
if [[ -r $pidfile ]]; then
pid=$(<"$pidfile")
if [[ -n $pid ]] && kill -0 "$pid" 2>/dev/null; then
exit 0
fi
fi
"$helper_bin" >/dev/null 2>&1 &
for ((i=0; i<30; i+=1)); do
if [[ -r $pidfile ]]; then
pid=$(<"$pidfile")
if [[ -n $pid ]] && kill -0 "$pid" 2>/dev/null; then
exit 0
fi
fi
sleep 0.1
done
exit 1
'
} }
resolve_requested_path() { resolve_requested_path() {
@@ -1174,6 +1121,45 @@ ensure_capability_trust() {
prompt_capability_trust prompt_capability_trust
} }
host_network_packet_capture_active() {
$ALLOW_HOST_NETWORK && capability_list_contains "$ENABLED_CAPABILITIES" "packet-capture"
}
prompt_runtime_packet_capture_ack() {
local tty_path="/dev/tty"
printf '%s' "$PREFIX_TEXT" >"$tty_path"
printf '%b' "$COLOR_TEXT" >"$tty_path"
printf 'Warning: host networking + packet-capture is a high-trust mode.\n' >"$tty_path"
printf 'If you continue, code inside this session can capture host-network traffic, including plaintext protocols and requests to local services.\n' >"$tty_path"
printf 'It can also transmit spoofed packets into the host network namespace for the duration of this run.\n' >"$tty_path"
printf 'This is not a normal sandboxed session boundary.\n' >"$tty_path"
printf 'Continue with host-network packet capture for this run? [y/N]: ' >"$tty_path"
printf '%b' "$RESET" >"$tty_path"
local input
if ! IFS= read -r input <"$tty_path"; then
error "host-network packet capture requires an interactive terminal acknowledgement"
fi
case "${input,,}" in
y|yes)
RUNTIME_PACKET_CAPTURE_ACKNOWLEDGED=true
;;
*)
error "host-network packet capture not acknowledged"
;;
esac
}
ensure_runtime_packet_capture_ack() {
host_network_packet_capture_active || return 0
$RUNTIME_PACKET_CAPTURE_ACKNOWLEDGED && return 0
if $DRY_RUN; then
warn "host networking with packet capture would require an interactive acknowledgement at runtime"
RUNTIME_PACKET_CAPTURE_ACKNOWLEDGED=true
return 0
fi
prompt_runtime_packet_capture_ack
}
write_capability_build_stamp() { write_capability_build_stamp() {
ensure_capability_state_paths ensure_capability_state_paths
if $DRY_RUN; then if $DRY_RUN; then
@@ -1732,8 +1718,12 @@ EOF
declare -a CONTAINER_SHARED_OPTS=() declare -a CONTAINER_SHARED_OPTS=()
declare -a BASE_CONTAINER_CMD=() declare -a BASE_CONTAINER_CMD=()
declare -a CAPTURE_POD_CREATE_CMD=()
declare -a CAPTURE_HELPER_BASE_CMD=()
SLOPTRAP_IMAGE_NAME="" SLOPTRAP_IMAGE_NAME=""
SLOPTRAP_CONTAINER_NAME="" SLOPTRAP_CONTAINER_NAME=""
SLOPTRAP_CAPTURE_CONTAINER_NAME=""
SLOPTRAP_POD_NAME=""
SLOPTRAP_DOCKERFILE_PATH="" SLOPTRAP_DOCKERFILE_PATH=""
SLOPTRAP_BUILD_CONTEXT="" SLOPTRAP_BUILD_CONTEXT=""
SLOPTRAP_DOCKERFILE_SOURCE="" SLOPTRAP_DOCKERFILE_SOURCE=""
@@ -1759,6 +1749,10 @@ SLOPTRAP_TMPFS_PATHS=""
SLOPTRAP_ROOTFS_READONLY="" SLOPTRAP_ROOTFS_READONLY=""
SLOPTRAP_ROOTFS_READONLY_DEFAULT="" SLOPTRAP_ROOTFS_READONLY_DEFAULT=""
SLOPTRAP_RUN_AS_ROOT=false SLOPTRAP_RUN_AS_ROOT=false
SLOPTRAP_MAIN_ACTIVE_CAPABILITIES=""
SLOPTRAP_PACKET_CAPTURE_ENABLED=false
SLOPTRAP_CAPTURE_HELPER_DIR_CONT=""
SLOPTRAP_CAPTURE_HELPER_DIR_HOST=""
get_env_default() { get_env_default() {
local var=$1 local var=$1
@@ -1821,6 +1815,87 @@ run_or_print() {
"$@" "$@"
} }
append_auth_mount_arg() {
local writable=$1
local -n out=$2
local suffix=""
if [[ $CONTAINER_ENGINE == "podman" ]]; then
suffix=":Z"
if [[ $writable != true ]]; then
suffix=":Z,ro"
fi
elif [[ $writable != true ]]; then
suffix=":ro"
fi
out+=(-v "$CODEX_AUTH_FILE_HOST:$SLOPTRAP_CODEX_HOME_CONT/auth.json$suffix")
}
ensure_capability_engine_supported() {
[[ -n $REQUESTED_CAPABILITIES ]] || return 0
if [[ $CONTAINER_ENGINE != "podman" ]]; then
error "capability-enabled runs require podman; docker is not supported for capabilities"
fi
}
packet_capture_enabled() {
capability_list_contains "$ENABLED_CAPABILITIES" "packet-capture"
}
stop_packet_capture_helper() {
[[ -n $SLOPTRAP_POD_NAME ]] || return 0
if $DRY_RUN; then
print_command "$CONTAINER_ENGINE" pod rm -f "$SLOPTRAP_POD_NAME"
return 0
fi
"$CONTAINER_ENGINE" pod rm -f "$SLOPTRAP_POD_NAME" >/dev/null 2>&1 || true
}
wait_for_path() {
local path=$1
local attempts=${2:-50}
local delay=${3:-0.1}
local i
for ((i=0; i<attempts; i+=1)); do
[[ -e $path ]] && return 0
sleep "$delay"
done
return 1
}
start_packet_capture_helper() {
packet_capture_enabled || return 0
ensure_runtime_packet_capture_ack
ensure_codex_directory "$SLOPTRAP_CAPTURE_HELPER_DIR_HOST" "capture helper state"
if $DRY_RUN; then
print_command "${CAPTURE_POD_CREATE_CMD[@]}"
print_command "${CAPTURE_HELPER_BASE_CMD[@]}"
return 0
fi
stop_packet_capture_helper
"${CAPTURE_POD_CREATE_CMD[@]}" >/dev/null
if ! "${CAPTURE_HELPER_BASE_CMD[@]}" >/dev/null; then
stop_packet_capture_helper
return 1
fi
if ! wait_for_path "$SLOPTRAP_CAPTURE_HELPER_DIR_HOST/helperd.pid"; then
stop_packet_capture_helper
error "packet capture helper failed to start"
fi
}
run_runtime_container_cmd() {
local -a cmd=("$@")
start_packet_capture_helper
local status=0
if run_or_print "${cmd[@]}"; then
status=0
else
status=$?
fi
stop_packet_capture_helper
return "$status"
}
ensure_codex_directory() { ensure_codex_directory() {
local path=$1 local path=$1
local label=$2 local label=$2
@@ -2057,7 +2132,17 @@ prepare_container_runtime() {
SLOPTRAP_IMAGE_NAME=$(sanitize_engine_name "$SLOPTRAP_IMAGE_NAME") SLOPTRAP_IMAGE_NAME=$(sanitize_engine_name "$SLOPTRAP_IMAGE_NAME")
SLOPTRAP_CONTAINER_NAME=$(sanitize_engine_name "$SLOPTRAP_CONTAINER_NAME") SLOPTRAP_CONTAINER_NAME=$(sanitize_engine_name "$SLOPTRAP_CONTAINER_NAME")
local -a network_opts=(--network "$SLOPTRAP_NETWORK_NAME" --init) SLOPTRAP_CAPTURE_CONTAINER_NAME=$(sanitize_engine_name "${PROJECT_NAME}-sloptrap-capture")
SLOPTRAP_POD_NAME=$(sanitize_engine_name "${PROJECT_NAME}-sloptrap-pod")
SLOPTRAP_CAPTURE_HELPER_DIR_CONT="$SLOPTRAP_CODEX_HOME_CONT/state/capture-helper"
SLOPTRAP_CAPTURE_HELPER_DIR_HOST="$CODEX_STATE_HOME_HOST/state/capture-helper"
local -a network_opts=(--init)
if packet_capture_enabled; then
network_opts+=(--pod "$SLOPTRAP_POD_NAME")
else
network_opts+=(--network "$SLOPTRAP_NETWORK_NAME")
fi
local -a security_opts=(--cap-drop=ALL) local -a security_opts=(--cap-drop=ALL)
local -a capability_opts=() local -a capability_opts=()
if [[ -n $SLOPTRAP_SECURITY_OPTS_EXTRA ]]; then if [[ -n $SLOPTRAP_SECURITY_OPTS_EXTRA ]]; then
@@ -2082,13 +2167,19 @@ prepare_container_runtime() {
done done
fi fi
SLOPTRAP_MAIN_ACTIVE_CAPABILITIES=""
SLOPTRAP_PACKET_CAPTURE_ENABLED=false
if capability_list_contains "$ENABLED_CAPABILITIES" "apt-install"; then if capability_list_contains "$ENABLED_CAPABILITIES" "apt-install"; then
SLOPTRAP_ROOTFS_READONLY=0 SLOPTRAP_ROOTFS_READONLY=0
SLOPTRAP_RUN_AS_ROOT=true SLOPTRAP_RUN_AS_ROOT=true
SLOPTRAP_MAIN_ACTIVE_CAPABILITIES="apt-install"
fi fi
if capability_list_contains "$ENABLED_CAPABILITIES" "packet-capture"; then if capability_list_contains "$ENABLED_CAPABILITIES" "packet-capture"; then
capability_opts+=(--cap-add NET_RAW --cap-add NET_ADMIN) SLOPTRAP_PACKET_CAPTURE_ENABLED=true
SLOPTRAP_RUN_AS_ROOT=true fi
local packet_capture_flag="0"
if $SLOPTRAP_PACKET_CAPTURE_ENABLED; then
packet_capture_flag="1"
fi fi
security_opts+=(--security-opt no-new-privileges) security_opts+=(--security-opt no-new-privileges)
if $SLOPTRAP_RUN_AS_ROOT; then if $SLOPTRAP_RUN_AS_ROOT; then
@@ -2096,8 +2187,6 @@ prepare_container_runtime() {
--cap-add SETUID --cap-add SETUID
--cap-add SETGID --cap-add SETGID
--cap-add CHOWN --cap-add CHOWN
--cap-add DAC_OVERRIDE
--cap-add FOWNER
) )
fi fi
@@ -2117,7 +2206,6 @@ prepare_container_runtime() {
local -a volume_opts=( local -a volume_opts=(
-v "$SLOPTRAP_SHARED_DIR_ABS:$SLOPTRAP_WORKDIR$SLOPTRAP_VOLUME_LABEL" -v "$SLOPTRAP_SHARED_DIR_ABS:$SLOPTRAP_WORKDIR$SLOPTRAP_VOLUME_LABEL"
-v "$CODEX_STATE_HOME_HOST:$SLOPTRAP_CODEX_HOME_CONT$SLOPTRAP_VOLUME_LABEL" -v "$CODEX_STATE_HOME_HOST:$SLOPTRAP_CODEX_HOME_CONT$SLOPTRAP_VOLUME_LABEL"
-v "$CODEX_AUTH_FILE_HOST:$SLOPTRAP_CODEX_HOME_CONT/auth.json$SLOPTRAP_VOLUME_LABEL"
) )
local -a env_args=( local -a env_args=(
@@ -2128,7 +2216,9 @@ prepare_container_runtime() {
-e "CODEX_HOME=$SLOPTRAP_CODEX_HOME_CONT" -e "CODEX_HOME=$SLOPTRAP_CODEX_HOME_CONT"
-e "SLOPTRAP_WORKDIR=$SLOPTRAP_WORKDIR" -e "SLOPTRAP_WORKDIR=$SLOPTRAP_WORKDIR"
-e "SLOPTRAP_HELPER_DIR=/tmp/sloptrap-helper" -e "SLOPTRAP_HELPER_DIR=/tmp/sloptrap-helper"
-e "SLOPTRAP_ACTIVE_CAPABILITIES=$ENABLED_CAPABILITIES" -e "SLOPTRAP_ACTIVE_CAPABILITIES=$SLOPTRAP_MAIN_ACTIVE_CAPABILITIES"
-e "SLOPTRAP_PACKET_CAPTURE_ENABLED=$packet_capture_flag"
-e "SLOPTRAP_CAPTURE_HELPER_DIR=$SLOPTRAP_CAPTURE_HELPER_DIR_CONT"
-e "SLOPTRAP_CAPTURE_DIR=$SLOPTRAP_CODEX_HOME_CONT/state/captures" -e "SLOPTRAP_CAPTURE_DIR=$SLOPTRAP_CODEX_HOME_CONT/state/captures"
-e "SLOPTRAP_AUDIT_LOG=$SLOPTRAP_CODEX_HOME_CONT/state/capabilities.log" -e "SLOPTRAP_AUDIT_LOG=$SLOPTRAP_CODEX_HOME_CONT/state/capabilities.log"
-e "SLOPTRAP_PREFER_CODEX_HOME=1" -e "SLOPTRAP_PREFER_CODEX_HOME=1"
@@ -2156,6 +2246,55 @@ prepare_container_runtime() {
fi fi
fi fi
CAPTURE_POD_CREATE_CMD=()
CAPTURE_HELPER_BASE_CMD=()
if $SLOPTRAP_PACKET_CAPTURE_ENABLED; then
local -a capture_env_args=(
-e "HOME=$SLOPTRAP_CODEX_HOME_CONT"
-e "XDG_CONFIG_HOME=$SLOPTRAP_CODEX_HOME_CONT/config"
-e "XDG_CACHE_HOME=$SLOPTRAP_CODEX_HOME_CONT/cache"
-e "XDG_STATE_HOME=$SLOPTRAP_CODEX_HOME_CONT/state"
-e "CODEX_HOME=$SLOPTRAP_CODEX_HOME_CONT"
-e "SLOPTRAP_WORKDIR=$SLOPTRAP_WORKDIR"
-e "SLOPTRAP_HELPER_DIR=$SLOPTRAP_CAPTURE_HELPER_DIR_CONT"
-e "SLOPTRAP_ACTIVE_CAPABILITIES=packet-capture"
-e "SLOPTRAP_CAPTURE_DIR=$SLOPTRAP_CODEX_HOME_CONT/state/captures"
-e "SLOPTRAP_AUDIT_LOG=$SLOPTRAP_CODEX_HOME_CONT/state/capabilities.log"
-e "SLOPTRAP_HOST_UID=$uid"
-e "SLOPTRAP_HOST_GID=$gid"
)
if [[ -n $user ]]; then
capture_env_args+=(-e "SLOPTRAP_HOST_USER=$user")
fi
local -a capture_user_opts=(--userns="keep-id:uid=$uid,gid=$gid")
CAPTURE_POD_CREATE_CMD=(
"$CONTAINER_ENGINE" pod create
--name "$SLOPTRAP_POD_NAME"
--network "$SLOPTRAP_NETWORK_NAME"
)
CAPTURE_HELPER_BASE_CMD=(
"$CONTAINER_ENGINE" run -d --rm
--name "$SLOPTRAP_CAPTURE_CONTAINER_NAME"
--pod "$SLOPTRAP_POD_NAME"
--cap-drop=ALL
--cap-add NET_RAW
--cap-add SETUID
--cap-add SETGID
--cap-add CHOWN
--security-opt no-new-privileges
"${resource_opts[@]}"
--read-only
"${tmpfs_opts[@]}"
"${volume_opts[@]}"
"${IGNORE_MOUNT_ARGS[@]}"
"${capture_env_args[@]}"
"${capture_user_opts[@]}"
-w "$SLOPTRAP_WORKDIR"
"$SLOPTRAP_IMAGE_NAME"
sleep infinity
)
fi
CONTAINER_SHARED_OPTS=( CONTAINER_SHARED_OPTS=(
"${network_opts[@]}" "${network_opts[@]}"
"${security_opts[@]}" "${security_opts[@]}"
@@ -2301,6 +2440,7 @@ build_if_missing() {
} }
stop_container() { stop_container() {
stop_packet_capture_helper
if $DRY_RUN; then if $DRY_RUN; then
print_command "$CONTAINER_ENGINE" stop "$SLOPTRAP_CONTAINER_NAME" print_command "$CONTAINER_ENGINE" stop "$SLOPTRAP_CONTAINER_NAME"
return 0 return 0
@@ -2343,15 +2483,17 @@ prune_sloptrap_images() {
run_codex_command() { run_codex_command() {
local -a extra_args=("$@") local -a extra_args=("$@")
local -a source_args=("$SLOPTRAP_IMAGE_NAME") local -a source_args=("$SLOPTRAP_IMAGE_NAME")
local -a auth_mount=()
ensure_codex_storage_paths ensure_codex_storage_paths
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${source_args[@]}" "codex") append_auth_mount_arg false auth_mount
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "codex")
if [[ ${#CODEX_ARGS_ARRAY[@]} -gt 0 ]]; then if [[ ${#CODEX_ARGS_ARRAY[@]} -gt 0 ]]; then
cmd+=("${CODEX_ARGS_ARRAY[@]}") cmd+=("${CODEX_ARGS_ARRAY[@]}")
fi fi
if [[ ${#extra_args[@]} -gt 0 ]]; then if [[ ${#extra_args[@]} -gt 0 ]]; then
cmd+=("${extra_args[@]}") cmd+=("${extra_args[@]}")
fi fi
run_or_print "${cmd[@]}" run_runtime_container_cmd "${cmd[@]}"
} }
run_codex() { run_codex() {
@@ -2366,21 +2508,25 @@ run_codex() {
run_login_target() { run_login_target() {
ensure_codex_storage_paths ensure_codex_storage_paths
local -a source_args=("$SLOPTRAP_IMAGE_NAME") local -a source_args=("$SLOPTRAP_IMAGE_NAME")
local -a auth_mount=()
if ! $DRY_RUN; then if ! $DRY_RUN; then
status_line "Login %s\n" "$SLOPTRAP_IMAGE_NAME" status_line "Login %s\n" "$SLOPTRAP_IMAGE_NAME"
fi fi
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${source_args[@]}" "codex" login) append_auth_mount_arg true auth_mount
run_or_print "${cmd[@]}" local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "codex" login)
run_runtime_container_cmd "${cmd[@]}"
} }
run_shell_target() { run_shell_target() {
ensure_codex_storage_paths ensure_codex_storage_paths
local -a source_args=("$SLOPTRAP_IMAGE_NAME") local -a source_args=("$SLOPTRAP_IMAGE_NAME")
local -a auth_mount=()
if ! $DRY_RUN; then if ! $DRY_RUN; then
status_line "Shell %s\n" "$SLOPTRAP_IMAGE_NAME" status_line "Shell %s\n" "$SLOPTRAP_IMAGE_NAME"
fi fi
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${source_args[@]}" /bin/bash) append_auth_mount_arg false auth_mount
run_or_print "${cmd[@]}" local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" /bin/bash)
run_runtime_container_cmd "${cmd[@]}"
} }
run_resume_target() { run_resume_target() {
@@ -2447,6 +2593,7 @@ DRY_RUN=false
PRINT_CONFIG=false PRINT_CONFIG=false
SKIP_BUILD_BANNER=false SKIP_BUILD_BANNER=false
TRUST_CAPABILITIES=false TRUST_CAPABILITIES=false
RUNTIME_PACKET_CAPTURE_ACKNOWLEDGED=false
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
@@ -2599,6 +2746,7 @@ if [[ -n $PACKAGES_EXTRA ]]; then
validate_package_list "packages_extra" "$PACKAGES_EXTRA" validate_package_list "packages_extra" "$PACKAGES_EXTRA"
fi fi
CONTAINER_ENGINE="$(detect_container_engine)" CONTAINER_ENGINE="$(detect_container_engine)"
ensure_capability_engine_supported
CODEX_ARGS_ARRAY=("${DEFAULT_CODEX_ARGS[@]}") CODEX_ARGS_ARRAY=("${DEFAULT_CODEX_ARGS[@]}")
ensure_safe_sandbox "${CODEX_ARGS_ARRAY[@]}" ensure_safe_sandbox "${CODEX_ARGS_ARRAY[@]}"
CODEX_ARGS_DISPLAY=$DEFAULT_CODEX_ARGS_DISPLAY CODEX_ARGS_DISPLAY=$DEFAULT_CODEX_ARGS_DISPLAY

View File

@@ -14,3 +14,4 @@ Current scenarios:
- `auth_file_mount` — verifies `~/.codex/auth.json` is mounted directly into `/codex/auth.json`. - `auth_file_mount` — verifies `~/.codex/auth.json` is mounted directly into `/codex/auth.json`.
- `project_state_isolation` — verifies different projects map `/codex` to different host state directories. - `project_state_isolation` — verifies different projects map `/codex` to different host state directories.
- `auto_login_empty_auth` — verifies an empty `auth.json` still triggers automatic login before the main target. - `auto_login_empty_auth` — verifies an empty `auth.json` still triggers automatic login before the main target.
- `host_network_packet_capture/` — exercises the per-run acknowledgement path for host networking combined with `packet-capture`.

View File

@@ -1,3 +1,3 @@
name=capability-repo name=capability-repo
capabilities=apt-install packet-capture capabilities=apt-install packet-capture
allow_host_network=true allow_host_network=false

View File

@@ -0,0 +1,3 @@
name=host-network-packet-capture
capabilities=packet-capture
allow_host_network=true

View File

@@ -0,0 +1,3 @@
name=host-network-repo
capabilities=apt-install
allow_host_network=true

View File

@@ -97,6 +97,47 @@ verify_secret_mounts() {
return 0 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 [[ ${1-} == "image" && ${2-} == "inspect" && ${FAKE_PODMAN_INSPECT_FAIL:-0} == 1 ]]; then
if [[ " $* " == *" --format "* ]]; then if [[ " $* " == *" --format "* ]]; then
printf 'fake-image-id\n' printf 'fake-image-id\n'
@@ -113,10 +154,16 @@ if [[ ${SECRET_MASK_VERIFY:-0} == 1 && ${1-} == "run" ]]; then
fi fi
fi fi
if [[ ${1-} == "run" ]]; then
maybe_create_helper_pidfile "$@"
fi
echo "FAKE PODMAN: $*" >>"$FAKE_PODMAN_LOG" echo "FAKE PODMAN: $*" >>"$FAKE_PODMAN_LOG"
exit 0 exit 0
EOF EOF
chmod +x "$STUB_BIN/podman" chmod +x "$STUB_BIN/podman"
cp "$STUB_BIN/podman" "$STUB_BIN/docker"
chmod +x "$STUB_BIN/docker"
cat >"$STUB_BIN/curl" <<'EOF' cat >"$STUB_BIN/curl" <<'EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
@@ -367,7 +414,7 @@ run_resume_target() {
} }
run_runtime_context_prompt() { run_runtime_context_prompt() {
local scenario_dir="$TEST_ROOT/capability_repo" local scenario_dir="$TEST_ROOT/host_network_repo"
printf '==> runtime_context_prompt\n' printf '==> runtime_context_prompt\n'
setup_stub_env setup_stub_env
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
@@ -382,8 +429,8 @@ run_runtime_context_prompt() {
if [[ -z $run_line || $run_line != *"You are running inside sloptrap"* ]]; then if [[ -z $run_line || $run_line != *"You are running inside sloptrap"* ]]; then
record_failure "runtime_context_prompt: startup prompt missing from fresh run" record_failure "runtime_context_prompt: startup prompt missing from fresh run"
fi fi
if ! grep -q -- "name=capability-repo" "$STUB_LOG" \ if ! grep -q -- "name=host-network-repo" "$STUB_LOG" \
|| ! grep -q -- "enabled_capabilities=apt-install packet-capture" "$STUB_LOG" \ || ! grep -q -- "enabled_capabilities=apt-install" "$STUB_LOG" \
|| ! grep -q -- "network_mode=host" "$STUB_LOG"; then || ! grep -q -- "network_mode=host" "$STUB_LOG"; then
record_failure "runtime_context_prompt: runtime summary missing manifest or capability state" record_failure "runtime_context_prompt: runtime summary missing manifest or capability state"
fi fi
@@ -394,7 +441,7 @@ run_runtime_context_prompt() {
} }
run_sh_reexec() { run_sh_reexec() {
local scenario_dir="$TEST_ROOT/capability_repo" local scenario_dir="$TEST_ROOT/host_network_repo"
printf '==> sh_reexec\n' printf '==> sh_reexec\n'
setup_stub_env setup_stub_env
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
@@ -410,7 +457,7 @@ run_sh_reexec() {
} }
run_resume_omits_runtime_context() { run_resume_omits_runtime_context() {
local scenario_dir="$TEST_ROOT/capability_repo" local scenario_dir="$TEST_ROOT/host_network_repo"
local session_id="019a81b7-32d2-7622-8639-6698c6579625" local session_id="019a81b7-32d2-7622-8639-6698c6579625"
printf '==> resume_omits_runtime_context\n' printf '==> resume_omits_runtime_context\n'
setup_stub_env setup_stub_env
@@ -442,8 +489,8 @@ run_auth_file_mount() {
teardown_stub_env teardown_stub_env
return return
fi fi
if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z,ro" "$STUB_LOG"; then
record_failure "auth_file_mount: missing auth file bind mount" record_failure "auth_file_mount: auth file should be mounted read-only for normal runs"
fi fi
if ! grep -q -- "-v ${STUB_HOME}/.codex/sloptrap/state/" "$STUB_LOG"; then if ! grep -q -- "-v ${STUB_HOME}/.codex/sloptrap/state/" "$STUB_LOG"; then
record_failure "auth_file_mount: missing project state bind mount" record_failure "auth_file_mount: missing project state bind mount"
@@ -466,8 +513,8 @@ run_codex_home_override() {
teardown_stub_env teardown_stub_env
return return
fi fi
if ! grep -q -- "-v ${codex_root}/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then if ! grep -q -- "-v ${codex_root}/auth.json:/codex/auth.json:Z,ro" "$STUB_LOG"; then
record_failure "codex_home_override: missing CODEX_HOME auth file mount" record_failure "codex_home_override: CODEX_HOME auth file should be mounted read-only for normal runs"
fi fi
if ! grep -q -- "-v ${codex_root}/sloptrap/state/" "$STUB_LOG"; then if ! grep -q -- "-v ${codex_root}/sloptrap/state/" "$STUB_LOG"; then
record_failure "codex_home_override: missing CODEX_HOME project state bind mount" record_failure "codex_home_override: missing CODEX_HOME project state bind mount"
@@ -564,8 +611,11 @@ run_auto_login_empty_auth() {
if [[ -z $first_run || $first_run != *" login" ]]; then if [[ -z $first_run || $first_run != *" login" ]]; then
record_failure "auto_login_empty_auth: expected login before primary run" record_failure "auto_login_empty_auth: expected login before primary run"
fi fi
if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then if [[ -z $first_run || $first_run != *"-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z "* ]]; then
record_failure "auto_login_empty_auth: missing auth file bind mount" 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 fi
teardown_stub_env teardown_stub_env
} }
@@ -687,6 +737,53 @@ run_invalid_allow_host_network() {
fi fi
} }
run_host_network_packet_capture_ack_required() {
local scenario_dir="$TEST_ROOT/host_network_packet_capture"
printf '==> host_network_packet_capture_ack_required\n'
local output_log
output_log=$(mktemp)
setup_stub_env
if PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
"$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" </dev/null >"$output_log" 2>&1; then
record_failure "host_network_packet_capture_ack_required: expected failure without interactive acknowledgement"
fi
if grep -q -- "FAKE PODMAN: run " "$STUB_LOG"; then
record_failure "host_network_packet_capture_ack_required: runtime container should not start without acknowledgement"
fi
teardown_stub_env
rm -f "$output_log"
}
run_host_network_packet_capture_ack_prompt() {
local scenario_dir="$TEST_ROOT/host_network_packet_capture"
printf '==> host_network_packet_capture_ack_prompt\n'
if ! can_run_script_pty; then
printf 'skipping host_network_packet_capture_ack_prompt: script PTY support not available\n'
return
fi
local output_log
output_log=$(mktemp)
setup_stub_env
if ! printf 'y\n' | script -q -c "env PATH=\"$STUB_BIN:$PATH\" HOME=\"$STUB_HOME\" FAKE_PODMAN_LOG=\"$STUB_LOG\" FAKE_PODMAN_INSPECT_FAIL=1 \"$SLOPTRAP_BIN\" --trust-capabilities \"$scenario_dir\"" "$output_log" >/dev/null 2>&1; then
record_failure "host_network_packet_capture_ack_prompt: interactive acknowledgement should allow the run"
teardown_stub_env
rm -f "$output_log"
return
fi
if [[ $(grep -c -- 'Continue with host-network packet capture for this run' "$output_log" || true) -ne 1 ]]; then
record_failure "host_network_packet_capture_ack_prompt: expected a single runtime acknowledgement prompt"
fi
if ! grep -q -- 'capture host-network traffic' "$output_log" \
|| ! grep -q -- 'transmit spoofed packets' "$output_log"; then
record_failure "host_network_packet_capture_ack_prompt: warning should describe concrete consequences"
fi
if ! grep -q -- "--network host" "$STUB_LOG"; then
record_failure "host_network_packet_capture_ack_prompt: host networking run did not reach the container engine"
fi
teardown_stub_env
rm -f "$output_log"
}
run_wizard_create_manifest() { run_wizard_create_manifest() {
local scenario_dir="$TEST_ROOT/wizard_empty" local scenario_dir="$TEST_ROOT/wizard_empty"
printf '==> wizard_create_manifest\n' printf '==> wizard_create_manifest\n'
@@ -736,7 +833,7 @@ run_wizard_existing_defaults() {
if ! grep -qx "packages_extra=make git" "$scenario_dir/.sloptrap"; then if ! grep -qx "packages_extra=make git" "$scenario_dir/.sloptrap"; then
record_failure "wizard_existing_defaults: packages_extra not preserved" record_failure "wizard_existing_defaults: packages_extra not preserved"
fi fi
if ! grep -qx "capabilities=apt-install packet-capture" "$scenario_dir/.sloptrap"; then if ! grep -qx "capabilities=apt-install" "$scenario_dir/.sloptrap"; then
record_failure "wizard_existing_defaults: capabilities not preserved" record_failure "wizard_existing_defaults: capabilities not preserved"
fi fi
if ! grep -qx "allow_host_network=true" "$scenario_dir/.sloptrap"; then if ! grep -qx "allow_host_network=true" "$scenario_dir/.sloptrap"; then
@@ -779,10 +876,31 @@ run_capability_trust_required() {
teardown_stub_env teardown_stub_env
} }
run_capabilities_require_podman() {
local scenario_dir="$TEST_ROOT/capability_repo"
printf '==> capabilities_require_podman\n'
local output_log
output_log=$(mktemp)
setup_stub_env
if PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" SLOPTRAP_CONTAINER_ENGINE=docker \
"$SLOPTRAP_BIN" --dry-run "$scenario_dir" >"$output_log" 2>&1; then
record_failure "capabilities_require_podman: expected docker capability run to be rejected"
elif ! grep -q -- 'capability-enabled runs require podman' "$output_log"; then
record_failure "capabilities_require_podman: missing explicit podman requirement"
fi
teardown_stub_env
rm -f "$output_log"
}
run_capability_profiles() { run_capability_profiles() {
local scenario_dir="$TEST_ROOT/capability_repo" local scenario_dir="$TEST_ROOT/capability_repo"
printf '==> capability_profiles\n' printf '==> capability_profiles\n'
setup_stub_env setup_stub_env
local main_lines capture_lines pod_lines
local expected_build_network="bridge"
if command -v slirp4netns >/dev/null 2>&1; then
expected_build_network="slirp4netns"
fi
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
"$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" </dev/null >/dev/null 2>&1; then "$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" </dev/null >/dev/null 2>&1; then
record_failure "capability_profiles: sloptrap exited non-zero" record_failure "capability_profiles: sloptrap exited non-zero"
@@ -792,44 +910,65 @@ run_capability_profiles() {
if ! grep -q -- "CAPABILITY_PACKAGES=tcpdump" "$STUB_LOG"; then if ! grep -q -- "CAPABILITY_PACKAGES=tcpdump" "$STUB_LOG"; then
record_failure "capability_profiles: build arg for capability packages missing" record_failure "capability_profiles: build arg for capability packages missing"
fi fi
if ! grep -q -- "FAKE PODMAN: build --quiet -t capability-repo-sloptrap-image -f .* --network host " "$STUB_LOG"; then if ! grep -q -- "FAKE PODMAN: build --quiet -t capability-repo-sloptrap-image -f .* --network $expected_build_network " "$STUB_LOG"; then
record_failure "capability_profiles: build should inherit host networking" record_failure "capability_profiles: build should stay on isolated networking"
fi fi
if ! grep -q -- "--cap-add NET_RAW" "$STUB_LOG"; then main_lines=$(grep "FAKE PODMAN: run " "$STUB_LOG" | grep -- "--name capability-repo-sloptrap-container" || true)
record_failure "capability_profiles: NET_RAW capability missing" capture_lines=$(grep "FAKE PODMAN: run " "$STUB_LOG" | grep -- "--name capability-repo-sloptrap-capture" || true)
pod_lines=$(grep "FAKE PODMAN: pod create " "$STUB_LOG" | grep -- "--name capability-repo-sloptrap-pod" || true)
if [[ -z $main_lines ]]; then
record_failure "capability_profiles: main runtime container did not reach the container engine"
fi fi
if ! grep -q -- "--cap-add NET_ADMIN" "$STUB_LOG"; then if [[ -z $pod_lines ]]; then
record_failure "capability_profiles: NET_ADMIN capability missing" record_failure "capability_profiles: packet capture should create a dedicated pod"
fi fi
if ! grep -q -- "--cap-add SETUID" "$STUB_LOG"; then if [[ -z $capture_lines || $capture_lines != *"--cap-add NET_RAW"* ]]; then
record_failure "capability_profiles: capture sidecar should receive NET_RAW"
fi
if [[ -n $main_lines && $main_lines == *"--cap-add NET_RAW"* ]]; then
record_failure "capability_profiles: main container should not receive NET_RAW"
fi
if grep -q -- "--cap-add NET_ADMIN" <<<"$capture_lines"; then
record_failure "capability_profiles: NET_ADMIN should not be granted"
fi
if [[ -z $capture_lines || $capture_lines != *"--cap-add SETUID"* ]]; then
record_failure "capability_profiles: SETUID capability missing" record_failure "capability_profiles: SETUID capability missing"
fi fi
if ! grep -q -- "--cap-add SETGID" "$STUB_LOG"; then if [[ -z $capture_lines || $capture_lines != *"--cap-add SETGID"* ]]; then
record_failure "capability_profiles: SETGID capability missing" record_failure "capability_profiles: SETGID capability missing"
fi fi
if ! grep -q -- "--cap-add CHOWN" "$STUB_LOG"; then if [[ -z $capture_lines || $capture_lines != *"--cap-add CHOWN"* ]]; then
record_failure "capability_profiles: CHOWN capability missing" record_failure "capability_profiles: CHOWN capability missing"
fi fi
if ! grep -q -- "--cap-add DAC_OVERRIDE" "$STUB_LOG"; then if grep -q -- "--cap-add DAC_OVERRIDE" <<<"$capture_lines$main_lines"; then
record_failure "capability_profiles: DAC_OVERRIDE capability missing" record_failure "capability_profiles: DAC_OVERRIDE should not be granted"
fi fi
if ! grep -q -- "--cap-add FOWNER" "$STUB_LOG"; then if grep -q -- "--cap-add FOWNER" <<<"$capture_lines$main_lines"; then
record_failure "capability_profiles: FOWNER capability missing" record_failure "capability_profiles: FOWNER should not be granted"
fi fi
if ! grep -q -- "--security-opt no-new-privileges" "$STUB_LOG"; then if ! grep -q -- "--security-opt no-new-privileges" <<<"$capture_lines$main_lines"; then
record_failure "capability_profiles: no-new-privileges missing" record_failure "capability_profiles: no-new-privileges missing"
fi fi
if grep -q -- "--read-only" "$STUB_LOG"; then if grep -q -- "--read-only" <<<"$main_lines"; then
record_failure "capability_profiles: apt profile should disable read-only rootfs" record_failure "capability_profiles: apt profile should disable read-only rootfs"
fi fi
if grep -q -- "--user " "$STUB_LOG"; then if grep -q -- "--user " <<<"$main_lines"; then
record_failure "capability_profiles: capability-enabled run should not force --user" record_failure "capability_profiles: capability-enabled run should not force --user"
fi fi
if ! grep -q -- "--userns=keep-id:uid=$(id -u),gid=$(id -g)" "$STUB_LOG"; then if ! grep -q -- "--userns=keep-id:uid=$(id -u),gid=$(id -g)" <<<"$capture_lines$main_lines"; then
record_failure "capability_profiles: podman keep-id user namespace missing" record_failure "capability_profiles: podman keep-id user namespace missing"
fi fi
if ! grep -q -- "SLOPTRAP_ACTIVE_CAPABILITIES=apt-install packet-capture" "$STUB_LOG"; then if ! grep -q -- "SLOPTRAP_ACTIVE_CAPABILITIES=apt-install" <<<"$main_lines"; then
record_failure "capability_profiles: active capability environment missing" record_failure "capability_profiles: main helper capability environment missing"
fi
if ! grep -q -- "SLOPTRAP_PACKET_CAPTURE_ENABLED=1" <<<"$main_lines"; then
record_failure "capability_profiles: main container should advertise packet capture availability"
fi
if ! grep -q -- "SLOPTRAP_HELPER_DIR=/codex/state/capture-helper" <<<"$capture_lines"; then
record_failure "capability_profiles: capture sidecar helper dir missing"
fi
if ! grep -q -- "SLOPTRAP_ACTIVE_CAPABILITIES=packet-capture" <<<"$capture_lines"; then
record_failure "capability_profiles: capture sidecar capability environment missing"
fi fi
if ! grep -q -- "SLOPTRAP_HOST_UID=$(id -u)" "$STUB_LOG"; then if ! grep -q -- "SLOPTRAP_HOST_UID=$(id -u)" "$STUB_LOG"; then
record_failure "capability_profiles: host uid environment missing" record_failure "capability_profiles: host uid environment missing"
@@ -926,28 +1065,29 @@ EOF
record_failure "embedded_capability_helpers: entrypoint did not expose helper queue to the dropped user" record_failure "embedded_capability_helpers: entrypoint did not expose helper queue to the dropped user"
fi fi
local autostart_helper_dir if grep -q -- "setpriv --reuid 0 --regid 0" "$helper_bin/slop-apt" \
autostart_helper_dir="$temp_root/helper-autostart" || grep -q -- "setpriv --reuid 0 --regid 0" "$helper_bin/slopcap"; then
if ! TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$autostart_helper_dir" \ record_failure "embedded_capability_helpers: helper clients should not attempt to regain root"
fi
if TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$temp_root/helper-missing" \
SLOPTRAP_ACTIVE_CAPABILITIES="apt-install packet-capture" \ SLOPTRAP_ACTIVE_CAPABILITIES="apt-install packet-capture" \
SLOPTRAP_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \ SLOPTRAP_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \
SLOPTRAP_AUDIT_LOG="$temp_root/autostart-audit.log" \ "$helper_bin/slop-apt" install jq >"$temp_root/missing-helper.out" 2>"$temp_root/missing-helper.err"; then
SLOPTRAP_HOST_UID="$(id -u)" SLOPTRAP_HOST_GID="$(id -g)" \ record_failure "embedded_capability_helpers: slop-apt should fail when the root helper is unavailable"
"$helper_bin/slop-apt" install jq >/dev/null 2>&1; then elif ! grep -q -- 'capability helper is unavailable' "$temp_root/missing-helper.err"; then
record_failure "embedded_capability_helpers: slop-apt did not self-bootstrap the helper daemon" record_failure "embedded_capability_helpers: missing helper failure should explain the boundary"
fi
if [[ ! -r $autostart_helper_dir/helperd.pid ]]; then
record_failure "embedded_capability_helpers: helper self-bootstrap did not create a pid file"
else
kill "$(cat "$autostart_helper_dir/helperd.pid")" >/dev/null 2>&1 || true
wait "$(cat "$autostart_helper_dir/helperd.pid")" >/dev/null 2>&1 || true
fi fi
TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \ TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \
SLOPTRAP_ACTIVE_CAPABILITIES="apt-install packet-capture" \ SLOPTRAP_ACTIVE_CAPABILITIES="apt-install packet-capture" \
SLOPTRAP_APT_GET_BIN="$helper_bin/apt-get" \
SLOPTRAP_TCPDUMP_BIN="$helper_bin/tcpdump" \
SLOPTRAP_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \ SLOPTRAP_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \
SLOPTRAP_AUDIT_LOG="$temp_root/audit.log" "$helper_bin/sloptrap-helperd" >/dev/null 2>&1 & SLOPTRAP_AUDIT_LOG="$temp_root/audit.log" "$helper_bin/sloptrap-helperd" >/dev/null 2>&1 &
helper_pid=$! helper_pid=$!
if ! wait_for_path "$helper_dir/helperd.pid"; then
record_failure "embedded_capability_helpers: helper daemon did not publish its pid file"
fi
if ! TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \ if ! TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \
"$helper_bin/slop-apt" install jq >/dev/null 2>&1; then "$helper_bin/slop-apt" install jq >/dev/null 2>&1; then
@@ -972,17 +1112,19 @@ EOF
if TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \ if TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \
SLOPTRAP_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \ SLOPTRAP_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \
SLOPTRAP_CAPTURE_HELPER_DIR="$helper_dir" SLOPTRAP_PACKET_CAPTURE_ENABLED=1 \
"$helper_bin/slopcap" capture --interface eth0 --output /tmp/escape.pcap >/dev/null 2>&1; then "$helper_bin/slopcap" capture --interface eth0 --output /tmp/escape.pcap >/dev/null 2>&1; then
record_failure "embedded_capability_helpers: slopcap accepted an out-of-bounds output path" record_failure "embedded_capability_helpers: slopcap accepted an out-of-bounds output path"
fi fi
if ! TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \ if ! TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \
SLOPTRAP_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \ SLOPTRAP_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \
SLOPTRAP_CAPTURE_HELPER_DIR="$helper_dir" SLOPTRAP_PACKET_CAPTURE_ENABLED=1 \
"$helper_bin/slopcap" capture --interface eth0 --filter 'tcp port 80' \ "$helper_bin/slopcap" capture --interface eth0 --filter 'tcp port 80' \
--output "$workspace_dir/capture.pcap" >/dev/null 2>&1; then --output "$workspace_dir/capture.pcap" >/dev/null 2>&1; then
record_failure "embedded_capability_helpers: slopcap failed for a workspace-local capture file" record_failure "embedded_capability_helpers: slopcap failed for a workspace-local capture file"
fi fi
if ! grep -q -- "tcpdump -i eth0 -w $workspace_dir/capture.pcap -- tcp port 80" "$tool_log"; then if ! grep -q -- "tcpdump -p -i eth0 -w $workspace_dir/capture.pcap -- tcp port 80" "$tool_log"; then
record_failure "embedded_capability_helpers: slopcap did not invoke tcpdump with the expected guarded arguments" record_failure "embedded_capability_helpers: slopcap did not invoke tcpdump with the expected guarded arguments"
fi fi
@@ -1080,11 +1222,14 @@ run_invalid_manifest_sandbox
run_invalid_manifest_packages run_invalid_manifest_packages
run_invalid_manifest_capabilities run_invalid_manifest_capabilities
run_invalid_allow_host_network run_invalid_allow_host_network
run_host_network_packet_capture_ack_required
run_host_network_packet_capture_ack_prompt
run_removed_nested_podman_manifest run_removed_nested_podman_manifest
run_wizard_create_manifest run_wizard_create_manifest
run_wizard_existing_defaults run_wizard_existing_defaults
run_wizard_build_trigger run_wizard_build_trigger
run_capability_trust_required run_capability_trust_required
run_capabilities_require_podman
run_capability_profiles run_capability_profiles
run_embedded_capability_helpers run_embedded_capability_helpers
run_make_install_single_file run_make_install_single_file

View File

@@ -1,4 +1,4 @@
name=custom-wizard name=custom-wizard
packages_extra=make git packages_extra=make git
capabilities=apt-install packet-capture capabilities=apt-install
allow_host_network=true allow_host_network=true