Compare commits
13 Commits
87a23e9772
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d781d74363 | ||
|
|
e7112db3d7 | ||
|
|
549862290f | ||
|
|
6dc7609f10 | ||
|
|
273e42dd2d | ||
|
|
6ca643830f | ||
|
|
0e02b78545 | ||
|
|
87d1577546 | ||
|
|
b080f06613 | ||
|
|
fb3aca0f7d | ||
|
|
438959ca03 | ||
|
|
3731094f60 | ||
|
|
1e97baa534 |
@@ -1,4 +1,4 @@
|
|||||||
name=skz-sloptrap
|
name=skz-sloptrap
|
||||||
packages_extra=bash make shellcheck jq podman
|
packages_extra=bash make shellcheck jq podman iproute2 strace
|
||||||
capabilities=apt-install nested-podman packet-capture
|
capabilities=
|
||||||
allow_host_network=false
|
allow_host_network=false
|
||||||
|
|||||||
24
AGENTS.md
Normal file
24
AGENTS.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# skz-sloptrap
|
||||||
|
|
||||||
|
This workspace is the source code of `sloptrap`, eg. your own runtime.
|
||||||
|
Your author, 'sk4nz', has ran the command `sloptrap path/to/skz-sloptrap` and you are living in your own reflection.
|
||||||
|
|
||||||
|
# Keep Memory
|
||||||
|
|
||||||
|
Future sloptrap instances should add short, useful, durable notes they learn during their session to this file.
|
||||||
|
|
||||||
|
Only add high-signal information that is likely to help later runs.
|
||||||
|
Keep additions brief.
|
||||||
|
Do not remove existing instructions unless they are outdated or wrong.
|
||||||
|
|
||||||
|
---
|
||||||
|
# Session Memory
|
||||||
|
|
||||||
|
- When runtime changes need a fresh sloptrap session to validate, ask sk4nz to restart you and give an exact prompt to continue from the current checkpoint.
|
||||||
|
- Useful checks in the skz-sloptrap repo:
|
||||||
|
`shellcheck sloptrap`
|
||||||
|
`bash tests/run_tests.sh` (you can also run them separately)
|
||||||
|
- When running tests from inside sloptrap, inherited `CODEX_HOME=/codex` plus `SLOPTRAP_PREFER_CODEX_HOME=1` can leak into host-style child launches; ignore that preference when `HOME` has been redirected elsewhere and the runtime hints still point into the inherited `/codex` tree.
|
||||||
|
- Capability-enabled runs are Podman-only. `packet-capture` uses a dedicated helper container/pod, and host-network capture must prompt for an explicit acknowledgement on every runtime launch.
|
||||||
|
- `agent=opencode` should download the latest Linux CLI release artifact from GitHub into the build context and verify its digest; it should not depend on a host-installed `opencode` binary.
|
||||||
|
- For isolated networking, sloptrap exposes the host inside the container as `sloptrap.host`; opencode localhost URLs should be rewritten to that alias, and Podman `slirp4netns` runs need `allow_host_loopback=true` for host-local servers.
|
||||||
80
README.md
80
README.md
@@ -34,17 +34,17 @@ brew install coreutils gnu-tar jq
|
|||||||
```
|
```
|
||||||
3. Run `./sloptrap path/to/project`. On the first invocation sloptrap:
|
3. Run `./sloptrap path/to/project`. On the first invocation sloptrap:
|
||||||
- builds `path/to/project-sloptrap-image` if missing,
|
- builds `path/to/project-sloptrap-image` if missing,
|
||||||
- verifies the Codex binary hash,
|
- verifies the selected backend CLI hash,
|
||||||
- creates `${HOME}/.codex`, prepares a per-project state directory, and runs `login` if `${HOME}/.codex/auth.json` is missing or empty.
|
- creates `${HOME}/.codex`, prepares a per-project state directory, and runs `login` if `${HOME}/.codex/auth.json` is missing or empty for the Codex backend.
|
||||||
|
|
||||||
> Use `./sloptrap path/to/project shell` to enter a troubleshooting shell inside the container or `./sloptrap path/to/project clean` to remove cached images and state.
|
> Use `./sloptrap path/to/project shell` to enter a troubleshooting shell inside the container or `./sloptrap path/to/project clean` to remove cached images and state.
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
- The project directory mounts at `/workspace`; project-scoped Codex state mounts at `/codex` from `${HOME}/.codex/sloptrap/state/<project-hash>`, and shared auth mounts from `${HOME}/.codex/auth.json` to `/codex/auth.json`.
|
- The project directory mounts at `/workspace`; project-scoped state mounts at `/codex` from `${HOME}/.codex/sloptrap/state/<project-hash>`. Codex also mounts shared auth from `${HOME}/.codex/auth.json` to `/codex/auth.json`; opencode does not.
|
||||||
- `.sloptrapignore` entries (if present in your project) are overlaid by tmpfs (for directories) or empty bind mounts (for files) so Codex cannot read the masked content.
|
- `.sloptrapignore` entries (if present in your project) are overlaid by tmpfs (for directories) or empty bind mounts (for files) so Codex cannot read the masked content.
|
||||||
- sloptrap launches containers on an isolated network (`bridge` on Docker, `slirp4netns` on Podman) with `--cap-drop=ALL`, `--security-opt no-new-privileges`, a read-only root filesystem, and tmpfs-backed `/tmp`, `/run`, and `/run/lock`. Projects that explicitly set `allow_host_network=true` in their manifest opt into `--network host`.
|
- sloptrap launches containers on an isolated network (`bridge` on Docker, `slirp4netns` on Podman) with `--cap-drop=ALL`, `--security-opt no-new-privileges`, a read-only root filesystem, and tmpfs-backed `/tmp`, `/run`, and `/run/lock`. Projects that explicitly set `allow_host_network=true` in their manifest opt into `--network host`.
|
||||||
- The helper Dockerfile is embedded inside `sloptrap`; set `SLOPTRAP_DOCKERFILE_PATH=/path/to/custom/Dockerfile` if you need to supply your own recipe. The default image installs `curl`, `bash`, `ca-certificates`, `libstdc++6`, `git`, `ripgrep`, `xxd`, and `file`, so most debugging helpers are already available without adding `packages_extra`.
|
- The helper Dockerfile is embedded inside `sloptrap`. The default image installs `curl`, `bash`, `ca-certificates`, `libstdc++6`, `git`, `ripgrep`, `xxd`, and `file`, so most debugging helpers are already available without adding `packages_extra`.
|
||||||
- The container user matches the host UID/GID (`--userns=keep-id` on Podman or `--user UID:GID` on Docker).
|
- The container user matches the host UID/GID (`--userns=keep-id` on Podman or `--user UID:GID` on Docker).
|
||||||
- The runtime environment is fixed to HOME/XDG variables pointing at `/codex`; manifest-controlled environment injection is disabled.
|
- The runtime environment is fixed to HOME/XDG variables pointing at `/codex`; manifest-controlled environment injection is disabled.
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ brew install coreutils gnu-tar jq
|
|||||||
The manifest is optional. When absent, sloptrap derives:
|
The manifest is optional. When absent, sloptrap derives:
|
||||||
- `name = basename(project directory)`
|
- `name = basename(project directory)`
|
||||||
- `packages_extra = ""` (none)
|
- `packages_extra = ""` (none)
|
||||||
- `capabilities = ""` (none)
|
- `agent = "codex"` (default AI backend)
|
||||||
If a build is requested and no `.sloptrap` exists, sloptrap prompts to create one interactively.
|
If a build is requested and no `.sloptrap` exists, sloptrap prompts to create one interactively.
|
||||||
|
|
||||||
Supported keys when the manifest is present:
|
Supported keys when the manifest is present:
|
||||||
@@ -62,13 +62,19 @@ Supported keys when the manifest is present:
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `name` | project directory name | Must match `^[A-Za-z0-9_.-]+$`. Used for image/container naming. |
|
| `name` | project directory name | Must match `^[A-Za-z0-9_.-]+$`. Used for image/container naming. |
|
||||||
| `packages_extra` | *empty* | Additional Debian packages installed during `docker/podman build`. Tokens must be alphanumeric plus `+.-`. |
|
| `packages_extra` | *empty* | Additional Debian packages installed during `docker/podman build`. Tokens must be alphanumeric plus `+.-`. |
|
||||||
| `capabilities` | *empty* | Optional privileged features. Supported values are `apt-install`, `packet-capture`, and `nested-podman`. |
|
| `agent` | `codex` | AI backend: `codex` (OpenAI Codex CLI) or `opencode` (Anomaly opencode CLI). |
|
||||||
|
| `opencode_server` | `http://localhost:8080` | OpenAI-compatible server URL (opencode only). Supports llama.cpp, Ollama, vLLM, etc. |
|
||||||
|
| `opencode_model` | `bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0` | Model name on the server (opencode only). |
|
||||||
|
| `opencode_context` | `256K` | Context window for the opencode model. Accepts an integer optionally suffixed with `K`, `M`, or `G`. |
|
||||||
| `allow_host_network` | `false` | `true` opts into `--network host`; keep `false` unless the project absolutely requires direct access to host-local services. |
|
| `allow_host_network` | `false` | `true` opts into `--network host`; keep `false` unless the project absolutely requires direct access to host-local services. |
|
||||||
|
|
||||||
Values containing `$`, `` ` ``, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start.
|
Values containing `$`, `` ` ``, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start.
|
||||||
sloptrap always runs Codex with `--sandbox danger-full-access --ask-for-approval never`. `codex_args` is deprecated and rejected if present.
|
|
||||||
|
|
||||||
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`. Trusted capabilities can then be activated per run with `--enable-capability <name>`.
|
### AI Backends
|
||||||
|
|
||||||
|
**Codex** (default): Uses OpenAI Codex CLI with state stored in `~/.codex/`. Supports login mode for credential sharing.
|
||||||
|
|
||||||
|
**opencode**: Uses Anomaly opencode CLI with project-scoped state and config stored under sloptrap's per-project state directory. sloptrap downloads the latest Linux CLI release artifact from Anomaly during image builds, verifies its digest from the GitHub release metadata, and copies it into the container image. Connects to any OpenAI-compatible inference server (llama.cpp, Ollama, vLLM, etc.). When `opencode_server` points at `localhost` under isolated networking, sloptrap rewrites it to `http://sloptrap.host:...` so host-local model servers remain reachable from inside the container. No Codex auth file is mounted for opencode sessions.
|
||||||
|
|
||||||
### `.sloptrapignore`
|
### `.sloptrapignore`
|
||||||
|
|
||||||
@@ -80,27 +86,35 @@ Capability trust is local state, not part of the repository. Builds for manifest
|
|||||||
## CLI Reference
|
## CLI Reference
|
||||||
|
|
||||||
```
|
```
|
||||||
./sloptrap [--dry-run] [--print-config] [--trust-capabilities] [--enable-capability <name> ...] <code-directory> [target ...]
|
./sloptrap [--dry-run] [--print-config] <code-directory> [target ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--dry-run` — print the container/engine commands that would run without executing them.
|
- `--dry-run` — print the container/engine commands that would run without executing them.
|
||||||
- `--print-config` — output the resolved manifest values, defaults, and ignore list.
|
- `--print-config` — output the resolved manifest values, defaults, and ignore list.
|
||||||
- `--trust-capabilities` — trust the manifest's requested capabilities for the current build flow.
|
|
||||||
- `--enable-capability <name>` — enable a trusted runtime capability for this invocation. Repeat for multiple capabilities.
|
|
||||||
- `-h, --help` — display usage.
|
- `-h, --help` — display usage.
|
||||||
- `--` — stop option parsing; remaining arguments are treated as targets.
|
- `--` — stop option parsing; remaining arguments are treated as targets.
|
||||||
|
|
||||||
|
Environment variables override manifest values:
|
||||||
|
- `SLOPTRAP_AGENT` — override `agent` key (codex or opencode)
|
||||||
|
- `SLOPTRAP_OPENCODE_SERVER` — override `opencode_server` key
|
||||||
|
- `SLOPTRAP_OPENCODE_MODEL` — override `opencode_model` key
|
||||||
|
- `SLOPTRAP_OPENCODE_CONTEXT` — override `opencode_context` key
|
||||||
|
- `SLOPTRAP_CONTAINER_ENGINE` — override container engine auto-detection
|
||||||
|
|
||||||
|
Security-sensitive runtime overrides such as `SLOPTRAP_SECURITY_OPTS_EXTRA`, `SLOPTRAP_ROOTFS_READONLY`, and `SLOPTRAP_NETWORK_NAME` are rejected.
|
||||||
|
Build-path overrides such as `SLOPTRAP_DOCKERFILE_PATH`, `SLOPTRAP_CODEX_URL`, `SLOPTRAP_CODEX_ARCHIVE`, and `SLOPTRAP_CODEX_BIN` are also rejected.
|
||||||
|
|
||||||
Behaviour:
|
Behaviour:
|
||||||
|
|
||||||
- Missing manifests are treated as default configuration; when a build is requested, sloptrap runs the interactive wizard if a TTY is available, otherwise it warns and continues with defaults.
|
- Missing manifests are treated as default configuration; when a build is requested, sloptrap runs the interactive wizard if a TTY is available, otherwise it warns and continues with defaults.
|
||||||
- `SLOPTRAP_CONTAINER_ENGINE` overrides engine auto-detection.
|
- `SLOPTRAP_CONTAINER_ENGINE` overrides engine auto-detection.
|
||||||
- If `${HOME}/.codex/auth.json` is absent or empty, sloptrap prepends a login run before executing your targets.
|
- If `${HOME}/.codex/auth.json` is absent or empty, sloptrap prepends a login run before executing your targets (Codex only).
|
||||||
- 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.
|
- 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.
|
- 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 backend configuration (Codex or opencode), resolved paths, and the sanitised ignore mount roots so you can confirm what will be hidden inside the container.
|
||||||
|
|
||||||
### Regression Suite
|
### Regression Suite
|
||||||
|
|
||||||
@@ -113,12 +127,12 @@ Targets are supplied after the code directory. When omitted, sloptrap defaults t
|
|||||||
|
|
||||||
| Target | Description |
|
| Target | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `build` | Download Codex (if missing), verify SHA-256, and build the container image. |
|
| `build` | Download the selected backend CLI (if missing), verify SHA-256, and build the container image. |
|
||||||
| `build-if-missing` | No-op when the image already exists; otherwise delegates to `build`. |
|
| `build-if-missing` | No-op when the image already exists; otherwise delegates to `build`. |
|
||||||
| `rebuild` | Rebuild the image from scratch (`--no-cache`). |
|
| `rebuild` | Rebuild the image from scratch (`--no-cache`). |
|
||||||
| `run` | Default goal. Runs the container with Codex using sloptrap's built-in runtime flags. |
|
| `run` | Default goal. Runs the container with the selected backend. Codex uses sloptrap's built-in runtime flags; opencode relies on its generated config. |
|
||||||
| `resume <session-id>` | Continues a Codex session by running `codex resume <session-id>` inside the container (builds if needed). |
|
| `resume <session-id>` | Continues a backend session inside the container (Codex uses `codex resume`; opencode uses its session flag). |
|
||||||
| `login` | Starts Codex in login mode to bootstrap shared `${HOME}/.codex/auth.json` credentials. |
|
| `login` | Starts Codex in login mode to bootstrap shared `${HOME}/.codex/auth.json` credentials. Not supported for opencode. |
|
||||||
| `shell` | Launches `/bin/bash` inside the container for debugging. |
|
| `shell` | Launches `/bin/bash` inside the container for debugging. |
|
||||||
| `wizard` | Creates or updates `.sloptrap` interactively (no build); rerun `build` or `rebuild` afterward. |
|
| `wizard` | Creates or updates `.sloptrap` interactively (no build); rerun `build` or `rebuild` afterward. |
|
||||||
| `stop` | Best-effort stop of the running container (if any). |
|
| `stop` | Best-effort stop of the running container (if any). |
|
||||||
@@ -126,32 +140,28 @@ 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.
|
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 a trusted capability is enabled for a run, the container includes helper commands:
|
|
||||||
|
|
||||||
- `slop-apt install <package...>` for session-scoped package installation.
|
|
||||||
- `slopcap capture --interface <iface> [--filter <expr>] [--output <path>] [--stdout]` for packet capture.
|
|
||||||
- `sloppodman <pull|build|tag|run|ps|logs|stop|rm|inspect> ...` for nested Podman workflows. `build` contexts and Dockerfiles must remain inside `/workspace`, and pushes are not supported.
|
|
||||||
|
|
||||||
## Execution Environment
|
## Execution Environment
|
||||||
|
|
||||||
- Container engine: Podman or Docker with identical command lines. Podman uses `--userns=keep-id`; Docker receives the equivalent `--user UID:GID` for standard runs.
|
- Container engine: Podman or Docker for standard runs. Podman uses `--userns=keep-id`; Docker receives the equivalent `--user UID:GID` for standard runs.
|
||||||
- Filesystem view: the project directory mounts at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` mounts at `/codex`; `${HOME}/.codex/auth.json` mounts at `/codex/auth.json`.
|
- Filesystem view:
|
||||||
- Ignore filter: `.sloptrapignore` entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to Codex.
|
- **Codex**: project directory at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` at `/codex`; auth at `/codex/auth.json`.
|
||||||
- Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`.
|
- **opencode**: project directory at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` at `/codex`; generated config at `/codex/config/opencode/opencode.json`; runtime state at `/codex/state/opencode`; no shared auth mount.
|
||||||
- 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.
|
- Ignore filter: `.sloptrapignore` entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to the agent.
|
||||||
- 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`.
|
- Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`. For isolated runs, sloptrap injects `sloptrap.host` as a container-side hostname for the host gateway. On Podman `slirp4netns`, opencode runs also enable host loopback access so host-local servers bound to `localhost` remain reachable.
|
||||||
|
- 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.
|
||||||
|
- Agent configuration:
|
||||||
|
- **Codex**: runtime flags fixed to `--sandbox danger-full-access --ask-for-approval never`. Supports login mode for credential sharing.
|
||||||
|
- **opencode**: connects to OpenAI-compatible server via `--server` and `--model` flags. No authentication required for self-hosted models.
|
||||||
|
|
||||||
## Threat Model and Limits
|
## Threat Model and Limits
|
||||||
|
|
||||||
- **Outbound disclosure**: prompts and referenced data travel from the container to the configured LLM endpoint. Any file content within `/workspace` or environment data exposed to the process can appear in that traffic.
|
- **Outbound disclosure**: prompts and referenced data travel from the container to the configured LLM endpoint. Any file content within `/workspace` or environment data exposed to the process can appear in that traffic.
|
||||||
- **Shared storage**: `/workspace`, project-scoped `/codex`, and `/codex/auth.json` are host mounts. Files written to these locations become visible on the host and to the LLM provider through prompts.
|
- **Shared storage**: `/workspace` and project-scoped `/codex` are host mounts. For Codex, `/codex/auth.json` is also mounted from the host; opencode sessions do not receive that shared credential file. Files written to mounted locations become visible on the host and may be surfaced to the configured provider through prompts.
|
||||||
- **Environment surface**: the container receives a minimal fixed environment (HOME/XDG paths, `CODEX_HOME`). The manifest no longer allows injecting additional environment variables.
|
- **Environment surface**: the container receives a minimal fixed environment (HOME/XDG paths, `CODEX_HOME`). The manifest no longer allows injecting additional environment variables.
|
||||||
- **Process isolation**: standard runs keep a read-only root filesystem and no extra Linux capabilities. Capability-enabled runs deliberately relax specific runtime controls for the enabled feature, so they should be treated as a stronger trust decision than a default session.
|
- **Process isolation**: standard runs keep a read-only root filesystem and no extra Linux capabilities.
|
||||||
- **Networking stance**: traffic is unrestricted once it leaves the container. sloptrap does not enforce an allowlist or DNS policy. Host networking is opt-in per manifest; if you require an offline or firewalled workflow, sloptrap is not an appropriate launcher.
|
- **Networking stance**: traffic is unrestricted once it leaves the container. sloptrap does not enforce an allowlist or DNS policy. Host networking is opt-in per manifest. If you require an offline or firewalled workflow, sloptrap is not an appropriate launcher.
|
||||||
- **Persistence**: Codex history and logs accumulate per project under `${HOME}/.codex/sloptrap/state/`. Sensitive prompts recorded on disk remain on the host after the session. Because `.git/` is ignored inside the container, any historical secrets in Git objects stay outside the LLM context unless explicitly surfaced in the working tree.
|
- **Persistence**: Codex history and logs accumulate per project under `${HOME}/.codex/sloptrap/state/`. Sensitive prompts recorded on disk remain on the host after the session. Because `.git/` is ignored inside the container, any historical secrets in Git objects stay outside the LLM context unless explicitly surfaced in the working tree.
|
||||||
- **Codex cache hygiene**: per-project state mounts remain writable by the container and hold prompts/history/state, while `${HOME}/.codex/auth.json` holds shared credentials. Rotate credentials regularly and protect both locations.
|
- **Codex cache hygiene**: per-project state mounts remain writable by the container and hold prompts/history/state, while `${HOME}/.codex/auth.json` holds shared Codex credentials when that backend is used. Rotate credentials regularly and protect both locations.
|
||||||
- **Secret scanning**: sloptrap does not perform secret discovery or redaction; any credentials present in the project remain available to Codex and the upstream provider.
|
- **Secret scanning**: sloptrap does not perform secret discovery or redaction; any credentials present in the project remain available to Codex and the upstream provider.
|
||||||
- **Local model exception**: pointing Codex at a local or self-hosted model keeps data within the host network boundary, but the filesystem and environment exposure described above is unchanged.
|
- **Local model exception**: pointing Codex at a local or self-hosted model keeps data within the host network boundary, but the filesystem and environment exposure described above is unchanged.
|
||||||
|
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ Current scenarios:
|
|||||||
- `secret_mask/` — verifies masked files remain hidden even when sloptrap remaps the workspace mount.
|
- `secret_mask/` — verifies masked files remain hidden even when sloptrap remaps the workspace mount.
|
||||||
- `resume_target/` — verifies the resume target passes the requested session identifier to Codex.
|
- `resume_target/` — verifies the resume target passes the requested session identifier to Codex.
|
||||||
- `auth_file_mount` — verifies `~/.codex/auth.json` is mounted directly into `/codex/auth.json`.
|
- `auth_file_mount` — verifies `~/.codex/auth.json` is mounted directly into `/codex/auth.json`.
|
||||||
|
- `runtime_hardening_flags` — verifies standard runs add `--cap-drop=ALL` and keep the root filesystem read-only.
|
||||||
- `project_state_isolation` — verifies different projects map `/codex` to different host state directories.
|
- `project_state_isolation` — verifies different projects map `/codex` to different host state directories.
|
||||||
- `auto_login_empty_auth` — verifies an empty `auth.json` still triggers automatic login before the main target.
|
- `auto_login_empty_auth` — verifies an empty `auth.json` still triggers automatic login before the main target.
|
||||||
|
- `opencode_*` — exercises opencode build/download, localhost rewriting, config generation, and backend-specific safety checks.
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
name=capability-repo
|
|
||||||
capabilities=apt-install packet-capture nested-podman
|
|
||||||
allow_host_network=true
|
|
||||||
3
tests/host_network_packet_capture/.sloptrap
Normal file
3
tests/host_network_packet_capture/.sloptrap
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name=host-network-packet-capture
|
||||||
|
capabilities=packet-capture
|
||||||
|
allow_host_network=true
|
||||||
3
tests/host_network_repo/.sloptrap
Normal file
3
tests/host_network_repo/.sloptrap
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name=host-network-repo
|
||||||
|
capabilities=apt-install
|
||||||
|
allow_host_network=true
|
||||||
@@ -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
|
|
||||||
4
tests/opencode_build/.sloptrap
Normal file
4
tests/opencode_build/.sloptrap
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
name=opencode-build
|
||||||
|
packages_extra=htop
|
||||||
|
agent=opencode
|
||||||
|
allow_host_network=false
|
||||||
4
tests/opencode_localhost/.sloptrap
Normal file
4
tests/opencode_localhost/.sloptrap
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
name=opencode-localhost
|
||||||
|
packages_extra=
|
||||||
|
agent=opencode
|
||||||
|
allow_host_network=false
|
||||||
7
tests/opencode_print_config/.sloptrap
Normal file
7
tests/opencode_print_config/.sloptrap
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
name=opencode-print-config
|
||||||
|
packages_extra=
|
||||||
|
agent=opencode
|
||||||
|
opencode_server=http://manifest:8080
|
||||||
|
opencode_model=manifest-model
|
||||||
|
opencode_context=128K
|
||||||
|
allow_host_network=false
|
||||||
@@ -97,7 +97,52 @@ verify_secret_mounts() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maybe_create_helper_pidfile() {
|
||||||
|
local -a args=("$@")
|
||||||
|
local codex_source=""
|
||||||
|
local helper_dir=""
|
||||||
|
local idx=0
|
||||||
|
while (( idx < ${#args[@]} )); do
|
||||||
|
local arg=${args[$idx]}
|
||||||
|
case "$arg" in
|
||||||
|
-v)
|
||||||
|
idx=$((idx + 1))
|
||||||
|
if (( idx < ${#args[@]} )); then
|
||||||
|
local spec=${args[$idx]}
|
||||||
|
case "$spec" in
|
||||||
|
*:/codex|*:/codex:* )
|
||||||
|
codex_source=${spec%%:/codex*}
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-e)
|
||||||
|
idx=$((idx + 1))
|
||||||
|
if (( idx < ${#args[@]} )); then
|
||||||
|
local envspec=${args[$idx]}
|
||||||
|
case "$envspec" in
|
||||||
|
SLOPTRAP_HELPER_DIR=*)
|
||||||
|
helper_dir=${envspec#SLOPTRAP_HELPER_DIR=}
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
idx=$((idx + 1))
|
||||||
|
done
|
||||||
|
if [[ -z $codex_source || $helper_dir != /codex/* ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local helper_host=${codex_source}/${helper_dir#/codex/}
|
||||||
|
mkdir -p "$helper_host"
|
||||||
|
printf '12345\n' >"$helper_host/helperd.pid"
|
||||||
|
}
|
||||||
|
|
||||||
if [[ ${1-} == "image" && ${2-} == "inspect" && ${FAKE_PODMAN_INSPECT_FAIL:-0} == 1 ]]; then
|
if [[ ${1-} == "image" && ${2-} == "inspect" && ${FAKE_PODMAN_INSPECT_FAIL:-0} == 1 ]]; then
|
||||||
|
if [[ " $* " == *" --format "* ]]; then
|
||||||
|
printf 'fake-image-id\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
echo "FAKE PODMAN (fail): $*" >>"$FAKE_PODMAN_LOG"
|
echo "FAKE PODMAN (fail): $*" >>"$FAKE_PODMAN_LOG"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -109,10 +154,16 @@ if [[ ${SECRET_MASK_VERIFY:-0} == 1 && ${1-} == "run" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ ${1-} == "run" ]]; then
|
||||||
|
maybe_create_helper_pidfile "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "FAKE PODMAN: $*" >>"$FAKE_PODMAN_LOG"
|
echo "FAKE PODMAN: $*" >>"$FAKE_PODMAN_LOG"
|
||||||
exit 0
|
exit 0
|
||||||
EOF
|
EOF
|
||||||
chmod +x "$STUB_BIN/podman"
|
chmod +x "$STUB_BIN/podman"
|
||||||
|
cp "$STUB_BIN/podman" "$STUB_BIN/docker"
|
||||||
|
chmod +x "$STUB_BIN/docker"
|
||||||
cat >"$STUB_BIN/curl" <<'EOF'
|
cat >"$STUB_BIN/curl" <<'EOF'
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -138,6 +189,10 @@ EOF
|
|||||||
cat >"$STUB_BIN/jq" <<'EOF'
|
cat >"$STUB_BIN/jq" <<'EOF'
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
if [[ ${1-} == "-n" ]]; then
|
||||||
|
shift
|
||||||
|
exec /usr/bin/jq -n "$@"
|
||||||
|
fi
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-r)
|
-r)
|
||||||
@@ -211,6 +266,33 @@ teardown_stub_env() {
|
|||||||
rm -f "${STUB_LOG:-}"
|
rm -f "${STUB_LOG:-}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extract_embedded_helper() {
|
||||||
|
local helper=$1
|
||||||
|
local output=$2
|
||||||
|
if ! awk -v helper="$helper" '
|
||||||
|
$0 ~ "^[[:space:]]*" helper "\\)$" { state=1; next }
|
||||||
|
state == 1 && /cat <<'\''EOF'\''$/ { state=2; next }
|
||||||
|
state == 2 && /^EOF$/ { exit }
|
||||||
|
state == 2 { print }
|
||||||
|
' "$SLOPTRAP_BIN" >"$output"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
chmod +x "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_path() {
|
||||||
|
local path=$1
|
||||||
|
local attempts=${2:-100}
|
||||||
|
while (( attempts > 0 )); do
|
||||||
|
if [[ -e $path ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
((attempts-=1))
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
record_failure() {
|
record_failure() {
|
||||||
failures+=("$1")
|
failures+=("$1")
|
||||||
}
|
}
|
||||||
@@ -302,6 +384,7 @@ run_secret_mask() {
|
|||||||
teardown_stub_env
|
teardown_stub_env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
run_resume_target() {
|
run_resume_target() {
|
||||||
local scenario_dir="$TEST_ROOT/resume_target"
|
local scenario_dir="$TEST_ROOT/resume_target"
|
||||||
printf '==> resume_target\n'
|
printf '==> resume_target\n'
|
||||||
@@ -320,11 +403,11 @@ run_resume_target() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run_runtime_context_prompt() {
|
run_runtime_context_prompt() {
|
||||||
local scenario_dir="$TEST_ROOT/capability_repo"
|
local scenario_dir="$TEST_ROOT/host_network_repo"
|
||||||
printf '==> runtime_context_prompt\n'
|
printf '==> runtime_context_prompt\n'
|
||||||
setup_stub_env
|
setup_stub_env
|
||||||
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
"$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"
|
record_failure "runtime_context_prompt: sloptrap exited non-zero"
|
||||||
teardown_stub_env
|
teardown_stub_env
|
||||||
return
|
return
|
||||||
@@ -335,11 +418,9 @@ run_runtime_context_prompt() {
|
|||||||
if [[ -z $run_line || $run_line != *"You are running inside sloptrap"* ]]; then
|
if [[ -z $run_line || $run_line != *"You are running inside sloptrap"* ]]; then
|
||||||
record_failure "runtime_context_prompt: startup prompt missing from fresh run"
|
record_failure "runtime_context_prompt: startup prompt missing from fresh run"
|
||||||
fi
|
fi
|
||||||
if ! grep -q -- "manifest_present=true" "$STUB_LOG" \
|
if ! grep -q -- "name=host-network-repo" "$STUB_LOG" \
|
||||||
|| ! grep -q -- "manifest_capabilities=apt-install nested-podman packet-capture" "$STUB_LOG" \
|
|| ! grep -q -- "network_mode=host" "$STUB_LOG"; then
|
||||||
|| ! grep -q -- "trusted_capabilities=apt-install nested-podman packet-capture" "$STUB_LOG" \
|
record_failure "runtime_context_prompt: runtime summary missing manifest state"
|
||||||
|| ! grep -q -- "enabled_capabilities=apt-install nested-podman packet-capture" "$STUB_LOG"; then
|
|
||||||
record_failure "runtime_context_prompt: runtime summary missing manifest or capability state"
|
|
||||||
fi
|
fi
|
||||||
if [[ -n $login_line && $login_line == *"You are running inside sloptrap"* ]]; then
|
if [[ -n $login_line && $login_line == *"You are running inside sloptrap"* ]]; then
|
||||||
record_failure "runtime_context_prompt: login flow should not receive startup prompt"
|
record_failure "runtime_context_prompt: login flow should not receive startup prompt"
|
||||||
@@ -348,11 +429,11 @@ run_runtime_context_prompt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run_sh_reexec() {
|
run_sh_reexec() {
|
||||||
local scenario_dir="$TEST_ROOT/capability_repo"
|
local scenario_dir="$TEST_ROOT/host_network_repo"
|
||||||
printf '==> sh_reexec\n'
|
printf '==> sh_reexec\n'
|
||||||
setup_stub_env
|
setup_stub_env
|
||||||
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
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"
|
record_failure "sh_reexec: sloptrap exited non-zero when launched via sh"
|
||||||
teardown_stub_env
|
teardown_stub_env
|
||||||
return
|
return
|
||||||
@@ -364,12 +445,12 @@ run_sh_reexec() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run_resume_omits_runtime_context() {
|
run_resume_omits_runtime_context() {
|
||||||
local scenario_dir="$TEST_ROOT/capability_repo"
|
local scenario_dir="$TEST_ROOT/host_network_repo"
|
||||||
local session_id="019a81b7-32d2-7622-8639-6698c6579625"
|
local session_id="019a81b7-32d2-7622-8639-6698c6579625"
|
||||||
printf '==> resume_omits_runtime_context\n'
|
printf '==> resume_omits_runtime_context\n'
|
||||||
setup_stub_env
|
setup_stub_env
|
||||||
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
"$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" 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"
|
record_failure "resume_omits_runtime_context: sloptrap exited non-zero"
|
||||||
teardown_stub_env
|
teardown_stub_env
|
||||||
return
|
return
|
||||||
@@ -377,12 +458,31 @@ run_resume_omits_runtime_context() {
|
|||||||
if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then
|
if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then
|
||||||
record_failure "resume_omits_runtime_context: resume should not receive startup prompt"
|
record_failure "resume_omits_runtime_context: resume should not receive startup prompt"
|
||||||
fi
|
fi
|
||||||
if ! grep -q -- "codex --sandbox danger-full-access --ask-for-approval never resume $session_id" "$STUB_LOG"; then
|
if ! grep -q -- "--sandbox danger-full-access --ask-for-approval never resume $session_id" "$STUB_LOG"; then
|
||||||
record_failure "resume_omits_runtime_context: resume invocation missing"
|
record_failure "resume_omits_runtime_context: resume invocation missing"
|
||||||
fi
|
fi
|
||||||
teardown_stub_env
|
teardown_stub_env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_shell_target_uses_entrypoint() {
|
||||||
|
local scenario_dir="$TEST_ROOT/opencode_localhost"
|
||||||
|
printf '==> shell_target_uses_entrypoint\n'
|
||||||
|
setup_stub_env
|
||||||
|
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
"$SLOPTRAP_BIN" "$scenario_dir" shell </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "shell_target_uses_entrypoint: shell target failed"
|
||||||
|
teardown_stub_env
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "--entrypoint /bin/bash" "$STUB_LOG"; then
|
||||||
|
record_failure "shell_target_uses_entrypoint: missing entrypoint override"
|
||||||
|
fi
|
||||||
|
if grep -q -- "/codex/auth.json" "$STUB_LOG"; then
|
||||||
|
record_failure "shell_target_uses_entrypoint: codex auth mount should not be present for opencode shell"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
run_auth_file_mount() {
|
run_auth_file_mount() {
|
||||||
local scenario_dir
|
local scenario_dir
|
||||||
scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P)
|
scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P)
|
||||||
@@ -396,8 +496,8 @@ run_auth_file_mount() {
|
|||||||
teardown_stub_env
|
teardown_stub_env
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then
|
if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z,ro" "$STUB_LOG"; then
|
||||||
record_failure "auth_file_mount: missing auth file bind mount"
|
record_failure "auth_file_mount: auth file should be mounted read-only for normal runs"
|
||||||
fi
|
fi
|
||||||
if ! grep -q -- "-v ${STUB_HOME}/.codex/sloptrap/state/" "$STUB_LOG"; then
|
if ! grep -q -- "-v ${STUB_HOME}/.codex/sloptrap/state/" "$STUB_LOG"; then
|
||||||
record_failure "auth_file_mount: missing project state bind mount"
|
record_failure "auth_file_mount: missing project state bind mount"
|
||||||
@@ -405,6 +505,61 @@ run_auth_file_mount() {
|
|||||||
teardown_stub_env
|
teardown_stub_env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_runtime_hardening_flags() {
|
||||||
|
local scenario_dir
|
||||||
|
scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P)
|
||||||
|
printf '==> runtime_hardening_flags\n'
|
||||||
|
setup_stub_env
|
||||||
|
mkdir -p "$STUB_HOME/.codex"
|
||||||
|
printf '{"access_token":"test"}\n' >"$STUB_HOME/.codex/auth.json"
|
||||||
|
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
"$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "runtime_hardening_flags: sloptrap exited non-zero"
|
||||||
|
teardown_stub_env
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "--cap-drop ALL" "$STUB_LOG"; then
|
||||||
|
record_failure "runtime_hardening_flags: cap-drop flag missing"
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "--read-only" "$STUB_LOG"; then
|
||||||
|
record_failure "runtime_hardening_flags: read-only rootfs flag missing"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
|
run_codex_home_override() {
|
||||||
|
local scenario_dir codex_root
|
||||||
|
scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P)
|
||||||
|
printf '==> codex_home_override\n'
|
||||||
|
setup_stub_env
|
||||||
|
codex_root="$STUB_HOME/codex-root"
|
||||||
|
mkdir -p "$codex_root"
|
||||||
|
printf '{"access_token":"test"}\n' >"$codex_root/auth.json"
|
||||||
|
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" CODEX_HOME="$codex_root" SLOPTRAP_PREFER_CODEX_HOME=1 \
|
||||||
|
FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
"$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "codex_home_override: sloptrap exited non-zero"
|
||||||
|
teardown_stub_env
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "-v ${codex_root}/auth.json:/codex/auth.json:Z,ro" "$STUB_LOG"; then
|
||||||
|
record_failure "codex_home_override: CODEX_HOME auth file should be mounted read-only for normal runs"
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "-v ${codex_root}/sloptrap/state/" "$STUB_LOG"; then
|
||||||
|
record_failure "codex_home_override: missing CODEX_HOME project state bind mount"
|
||||||
|
fi
|
||||||
|
if grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then
|
||||||
|
record_failure "codex_home_override: should not fall back to HOME/.codex when CODEX_HOME is set"
|
||||||
|
fi
|
||||||
|
local first_run
|
||||||
|
first_run=$(grep "FAKE PODMAN: run " "$STUB_LOG" | head -n 1 || true)
|
||||||
|
if [[ -z $first_run || $first_run == *" login" ]]; then
|
||||||
|
record_failure "codex_home_override: existing CODEX_HOME auth should avoid login target"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
run_project_state_isolation() {
|
run_project_state_isolation() {
|
||||||
local scenario_a scenario_b
|
local scenario_a scenario_b
|
||||||
scenario_a=$(cd "$TEST_ROOT/resume_target" && pwd -P)
|
scenario_a=$(cd "$TEST_ROOT/resume_target" && pwd -P)
|
||||||
@@ -465,8 +620,11 @@ run_auto_login_empty_auth() {
|
|||||||
if [[ -z $first_run || $first_run != *" login" ]]; then
|
if [[ -z $first_run || $first_run != *" login" ]]; then
|
||||||
record_failure "auto_login_empty_auth: expected login before primary run"
|
record_failure "auto_login_empty_auth: expected login before primary run"
|
||||||
fi
|
fi
|
||||||
if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z" "$STUB_LOG"; then
|
if [[ -z $first_run || $first_run != *"-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z "* ]]; then
|
||||||
record_failure "auto_login_empty_auth: missing auth file bind mount"
|
record_failure "auto_login_empty_auth: login target should keep auth file writable"
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "-v ${STUB_HOME}/.codex/auth.json:/codex/auth.json:Z,ro" "$STUB_LOG"; then
|
||||||
|
record_failure "auto_login_empty_auth: post-login runtime should remount auth file read-only"
|
||||||
fi
|
fi
|
||||||
teardown_stub_env
|
teardown_stub_env
|
||||||
}
|
}
|
||||||
@@ -494,59 +652,6 @@ run_root_directory_project() {
|
|||||||
rm -rf "$tmp_home"
|
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() {
|
run_invalid_manifest_name() {
|
||||||
local scenario_dir="$TEST_ROOT/invalid_manifest_name"
|
local scenario_dir="$TEST_ROOT/invalid_manifest_name"
|
||||||
@@ -572,198 +677,357 @@ run_invalid_manifest_packages() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
run_invalid_manifest_capabilities() {
|
run_invalid_manifest_agent() {
|
||||||
local scenario_dir="$TEST_ROOT/invalid_manifest_capabilities"
|
printf '==> invalid_manifest_agent\n'
|
||||||
printf '==> invalid_manifest_capabilities\n'
|
local scenario_dir
|
||||||
|
scenario_dir=$(mktemp -d)
|
||||||
|
cat >"$scenario_dir/.sloptrap" <<'EOF'
|
||||||
|
name=invalid-agent
|
||||||
|
agent=bogus
|
||||||
|
EOF
|
||||||
if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
record_failure "invalid_manifest_capabilities: expected rejection for bad capabilities"
|
record_failure "invalid_manifest_agent: expected rejection for invalid agent"
|
||||||
|
fi
|
||||||
|
rm -rf "$scenario_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_invalid_agent_env_override() {
|
||||||
|
local scenario_dir="$TEST_ROOT/opencode_print_config"
|
||||||
|
printf '==> invalid_agent_env_override\n'
|
||||||
|
if SLOPTRAP_AGENT=bogus "$SLOPTRAP_BIN" --dry-run "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "invalid_agent_env_override: expected rejection for invalid SLOPTRAP_AGENT"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
run_invalid_allow_host_network() {
|
run_removed_runtime_override_envs() {
|
||||||
local scenario_dir="$TEST_ROOT/invalid_allow_host_network"
|
local scenario_dir="$TEST_ROOT/resume_target"
|
||||||
printf '==> invalid_allow_host_network\n'
|
printf '==> removed_runtime_override_envs\n'
|
||||||
if "$SLOPTRAP_BIN" --dry-run "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
if SLOPTRAP_SECURITY_OPTS_EXTRA='--privileged' "$SLOPTRAP_BIN" --dry-run "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
record_failure "invalid_allow_host_network: expected rejection for invalid value"
|
record_failure "removed_runtime_override_envs: expected rejection for SLOPTRAP_SECURITY_OPTS_EXTRA"
|
||||||
|
fi
|
||||||
|
if SLOPTRAP_ROOTFS_READONLY=0 "$SLOPTRAP_BIN" --dry-run "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "removed_runtime_override_envs: expected rejection for SLOPTRAP_ROOTFS_READONLY"
|
||||||
|
fi
|
||||||
|
if SLOPTRAP_NETWORK_NAME=host "$SLOPTRAP_BIN" --dry-run "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "removed_runtime_override_envs: expected rejection for SLOPTRAP_NETWORK_NAME"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_removed_build_override_envs() {
|
||||||
|
local scenario_dir="$TEST_ROOT/resume_target"
|
||||||
|
printf '==> removed_build_override_envs\n'
|
||||||
|
if SLOPTRAP_DOCKERFILE_PATH=/etc/passwd "$SLOPTRAP_BIN" --dry-run "$scenario_dir" build </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "removed_build_override_envs: expected rejection for SLOPTRAP_DOCKERFILE_PATH"
|
||||||
|
fi
|
||||||
|
if SLOPTRAP_CODEX_URL=https://example.invalid/codex.tgz "$SLOPTRAP_BIN" --dry-run "$scenario_dir" build </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "removed_build_override_envs: expected rejection for SLOPTRAP_CODEX_URL"
|
||||||
|
fi
|
||||||
|
if SLOPTRAP_CODEX_ARCHIVE=codex-custom "$SLOPTRAP_BIN" --dry-run "$scenario_dir" build </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "removed_build_override_envs: expected rejection for SLOPTRAP_CODEX_ARCHIVE"
|
||||||
|
fi
|
||||||
|
if SLOPTRAP_CODEX_BIN=custom-codex "$SLOPTRAP_BIN" --dry-run "$scenario_dir" build </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "removed_build_override_envs: expected rejection for SLOPTRAP_CODEX_BIN"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
run_wizard_create_manifest() {
|
run_wizard_create_manifest() {
|
||||||
local scenario_dir="$TEST_ROOT/wizard_empty"
|
local scenario_dir="$TEST_ROOT/wizard_empty"
|
||||||
printf '==> wizard_create_manifest\n'
|
printf '==> wizard_create_manifest\n'
|
||||||
if ! can_run_script_pty; then
|
if ! can_run_script_pty; then
|
||||||
printf 'skipping wizard_create_manifest: script PTY support not available\n'
|
printf 'skipping wizard_create_manifest: script PTY support not available\n'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
rm -f "$scenario_dir/.sloptrap"
|
rm -f "$scenario_dir/.sloptrap"
|
||||||
local input=$'\n\n\n\n'
|
# Wizard now has: name, packages_extra, agent (codex), allow_host_network
|
||||||
if ! printf '%s' "$input" | script -q -c "$SLOPTRAP_BIN \"$scenario_dir\" wizard" /dev/null >/dev/null 2>&1; then
|
# Use empty for name (default), empty for packages_extra, empty for agent (uses default), false for allow_host_network
|
||||||
record_failure "wizard_create_manifest: wizard failed"
|
if ! printf '\n\n\nfalse\n\n' | "$SLOPTRAP_BIN" "$scenario_dir" wizard >/dev/null 2>&1; then
|
||||||
return
|
record_failure "wizard_create_manifest: wizard failed"
|
||||||
fi
|
return
|
||||||
if [[ ! -f $scenario_dir/.sloptrap ]]; then
|
fi
|
||||||
record_failure "wizard_create_manifest: manifest not created"
|
if [[ ! -f $scenario_dir/.sloptrap ]]; then
|
||||||
return
|
record_failure "wizard_create_manifest: manifest not created"
|
||||||
fi
|
return
|
||||||
if ! grep -qx "name=wizard_empty" "$scenario_dir/.sloptrap"; then
|
fi
|
||||||
record_failure "wizard_create_manifest: name default mismatch"
|
if ! grep -qx "name=wizard_empty" "$scenario_dir/.sloptrap"; then
|
||||||
fi
|
record_failure "wizard_create_manifest: name default mismatch"
|
||||||
if ! grep -qx "packages_extra=" "$scenario_dir/.sloptrap"; then
|
fi
|
||||||
record_failure "wizard_create_manifest: packages_extra mismatch"
|
if ! grep -qx "packages_extra=" "$scenario_dir/.sloptrap"; then
|
||||||
fi
|
record_failure "wizard_create_manifest: packages_extra mismatch"
|
||||||
if ! grep -qx "capabilities=" "$scenario_dir/.sloptrap"; then
|
fi
|
||||||
record_failure "wizard_create_manifest: capabilities mismatch"
|
if ! grep -qx "agent=codex" "$scenario_dir/.sloptrap"; then
|
||||||
fi
|
record_failure "wizard_create_manifest: agent mismatch"
|
||||||
if ! grep -qx "allow_host_network=false" "$scenario_dir/.sloptrap"; then
|
fi
|
||||||
record_failure "wizard_create_manifest: allow_host_network mismatch"
|
if ! grep -qx "allow_host_network=false" "$scenario_dir/.sloptrap"; then
|
||||||
fi
|
record_failure "wizard_create_manifest: allow_host_network mismatch"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
run_wizard_existing_defaults() {
|
run_wizard_existing_defaults() {
|
||||||
local scenario_dir="$TEST_ROOT/wizard_existing"
|
local scenario_dir="$TEST_ROOT/wizard_existing"
|
||||||
printf '==> wizard_existing_defaults\n'
|
printf '==> wizard_existing_defaults\n'
|
||||||
if ! can_run_script_pty; then
|
if ! can_run_script_pty; then
|
||||||
printf 'skipping wizard_existing_defaults: script PTY support not available\n'
|
printf 'skipping wizard_existing_defaults: script PTY support not available\n'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
local input=$'\n\n\n\n'
|
# Create initial manifest with custom-wizard name
|
||||||
if ! printf '%s' "$input" | script -q -c "$SLOPTRAP_BIN \"$scenario_dir\" wizard" /dev/null >/dev/null 2>&1; then
|
cat > "$scenario_dir/.sloptrap" <<EOF
|
||||||
record_failure "wizard_existing_defaults: wizard failed"
|
name=custom-wizard
|
||||||
return
|
packages_extra=make git
|
||||||
fi
|
agent=codex
|
||||||
if ! grep -qx "name=custom-wizard" "$scenario_dir/.sloptrap"; then
|
allow_host_network=true
|
||||||
record_failure "wizard_existing_defaults: name not preserved"
|
EOF
|
||||||
fi
|
# Wizard now has: name, packages_extra, agent (codex), allow_host_network
|
||||||
if ! grep -qx "packages_extra=make git" "$scenario_dir/.sloptrap"; then
|
# Use empty for name (default), make git for packages_extra, empty for agent (uses default), false for allow_host_network
|
||||||
record_failure "wizard_existing_defaults: packages_extra not preserved"
|
if ! printf '\nmake git\n\n\nfalse\n\n' | "$SLOPTRAP_BIN" "$scenario_dir" wizard >/dev/null 2>&1; then
|
||||||
fi
|
record_failure "wizard_existing_defaults: wizard failed"
|
||||||
if ! grep -qx "capabilities=apt-install packet-capture" "$scenario_dir/.sloptrap"; then
|
return
|
||||||
record_failure "wizard_existing_defaults: capabilities not preserved"
|
fi
|
||||||
fi
|
if ! grep -qx "name=custom-wizard" "$scenario_dir/.sloptrap"; then
|
||||||
if ! grep -qx "allow_host_network=true" "$scenario_dir/.sloptrap"; then
|
record_failure "wizard_existing_defaults: name not preserved"
|
||||||
record_failure "wizard_existing_defaults: allow_host_network not preserved"
|
fi
|
||||||
fi
|
if ! grep -qx "packages_extra=make git" "$scenario_dir/.sloptrap"; then
|
||||||
|
record_failure "wizard_existing_defaults: packages_extra not preserved"
|
||||||
|
fi
|
||||||
|
if ! grep -qx "agent=codex" "$scenario_dir/.sloptrap"; then
|
||||||
|
record_failure "wizard_existing_defaults: agent not preserved"
|
||||||
|
fi
|
||||||
|
if ! grep -qx "allow_host_network=true" "$scenario_dir/.sloptrap"; then
|
||||||
|
record_failure "wizard_existing_defaults: allow_host_network not preserved"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
run_wizard_build_trigger() {
|
run_wizard_build_trigger() {
|
||||||
local scenario_dir="$TEST_ROOT/wizard_build"
|
local scenario_dir="$TEST_ROOT/wizard_build"
|
||||||
printf '==> wizard_build_trigger\n'
|
printf '==> wizard_build_trigger\n'
|
||||||
if ! can_run_script_pty; then
|
if ! can_run_script_pty; then
|
||||||
printf 'skipping wizard_build_trigger: script PTY support not available\n'
|
printf 'skipping wizard_build_trigger: script PTY support not available\n'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
setup_stub_env
|
setup_stub_env
|
||||||
rm -f "$scenario_dir/.sloptrap"
|
rm -f "$scenario_dir/.sloptrap"
|
||||||
local input=$'\n\n\n\n'
|
# Wizard now has: name, packages_extra, agent (codex), allow_host_network
|
||||||
if ! printf '%s' "$input" | script -q -c "env 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
|
# Use empty for name (default), empty for packages_extra, empty for agent (uses default), false for allow_host_network
|
||||||
record_failure "wizard_build_trigger: sloptrap failed"
|
if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
teardown_stub_env
|
printf '\n\n\nfalse\n\n' | "$SLOPTRAP_BIN" "$scenario_dir" wizard >/dev/null 2>&1; then
|
||||||
return
|
record_failure "wizard_build_trigger: wizard failed"
|
||||||
fi
|
teardown_stub_env
|
||||||
if [[ ! -f $scenario_dir/.sloptrap ]]; then
|
return
|
||||||
record_failure "wizard_build_trigger: manifest not created"
|
fi
|
||||||
fi
|
if [[ ! -f $scenario_dir/.sloptrap ]]; then
|
||||||
if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then
|
record_failure "wizard_build_trigger: manifest not created"
|
||||||
record_failure "wizard_build_trigger: build not invoked after wizard"
|
teardown_stub_env
|
||||||
fi
|
return
|
||||||
teardown_stub_env
|
fi
|
||||||
}
|
# Run build to trigger image build
|
||||||
|
if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
run_capability_trust_required() {
|
"$SLOPTRAP_BIN" "$scenario_dir" build >/dev/null 2>&1; then
|
||||||
local scenario_dir="$TEST_ROOT/capability_repo"
|
record_failure "wizard_build_trigger: build failed"
|
||||||
printf '==> capability_trust_required\n'
|
teardown_stub_env
|
||||||
setup_stub_env
|
return
|
||||||
if PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
fi
|
||||||
"$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then
|
||||||
record_failure "capability_trust_required: expected failure without trusted capabilities"
|
record_failure "wizard_build_trigger: build not invoked"
|
||||||
fi
|
|
||||||
teardown_stub_env
|
|
||||||
}
|
|
||||||
|
|
||||||
run_capability_profiles() {
|
|
||||||
local scenario_dir="$TEST_ROOT/capability_repo"
|
|
||||||
printf '==> capability_profiles\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 "capability_profiles: sloptrap exited non-zero"
|
|
||||||
teardown_stub_env
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
if ! grep -q -- "CAPABILITY_PACKAGES=tcpdump podman fuse-overlayfs slirp4netns" "$STUB_LOG"; then
|
|
||||||
record_failure "capability_profiles: build arg for capability packages missing"
|
|
||||||
fi
|
|
||||||
if ! grep -q -- "--cap-add NET_RAW" "$STUB_LOG"; then
|
|
||||||
record_failure "capability_profiles: NET_RAW capability missing"
|
|
||||||
fi
|
|
||||||
if ! grep -q -- "--cap-add NET_ADMIN" "$STUB_LOG"; then
|
|
||||||
record_failure "capability_profiles: NET_ADMIN capability missing"
|
|
||||||
fi
|
|
||||||
if ! grep -q -- "--device /dev/fuse" "$STUB_LOG"; then
|
|
||||||
record_failure "capability_profiles: /dev/fuse device missing"
|
|
||||||
fi
|
|
||||||
if grep -q -- "--read-only" "$STUB_LOG"; then
|
|
||||||
record_failure "capability_profiles: apt profile should disable read-only rootfs"
|
|
||||||
fi
|
|
||||||
if grep -q -- "--user " "$STUB_LOG"; then
|
|
||||||
record_failure "capability_profiles: capability-enabled run should not force --user"
|
|
||||||
fi
|
|
||||||
if ! grep -q -- "SLOPTRAP_ACTIVE_CAPABILITIES=apt-install nested-podman packet-capture" "$STUB_LOG"; then
|
|
||||||
record_failure "capability_profiles: active capability environment missing"
|
|
||||||
fi
|
|
||||||
if ! grep -q -- "SLOPTRAP_INNER_PODMAN_HOST_NETWORK=1" "$STUB_LOG"; then
|
|
||||||
record_failure "capability_profiles: inner podman host-network mirror flag missing"
|
|
||||||
fi
|
|
||||||
teardown_stub_env
|
|
||||||
}
|
|
||||||
|
|
||||||
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 sloppodman; do
|
|
||||||
if [[ -e $install_dir/$helper ]]; then
|
|
||||||
record_failure "make_install_single_file: unexpected helper installed ($helper)"
|
|
||||||
fi
|
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_opencode_build_downloads_release_cli() {
|
||||||
run_mount_injection
|
local scenario_dir="$TEST_ROOT/opencode_build"
|
||||||
run_root_target
|
printf '==> opencode_build_downloads_release_cli\n'
|
||||||
|
setup_stub_env
|
||||||
|
mkdir -p "$scenario_dir"
|
||||||
|
cat > "$scenario_dir/.sloptrap" <<'EOF'
|
||||||
|
name=opencode-build
|
||||||
|
packages_extra=htop
|
||||||
|
agent=opencode
|
||||||
|
allow_host_network=false
|
||||||
|
EOF
|
||||||
|
if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
"$SLOPTRAP_BIN" "$scenario_dir" build >/dev/null 2>&1; then
|
||||||
|
record_failure "opencode_build_downloads_release_cli: build failed"
|
||||||
|
teardown_stub_env
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_build_downloads_release_cli: build not invoked"
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "--build-arg OPENCODE_BIN=opencode" "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_build_downloads_release_cli: default OPENCODE_BIN build arg missing"
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "--build-arg EXTRA_PACKAGES=htop" "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_build_downloads_release_cli: EXTRA_PACKAGES build arg missing"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
|
run_opencode_localhost_rewrite() {
|
||||||
|
local scenario_dir="$TEST_ROOT/opencode_localhost"
|
||||||
|
printf '==> opencode_localhost_rewrite\n'
|
||||||
|
setup_stub_env
|
||||||
|
mkdir -p "$scenario_dir"
|
||||||
|
cat > "$scenario_dir/.sloptrap" <<'EOF'
|
||||||
|
name=opencode-localhost
|
||||||
|
packages_extra=
|
||||||
|
agent=opencode
|
||||||
|
opencode_server=http://localhost:8080
|
||||||
|
allow_host_network=false
|
||||||
|
EOF
|
||||||
|
cat > "$STUB_BIN/slirp4netns" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x "$STUB_BIN/slirp4netns"
|
||||||
|
if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
"$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "opencode_localhost_rewrite: run failed"
|
||||||
|
teardown_stub_env
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local config_path
|
||||||
|
config_path=$(find "$STUB_HOME" -path '*/config/opencode/opencode.json' | head -n 1 || true)
|
||||||
|
if [[ -z $config_path || ! -f $config_path ]]; then
|
||||||
|
record_failure "opencode_localhost_rewrite: opencode config not written"
|
||||||
|
teardown_stub_env
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "--network slirp4netns:allow_host_loopback=true" "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_localhost_rewrite: slirp host loopback not enabled"
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "--add-host sloptrap.host:host-gateway" "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_localhost_rewrite: host alias not injected"
|
||||||
|
fi
|
||||||
|
if ! grep -q -- "OPENCODE_CONFIG=/codex/config/opencode/opencode.json" "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_localhost_rewrite: opencode config path not exported"
|
||||||
|
fi
|
||||||
|
if grep -q -- "/codex/auth.json" "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_localhost_rewrite: codex auth mount should not be present for opencode"
|
||||||
|
fi
|
||||||
|
if ! grep -q -- '"baseURL": "http://sloptrap.host:8080/v1"' "$config_path"; then
|
||||||
|
record_failure "opencode_localhost_rewrite: localhost server not rewritten in config"
|
||||||
|
fi
|
||||||
|
if [[ $(jq -r '.provider["llama.cpp"].name' "$config_path") != "llama-server (local)" ]]; then
|
||||||
|
record_failure "opencode_localhost_rewrite: provider name not merged into config"
|
||||||
|
fi
|
||||||
|
if [[ $(jq -r '.model' "$config_path") != "llama.cpp/bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0 - 262144" ]]; then
|
||||||
|
record_failure "opencode_localhost_rewrite: opencode model not written to config"
|
||||||
|
fi
|
||||||
|
if [[ $(jq -r '.enabled_providers[0]' "$config_path") != "llama.cpp" ]]; then
|
||||||
|
record_failure "opencode_localhost_rewrite: enabled_providers not merged into config"
|
||||||
|
fi
|
||||||
|
if [[ $(jq -r '.provider["llama.cpp"].models["bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0 - 262144"].limit.context' "$config_path") != "262144" ]]; then
|
||||||
|
record_failure "opencode_localhost_rewrite: opencode context not written to config"
|
||||||
|
fi
|
||||||
|
if grep -q -- "--server " "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_localhost_rewrite: deprecated --server flag should not be passed"
|
||||||
|
fi
|
||||||
|
if grep -q -- "--sandbox workspace-write" "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_localhost_rewrite: codex sandbox args leaked into opencode run"
|
||||||
|
fi
|
||||||
|
if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then
|
||||||
|
record_failure "opencode_localhost_rewrite: codex-style startup prompt should not be passed to opencode"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
|
run_opencode_print_config_runtime_flags() {
|
||||||
|
local scenario_dir="$TEST_ROOT/opencode_print_config"
|
||||||
|
printf '==> opencode_print_config_runtime_flags\n'
|
||||||
|
setup_stub_env
|
||||||
|
mkdir -p "$scenario_dir"
|
||||||
|
cat > "$scenario_dir/.sloptrap" <<'EOF'
|
||||||
|
name=opencode-print-config
|
||||||
|
packages_extra=
|
||||||
|
agent=opencode
|
||||||
|
allow_host_network=false
|
||||||
|
EOF
|
||||||
|
local output
|
||||||
|
if ! output=$(env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
"$SLOPTRAP_BIN" --print-config "$scenario_dir" 2>/dev/null); then
|
||||||
|
record_failure "opencode_print_config_runtime_flags: print-config failed"
|
||||||
|
teardown_stub_env
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! grep -q 'runtime_flags=' <<<"$output"; then
|
||||||
|
record_failure "opencode_print_config_runtime_flags: runtime_flags line missing for opencode"
|
||||||
|
fi
|
||||||
|
if ! grep -q 'opencode_config=' <<<"$output"; then
|
||||||
|
record_failure "opencode_print_config_runtime_flags: opencode config path missing"
|
||||||
|
fi
|
||||||
|
if grep -q -- '--sandbox danger-full-access --ask-for-approval never' <<<"$output"; then
|
||||||
|
record_failure "opencode_print_config_runtime_flags: codex runtime flags leaked into opencode config"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
|
run_opencode_env_overrides() {
|
||||||
|
local scenario_dir="$TEST_ROOT/opencode_print_config"
|
||||||
|
printf '==> opencode_env_overrides\n'
|
||||||
|
setup_stub_env
|
||||||
|
mkdir -p "$scenario_dir"
|
||||||
|
cat > "$scenario_dir/.sloptrap" <<'EOF'
|
||||||
|
name=opencode-print-config
|
||||||
|
packages_extra=
|
||||||
|
agent=opencode
|
||||||
|
opencode_server=http://manifest:8080
|
||||||
|
opencode_model=manifest-model
|
||||||
|
opencode_context=128K
|
||||||
|
allow_host_network=false
|
||||||
|
EOF
|
||||||
|
local output
|
||||||
|
local plain_output
|
||||||
|
if ! output=$(env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
SLOPTRAP_OPENCODE_SERVER=http://env:8080 SLOPTRAP_OPENCODE_MODEL=env-model SLOPTRAP_OPENCODE_CONTEXT=64K \
|
||||||
|
"$SLOPTRAP_BIN" --print-config "$scenario_dir" 2>/dev/null); then
|
||||||
|
record_failure "opencode_env_overrides: print-config failed"
|
||||||
|
teardown_stub_env
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
plain_output=$(printf '%s' "$output" | sed -E $'s/\x1B\\[[0-9;]*m//g')
|
||||||
|
if ! grep -q 'opencode_server=http://env:8080' <<<"$plain_output"; then
|
||||||
|
record_failure "opencode_env_overrides: server env override missing"
|
||||||
|
fi
|
||||||
|
if ! grep -q 'opencode_model=env-model' <<<"$plain_output"; then
|
||||||
|
record_failure "opencode_env_overrides: model env override missing"
|
||||||
|
fi
|
||||||
|
if ! grep -q 'opencode_context=64K' <<<"$plain_output"; then
|
||||||
|
record_failure "opencode_env_overrides: context env override missing"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
|
run_opencode_config_symlink_rejected() {
|
||||||
|
local scenario_dir="$TEST_ROOT/opencode_localhost"
|
||||||
|
printf '==> opencode_config_symlink_rejected\n'
|
||||||
|
setup_stub_env
|
||||||
|
mkdir -p "$scenario_dir"
|
||||||
|
cat > "$scenario_dir/.sloptrap" <<'EOF'
|
||||||
|
name=opencode-localhost
|
||||||
|
packages_extra=
|
||||||
|
agent=opencode
|
||||||
|
allow_host_network=false
|
||||||
|
EOF
|
||||||
|
local state_key
|
||||||
|
state_key=$(printf '%s' "$scenario_dir" | sha256sum | awk '{print $1}')
|
||||||
|
local config_dir="$STUB_HOME/.codex/sloptrap/state/$state_key/config/opencode"
|
||||||
|
mkdir -p "$config_dir"
|
||||||
|
ln -s "$STUB_HOME/target.json" "$config_dir/opencode.json"
|
||||||
|
if env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
"$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "opencode_config_symlink_rejected: expected rejection for symlinked config"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
|
run_opencode_login_rejected() {
|
||||||
|
local scenario_dir="$TEST_ROOT/opencode_localhost"
|
||||||
|
printf '==> opencode_login_rejected\n'
|
||||||
|
setup_stub_env
|
||||||
|
if env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||||
|
"$SLOPTRAP_BIN" "$scenario_dir" login </dev/null >/dev/null 2>&1; then
|
||||||
|
record_failure "opencode_login_rejected: expected login rejection for opencode"
|
||||||
|
fi
|
||||||
|
teardown_stub_env
|
||||||
|
}
|
||||||
|
|
||||||
run_symlink_escape
|
run_symlink_escape
|
||||||
run_manifest_injection
|
run_manifest_injection
|
||||||
run_helper_symlink
|
run_helper_symlink
|
||||||
@@ -772,26 +1036,27 @@ run_resume_target
|
|||||||
run_runtime_context_prompt
|
run_runtime_context_prompt
|
||||||
run_sh_reexec
|
run_sh_reexec
|
||||||
run_resume_omits_runtime_context
|
run_resume_omits_runtime_context
|
||||||
|
run_shell_target_uses_entrypoint
|
||||||
run_auth_file_mount
|
run_auth_file_mount
|
||||||
|
run_runtime_hardening_flags
|
||||||
|
run_codex_home_override
|
||||||
run_project_state_isolation
|
run_project_state_isolation
|
||||||
run_auto_login_empty_auth
|
run_auto_login_empty_auth
|
||||||
run_codex_symlink_home
|
run_codex_symlink_home
|
||||||
run_root_directory_project
|
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_wizard_create_manifest
|
run_wizard_create_manifest
|
||||||
run_wizard_existing_defaults
|
run_wizard_existing_defaults
|
||||||
run_wizard_build_trigger
|
run_wizard_build_trigger
|
||||||
run_capability_trust_required
|
run_invalid_manifest_agent
|
||||||
run_capability_profiles
|
run_invalid_agent_env_override
|
||||||
run_make_install_single_file
|
run_removed_runtime_override_envs
|
||||||
|
run_removed_build_override_envs
|
||||||
|
run_opencode_build_downloads_release_cli
|
||||||
|
run_opencode_localhost_rewrite
|
||||||
|
run_opencode_print_config_runtime_flags
|
||||||
|
run_opencode_env_overrides
|
||||||
|
run_opencode_config_symlink_rejected
|
||||||
|
run_opencode_login_rejected
|
||||||
|
|
||||||
if [[ ${#failures[@]} -gt 0 ]]; then
|
if [[ ${#failures[@]} -gt 0 ]]; then
|
||||||
printf '\nTest failures:\n'
|
printf '\nTest failures:\n'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name=wizard_build
|
name=wizard_build
|
||||||
packages_extra=
|
packages_extra=
|
||||||
capabilities=
|
agent=codex
|
||||||
allow_host_network=false
|
allow_host_network=false
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name=wizard_empty
|
name=wizard_empty
|
||||||
packages_extra=
|
packages_extra=
|
||||||
capabilities=
|
agent=codex
|
||||||
allow_host_network=false
|
allow_host_network=false
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name=custom-wizard
|
name=custom-wizard
|
||||||
packages_extra=make git
|
packages_extra=make git
|
||||||
capabilities=apt-install packet-capture
|
agent=codex
|
||||||
allow_host_network=true
|
allow_host_network=true
|
||||||
|
|||||||
Reference in New Issue
Block a user