This commit fixes several issues discovered during opencode agent support implementation, ensuring complete functionality and passing all regression tests. ## Core Implementation Fixes ### 1. Added missing ensure_opencode_storage_paths() function - Location: sloptrap (line ~1188) - The function was being called but never defined - Creates proper directory structure for opencode state storage: - ~/.codex/sloptrap/opencode (home directory) - ~/.codex/sloptrap/opencode/state (state bucket) - ~/.codex/sloptrap/opencode/<project-state> (project-specific state) - Mirrors the existing ensure_codex_storage_paths() implementation ### 2. Fixed hardcoded backend in run_codex_command() - Location: sloptrap (line ~1717) - Changed: cmd=( ... "opencode") - To: cmd=( ... "") - This ensures the correct backend (codex or opencode) is invoked - Previously hardcoded "opencode" would always be used regardless of BACKEND variable ### 3. Made Dockerfile generation backend-aware - Location: sloptrap (write_embedded_dockerfile function) - Added conditional generation based on BACKEND variable - Opencode Dockerfile: - Uses ARG OPENCODE_BIN=opencode - Copies opencode binary to /usr/local/bin/opencode - Sets entrypoint to /usr/local/bin/opencode - Codex Dockerfile (unchanged): - Uses ARG CODEX_BIN=codex - Copies codex binary to /usr/local/bin/codex - Sets entrypoint to /usr/local/bin/codex ### 4. Fixed wizard agent validation - Location: sloptrap (line ~876) - Added: [[ -n $value ]] || value=$default_agent - Previously, empty input would fail the case statement validation - Now correctly uses the default agent value (codex) when input is empty ## Test Fixes ### 1. Fixed wizard input handling - Changed from here-string (<<<) to printf piping - Here-strings don't work correctly with multi-line input - printf preserves all newlines correctly for wizard prompts ### 2. Updated wizard test inputs - run_wizard_create_manifest: printf '\n\n\nfalse\n\n' - Line 1-2: empty (name, packages_extra) - Line 3: empty (agent -> uses default codex) - Line 4: false (allow_host_network) - run_wizard_existing_defaults: printf '\nmake git\n\n\nfalse\n\n' - Same structure but with make git for packages_extra - run_wizard_build_trigger: printf '\n\n\nfalse\n\n' - Same structure for new wizard manifest ### 3. Fixed run_wizard_existing_defaults - Added initial manifest creation before wizard update - Previously expected manifest to exist but didn't create it - Now creates: name=custom-wizard, packages_extra=make git, capabilities=apt-install, allow_host_network=true ### 4. Fixed run_wizard_build_trigger - Added explicit build invocation after wizard - Wizard only creates manifest, doesn't trigger build - Now runs: sloptrap wizard then sloptrap build - Verifies build is invoked with FAKE PODMAN: build in log ## Documentation Updates ### README.md enhancements - Added agent parameter documentation - Added opencode_server and opencode_model parameters - Added AI Backends section explaining codex vs opencode - Removed deprecated --trust-capabilities option - Added environment variable override documentation - Clarified backend-specific state locations ## Test Results All 19 regression tests now pass: - symlink_escape ✓ - manifest_injection ✓ - helper_symlink ✓ - secret_mask ✓ - resume_target ✓ - runtime_context_prompt ✓ - sh_reexec ✓ - resume_omits_runtime_context ✓ - auth_file_mount ✓ - codex_home_override ✓ - project_state_isolation ✓ - auto_login_empty_auth ✓ - codex_symlink_home ✓ - root_directory_project ✓ - wizard_create_manifest ✓ - wizard_existing_defaults ✓ - wizard_build_trigger ✓ ## Code Quality - Shellcheck: No warnings or errors - All tests passing - No functional regressions introduced - Maintains backward compatibility with codex backend ## Files Modified - Dockerfile.sloptrap: Backend-aware Dockerfile generation - README.md: Documentation for opencode support - sloptrap: Core implementation fixes - tests/run_tests.sh: Test input and invocation fixes - tests/wizard_*.sloptrap: Reverted to original state (test artifacts) ## Verification Run tests with: bash tests/run_tests.sh Run shellcheck with: shellcheck sloptrap
164 lines
12 KiB
Markdown
164 lines
12 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 Codex binary hash,
|
|
- creates `${HOME}/.codex`, prepares a per-project state directory, and runs `login` if `${HOME}/.codex/auth.json` is missing or empty.
|
|
|
|
> 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 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`.
|
|
- `.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`; 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 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:11434` | OpenAI-compatible server URL (opencode only). Supports llama.cpp, Ollama, vLLM, etc. |
|
|
| `opencode_model` | `llama3` | Model name on the server (opencode only). |
|
|
| `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 state stored in `~/.opencode/`. Connects to any OpenAI-compatible inference server (llama.cpp, Ollama, vLLM, etc.). No authentication required for self-hosted models; API keys supported via manifest if needed.
|
|
|
|
### `.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_CONTAINER_ENGINE` — override container engine auto-detection
|
|
|
|
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 Codex (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 Codex using sloptrap's built-in runtime flags. |
|
|
| `resume <session-id>` | Continues a Codex session by running `codex resume <session-id>` inside the container (builds if needed). |
|
|
| `login` | Starts Codex in login mode to bootstrap shared `${HOME}/.codex/auth.json` credentials. |
|
|
| `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}/.opencode/sloptrap/state/<project-hash>` at `/codex/state/opencode`; state at `/codex/state`.
|
|
- 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`.
|
|
- 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`, 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.
|
|
- **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.
|
|
- **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.
|