| title | Security policy | ||
|---|---|---|---|
| description | Supported versions, vulnerability reporting process, Ralph's threat model, and defensive mechanisms. | ||
| audience |
|
||
| diataxis | reference | ||
| last_reviewed | 2026-04-23 |
Ralph is an autonomous agent that invokes the Claude Code CLI, writes files, runs shell commands, and talks to external services. That makes security posture a first-class concern. This document covers:
- Supported versions
- Reporting a vulnerability
- Threat model
- Defensive mechanisms
- Automated scanning
- Security hardening checklist
- Known tradeoffs
| Version | Supported |
|---|---|
| 2.8.x (latest) | Yes — full support |
| 2.7.x | Yes — security fixes only |
| 2.6.x | Critical security fixes only |
| ≤ 2.5.x | End of life |
Ralph follows a rolling-support policy: only the last two minor lines receive patches. Upgrade regularly.
Do not open a public GitHub issue for security vulnerabilities.
Use GitHub's private vulnerability reporting on wtthornton/ralph-claude-code. Include:
- A description of the vulnerability
- Steps to reproduce (minimal reproducer is ideal)
- Potential impact (data exfiltration, code execution, DoS, etc.)
- Suggested fix if you have one
| Stage | SLA |
|---|---|
| Acknowledgment | 48 hours |
| Initial assessment (severity, affected versions) | 1 week |
| Fix or mitigation for critical issues | 2 weeks |
| Coordinated disclosure | Negotiated with reporter |
We will credit you in the security advisory unless you request otherwise.
Ralph runs as the operator's user with broad filesystem access. The primary adversaries to consider:
| Threat | Mitigation |
|---|---|
Malicious fix_plan.md / PROMPT.md content |
File-protection hooks, ALLOWED_TOOLS whitelist, hook-based sanitization |
| Prompt injection via issue trackers, PRDs, web content | validate-command.sh blocks destructive shell commands regardless of source |
| Shell injection via task titles or commit messages | jq --arg everywhere; historical issues closed by TAP-622, TAP-633, TAP-641, TAP-643 |
| Ralph loop editing its own control plane | protect-ralph-files.sh blocks writes to .ralph/ and .claude/ (TAP-623) |
| Credential leakage via logs or tracing | Secret sanitization in lib/tracing.sh; secrets.env never logged |
| Untrusted code execution from cloned repos | ralph --sandbox runs the loop in Docker with --network none and gVisor support |
| Concurrent-instance corruption | flock on .ralph/.ralph.lock |
| API key exfiltration | Keys loaded from ~/.ralph/secrets.env or env vars; never written to state files |
- Anthropic API compromise. Ralph trusts API responses. A compromised API would let an attacker run code via tool calls.
- Local-user privilege escalation. Ralph runs as the invoking user; it does not attempt privilege separation.
- Side-channel attacks on the host (timing, cache). Ralph is not hardened against them.
- Supply-chain attacks on dependencies. We run
pip-auditand Dependabot, but can't guarantee upstream integrity.
┌─────────────────────────────────────────────────────────┐
│ Operator (fully trusted) │
│ ─ starts Ralph, writes PROMPT.md, approves PRs │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Ralph loop (partially trusted) │
│ ─ reads PROMPT.md, fix_plan.md │
│ ─ cannot modify .ralph/ or .claude/ (hook-enforced) │
│ ─ bound by ALLOWED_TOOLS whitelist │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Claude Code CLI + Anthropic API (external) │
│ ─ runs tool calls the loop permits │
│ ─ subject to Anthropic's content filtering │
└─────────────────────────────────────────────────────────┘
Every tool Claude tries to use must match the ALLOWED_TOOLS whitelist in .ralphrc. Granular bash patterns are supported:
ALLOWED_TOOLS="Write,Read,Edit,Bash(git add *),Bash(git commit *),Bash(grep *),Bash(find *),Bash(pytest)"Claude Code enforces this at the tool layer; a denial generates an explicit error Ralph logs and the circuit breaker tracks.
protect-ralph-files.shblocksWrite,Edit, andBashcommands that modify anything under.ralph/or.claude/. Prevents the loop from editing its own control plane.validate-command.shblocks destructive git commands (reset --hardto unknown refs,push --forcetomain,rm -rfwith path traversal, etc.). TAP-624 closed multiple whitelist bypasses.
The Stop hook is the single sanitization point for Claude's output. The loop never sees raw output — it reads the structured status.json written by the hook. This means any prompt injection that tries to manipulate RALPH_STATUS still goes through the hook's parser, which uses jq --arg for safe JSON construction.
TAPPS_BRAIN_AUTH_TOKENand similar credentials are loaded from~/.ralph/secrets.env(gitignored) or environment variables. Linear access is via the Linear MCP plugin's OAuth flow — no harness-side API key.- Never written to
.ralph/status.json, metrics files, or traces. - Webhooks are built with
jq --arg— sed-based escape was replaced in TAP-659 after an injection finding.
ralph --sandbox runs the entire loop inside a Docker container with:
- Rootless Docker detection (preferred) or rootful fallback
--network noneby default (override withRALPH_SANDBOX_NETWORK)- gVisor runtime support (
RALPH_SANDBOX_RUNTIME=runsc) - Resource limits (CPU, memory, pids)
- Fresh filesystem each invocation
Use this when running Ralph on code you haven't reviewed.
flock on .ralph/.ralph.lock ensures only one Ralph process per project. State corruption from parallel loops is a class of bug this eliminates (LOCK-1).
Every counter or state-file write goes through atomic_write: temp file → fsync → mv -f. A SIGTERM between truncate and write cannot leave a zero-byte counter (TAP-535).
Touch .ralph/.killswitch or send SIGTERM to halt cleanly. See KILLSWITCH.md for cleanup guarantees.
Circuit breaker history and event logs are capped to prevent unbounded growth (TAP-658). Long-running sessions can't OOM the host via state-file accumulation.
CI runs the following on every push to main and every pull request:
| Tool | Scope | Enforcement |
|---|---|---|
| Bandit | Python static analysis (SDK) | Blocking |
| Secret scanning | API keys, tokens in diffs | Blocking |
pip-audit |
Python dependency CVEs | Blocking (high/critical) |
| CodeQL | Semantic code analysis | Blocking |
| Dependabot | Dependency updates with grouped security patches | Auto-PR |
shellcheck |
Bash lint (ralph_loop.sh + lib/) | Blocking on errors |
Results are attached to the GitHub Security tab.
For operators running Ralph in a sensitive environment:
- Keep Ralph on the latest minor version (
ralph-upgrade) - Set
ALLOWED_TOOLSto the minimum your workflow needs - Keep
.ralph/on an encrypted filesystem if tasks contain sensitive data - Store API keys in
~/.ralph/secrets.env, not.ralphrc(which may end up in backups) - Run
ralph --sandboxwhen working on unreviewed code - Set
RALPH_SANDBOX_NETWORK=none(the default) unless you need egress - Review
.ralph/logs/periodically; rotate or archive sensitive content - Enable
CB_AUTO_RESET=falsein production so a tripped breaker requires manual review - Pin Claude CLI version in
.ralphrc(CLAUDE_CODE_CMD) to prevent unexpected CLI updates - Use branch protection on
main; require CI + code review - Review
FAILSAFE.mddegradation hierarchy and confirm your monitoring catches each level
Ralph makes deliberate speed-vs-safety tradeoffs. Operators should know them:
| Tradeoff | Why |
|---|---|
PostToolUse hooks disabled by default (v1.8.4+) |
Per-tool-call overhead; PreToolUse catches problems before they land |
bypassPermissions mode for main agent |
Throughput win; ALLOWED_TOOLS still enforced by Claude Code |
effort: medium (not high) for main agent |
Cost win; architect handles LARGE tasks at higher effort |
| MCP grandchildren only killed if orphaned | Avoids killing editor MCPs belonging to Cursor / VS Code |
tapps-brain memory writes are fire-and-forget |
Deterministic brain-client won't block the loop on brain outages |
| Hook failures are advisory (don't halt loop) | A broken hook shouldn't stop productive work — the loop uses last-known-good status.json |
If any of these are unacceptable for your environment, file an issue describing the use case and we'll discuss tightening the default.
- FAILURE.md — failure modes and response procedures
- FAILSAFE.md — safe-default behaviors
- KILLSWITCH.md — emergency stop
- docs/ARCHITECTURE.md — defensive mechanisms in architectural context
- CLAUDE.md — invariants and historical security fixes