Cleanup capabilities
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name=skz-sloptrap
|
||||
packages_extra=bash make shellcheck jq podman iproute2 strace
|
||||
capabilities=apt-install packet-capture
|
||||
capabilities=
|
||||
allow_host_network=false
|
||||
|
||||
@@ -19,3 +19,4 @@ Do not remove existing instructions unless they are outdated or wrong.
|
||||
`shellcheck sloptrap`
|
||||
`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.
|
||||
- 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.
|
||||
|
||||
16
README.md
16
README.md
@@ -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. |
|
||||
| `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. |
|
||||
|
||||
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.
|
||||
|
||||
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`
|
||||
|
||||
@@ -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:
|
||||
|
||||
- `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
|
||||
|
||||
- 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`.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
- **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.
|
||||
- **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.
|
||||
- **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.
|
||||
- **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; 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.
|
||||
- **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.
|
||||
|
||||
362
sloptrap
362
sloptrap
@@ -351,6 +351,7 @@ write_embedded_helper() {
|
||||
cat <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
helper_pid=""
|
||||
helperd_bin=${SLOPTRAP_HELPERD_BIN:-/usr/local/bin/sloptrap-helperd}
|
||||
@@ -394,6 +395,7 @@ EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper}
|
||||
queue_dir="$helper_dir/queue"
|
||||
@@ -482,15 +484,15 @@ release_request_dir() {
|
||||
local owner_gid=$3
|
||||
local path
|
||||
[[ $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
|
||||
for path in "$request_dir/status" "$request_dir/stdout" "$request_dir/stderr"; do
|
||||
[[ -e $path && ! -L $path ]] || continue
|
||||
chmod 600 "$path" 2>/dev/null || true
|
||||
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() {
|
||||
@@ -537,6 +539,7 @@ write_status() {
|
||||
|
||||
run_apt_install() {
|
||||
local request_dir=$1
|
||||
local apt_get_bin
|
||||
has_capability "apt-install" || {
|
||||
printf 'capability apt-install is not active\n' >"$request_dir/stderr"
|
||||
write_status "$request_dir" 126
|
||||
@@ -566,8 +569,18 @@ run_apt_install() {
|
||||
return
|
||||
fi
|
||||
done
|
||||
if apt-get update >"$request_dir/stdout" 2>"$request_dir/stderr" \
|
||||
&& apt-get install -y --no-install-recommends "${packages[@]}" >>"$request_dir/stdout" 2>>"$request_dir/stderr"; then
|
||||
apt_get_bin=${SLOPTRAP_APT_GET_BIN:-}
|
||||
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
|
||||
log_action "apt-install" "packages=${packages[*]}" 0
|
||||
return
|
||||
@@ -578,6 +591,7 @@ run_apt_install() {
|
||||
|
||||
run_packet_capture() {
|
||||
local request_dir=$1
|
||||
local tcpdump_bin
|
||||
has_capability "packet-capture" || {
|
||||
printf 'capability packet-capture is not active\n' >"$request_dir/stderr"
|
||||
write_status "$request_dir" 126
|
||||
@@ -609,7 +623,17 @@ run_packet_capture() {
|
||||
log_action "packet-capture" "interface=$iface stdout=invalid" 2
|
||||
return
|
||||
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
|
||||
local capture_path
|
||||
capture_path=$(read_request_value "$output_file" || true)
|
||||
@@ -704,6 +728,7 @@ EOF
|
||||
cat <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
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}
|
||||
queue_dir="$helper_dir/queue"
|
||||
@@ -721,50 +746,8 @@ ensure_helper_ready() {
|
||||
if [[ -w $queue_dir ]] && helper_running; then
|
||||
return 0
|
||||
fi
|
||||
if [[ -z ${SLOPTRAP_ACTIVE_CAPABILITIES:-} ]]; then
|
||||
printf 'slop-apt: capability helper is not available in this session\n' >&2
|
||||
printf 'slop-apt: capability helper is unavailable; start a fresh sloptrap session with apt-install enabled\n' >&2
|
||||
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
|
||||
@@ -811,12 +794,14 @@ EOF
|
||||
cat <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
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"
|
||||
default_output=${SLOPTRAP_CAPTURE_DIR:-/codex/state/captures}
|
||||
workspace_root=${SLOPTRAP_WORKDIR:-/workspace}
|
||||
pidfile="$helper_dir/helperd.pid"
|
||||
packet_capture_enabled=${SLOPTRAP_PACKET_CAPTURE_ENABLED:-0}
|
||||
mkdir -p "$default_output"
|
||||
|
||||
helper_running() {
|
||||
@@ -828,53 +813,15 @@ helper_running() {
|
||||
}
|
||||
|
||||
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
|
||||
return 0
|
||||
fi
|
||||
if [[ -z ${SLOPTRAP_ACTIVE_CAPABILITIES:-} ]]; then
|
||||
printf 'slopcap: capability helper is not available in this session\n' >&2
|
||||
printf 'slopcap: capture helper is unavailable; start a fresh sloptrap session with packet-capture enabled\n' >&2
|
||||
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() {
|
||||
@@ -1174,6 +1121,45 @@ ensure_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() {
|
||||
ensure_capability_state_paths
|
||||
if $DRY_RUN; then
|
||||
@@ -1732,8 +1718,12 @@ EOF
|
||||
|
||||
declare -a CONTAINER_SHARED_OPTS=()
|
||||
declare -a BASE_CONTAINER_CMD=()
|
||||
declare -a CAPTURE_POD_CREATE_CMD=()
|
||||
declare -a CAPTURE_HELPER_BASE_CMD=()
|
||||
SLOPTRAP_IMAGE_NAME=""
|
||||
SLOPTRAP_CONTAINER_NAME=""
|
||||
SLOPTRAP_CAPTURE_CONTAINER_NAME=""
|
||||
SLOPTRAP_POD_NAME=""
|
||||
SLOPTRAP_DOCKERFILE_PATH=""
|
||||
SLOPTRAP_BUILD_CONTEXT=""
|
||||
SLOPTRAP_DOCKERFILE_SOURCE=""
|
||||
@@ -1759,6 +1749,10 @@ SLOPTRAP_TMPFS_PATHS=""
|
||||
SLOPTRAP_ROOTFS_READONLY=""
|
||||
SLOPTRAP_ROOTFS_READONLY_DEFAULT=""
|
||||
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() {
|
||||
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() {
|
||||
local path=$1
|
||||
local label=$2
|
||||
@@ -2057,7 +2132,17 @@ prepare_container_runtime() {
|
||||
SLOPTRAP_IMAGE_NAME=$(sanitize_engine_name "$SLOPTRAP_IMAGE_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 capability_opts=()
|
||||
if [[ -n $SLOPTRAP_SECURITY_OPTS_EXTRA ]]; then
|
||||
@@ -2082,13 +2167,19 @@ prepare_container_runtime() {
|
||||
done
|
||||
fi
|
||||
|
||||
SLOPTRAP_MAIN_ACTIVE_CAPABILITIES=""
|
||||
SLOPTRAP_PACKET_CAPTURE_ENABLED=false
|
||||
if capability_list_contains "$ENABLED_CAPABILITIES" "apt-install"; then
|
||||
SLOPTRAP_ROOTFS_READONLY=0
|
||||
SLOPTRAP_RUN_AS_ROOT=true
|
||||
SLOPTRAP_MAIN_ACTIVE_CAPABILITIES="apt-install"
|
||||
fi
|
||||
if capability_list_contains "$ENABLED_CAPABILITIES" "packet-capture"; then
|
||||
capability_opts+=(--cap-add NET_RAW --cap-add NET_ADMIN)
|
||||
SLOPTRAP_RUN_AS_ROOT=true
|
||||
SLOPTRAP_PACKET_CAPTURE_ENABLED=true
|
||||
fi
|
||||
local packet_capture_flag="0"
|
||||
if $SLOPTRAP_PACKET_CAPTURE_ENABLED; then
|
||||
packet_capture_flag="1"
|
||||
fi
|
||||
security_opts+=(--security-opt no-new-privileges)
|
||||
if $SLOPTRAP_RUN_AS_ROOT; then
|
||||
@@ -2096,8 +2187,6 @@ prepare_container_runtime() {
|
||||
--cap-add SETUID
|
||||
--cap-add SETGID
|
||||
--cap-add CHOWN
|
||||
--cap-add DAC_OVERRIDE
|
||||
--cap-add FOWNER
|
||||
)
|
||||
fi
|
||||
|
||||
@@ -2117,7 +2206,6 @@ prepare_container_runtime() {
|
||||
local -a volume_opts=(
|
||||
-v "$SLOPTRAP_SHARED_DIR_ABS:$SLOPTRAP_WORKDIR$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=(
|
||||
@@ -2128,7 +2216,9 @@ prepare_container_runtime() {
|
||||
-e "CODEX_HOME=$SLOPTRAP_CODEX_HOME_CONT"
|
||||
-e "SLOPTRAP_WORKDIR=$SLOPTRAP_WORKDIR"
|
||||
-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_AUDIT_LOG=$SLOPTRAP_CODEX_HOME_CONT/state/capabilities.log"
|
||||
-e "SLOPTRAP_PREFER_CODEX_HOME=1"
|
||||
@@ -2156,6 +2246,55 @@ prepare_container_runtime() {
|
||||
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=(
|
||||
"${network_opts[@]}"
|
||||
"${security_opts[@]}"
|
||||
@@ -2301,6 +2440,7 @@ build_if_missing() {
|
||||
}
|
||||
|
||||
stop_container() {
|
||||
stop_packet_capture_helper
|
||||
if $DRY_RUN; then
|
||||
print_command "$CONTAINER_ENGINE" stop "$SLOPTRAP_CONTAINER_NAME"
|
||||
return 0
|
||||
@@ -2343,15 +2483,17 @@ prune_sloptrap_images() {
|
||||
run_codex_command() {
|
||||
local -a extra_args=("$@")
|
||||
local -a source_args=("$SLOPTRAP_IMAGE_NAME")
|
||||
local -a auth_mount=()
|
||||
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
|
||||
cmd+=("${CODEX_ARGS_ARRAY[@]}")
|
||||
fi
|
||||
if [[ ${#extra_args[@]} -gt 0 ]]; then
|
||||
cmd+=("${extra_args[@]}")
|
||||
fi
|
||||
run_or_print "${cmd[@]}"
|
||||
run_runtime_container_cmd "${cmd[@]}"
|
||||
}
|
||||
|
||||
run_codex() {
|
||||
@@ -2366,21 +2508,25 @@ run_codex() {
|
||||
run_login_target() {
|
||||
ensure_codex_storage_paths
|
||||
local -a source_args=("$SLOPTRAP_IMAGE_NAME")
|
||||
local -a auth_mount=()
|
||||
if ! $DRY_RUN; then
|
||||
status_line "Login %s\n" "$SLOPTRAP_IMAGE_NAME"
|
||||
fi
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${source_args[@]}" "codex" login)
|
||||
run_or_print "${cmd[@]}"
|
||||
append_auth_mount_arg true auth_mount
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "codex" login)
|
||||
run_runtime_container_cmd "${cmd[@]}"
|
||||
}
|
||||
|
||||
run_shell_target() {
|
||||
ensure_codex_storage_paths
|
||||
local -a source_args=("$SLOPTRAP_IMAGE_NAME")
|
||||
local -a auth_mount=()
|
||||
if ! $DRY_RUN; then
|
||||
status_line "Shell %s\n" "$SLOPTRAP_IMAGE_NAME"
|
||||
fi
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${source_args[@]}" /bin/bash)
|
||||
run_or_print "${cmd[@]}"
|
||||
append_auth_mount_arg false auth_mount
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" /bin/bash)
|
||||
run_runtime_container_cmd "${cmd[@]}"
|
||||
}
|
||||
|
||||
run_resume_target() {
|
||||
@@ -2447,6 +2593,7 @@ DRY_RUN=false
|
||||
PRINT_CONFIG=false
|
||||
SKIP_BUILD_BANNER=false
|
||||
TRUST_CAPABILITIES=false
|
||||
RUNTIME_PACKET_CAPTURE_ACKNOWLEDGED=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
@@ -2599,6 +2746,7 @@ if [[ -n $PACKAGES_EXTRA ]]; then
|
||||
validate_package_list "packages_extra" "$PACKAGES_EXTRA"
|
||||
fi
|
||||
CONTAINER_ENGINE="$(detect_container_engine)"
|
||||
ensure_capability_engine_supported
|
||||
CODEX_ARGS_ARRAY=("${DEFAULT_CODEX_ARGS[@]}")
|
||||
ensure_safe_sandbox "${CODEX_ARGS_ARRAY[@]}"
|
||||
CODEX_ARGS_DISPLAY=$DEFAULT_CODEX_ARGS_DISPLAY
|
||||
|
||||
@@ -14,3 +14,4 @@ Current scenarios:
|
||||
- `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.
|
||||
- `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`.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
name=capability-repo
|
||||
capabilities=apt-install packet-capture
|
||||
allow_host_network=true
|
||||
allow_host_network=false
|
||||
|
||||
3
tests/host_network_packet_capture/.sloptrap
Normal file
3
tests/host_network_packet_capture/.sloptrap
Normal file
@@ -0,0 +1,3 @@
|
||||
name=host-network-packet-capture
|
||||
capabilities=packet-capture
|
||||
allow_host_network=true
|
||||
3
tests/host_network_repo/.sloptrap
Normal file
3
tests/host_network_repo/.sloptrap
Normal file
@@ -0,0 +1,3 @@
|
||||
name=host-network-repo
|
||||
capabilities=apt-install
|
||||
allow_host_network=true
|
||||
@@ -97,6 +97,47 @@ verify_secret_mounts() {
|
||||
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'
|
||||
@@ -113,10 +154,16 @@ if [[ ${SECRET_MASK_VERIFY:-0} == 1 && ${1-} == "run" ]]; then
|
||||
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
|
||||
@@ -367,7 +414,7 @@ run_resume_target() {
|
||||
}
|
||||
|
||||
run_runtime_context_prompt() {
|
||||
local scenario_dir="$TEST_ROOT/capability_repo"
|
||||
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 \
|
||||
@@ -382,8 +429,8 @@ run_runtime_context_prompt() {
|
||||
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=capability-repo" "$STUB_LOG" \
|
||||
|| ! grep -q -- "enabled_capabilities=apt-install packet-capture" "$STUB_LOG" \
|
||||
if ! grep -q -- "name=host-network-repo" "$STUB_LOG" \
|
||||
|| ! grep -q -- "enabled_capabilities=apt-install" "$STUB_LOG" \
|
||||
|| ! grep -q -- "network_mode=host" "$STUB_LOG"; then
|
||||
record_failure "runtime_context_prompt: runtime summary missing manifest or capability state"
|
||||
fi
|
||||
@@ -394,7 +441,7 @@ run_runtime_context_prompt() {
|
||||
}
|
||||
|
||||
run_sh_reexec() {
|
||||
local scenario_dir="$TEST_ROOT/capability_repo"
|
||||
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 \
|
||||
@@ -410,7 +457,7 @@ run_sh_reexec() {
|
||||
}
|
||||
|
||||
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"
|
||||
printf '==> resume_omits_runtime_context\n'
|
||||
setup_stub_env
|
||||
@@ -442,8 +489,8 @@ run_auth_file_mount() {
|
||||
teardown_stub_env
|
||||
return
|
||||
fi
|
||||
if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then
|
||||
record_failure "auth_file_mount: missing auth file bind mount"
|
||||
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"
|
||||
@@ -466,8 +513,8 @@ run_codex_home_override() {
|
||||
teardown_stub_env
|
||||
return
|
||||
fi
|
||||
if ! grep -q -- "-v ${codex_root}/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then
|
||||
record_failure "codex_home_override: missing CODEX_HOME auth file mount"
|
||||
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"
|
||||
@@ -564,8 +611,11 @@ run_auto_login_empty_auth() {
|
||||
if [[ -z $first_run || $first_run != *" login" ]]; then
|
||||
record_failure "auto_login_empty_auth: expected login before primary run"
|
||||
fi
|
||||
if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then
|
||||
record_failure "auto_login_empty_auth: missing auth file bind mount"
|
||||
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
|
||||
}
|
||||
@@ -687,6 +737,53 @@ run_invalid_allow_host_network() {
|
||||
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() {
|
||||
local scenario_dir="$TEST_ROOT/wizard_empty"
|
||||
printf '==> wizard_create_manifest\n'
|
||||
@@ -736,7 +833,7 @@ run_wizard_existing_defaults() {
|
||||
if ! grep -qx "packages_extra=make git" "$scenario_dir/.sloptrap"; then
|
||||
record_failure "wizard_existing_defaults: packages_extra not preserved"
|
||||
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"
|
||||
fi
|
||||
if ! grep -qx "allow_host_network=true" "$scenario_dir/.sloptrap"; then
|
||||
@@ -779,10 +876,31 @@ run_capability_trust_required() {
|
||||
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() {
|
||||
local scenario_dir="$TEST_ROOT/capability_repo"
|
||||
printf '==> capability_profiles\n'
|
||||
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 \
|
||||
"$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||
record_failure "capability_profiles: sloptrap exited non-zero"
|
||||
@@ -792,44 +910,65 @@ run_capability_profiles() {
|
||||
if ! grep -q -- "CAPABILITY_PACKAGES=tcpdump" "$STUB_LOG"; then
|
||||
record_failure "capability_profiles: build arg for capability packages missing"
|
||||
fi
|
||||
if ! grep -q -- "FAKE PODMAN: build --quiet -t capability-repo-sloptrap-image -f .* --network host " "$STUB_LOG"; then
|
||||
record_failure "capability_profiles: build should inherit host networking"
|
||||
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 stay on isolated networking"
|
||||
fi
|
||||
if ! grep -q -- "--cap-add NET_RAW" "$STUB_LOG"; then
|
||||
record_failure "capability_profiles: NET_RAW capability missing"
|
||||
main_lines=$(grep "FAKE PODMAN: run " "$STUB_LOG" | grep -- "--name capability-repo-sloptrap-container" || true)
|
||||
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
|
||||
if ! grep -q -- "--cap-add NET_ADMIN" "$STUB_LOG"; then
|
||||
record_failure "capability_profiles: NET_ADMIN capability missing"
|
||||
if [[ -z $pod_lines ]]; then
|
||||
record_failure "capability_profiles: packet capture should create a dedicated pod"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
fi
|
||||
if ! grep -q -- "--cap-add DAC_OVERRIDE" "$STUB_LOG"; then
|
||||
record_failure "capability_profiles: DAC_OVERRIDE capability missing"
|
||||
if grep -q -- "--cap-add DAC_OVERRIDE" <<<"$capture_lines$main_lines"; then
|
||||
record_failure "capability_profiles: DAC_OVERRIDE should not be granted"
|
||||
fi
|
||||
if ! grep -q -- "--cap-add FOWNER" "$STUB_LOG"; then
|
||||
record_failure "capability_profiles: FOWNER capability missing"
|
||||
if grep -q -- "--cap-add FOWNER" <<<"$capture_lines$main_lines"; then
|
||||
record_failure "capability_profiles: FOWNER should not be granted"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
fi
|
||||
if ! grep -q -- "SLOPTRAP_ACTIVE_CAPABILITIES=apt-install packet-capture" "$STUB_LOG"; then
|
||||
record_failure "capability_profiles: active capability environment missing"
|
||||
if ! grep -q -- "SLOPTRAP_ACTIVE_CAPABILITIES=apt-install" <<<"$main_lines"; then
|
||||
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
|
||||
if ! grep -q -- "SLOPTRAP_HOST_UID=$(id -u)" "$STUB_LOG"; then
|
||||
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"
|
||||
fi
|
||||
|
||||
local autostart_helper_dir
|
||||
autostart_helper_dir="$temp_root/helper-autostart"
|
||||
if ! TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$autostart_helper_dir" \
|
||||
if grep -q -- "setpriv --reuid 0 --regid 0" "$helper_bin/slop-apt" \
|
||||
|| grep -q -- "setpriv --reuid 0 --regid 0" "$helper_bin/slopcap"; then
|
||||
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_CAPTURE_DIR="$capture_dir" SLOPTRAP_WORKDIR="$workspace_dir" \
|
||||
SLOPTRAP_AUDIT_LOG="$temp_root/autostart-audit.log" \
|
||||
SLOPTRAP_HOST_UID="$(id -u)" SLOPTRAP_HOST_GID="$(id -g)" \
|
||||
"$helper_bin/slop-apt" install jq >/dev/null 2>&1; then
|
||||
record_failure "embedded_capability_helpers: slop-apt did not self-bootstrap the helper daemon"
|
||||
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
|
||||
"$helper_bin/slop-apt" install jq >"$temp_root/missing-helper.out" 2>"$temp_root/missing-helper.err"; then
|
||||
record_failure "embedded_capability_helpers: slop-apt should fail when the root helper is unavailable"
|
||||
elif ! grep -q -- 'capability helper is unavailable' "$temp_root/missing-helper.err"; then
|
||||
record_failure "embedded_capability_helpers: missing helper failure should explain the boundary"
|
||||
fi
|
||||
|
||||
TEST_TOOL_LOG="$tool_log" PATH="$helper_bin:$PATH" SLOPTRAP_HELPER_DIR="$helper_dir" \
|
||||
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_AUDIT_LOG="$temp_root/audit.log" "$helper_bin/sloptrap-helperd" >/dev/null 2>&1 &
|
||||
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" \
|
||||
"$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" \
|
||||
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
|
||||
record_failure "embedded_capability_helpers: slopcap accepted an out-of-bounds output path"
|
||||
fi
|
||||
|
||||
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_HELPER_DIR="$helper_dir" SLOPTRAP_PACKET_CAPTURE_ENABLED=1 \
|
||||
"$helper_bin/slopcap" capture --interface eth0 --filter 'tcp port 80' \
|
||||
--output "$workspace_dir/capture.pcap" >/dev/null 2>&1; then
|
||||
record_failure "embedded_capability_helpers: slopcap failed for a workspace-local capture file"
|
||||
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"
|
||||
fi
|
||||
|
||||
@@ -1080,11 +1222,14 @@ run_invalid_manifest_sandbox
|
||||
run_invalid_manifest_packages
|
||||
run_invalid_manifest_capabilities
|
||||
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_wizard_create_manifest
|
||||
run_wizard_existing_defaults
|
||||
run_wizard_build_trigger
|
||||
run_capability_trust_required
|
||||
run_capabilities_require_podman
|
||||
run_capability_profiles
|
||||
run_embedded_capability_helpers
|
||||
run_make_install_single_file
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name=custom-wizard
|
||||
packages_extra=make git
|
||||
capabilities=apt-install packet-capture
|
||||
capabilities=apt-install
|
||||
allow_host_network=true
|
||||
|
||||
Reference in New Issue
Block a user