# Security Policy ## Reporting a vulnerability **Please do not open a public issue for security vulnerabilities.** Report privately via GitHub Security Advisories — open the repository's **[Security → Report a vulnerability](https://github.com/daonhan/ralph/security/advisories/new)** form — or email **daonhan@gmail.com**. Include a description, reproduction steps, and the affected version. You'll get an acknowledgement within a few days and a fix or mitigation plan. ## Supported versions Only the latest published minor of each package (`@daonhan/ralph`, `@daonhan/ralph-core`) and the latest `ralph-sandbox` image are supported with security fixes. Pin by digest for the image and by exact version for the packages if you need reproducibility. ## Threat model — read before running Ralph is an **autonomous agent harness**. By design it runs the selected coding agent without interactive approval inside the sandbox container: - Claude uses `--permission-mode bypassPermissions`. - Codex uses `--dangerously-bypass-approvals-and-sandbox`. Treat everything Ralph ingests as instructions the selected agent may execute. The trust boundary is: - **Only run Ralph against repositories, plans/PRDs, and GitHub issues you trust.** The plan/PRD string (`{{ INPUTS }}`), issue bodies/comments (`ralph-ghafk`), and commit messages are all fed to the selected agent running without interactive approval. `ralph-ghafk` in particular pulls **public GitHub issues** — text authored by strangers — into that agent. Do not point it at a repo whose open issues you have not vetted. - **The host Docker socket is bind-mounted by default**, which grants the sandbox **root-equivalent access to the host Docker daemon** (it can start a sibling container that bind-mounts the host filesystem as root). This is on so Testcontainers inside the sandbox can spawn sibling containers. **Disable it with `RALPH_DOCKER_SOCK=0`** when running anything you do not fully trust. Disabling it removes host-Docker control, but persistent host-write exposure still includes the bind-mounted workspace and, for Claude, the read-write credential store. `~/.config/gh` remains read-only. - **Selected-provider host credentials are bind-mounted.** Claude mounts `~/.claude` and `~/.claude.json` read-write; the agent can read or overwrite those reusable credentials. Codex mounts `~/.codex` read-only at `/mnt/codex-creds` and copies `auth.json` (plus `config.toml` and `AGENTS.md` when present) into a container-local `CODEX_HOME`; the agent cannot modify the host Codex store, but `auth.json` remains a readable reusable secret. `~/.config/gh` is mounted read-only. Isolated Codex configuration prevents personal config, MCP, and hook loading; it does not conceal `~/.codex/auth.json` from the process. - **Ralph's own shipped skills are mounted read-only.** The `templates/skills/` directory of the installed `@daonhan/ralph-core` (today one skill, `ralph-tdd`) is bind-mounted into every stage at a container-local path — Claude `/home/agent/ralph-skills/.claude/skills`, Codex `/home/agent/.agents/skills`. It holds only Ralph's own shipped files, contains no secrets, and the agent cannot modify it or the host copy. No host directory is created by the mount. - **The Claude sandbox fetches its CLI from the network at run time.** Every Claude stage runs `claude update` before its own command, downloading the Claude Code binary with the same trust as the `curl … install.sh` the image build runs, and caches it in the host-wide named Docker volume `ralph-claude-home` (shared by every workspace on the host, mounted at `/home/agent/.local`). Set `RALPH_CLAUDE_UPDATE=0` to pin the stage to the image's baked copy and drop the volume mount. - **The Codex sandbox does the same.** Every Codex stage runs `codex update` before its own command, fetching from the npm registry with the same trust as the build-time `npm install --global "@openai/codex@${CODEX_VERSION}"`, and caches it in the host-wide named Docker volume `ralph-codex-cli` (mounted at the agent-owned npm prefix `/home/agent/.npm-global`). Set `RALPH_CODEX_UPDATE=0` to pin the stage to the copy the image ships and drop the volume mount. - **The run event log is advisory, not a security boundary.** Each run's `.ralph/history/.jsonl` sits in the bind-mounted workspace the agent can write to, so the agent can forge, edit or delete records. The one-run-per-workspace check and any supervisor reading the log trust what it says; a forged live log can refuse relaunches (exit 75) until it is removed, and a deleted one lets a second run start. The log repeats `inputs` (the plan/PRD string) and every stage's agent output, so it carries the same sensitivity as the Markdown history beside it. ### Reducing blast radius - Set `RALPH_DOCKER_SOCK=0` unless you specifically need Testcontainers. - Run Ralph on a disposable VM / dedicated machine, not your primary workstation, for untrusted inputs. - Review open issues before running `ralph-ghafk`. - Use a scoped, short-lived `gh` token. ## Template authoring (contributors) The prompt-template renderer (`render.ts`) executes the **command bodies** of the `` !`cmd` ``, `` !?`cmd` ``, and `@spill` tags on the **host shell**. The shipped templates only ever use **static** command strings, and `{{ INPUTS }}` is substituted last (written to a file the agent reads inside the container, never re-shelled on the host) — so there is no host command-injection vector today. **This invariant must be preserved:** never interpolate runtime or untrusted data into a tag command body. Doing so would create direct host RCE.