- remove codex auth mounts from opencode run/shell paths - reject opencode login and invalid backend values - harden opencode config writes against symlink clobbering - fix opencode build args and packages_extra handling - enforce cap-drop and read-only rootfs in runtime commands - reject dangerous runtime/build env overrides - update README and test docs to match actual behavior - extend regression coverage for backend safety and hardening
169 lines
13 KiB
Markdown
169 lines
13 KiB
Markdown
# sloptrap
|
|
|
|
sloptrap runs the OpenAI Codex CLI inside a container with a predictable and locked-down filesystem view. The launcher script (`sloptrap`) resolves the project manifest, builds the support image directly, and starts Codex with the requested target (defaults to `run`). Hardened parsing blocks container escapes via manifest or ignore directives, verifies the downloaded Codex binary, and keeps the runtime environment minimal.
|
|
|
|
## Dependencies
|
|
|
|
- Podman ≥ 4 (sloptrap refuses to run without it unless you explicitly override `SLOPTRAP_CONTAINER_ENGINE`).
|
|
- GNU `bash`, `curl`, `tar`, `sha256sum`, `realpath` (from GNU coreutils), and `jq` on the host.
|
|
|
|
> Tip: set `SLOPTRAP_CONTAINER_ENGINE=<engine>` if you need to override the default Podman requirement.
|
|
|
|
### macOS setup
|
|
|
|
sloptrap targets GNU userland. On macOS, install the GNU tools via Homebrew and the launcher will prepend their `gnubin` paths automatically:
|
|
|
|
```
|
|
brew install coreutils gnu-tar jq
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
1. Place `sloptrap` somewhere on your PATH/shared drive, for example with `make install` (the helper payload, helper Dockerfile, and Codex binary handling are bundled into the launcher).
|
|
2. (Optional) Create a project-specific manifest and ignore file:
|
|
```bash
|
|
cat > path/to/project/.sloptrap <<'EOF'
|
|
name=path/to/project
|
|
packages_extra=make
|
|
EOF
|
|
|
|
cat > path/to/project/.sloptrapignore <<'EOF'
|
|
.git/
|
|
secrets/
|
|
EOF
|
|
```
|
|
3. Run `./sloptrap path/to/project`. On the first invocation sloptrap:
|
|
- builds `path/to/project-sloptrap-image` if missing,
|
|
- 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 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.
|
|
|
|
## How It Works
|
|
|
|
- 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.
|
|
- 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`. 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 runtime environment is fixed to HOME/XDG variables pointing at `/codex`; manifest-controlled environment injection is disabled.
|
|
|
|
## `.sloptrap` Manifest Reference
|
|
|
|
The manifest is optional. When absent, sloptrap derives:
|
|
- `name = basename(project directory)`
|
|
- `packages_extra = ""` (none)
|
|
- `agent = "codex"` (default AI backend)
|
|
If a build is requested and no `.sloptrap` exists, sloptrap prompts to create one interactively.
|
|
|
|
Supported keys when the manifest is present:
|
|
|
|
| Key | Default | Notes |
|
|
| --- | --- | --- |
|
|
| `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 `+.-`. |
|
|
| `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. |
|
|
|
|
Values containing `$`, `` ` ``, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start.
|
|
|
|
### 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`
|
|
|
|
- Parsed using gitignore-style globbing with support for `!negation`.
|
|
- Entries must stay within the project root after resolving symlinks; attempts to reference `.`/`..`, absolute paths, or symlink escapes raise errors.
|
|
- Directory matches become `--mount type=tmpfs,target=/workspace/<path>`. File matches bind to empty files within `.sloptrap-ignores/session-<pid>/`.
|
|
- The helper directory is removed automatically on exit or during `./sloptrap <project> clean`.
|
|
|
|
## CLI Reference
|
|
|
|
```
|
|
./sloptrap [--dry-run] [--print-config] <code-directory> [target ...]
|
|
```
|
|
|
|
Options:
|
|
|
|
- `--dry-run` — print the container/engine commands that would run without executing them.
|
|
- `--print-config` — output the resolved manifest values, defaults, and ignore list.
|
|
- `-h, --help` — display usage.
|
|
- `--` — 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:
|
|
|
|
- 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.
|
|
- 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.
|
|
- Exit status mirrors the last target executed; errors in parsing or setup abort early with a message.
|
|
|
|
`--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
|
|
|
|
- `make regress` (or `tests/run_tests.sh`) runs `shellcheck` against `sloptrap` and then executes every scenario in `tests/run_tests.sh`, including the container build path check.
|
|
- The suite must pass cleanly; ShellCheck diagnostics or scenario regressions cause a non-zero exit and should be fixed before shipping changes.
|
|
|
|
## Built-in Targets
|
|
|
|
Targets are supplied after the code directory. When omitted, sloptrap defaults to `run`.
|
|
|
|
| Target | Description |
|
|
| --- | --- |
|
|
| `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`. |
|
|
| `rebuild` | Rebuild the image from scratch (`--no-cache`). |
|
|
| `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 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. Not supported for opencode. |
|
|
| `shell` | Launches `/bin/bash` inside the container for debugging. |
|
|
| `wizard` | Creates or updates `.sloptrap` interactively (no build); rerun `build` or `rebuild` afterward. |
|
|
| `stop` | Best-effort stop of the running container (if any). |
|
|
| `clean` | Removes `.sloptrap-ignores`, deletes the container/image, and stops the container if necessary. |
|
|
|
|
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.
|
|
|
|
## Execution Environment
|
|
|
|
- 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:
|
|
- **Codex**: project directory at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` at `/codex`; auth at `/codex/auth.json`.
|
|
- **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.
|
|
- Ignore filter: `.sloptrapignore` entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to the agent.
|
|
- 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
|
|
|
|
- **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` 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.
|
|
- **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 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.
|
|
- **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.
|
|
|
|
These constraints focus on limiting host data exposure to the Codex session while acknowledging that any material introduced into the context window may leave the environment through the upstream API.
|