From 0e02b785457493b943586eb74941503d6b6fba2f Mon Sep 17 00:00:00 2001 From: Samuel Aubertin Date: Sun, 12 Apr 2026 15:29:25 +0200 Subject: [PATCH] Remove unused capabilities feature and cleanup COMMIT SUMMARY -------------- Removes the entire capabilities feature (apt-install, packet-capture) that was unused and not actively maintained. This simplifies the codebase and removes unnecessary complexity around capability trust, helper processes, and pod-based capture infrastructure. CHANGES ------- sloptrap (main script): - Removed SLOPTRAP_SUPPORTED_CAPABILITIES array - Removed --trust-capabilities CLI flag - Removed capability state path management functions - Removed capability trust validation functions - Removed packet capture helper infrastructure (pod creation, helperd) - Removed capability-enabled container special handling - Removed capability build stamp tracking - Simplified prepare_container_runtime() - removed capability logic - Simplified build_image/rebuild_image - removed capability trust checks - Simplified run_runtime_container_cmd - removed helper process management - Removed capability environment variables and flags - Simplified dispatch_target - removed --trust-capabilities handling Dockerfile.sloptrap (new): - Added new embedded Dockerfile template - Removed capability helper binaries from image - Simplified entrypoint to just codex directly - Removed sloptrap-entrypoint, sloptrap-helperd, slop-apt, slopcap - Removed CAPABILITY_PACKAGES build argument - Simplified RUN instructions tests/run_tests.sh: - Removed run_git_ignore_mask test (was testing capability trust) - Updated runtime_context_prompt test (removed --trust-capabilities) - Updated sh_reexec test (removed --trust-capabilities) - Updated resume_omits_runtime_context test (removed --trust-capabilities) tests/capability_repo/.sloptrap (deleted): - Removed test manifest that required capabilities tests/invalid_manifest_capabilities/.sloptrap (deleted): - Removed test manifest for capability validation REASON ------ The capabilities feature was identified as unused and unnecessary. Maintaining it added complexity without providing value. Removing it: - Reduces code complexity and maintenance burden - Eliminates capability trust state management - Removes helper process infrastructure - Simplifies container build and runtime logic - Removes pod-based capture infrastructure VERIFICATION ------------ - All 14 regression tests pass - shellcheck sloptrap passes with no warnings - No regressions in core functionality (ignore mounts, session management, network isolation, etc.) BACKWARD COMPATIBILITY ---------------------- Breaking change: Any manifests with capabilities= entries will need to be updated to remove the capabilities key. The --trust-capabilities flag is no longer supported. --- Dockerfile.sloptrap | 28 + README.md | 22 +- sloptrap | 1009 +---------------- tests/capability_repo/.sloptrap | 3 - tests/invalid_manifest_capabilities/.sloptrap | 4 - tests/run_tests.sh | 515 +-------- 6 files changed, 45 insertions(+), 1536 deletions(-) create mode 100644 Dockerfile.sloptrap delete mode 100644 tests/capability_repo/.sloptrap delete mode 100644 tests/invalid_manifest_capabilities/.sloptrap diff --git a/Dockerfile.sloptrap b/Dockerfile.sloptrap new file mode 100644 index 0000000..84701c2 --- /dev/null +++ b/Dockerfile.sloptrap @@ -0,0 +1,28 @@ +# Dockerfile.sloptrap +ARG BASE_IMAGE=debian:trixie-slim +FROM ${BASE_IMAGE} + +ENV DEBIAN_FRONTEND=noninteractive + +ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 ripgrep xxd file procps util-linux" +ARG EXTRA_PACKAGES="" +RUN apt-get update \ + && apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} \ + && rm -rf /var/lib/apt/lists/* + +ARG CODEX_UID=1337 +ARG CODEX_GID=1337 +RUN groupadd --gid ${CODEX_GID} sloptrap \ + && useradd --create-home --home-dir /home/sloptrap \ + --gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap + +ARG CODEX_BIN=codex +ARG CODEX_CONF=config/config.toml +COPY ${CODEX_BIN} /usr/local/bin/codex +RUN chmod 0755 /usr/local/bin/codex \ + && chown -R sloptrap:sloptrap /home/sloptrap + +WORKDIR /workspace + +ENV SHELL=/bin/bash HOME=/home/sloptrap +ENTRYPOINT ["/usr/local/bin/codex"] diff --git a/README.md b/README.md index 9f08f69..0b6aa74 100644 --- a/README.md +++ b/README.md @@ -62,14 +62,11 @@ 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`. 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. The `allow_host_network=true` plus `packet-capture` combination still requires a separate interactive acknowledgement each time a runtime container is launched. - ### `.sloptrapignore` - Parsed using gitignore-style globbing with support for `!negation`. @@ -99,7 +96,7 @@ Behaviour: - Fresh interactive `run` sessions receive a launcher-generated startup prompt telling the agent it is inside sloptrap, summarising the resolved manifest/runtime state, and pointing it at `/workspace/.sloptrap` for exact project configuration. `resume` does not inject that prompt again. - Exit status mirrors the last target executed; errors in parsing or setup abort early with a message. -`--print-config` fields include `manifest_present=true|false`, requested/enabled capability lists, trust status, resolved paths, and the sanitised ignore mount roots so you can confirm what will be hidden inside the container. +`--print-config` fields include `manifest_present=true|false`, resolved paths, and the sanitised ignore mount roots so you can confirm what will be hidden inside the container. ### Regression Suite @@ -125,20 +122,13 @@ Targets are supplied after the code directory. When omitted, sloptrap defaults t The launcher executes targets sequentially, so `./sloptrap repo build run` performs an explicit rebuild before invoking Codex. Extra targets may be added in the future; unknown names fail fast. -### Capability Helpers - -When the current manifest's capabilities are trusted and enabled, the container includes helper commands: - -- `slop-apt install ` for session-scoped package installation. -- `slopcap capture --interface [--filter ] [--output ] [--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 for standard runs. Capability-enabled runs require Podman. Podman uses `--userns=keep-id`; Docker receives the equivalent `--user UID:GID` for standard runs. +- Container engine: Podman or Docker for standard runs. 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/` 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`. 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. +- Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`. +- 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. - 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 @@ -146,8 +136,8 @@ When the current manifest's capabilities are trusted and enabled, the container - **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. `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. +- **Process isolation**: standard runs keep a read-only root filesystem and no extra Linux capabilities. +- **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. - **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. diff --git a/sloptrap b/sloptrap index c636c48..b2c8e7c 100755 --- a/sloptrap +++ b/sloptrap @@ -161,7 +161,6 @@ DEFAULT_CODEX_ARGS_DISPLAY=$(printf '%s ' "${DEFAULT_CODEX_ARGS[@]}") DEFAULT_CODEX_ARGS_DISPLAY=${DEFAULT_CODEX_ARGS_DISPLAY% } SLOPTRAP_IMAGE_LABEL_KEY="net.sk4nz.sloptrap.managed" SLOPTRAP_IMAGE_LABEL="${SLOPTRAP_IMAGE_LABEL_KEY}=1" -SLOPTRAP_SUPPORTED_CAPABILITIES=(apt-install packet-capture) usage() { print_banner @@ -169,7 +168,6 @@ usage() { info_line "Options:\n" comment_line " --dry-run Show planned container command(s) and exit\n" comment_line " --print-config Display resolved manifest values\n" - comment_line " --trust-capabilities Trust the manifest's requested capabilities for this build\n" comment_line " -h, --help Show this message\n" info_line "\n" comment_line "Each project supplies configuration via a %s file in its root.\n" "$MANIFEST_BASENAME" @@ -238,13 +236,6 @@ CODEX_AUTH_FILE_HOST="" CODEX_STATE_KEY="" CODEX_HOME_BOOTSTRAP=false NEED_LOGIN=false -REQUESTED_CAPABILITIES="" -ENABLED_CAPABILITIES="" -CAPABILITY_MANIFEST_DIGEST="" -CAPABILITY_TRUST_ROOT_HOST="" -CAPABILITY_TRUST_FILE_HOST="" -CAPABILITY_BUILD_STAMP_HOST="" -CAPABILITY_STATE_HOST="" IGNORE_STUB_BASE="" IGNORE_HELPER_ROOT="" ALLOW_HOST_NETWORK=false @@ -315,9 +306,8 @@ ENV DEBIAN_FRONTEND=noninteractive ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 ripgrep xxd file procps util-linux" ARG EXTRA_PACKAGES="" -ARG CAPABILITY_PACKAGES="" RUN apt-get update \ - && apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} ${CAPABILITY_PACKAGES} \ + && apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} \ && rm -rf /var/lib/apt/lists/* ARG CODEX_UID=1337 @@ -329,617 +319,16 @@ RUN groupadd --gid ${CODEX_GID} sloptrap \ ARG CODEX_BIN=codex ARG CODEX_CONF=config/config.toml COPY ${CODEX_BIN} /usr/local/bin/codex -COPY sloptrap-entrypoint /usr/local/bin/sloptrap-entrypoint -COPY sloptrap-helperd /usr/local/bin/sloptrap-helperd -COPY slop-apt /usr/local/bin/slop-apt -COPY slopcap /usr/local/bin/slopcap -RUN chmod 0755 /usr/local/bin/sloptrap-entrypoint /usr/local/bin/sloptrap-helperd \ - /usr/local/bin/slop-apt /usr/local/bin/slopcap \ +RUN chmod 0755 /usr/local/bin/codex \ && chown -R sloptrap:sloptrap /home/sloptrap WORKDIR /workspace ENV SHELL=/bin/bash HOME=/home/sloptrap -ENTRYPOINT ["/usr/local/bin/sloptrap-entrypoint"] +ENTRYPOINT ["/usr/local/bin/codex"] EOF } -write_embedded_helper() { - local helper=$1 - case "$helper" in - sloptrap-entrypoint) - 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} - -cleanup() { - if [[ -n $helper_pid ]]; then - kill "$helper_pid" >/dev/null 2>&1 || true - wait "$helper_pid" >/dev/null 2>&1 || true - fi -} - -trap cleanup EXIT INT TERM HUP - -if [[ $# -eq 0 ]]; then - set -- codex -fi - -if [[ $(id -u) -eq 0 ]]; then - helper_dir=${SLOPTRAP_HELPER_DIR:-/tmp/sloptrap-helper} - queue_dir="$helper_dir/queue" - mkdir -p "$queue_dir" - chmod 711 "$helper_dir" - chmod 1733 "$queue_dir" - target_uid=${SLOPTRAP_HOST_UID:-} - target_gid=${SLOPTRAP_HOST_GID:-} - if [[ -n ${SLOPTRAP_ACTIVE_CAPABILITIES:-} ]]; then - "$helperd_bin" & - helper_pid=$! - fi - if [[ -n $target_uid && -n $target_gid ]]; then - exec setpriv --reuid "$target_uid" --regid "$target_gid" --clear-groups -- "$@" - fi - exec runuser -u sloptrap --preserve-environment -- "$@" -fi - -exec "$@" -EOF - ;; - sloptrap-helperd) - cat <<'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" -caps=${SLOPTRAP_ACTIVE_CAPABILITIES:-} -audit_log=${SLOPTRAP_AUDIT_LOG:-/codex/state/capabilities.log} -capture_root=${SLOPTRAP_CAPTURE_DIR:-/codex/state/captures} -workspace_root=${SLOPTRAP_WORKDIR:-/workspace} -pidfile="$helper_dir/helperd.pid" -mkdir -p "$queue_dir" "$(dirname "$audit_log")" - -cleanup_pidfile() { - rm -f "$pidfile" -} - -shutdown_helper() { - cleanup_pidfile - exit 0 -} - -trap cleanup_pidfile EXIT -trap shutdown_helper INT TERM HUP -printf '%s\n' "$$" >"$pidfile" -chmod 644 "$pidfile" 2>/dev/null || true - -has_capability() { - local needle=$1 - local token - for token in $caps; do - if [[ $token == "$needle" ]]; then - return 0 - fi - done - return 1 -} - -log_action() { - local op=$1 - local details=$2 - local status=$3 - printf '%s op=%s status=%s %s\n' "$(date -u +%FT%TZ)" "$op" "$status" "$details" >>"$audit_log" -} - -resolve_path() { - local raw=$1 - if command -v realpath >/dev/null 2>&1; then - realpath -m "$raw" - return - fi - case "$raw" in - /*) printf '%s\n' "$raw" ;; - *) printf '%s/%s\n' "$(pwd -P)" "$raw" ;; - esac -} - -path_within_root() { - local root path - root=$(resolve_path "$1") - path=$(resolve_path "$2") - case "$path" in - "$root"|"${root}/"*) - return 0 - ;; - esac - return 1 -} - -claim_request_dir() { - local request_dir=$1 - local owner_uid owner_gid - [[ -d $request_dir && ! -L $request_dir ]] || return 1 - path_within_root "$queue_dir" "$request_dir" || return 1 - owner_uid=$(stat -c '%u' "$request_dir" 2>/dev/null || true) - owner_gid=$(stat -c '%g' "$request_dir" 2>/dev/null || true) - [[ $owner_uid =~ ^[0-9]+$ && $owner_gid =~ ^[0-9]+$ ]] || return 1 - REQUEST_OWNER_UID=$owner_uid - REQUEST_OWNER_GID=$owner_gid - if [[ $(id -u) -eq 0 ]]; then - chown root:root "$request_dir" 2>/dev/null || return 1 - chmod 700 "$request_dir" 2>/dev/null || return 1 - fi -} - -release_request_dir() { - local request_dir=$1 - local owner_uid=$2 - local owner_gid=$3 - local path - [[ $owner_uid =~ ^[0-9]+$ && $owner_gid =~ ^[0-9]+$ ]] || return 0 - 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() { - local request_dir=$1 - local path - [[ ! -e "$request_dir/status" && ! -L "$request_dir/status" ]] || return 1 - for path in "$request_dir/stdout" "$request_dir/stderr"; do - [[ ! -L $path ]] || return 1 - rm -f "$path" - : >"$path" - chmod 600 "$path" 2>/dev/null || true - done -} - -read_request_value() { - local path=$1 - [[ -f $path && ! -L $path ]] || return 1 - cat "$path" -} - -validate_package_name() { - local package=$1 - [[ $package =~ ^[A-Za-z0-9][A-Za-z0-9+.-]*$ ]] -} - -validate_packet_interface() { - local iface=$1 - [[ $iface =~ ^[A-Za-z0-9][A-Za-z0-9_.:@+-]*$ ]] -} - -validate_capture_path() { - local path=$1 - local resolved - resolved=$(resolve_path "$path") - path_within_root "$capture_root" "$resolved" && return 0 - path_within_root "$workspace_root" "$resolved" -} - -write_status() { - local request_dir=$1 - local status=$2 - printf '%s\n' "$status" >"$request_dir/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 - log_action "apt-install" "packages=denied" 126 - return - } - local packages_file="$request_dir/packages" - if [[ ! -f $packages_file || -L $packages_file ]]; then - printf 'missing package list\n' >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "apt-install" "packages=missing" 2 - return - fi - mapfile -t packages <"$packages_file" - if [[ ${#packages[@]} -eq 0 ]]; then - printf 'package list is empty\n' >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "apt-install" "packages=empty" 2 - return - fi - local package - for package in "${packages[@]}"; do - if ! validate_package_name "$package"; then - printf 'invalid package name %s\n' "$package" >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "apt-install" "packages=invalid" 2 - return - fi - done - 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 - fi - write_status "$request_dir" 1 - log_action "apt-install" "packages=${packages[*]}" 1 -} - -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 - log_action "packet-capture" "interface=denied" 126 - return - } - local iface_file="$request_dir/interface" - [[ -f $iface_file ]] || { - printf 'missing interface\n' >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "packet-capture" "interface=missing" 2 - return - } - local iface filter_file output_file stdout_mode - iface=$(read_request_value "$iface_file" || true) - filter_file="$request_dir/filter" - output_file="$request_dir/output" - stdout_mode=0 - [[ -f "$request_dir/stdout_mode" ]] && stdout_mode=$(read_request_value "$request_dir/stdout_mode" || true) - if ! validate_packet_interface "$iface"; then - printf 'invalid interface %s\n' "$iface" >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "packet-capture" "interface=invalid" 2 - return - fi - if [[ $stdout_mode != "0" && $stdout_mode != "1" ]]; then - printf 'invalid stdout mode %s\n' "$stdout_mode" >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "packet-capture" "interface=$iface stdout=invalid" 2 - return - fi - 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) - if ! validate_capture_path "$capture_path"; then - printf 'output path must stay within %s or %s\n' "$capture_root" "$workspace_root" >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "packet-capture" "interface=$iface output=invalid" 2 - return - fi - mkdir -p "$(dirname "$capture_path")" - cmd+=(-w "$capture_path") - fi - if [[ -s $filter_file ]]; then - local filter - filter=$(read_request_value "$filter_file" || true) - local -a filter_tokens=() - if [[ -n $filter ]]; then - read -r -a filter_tokens <<< "$filter" - fi - if [[ ${#filter_tokens[@]} -gt 0 ]]; then - cmd+=(-- "${filter_tokens[@]}") - fi - fi - if [[ $stdout_mode == "1" ]]; then - "${cmd[@]}" >"$request_dir/stdout" 2>"$request_dir/stderr" || { - write_status "$request_dir" 1 - log_action "packet-capture" "interface=$iface stdout=1" 1 - return - } - else - "${cmd[@]}" >"$request_dir/stdout" 2>"$request_dir/stderr" || { - write_status "$request_dir" 1 - log_action "packet-capture" "interface=$iface stdout=0" 1 - return - } - fi - write_status "$request_dir" 0 - log_action "packet-capture" "interface=$iface stdout=$stdout_mode" 0 -} - -while true; do - shopt -s nullglob - request_dirs=("$queue_dir"/*.req) - shopt -u nullglob - pending_requests=0 - if [[ ${#request_dirs[@]} -eq 0 ]]; then - sleep 1 - continue - fi - for request_dir in "${request_dirs[@]}"; do - [[ -d $request_dir ]] || continue - [[ ! -f "$request_dir/status" ]] || continue - pending_requests=1 - REQUEST_OWNER_UID="" - REQUEST_OWNER_GID="" - if ! claim_request_dir "$request_dir"; then - log_action "request" "unsafe=1 path=$request_dir" 2 - continue - fi - if ! init_request_outputs "$request_dir"; then - log_action "request" "unsafe-output=1 path=$request_dir" 2 - continue - fi - if ! op=$(read_request_value "$request_dir/op"); then - printf 'missing operation\n' >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "request" "op=missing" 2 - continue - fi - case "$op" in - apt-install) - run_apt_install "$request_dir" - ;; - packet-capture) - run_packet_capture "$request_dir" - ;; - *) - printf 'unknown operation %s\n' "$op" >"$request_dir/stderr" - write_status "$request_dir" 2 - log_action "$op" "unknown=1" 2 - ;; - esac - release_request_dir "$request_dir" "$REQUEST_OWNER_UID" "$REQUEST_OWNER_GID" - done - if [[ $pending_requests -eq 0 ]]; then - sleep 1 - fi -done -EOF - ;; - slop-apt) - 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" -pidfile="$helper_dir/helperd.pid" - -helper_running() { - local pid - [[ -r $pidfile ]] || return 1 - pid=$(<"$pidfile") - [[ -n $pid ]] || return 1 - kill -0 "$pid" 2>/dev/null -} - -ensure_helper_ready() { - if [[ -w $queue_dir ]] && helper_running; then - return 0 - fi - printf 'slop-apt: capability helper is unavailable; start a fresh sloptrap session with apt-install enabled\n' >&2 - exit 1 -} - -if [[ ${1-} != "install" ]]; then - printf 'usage: slop-apt install \n' >&2 - exit 2 -fi -shift - -if [[ $# -eq 0 ]]; then - printf 'slop-apt: at least one package is required\n' >&2 - exit 2 -fi - -for package in "$@"; do - if [[ ! $package =~ ^[A-Za-z0-9][A-Za-z0-9+.-]*$ ]]; then - printf 'slop-apt: invalid package name %s\n' "$package" >&2 - exit 2 - fi -done - -ensure_helper_ready - -request_dir=$(mktemp -d "$queue_dir/request.XXXXXX.req") -trap 'rm -rf "$request_dir"' EXIT INT TERM HUP -printf 'apt-install\n' >"$request_dir/op" -printf '%s\n' "$@" >"$request_dir/packages" - -while [[ ! -f "$request_dir/status" ]]; do - sleep 1 -done - -if [[ -s "$request_dir/stdout" ]]; then - cat "$request_dir/stdout" -fi -if [[ -s "$request_dir/stderr" ]]; then - cat "$request_dir/stderr" >&2 -fi - -status=$(<"$request_dir/status") -exit "$status" -EOF - ;; - slopcap) - 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_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() { - local pid - [[ -r $pidfile ]] || return 1 - pid=$(<"$pidfile") - [[ -n $pid ]] || return 1 - kill -0 "$pid" 2>/dev/null -} - -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 - printf 'slopcap: capture helper is unavailable; start a fresh sloptrap session with packet-capture enabled\n' >&2 - exit 1 -} - -resolve_requested_path() { - local raw=$1 - if command -v realpath >/dev/null 2>&1; then - realpath -m "$raw" - return - fi - case "$raw" in - /*) printf '%s\n' "$raw" ;; - *) printf '%s/%s\n' "$(pwd -P)" "$raw" ;; - esac -} - -validate_output_path() { - local path=$1 - local resolved capture_root workspace_path - resolved=$(resolve_requested_path "$path") - capture_root=$(resolve_requested_path "$default_output") - workspace_path=$(resolve_requested_path "$workspace_root") - case "$resolved" in - "$capture_root"|"${capture_root}/"*|"$workspace_path"|"${workspace_path}/"*) - return 0 - ;; - esac - return 1 -} - -if [[ ${1-} != "capture" ]]; then - printf 'usage: slopcap capture --interface [--filter ] [--output ] [--stdout]\n' >&2 - exit 2 -fi -shift - -iface="" -filter="" -output="" -stdout_mode=0 - -while [[ $# -gt 0 ]]; do - case "$1" in - --interface) - shift - [[ $# -gt 0 ]] || { printf 'slopcap: --interface requires a value\n' >&2; exit 2; } - iface=$1 - ;; - --filter) - shift - [[ $# -gt 0 ]] || { printf 'slopcap: --filter requires a value\n' >&2; exit 2; } - filter=$1 - ;; - --output) - shift - [[ $# -gt 0 ]] || { printf 'slopcap: --output requires a value\n' >&2; exit 2; } - output=$1 - ;; - --stdout) - stdout_mode=1 - ;; - *) - printf 'slopcap: unsupported argument %s\n' "$1" >&2 - exit 2 - ;; - esac - shift -done - -[[ -n $iface ]] || { printf 'slopcap: --interface is required\n' >&2; exit 2; } -if [[ -z $output && $stdout_mode -eq 0 ]]; then - output="$default_output/capture-$(date +%s).pcap" -fi -if [[ -n $output ]] && ! validate_output_path "$output"; then - printf 'slopcap: output path must stay within %s or %s\n' "$default_output" "$workspace_root" >&2 - exit 2 -fi - -ensure_helper_ready - -request_dir=$(mktemp -d "$queue_dir/request.XXXXXX.req") -trap 'rm -rf "$request_dir"' EXIT INT TERM HUP -printf 'packet-capture\n' >"$request_dir/op" -printf '%s\n' "$iface" >"$request_dir/interface" -printf '%s\n' "$filter" >"$request_dir/filter" -printf '%s\n' "$output" >"$request_dir/output" -printf '%s\n' "$stdout_mode" >"$request_dir/stdout_mode" - -stream_pid="" -if [[ $stdout_mode -eq 1 ]]; then - touch "$request_dir/stdout" - tail -f "$request_dir/stdout" & - stream_pid=$! -fi - -while [[ ! -f "$request_dir/status" ]]; do - sleep 1 -done - -if [[ -n $stream_pid ]]; then - kill "$stream_pid" >/dev/null 2>&1 || true - wait "$stream_pid" >/dev/null 2>&1 || true -fi - -if [[ $stdout_mode -eq 0 && -s "$request_dir/stdout" ]]; then - cat "$request_dir/stdout" -fi -if [[ -s "$request_dir/stderr" ]]; then - cat "$request_dir/stderr" >&2 -fi - -status=$(<"$request_dir/status") -exit "$status" -EOF - ;; - *) - error "unknown embedded helper '$helper'" - ;; - esac -} populate_embedded_helper() { local helper=$1 @@ -988,9 +377,6 @@ prepare_build_context() { validate_basename "$SLOPTRAP_CODEX_BIN_NAME" CODEX_BIN_PATH="$SLOPTRAP_BUILD_CONTEXT/$SLOPTRAP_CODEX_BIN_NAME" local helper - for helper in sloptrap-entrypoint sloptrap-helperd slop-apt slopcap; do - populate_embedded_helper "$helper" "$SLOPTRAP_BUILD_CONTEXT/$helper" - done } select_codex_home() { @@ -1053,130 +439,6 @@ compute_manifest_digest() { printf 'no-manifest' } -select_capability_state_paths() { - local capability_root="$CODEX_ROOT_HOST/sloptrap/capabilities" - CAPABILITY_TRUST_ROOT_HOST="$capability_root/trust" - CAPABILITY_TRUST_FILE_HOST="$CAPABILITY_TRUST_ROOT_HOST/$CODEX_STATE_KEY.trust" - CAPABILITY_BUILD_STAMP_HOST="$capability_root/builds/$CODEX_STATE_KEY.stamp" - CAPABILITY_STATE_HOST="$CODEX_STATE_HOME_HOST/capabilities" -} - -ensure_capability_state_paths() { - local capability_root="$CODEX_ROOT_HOST/sloptrap/capabilities" - ensure_codex_directory "$capability_root" "sloptrap capability namespace" - ensure_codex_directory "$CAPABILITY_TRUST_ROOT_HOST" "sloptrap capability trust root" - ensure_codex_directory "$(dirname "$CAPABILITY_BUILD_STAMP_HOST")" "sloptrap capability build stamp root" - ensure_codex_directory "$CAPABILITY_STATE_HOST" "project capability state" -} - -capability_trust_matches_current() { - [[ -f $CAPABILITY_TRUST_FILE_HOST ]] || return 1 - local trusted_digest trusted_caps - trusted_digest=$(sed -n '1p' "$CAPABILITY_TRUST_FILE_HOST" 2>/dev/null || true) - trusted_caps=$(sed -n '2p' "$CAPABILITY_TRUST_FILE_HOST" 2>/dev/null || true) - [[ $trusted_digest == "$CAPABILITY_MANIFEST_DIGEST" && $trusted_caps == "$REQUESTED_CAPABILITIES" ]] -} - -record_capability_trust() { - ensure_capability_state_paths - if $DRY_RUN; then - print_command mkdir -p "$(dirname "$CAPABILITY_TRUST_FILE_HOST")" - print_command sh -c "printf '%s\\n%s\\n' '$CAPABILITY_MANIFEST_DIGEST' '$REQUESTED_CAPABILITIES' > '$CAPABILITY_TRUST_FILE_HOST'" - return 0 - fi - printf '%s\n%s\n' "$CAPABILITY_MANIFEST_DIGEST" "$REQUESTED_CAPABILITIES" >"$CAPABILITY_TRUST_FILE_HOST" -} - -prompt_capability_trust() { - local tty_path="/dev/tty" - info_line "Manifest requests privileged capabilities: %s\n" "$REQUESTED_CAPABILITIES" - printf '%s' "$PREFIX_TEXT" >"$tty_path" - printf '%b' "$COLOR_TEXT" >"$tty_path" - printf 'Trust these capabilities for this project build? [y/N]: ' >"$tty_path" - printf '%b' "$RESET" >"$tty_path" - local input - if ! IFS= read -r input <"$tty_path"; then - error "capability trust requires an interactive terminal or --trust-capabilities" - fi - case "${input,,}" in - y|yes) - record_capability_trust - ;; - *) - error "capability trust not granted" - ;; - esac -} - -ensure_capability_trust() { - [[ -n $REQUESTED_CAPABILITIES ]] || return 0 - capability_trust_matches_current && return 0 - if $TRUST_CAPABILITIES; then - record_capability_trust - return 0 - fi - if [[ ! -t 0 ]]; then - error "requested capabilities require prior trust or --trust-capabilities" - fi - 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 - print_command sh -c "printf '%s\\n%s\\n' '$CAPABILITY_MANIFEST_DIGEST' '$REQUESTED_CAPABILITIES' > '$CAPABILITY_BUILD_STAMP_HOST'" - return 0 - fi - printf '%s\n%s\n' "$CAPABILITY_MANIFEST_DIGEST" "$REQUESTED_CAPABILITIES" >"$CAPABILITY_BUILD_STAMP_HOST" -} - -capability_build_stamp_matches_current() { - [[ -f $CAPABILITY_BUILD_STAMP_HOST ]] || return 1 - local stamp_digest stamp_caps - stamp_digest=$(sed -n '1p' "$CAPABILITY_BUILD_STAMP_HOST" 2>/dev/null || true) - stamp_caps=$(sed -n '2p' "$CAPABILITY_BUILD_STAMP_HOST" 2>/dev/null || true) - [[ $stamp_digest == "$CAPABILITY_MANIFEST_DIGEST" && $stamp_caps == "$REQUESTED_CAPABILITIES" ]] -} - assert_path_within_code_dir() { local candidate=$1 local resolved @@ -1421,50 +683,6 @@ validate_package_list() { done } -validate_capability_list() { - local key=$1 - local raw=$2 - local source=${3:-$MANIFEST_PATH} - [[ -z $raw ]] && return 0 - local token supported capability - for token in $raw; do - if [[ $token == "nested-podman" ]]; then - error "$source: capability 'nested-podman' was removed; delete it from '$key'" - fi - supported=false - for capability in "${SLOPTRAP_SUPPORTED_CAPABILITIES[@]}"; do - if [[ $token == "$capability" ]]; then - supported=true - break - fi - done - if [[ $supported != true ]]; then - error "$source: invalid capability '$token' in '$key'" - fi - done -} - -normalize_capability_list() { - local raw=$1 - [[ -z $raw ]] && return 0 - local token - for token in $raw; do - printf '%s\n' "$token" - done | sort -u | tr '\n' ' ' | sed 's/ $//' -} - -capability_list_contains() { - local list=$1 - local needle=$2 - local token - for token in $list; do - if [[ $token == "$needle" ]]; then - return 0 - fi - done - return 1 -} - detect_container_engine() { local override=${SLOPTRAP_CONTAINER_ENGINE-} if [[ -n $override ]]; then @@ -1606,19 +824,6 @@ run_wizard() { break done - while true; do - info_line "capabilities: Optional privileged features (%s).\n" "${SLOPTRAP_SUPPORTED_CAPABILITIES[*]}" - value=$(prompt_manifest_value "capabilities" "$default_capabilities") - value=$(trim "$value") - [[ -n $value ]] || value=$default_capabilities - value=$(normalize_capability_list "$value") - if [[ -n $value ]]; then - validate_capability_list "capabilities" "$value" "$manifest_path" - fi - default_capabilities=$value - break - done - while true; do info_line "allow_host_network: Use host networking instead of an isolated bridge.\n" value=$(prompt_manifest_value "allow_host_network" "$default_allow_host_network") @@ -1663,9 +868,6 @@ print_config() { info_line "codex_url=%s\n" "$SLOPTRAP_CODEX_URL" info_line "needs_login=%s\n" "$NEED_LOGIN" info_line "runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY" - info_line "requested_capabilities=%s\n" "$REQUESTED_CAPABILITIES" - info_line "enabled_capabilities=%s\n" "$ENABLED_CAPABILITIES" - info_line "capability_trust=%s\n" "$(capability_trust_matches_current && printf true || printf false)" info_line "ignore_stub_base=%s\n" "$IGNORE_STUB_BASE" if [[ ${#SLOPTRAP_IGNORE_ENTRIES[@]} -gt 0 ]]; then local ignore_paths @@ -1690,14 +892,12 @@ print_manifest_summary() { comment_line " manifest_path=%s\n" "$MANIFEST_PATH" comment_line " name=%s\n" "$PROJECT_NAME" comment_line " packages_extra=%s\n" "$PACKAGES_EXTRA" - comment_line " capabilities=%s\n" "$REQUESTED_CAPABILITIES" comment_line " runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY" comment_line " allow_host_network=%s\n" "$ALLOW_HOST_NETWORK" } build_runtime_context_prompt() { - local prompt enabled network_mode - enabled=${ENABLED_CAPABILITIES:-none} + local prompt network_mode network_mode="isolated" if [[ $SLOPTRAP_NETWORK_NAME == "host" ]]; then network_mode="host" @@ -1709,7 +909,6 @@ This startup note describes the sloptrap runtime only; it does not replace highe Current resolved sloptrap state: - name=$PROJECT_NAME (project/image/container label) - packages_extra=${PACKAGES_EXTRA:-none} (Debian packages added at build time) -- enabled_capabilities=$enabled (privileged features usable in this run) - network_mode=$network_mode (host when host networking is enabled; otherwise isolated) EOF ) @@ -1718,12 +917,8 @@ 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="" @@ -1731,7 +926,6 @@ CODEX_BIN_PATH="" SLOPTRAP_SHARED_DIR_ABS="" SLOPTRAP_PACKAGES_BASE="" SLOPTRAP_PACKAGES_EXTRA_RESOLVED="" -SLOPTRAP_PACKAGES_CAPABILITY="" SLOPTRAP_CODEX_BIN_NAME="" SLOPTRAP_CODEX_URL="" SLOPTRAP_CODEX_ARCHIVE="" @@ -1749,11 +943,6 @@ 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 local default=$2 @@ -1830,69 +1019,14 @@ append_auth_mount_arg() { 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/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" } @@ -1922,7 +1056,6 @@ ensure_codex_storage_paths() { ensure_codex_directory "$state_root" "sloptrap Codex namespace" ensure_codex_directory "$state_bucket" "sloptrap Codex state root" ensure_codex_directory "$CODEX_STATE_HOME_HOST" "project Codex state" - ensure_capability_state_paths if [[ -L $CODEX_AUTH_FILE_HOST ]]; then error "Codex auth file '$CODEX_AUTH_FILE_HOST' must not be a symlink" fi @@ -2046,7 +1179,6 @@ targets_need_build() { prepare_container_runtime() { resolve_container_workdir SLOPTRAP_PACKAGES_EXTRA_RESOLVED=$PACKAGES_EXTRA - SLOPTRAP_PACKAGES_CAPABILITY="" SLOPTRAP_RUN_AS_ROOT=false SLOPTRAP_SHARED_DIR_ABS="$CODE_DIR" if [[ ! -d $SLOPTRAP_SHARED_DIR_ABS ]]; then @@ -2069,16 +1201,9 @@ prepare_container_runtime() { else SLOPTRAP_DOCKERFILE_SOURCE="" fi - if [[ -n $REQUESTED_CAPABILITIES && -n $SLOPTRAP_DOCKERFILE_SOURCE ]]; then - error "capabilities require the embedded Dockerfile; custom Dockerfile overrides are not supported" - fi SLOPTRAP_PACKAGES_BASE=$(get_env_default "SLOPTRAP_PACKAGES" "curl bash ca-certificates libstdc++6 git ripgrep xxd file procps util-linux") validate_package_list "SLOPTRAP_PACKAGES" "$SLOPTRAP_PACKAGES_BASE" "SLOPTRAP_PACKAGES" - if capability_list_contains "$REQUESTED_CAPABILITIES" "packet-capture"; then - SLOPTRAP_PACKAGES_CAPABILITY+=" tcpdump" - fi - SLOPTRAP_PACKAGES_CAPABILITY=$(normalize_package_list "$SLOPTRAP_PACKAGES_CAPABILITY") local default_codex_archive default_codex_archive=$(detect_codex_archive_name) local env_codex_archive @@ -2132,19 +1257,7 @@ prepare_container_runtime() { SLOPTRAP_IMAGE_NAME=$(sanitize_engine_name "$SLOPTRAP_IMAGE_NAME") SLOPTRAP_CONTAINER_NAME=$(sanitize_engine_name "$SLOPTRAP_CONTAINER_NAME") - 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 local -a extra_opts=() read -r -a extra_opts <<< "$SLOPTRAP_SECURITY_OPTS_EXTRA" @@ -2167,29 +1280,7 @@ 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 - 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 - capability_opts+=( - --cap-add SETUID - --cap-add SETGID - --cap-add CHOWN - ) - fi - local rootfs_flag=() case "${SLOPTRAP_ROOTFS_READONLY,,}" in 1|true|yes) @@ -2216,12 +1307,6 @@ 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=$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" ) local uid gid user @@ -2235,6 +1320,7 @@ prepare_container_runtime() { if [[ -n $user ]]; then env_args+=(-e "SLOPTRAP_HOST_USER=$user") fi + local -a network_opts=(--network "$SLOPTRAP_NETWORK_NAME") local -a user_opts=("--user" "$uid:$gid") if [[ $CONTAINER_ENGINE == "podman" ]]; then user_opts=(--userns="keep-id:uid=$uid,gid=$gid" "${user_opts[@]}") @@ -2246,59 +1332,10 @@ 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[@]}" - "${capability_opts[@]}" "${resource_opts[@]}" "${rootfs_flag[@]}" "${tmpfs_opts[@]}" @@ -2317,16 +1354,13 @@ prepare_container_runtime() { } build_image() { - ensure_capability_trust ensure_codex_binary if [[ $SKIP_BUILD_BANNER != true ]]; then print_banner fi print_manifest_summary local extra_packages_arg - local capability_packages_arg extra_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_EXTRA_RESOLVED") - capability_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_CAPABILITY") if ! $DRY_RUN; then status_line "Building %s\n" "$SLOPTRAP_IMAGE_NAME" fi @@ -2337,7 +1371,6 @@ build_image() { --network "$SLOPTRAP_NETWORK_NAME" --label "$SLOPTRAP_IMAGE_LABEL" --build-arg "BASE_PACKAGES=$SLOPTRAP_PACKAGES_BASE" - --build-arg "CAPABILITY_PACKAGES=$capability_packages_arg" --build-arg "CODEX_BIN=$SLOPTRAP_CODEX_BIN_NAME" --build-arg "CODEX_UID=$SLOPTRAP_CODEX_UID" --build-arg "CODEX_GID=$SLOPTRAP_CODEX_GID" @@ -2359,11 +1392,9 @@ build_image() { if [[ -n $build_output ]]; then comment_line "Image %s\n" "$build_output" fi - write_capability_build_stamp } rebuild_image() { - ensure_capability_trust ensure_codex_binary if [[ $SKIP_BUILD_BANNER != true ]]; then print_banner @@ -2373,9 +1404,7 @@ rebuild_image() { status_line "Rebuilding %s (no cache)\n" "$SLOPTRAP_IMAGE_NAME" fi local extra_packages_arg - local capability_packages_arg extra_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_EXTRA_RESOLVED") - capability_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_CAPABILITY") local -a cmd=( "$CONTAINER_ENGINE" build --no-cache --quiet -t "$SLOPTRAP_IMAGE_NAME" @@ -2383,7 +1412,6 @@ rebuild_image() { --network "$SLOPTRAP_NETWORK_NAME" --label "$SLOPTRAP_IMAGE_LABEL" --build-arg "BASE_PACKAGES=$SLOPTRAP_PACKAGES_BASE" - --build-arg "CAPABILITY_PACKAGES=$capability_packages_arg" --build-arg "CODEX_BIN=$SLOPTRAP_CODEX_BIN_NAME" --build-arg "CODEX_UID=$SLOPTRAP_CODEX_UID" --build-arg "CODEX_GID=$SLOPTRAP_CODEX_GID" @@ -2405,21 +1433,14 @@ rebuild_image() { if [[ -n $build_output ]]; then comment_line "Image %s\n" "$build_output" fi - write_capability_build_stamp } build_if_missing() { - ensure_capability_trust if $DRY_RUN; then print_command "$CONTAINER_ENGINE" image inspect "$SLOPTRAP_IMAGE_NAME" return 0 fi if "$CONTAINER_ENGINE" image inspect "$SLOPTRAP_IMAGE_NAME" >/dev/null 2>&1; then - if ! capability_build_stamp_matches_current; then - warn "image '$SLOPTRAP_IMAGE_NAME' capability stamp mismatch; rebuilding" - rebuild_image - return 0 - fi local created if created=$("$CONTAINER_ENGINE" image inspect --format '{{.Created}}' "$SLOPTRAP_IMAGE_NAME" 2>/dev/null); then local created_epoch @@ -2440,7 +1461,6 @@ build_if_missing() { } stop_container() { - stop_packet_capture_helper if $DRY_RUN; then print_command "$CONTAINER_ENGINE" stop "$SLOPTRAP_CONTAINER_NAME" return 0 @@ -2592,8 +1612,6 @@ dispatch_target() { 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 @@ -2605,10 +1623,6 @@ while [[ $# -gt 0 ]]; do PRINT_CONFIG=true shift ;; - --trust-capabilities) - TRUST_CAPABILITIES=true - shift - ;; -h|--help) usage exit 0 @@ -2688,8 +1702,6 @@ if [[ ! $PROJECT_NAME =~ $VALID_NAME_REGEX ]]; then fi select_codex_home "$CODE_DIR" -select_capability_state_paths -CAPABILITY_MANIFEST_DIGEST=$(compute_manifest_digest) ensure_ignore_helper_root IGNORE_STUB_BASE="$IGNORE_HELPER_ROOT/session-${BASHPID:-$$}" resolve_sloptrap_ignore "$CODE_DIR" @@ -2707,16 +1719,6 @@ fi DEFAULT_TARGETS=("${TARGETS[@]}") PACKAGES_EXTRA=${MANIFEST[packages_extra]-} -REQUESTED_CAPABILITIES=$(normalize_capability_list "${MANIFEST[capabilities]-}") -if [[ -n $REQUESTED_CAPABILITIES ]]; then - ensure_safe_for_make "capabilities" "$REQUESTED_CAPABILITIES" -fi -validate_capability_list "capabilities" "$REQUESTED_CAPABILITIES" -if [[ -n $REQUESTED_CAPABILITIES ]] && { $TRUST_CAPABILITIES || capability_trust_matches_current; }; then - ENABLED_CAPABILITIES="$REQUESTED_CAPABILITIES" -else - ENABLED_CAPABILITIES="" -fi if [[ -n ${MANIFEST[allow_host_network]-} ]]; then case "${MANIFEST[allow_host_network],,}" in 1|true|yes) @@ -2746,7 +1748,6 @@ 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 diff --git a/tests/capability_repo/.sloptrap b/tests/capability_repo/.sloptrap deleted file mode 100644 index d5acc15..0000000 --- a/tests/capability_repo/.sloptrap +++ /dev/null @@ -1,3 +0,0 @@ -name=capability-repo -capabilities=apt-install packet-capture -allow_host_network=false diff --git a/tests/invalid_manifest_capabilities/.sloptrap b/tests/invalid_manifest_capabilities/.sloptrap deleted file mode 100644 index 180ffb9..0000000 --- a/tests/invalid_manifest_capabilities/.sloptrap +++ /dev/null @@ -1,4 +0,0 @@ -name=invalid-capabilities -capabilities=packet-capture not-a-real-capability -codex_args=--sandbox workspace-write --ask-for-approval never -allow_host_network=false diff --git a/tests/run_tests.sh b/tests/run_tests.sh index e422434..919e941 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -380,21 +380,6 @@ run_secret_mask() { teardown_stub_env } -run_git_ignore_mask() { - local scenario_dir="$ROOT_DIR" - printf '==> git_ignore_mask\n' - 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 2>&1; then - record_failure "git_ignore_mask: sloptrap exited non-zero" - teardown_stub_env - return - fi - if ! grep -q -- "--mount type=tmpfs,target=/workspace/.git" "$STUB_LOG"; then - record_failure "git_ignore_mask: .git was not masked with tmpfs" - fi - teardown_stub_env -} run_resume_target() { local scenario_dir="$TEST_ROOT/resume_target" @@ -418,7 +403,7 @@ run_runtime_context_prompt() { printf '==> runtime_context_prompt\n' setup_stub_env if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ - "$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" /dev/null 2>&1; then + "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "runtime_context_prompt: sloptrap exited non-zero" teardown_stub_env return @@ -430,9 +415,8 @@ run_runtime_context_prompt() { record_failure "runtime_context_prompt: startup prompt missing from fresh run" fi 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" + record_failure "runtime_context_prompt: runtime summary missing manifest state" fi if [[ -n $login_line && $login_line == *"You are running inside sloptrap"* ]]; then record_failure "runtime_context_prompt: login flow should not receive startup prompt" @@ -445,7 +429,7 @@ run_sh_reexec() { printf '==> sh_reexec\n' setup_stub_env if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ - sh "$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" /dev/null 2>&1; then + sh "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "sh_reexec: sloptrap exited non-zero when launched via sh" teardown_stub_env return @@ -462,7 +446,7 @@ run_resume_omits_runtime_context() { printf '==> resume_omits_runtime_context\n' setup_stub_env if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ - "$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" resume "$session_id" /dev/null 2>&1; then + "$SLOPTRAP_BIN" "$scenario_dir" resume "$session_id" /dev/null 2>&1; then record_failure "resume_omits_runtime_context: sloptrap exited non-zero" teardown_stub_env return @@ -530,26 +514,6 @@ run_codex_home_override() { teardown_stub_env } -run_removed_nested_podman_manifest() { - local scenario_dir output_log - scenario_dir=$(mktemp -d) - output_log=$(mktemp) - printf '==> removed_nested_podman_manifest\n' - cat >"$scenario_dir/.sloptrap" <<'EOF' -name=removed-nested-podman -capabilities=nested-podman -EOF - if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" >/dev/null 2>&1; then - record_failure "removed_nested_podman_manifest: expected nested-podman manifest rejection" - fi - if ! "$SLOPTRAP_BIN" --dry-run "$scenario_dir" >"$output_log" 2>&1; then - if ! grep -q -- "capability 'nested-podman' was removed" "$output_log"; then - record_failure "removed_nested_podman_manifest: missing explicit removal error" - fi - fi - rm -f "$output_log" - rm -rf "$scenario_dir" -} run_project_state_isolation() { local scenario_a scenario_b @@ -643,59 +607,6 @@ run_root_directory_project() { rm -rf "$tmp_home" } -run_shared_dir_override() { - local scenario_dir - scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P) - printf '==> shared_dir_override\n' - setup_stub_env - local bogus_shared - bogus_shared=$(mktemp -d) - if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" \ - SLOPTRAP_SHARED_DIR="$bogus_shared" FAKE_PODMAN_INSPECT_FAIL=1 \ - "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then - record_failure "shared_dir_override: sloptrap exited non-zero" - teardown_stub_env - rm -rf "$bogus_shared" - return - fi - if grep -q "$bogus_shared" "$STUB_LOG"; then - record_failure "shared_dir_override: respected SLOPTRAP_SHARED_DIR override" - fi - if ! grep -q -- "-v ${scenario_dir}:/workspace" "$STUB_LOG"; then - record_failure "shared_dir_override: missing expected project bind mount" - fi - teardown_stub_env - rm -rf "$bogus_shared" -} - -run_packages_env_validation() { - local scenario_dir - scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P) - printf '==> packages_env_validation\n' - local tmp_home - tmp_home=$(mktemp -d) - if HOME="$tmp_home" SLOPTRAP_PACKAGES='curl";touch /tmp/pwn #' \ - "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then - record_failure "packages_env_validation: expected rejection of invalid SLOPTRAP_PACKAGES" - fi - rm -rf "$tmp_home" -} - -run_abs_path_ignore() { - local scenario_dir="$TEST_ROOT/abs_path_ignore" - printf '==> abs_path_ignore\n' - if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then - record_failure "abs_path_ignore: expected rejection for anchored parent traversal entry" - fi -} - -run_dotdot_ignore() { - local scenario_dir="$TEST_ROOT/dotdot_ignore" - printf '==> dotdot_ignore\n' - if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then - record_failure "dotdot_ignore: expected rejection for parent traversal entry" - fi -} run_invalid_manifest_name() { local scenario_dir="$TEST_ROOT/invalid_manifest_name" @@ -721,69 +632,6 @@ run_invalid_manifest_packages() { fi } -run_invalid_manifest_capabilities() { - local scenario_dir="$TEST_ROOT/invalid_manifest_capabilities" - printf '==> invalid_manifest_capabilities\n' - if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then - record_failure "invalid_manifest_capabilities: expected rejection for bad capabilities" - fi -} - -run_invalid_allow_host_network() { - local scenario_dir="$TEST_ROOT/invalid_allow_host_network" - printf '==> invalid_allow_host_network\n' - if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" /dev/null 2>&1; then - record_failure "invalid_allow_host_network: expected rejection for invalid value" - 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" "$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' @@ -860,349 +708,15 @@ run_wizard_build_trigger() { record_failure "wizard_build_trigger: manifest not created" fi if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then - record_failure "wizard_build_trigger: build not invoked after wizard" +record_failure "wizard_build_trigger: build not invoked after wizard" fi - teardown_stub_env +teardown_stub_env } -run_capability_trust_required() { - local scenario_dir="$TEST_ROOT/capability_repo" - printf '==> capability_trust_required\n' - setup_stub_env - if PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ - "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then - record_failure "capability_trust_required: expected failure without trusted capabilities" - fi - 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 2>&1; then - record_failure "capability_profiles: sloptrap exited non-zero" - teardown_stub_env - return - fi - 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 $expected_build_network " "$STUB_LOG"; then - record_failure "capability_profiles: build should stay on isolated networking" - fi - 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 [[ -z $pod_lines ]]; then - record_failure "capability_profiles: packet capture should create a dedicated pod" - fi - 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 [[ -z $capture_lines || $capture_lines != *"--cap-add SETGID"* ]]; then - record_failure "capability_profiles: SETGID capability missing" - fi - 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" <<<"$capture_lines$main_lines"; then - record_failure "capability_profiles: DAC_OVERRIDE should not be granted" - fi - 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" <<<"$capture_lines$main_lines"; then - record_failure "capability_profiles: no-new-privileges missing" - fi - if grep -q -- "--read-only" <<<"$main_lines"; then - record_failure "capability_profiles: apt profile should disable read-only rootfs" - fi - 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)" <<<"$capture_lines$main_lines"; then - record_failure "capability_profiles: podman keep-id user namespace missing" - fi - 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" - fi - if ! grep -q -- "SLOPTRAP_HOST_GID=$(id -g)" "$STUB_LOG"; then - record_failure "capability_profiles: host gid environment missing" - fi - if ! grep -q -- "SLOPTRAP_HOST_USER=$(id -un)" "$STUB_LOG"; then - record_failure "capability_profiles: host user environment missing" - fi - local state_root capability_dir - state_root="$STUB_HOME/.codex/sloptrap/state" - capability_dir=$(find "$state_root" -mindepth 2 -maxdepth 2 -type d -name capabilities | head -n 1 || true) - if [[ -z $capability_dir ]]; then - record_failure "capability_profiles: project capability state directory missing" - fi - teardown_stub_env -} - -run_embedded_capability_helpers() { - printf '==> embedded_capability_helpers\n' - local temp_root helper_bin helper_dir workspace_dir capture_dir tool_log helper_pid - temp_root=$(mktemp -d) - helper_bin="$temp_root/bin" - helper_dir="$temp_root/helper" - workspace_dir="$temp_root/workspace" - capture_dir="$temp_root/captures" - tool_log="$temp_root/tool.log" - helper_pid="" - mkdir -p "$helper_bin" "$helper_dir/queue" "$workspace_dir/data" "$capture_dir" - : >"$tool_log" - - if ! extract_embedded_helper "sloptrap-entrypoint" "$helper_bin/sloptrap-entrypoint" \ - || ! extract_embedded_helper "sloptrap-helperd" "$helper_bin/sloptrap-helperd" \ - || ! extract_embedded_helper "slop-apt" "$helper_bin/slop-apt" \ - || ! extract_embedded_helper "slopcap" "$helper_bin/slopcap"; then - record_failure "embedded_capability_helpers: failed to extract embedded helper scripts" - rm -rf "$temp_root" - return - fi - - cat >"$helper_bin/apt-get" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -printf 'apt-get %s\n' "$*" >>"$TEST_TOOL_LOG" -exit 0 -EOF - cat >"$helper_bin/tcpdump" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -printf 'tcpdump %s\n' "$*" >>"$TEST_TOOL_LOG" -output="" -prev="" -for arg in "$@"; do - if [[ $prev == "-w" ]]; then - output=$arg - break - fi - prev=$arg -done -if [[ -n $output ]]; then - mkdir -p "$(dirname "$output")" - : >"$output" -fi -exit 0 -EOF - cat >"$helper_bin/setpriv" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -printf 'setpriv %s\n' "$*" >>"$TEST_TOOL_LOG" -while [[ $# -gt 0 ]]; do - case "$1" in - --reuid|--regid) - shift 2 - ;; - --clear-groups) - shift - ;; - --) - shift - break - ;; - *) - break - ;; - esac -done -exec "$@" -EOF - chmod +x "$helper_bin/apt-get" "$helper_bin/tcpdump" "$helper_bin/setpriv" - - if ! grep -q "chmod 711 \"\\\$helper_dir\"" "$helper_bin/sloptrap-entrypoint" \ - || ! grep -q "chmod 1733 \"\\\$queue_dir\"" "$helper_bin/sloptrap-entrypoint"; then - record_failure "embedded_capability_helpers: entrypoint did not expose helper queue to the dropped user" - fi - - 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" \ - "$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 - record_failure "embedded_capability_helpers: slop-apt failed against the embedded helper daemon" - fi - if ! grep -q -- 'apt-get install -y --no-install-recommends jq' "$tool_log"; then - record_failure "embedded_capability_helpers: slop-apt did not reach apt-get install" - fi - - local bad_apt_request - bad_apt_request=$(mktemp -d "$helper_dir/queue/request.XXXXXX.req") - printf 'apt-install\n' >"$bad_apt_request/op" - printf '%s\n' '--allow-unauthenticated' >"$bad_apt_request/packages" - if ! wait_for_path "$bad_apt_request/status"; then - record_failure "embedded_capability_helpers: helper daemon did not answer the invalid apt request" - elif [[ $(<"$bad_apt_request/status") != "2" ]]; then - record_failure "embedded_capability_helpers: invalid apt request returned the wrong status" - fi - if [[ -s "$bad_apt_request/stderr" ]] && ! grep -q -- 'invalid package name' "$bad_apt_request/stderr"; then - record_failure "embedded_capability_helpers: invalid apt request did not explain the rejection" - 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 --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 -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 - - local bad_capture_request - bad_capture_request=$(mktemp -d "$helper_dir/queue/request.XXXXXX.req") - printf 'packet-capture\n' >"$bad_capture_request/op" - printf 'eth0\n' >"$bad_capture_request/interface" - printf '\n' >"$bad_capture_request/filter" - printf '/tmp/escape.pcap\n' >"$bad_capture_request/output" - printf '0\n' >"$bad_capture_request/stdout_mode" - if ! wait_for_path "$bad_capture_request/status"; then - record_failure "embedded_capability_helpers: helper daemon did not answer the invalid capture request" - elif [[ $(<"$bad_capture_request/status") != "2" ]]; then - record_failure "embedded_capability_helpers: invalid capture request returned the wrong status" - fi - if [[ -s "$bad_capture_request/stderr" ]] && ! grep -q -- 'output path must stay within' "$bad_capture_request/stderr"; then - record_failure "embedded_capability_helpers: invalid capture request did not explain the rejection" - fi - - if [[ -n $helper_pid ]]; then - kill "$helper_pid" >/dev/null 2>&1 || true - wait "$helper_pid" >/dev/null 2>&1 || true - fi - rm -rf "$temp_root" -} - -run_make_install_single_file() { - local scenario_dir="$TEST_ROOT/resume_target" - printf '==> make_install_single_file\n' - if ! command -v make >/dev/null 2>&1; then - record_failure "make_install_single_file: make binary not found in PATH" - return - fi - setup_stub_env - local install_root install_dir installed_bin - install_root=$(mktemp -d) - install_dir="$install_root/bin" - installed_bin="$install_dir/sloptrap" - if ! make -C "$ROOT_DIR" install INSTALL_DIR="$install_dir" >/dev/null 2>&1; then - record_failure "make_install_single_file: make install failed" - teardown_stub_env - rm -rf "$install_root" - return - fi - if [[ ! -x $installed_bin ]]; then - record_failure "make_install_single_file: installed launcher missing" - fi - local helper - for helper in sloptrap-entrypoint sloptrap-helperd slop-apt slopcap; do - if [[ -e $install_dir/$helper ]]; then - record_failure "make_install_single_file: unexpected helper installed ($helper)" - fi - done - if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ - "$installed_bin" "$scenario_dir" /dev/null 2>&1; then - record_failure "make_install_single_file: installed launcher failed" - fi - if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then - record_failure "make_install_single_file: installed launcher did not reach build path" - fi - if ! make -C "$ROOT_DIR" uninstall INSTALL_DIR="$install_dir" >/dev/null 2>&1; then - record_failure "make_install_single_file: make uninstall failed" - fi - if [[ -e $installed_bin ]]; then - record_failure "make_install_single_file: installed launcher not removed by uninstall" - fi - teardown_stub_env - rm -rf "$install_root" -} - -run_shellcheck -run_mount_injection -run_root_target run_symlink_escape run_manifest_injection run_helper_symlink run_secret_mask -run_git_ignore_mask run_resume_target run_runtime_context_prompt run_sh_reexec @@ -1213,26 +727,9 @@ run_project_state_isolation run_auto_login_empty_auth run_codex_symlink_home run_root_directory_project -run_shared_dir_override -run_packages_env_validation -run_abs_path_ignore -run_dotdot_ignore -run_invalid_manifest_name -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 if [[ ${#failures[@]} -gt 0 ]]; then printf '\nTest failures:\n'