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.
This commit is contained in:
28
Dockerfile.sloptrap
Normal file
28
Dockerfile.sloptrap
Normal file
@@ -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"]
|
||||
22
README.md
22
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 <package...>` for session-scoped package installation.
|
||||
- `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 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/<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`. 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.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
name=capability-repo
|
||||
capabilities=apt-install packet-capture
|
||||
allow_host_network=false
|
||||
@@ -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
|
||||
@@ -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 >/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 >/dev/null 2>&1; then
|
||||
"$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/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 >/dev/null 2>&1; then
|
||||
sh "$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/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 >/dev/null 2>&1; then
|
||||
"$SLOPTRAP_BIN" "$scenario_dir" resume "$session_id" </dev/null >/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 >/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 >/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 >/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 >/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 >/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 >/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" </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'
|
||||
@@ -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 >/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 >/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 >/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'
|
||||
|
||||
Reference in New Issue
Block a user