15-Security
🔒
Security is mandatory, not a feature. This page is the canonical threat model and the binding ruleset for the entire system.
1. Trust boundaries
flowchart LR
User(["User (token bearer)"]) --- API["Laravel API"]
API --- Workers["Horizon workers"]
Workers --- Providers["AI providers (egress)"]
Workers --- WS["Workspace FS (sandboxed)"]
Workers --- Git["Remote Git host (egress)"]
API --- DB[("PostgreSQL")]
API --- Redis[("Redis")]
Workers --- S3[("Object store")]The only ingress is the Laravel API. All other components are internal.
2. Threats and mitigations
| Threat | Mitigation |
|---|---|
| Stolen API token | Sanctum tokens are scoped (abilities), expire, and are revocable. Login bumps tokens_version. APNs tokens are revoked on logout. |
| Token used from new IP | Optional per-user IP allowlist and login alerts. |
| Malicious prompt (prompt injection) | Tool surface is the only way to act. Tools enforce allowlist/blocklist. Provider output is never executed as shell. |
| Path traversal | WorkspaceService::resolve() rejects .. and absolute paths; only resolves under workspace.root/{project_slug}/. |
| Disk exhaustion | Per-project disk quota; soft warn at 80%, hard refuse at 100%. Old snapshot tarballs garbage-collected after N days. |
| Forkbomb / runaway command | Subprocess wall-clock + CPU time limits; cgroup memory limits in production; was_killed=true flagged. |
| Secret exfiltration via printing | SecretRedactionProcessor filters known secret patterns from any output before logging or persisting. |
| Secret leakage in docs/Git | Generated docs and committed content pass through the redaction processor. Pre-commit hook in the run branch refuses files containing OPENAI_API_KEY= / sk-. |
| Arbitrary repo URL fetch | Hostname allowlist + scheme allowlist. No credentials in URLs; deploy keys / GitHub App. |
| Force-push / history rewrite | Hard-blocked in GitService. |
| Cross-project contamination | Workspace lock per project + per-project FS root + per-project RAG project_id filter on every retrieval. |
| Replay of old events to clients | SSE includes Last-Event-ID; server validates the requester still owns the run. |
| Privilege escalation via settings | settings:write is a separate ability; secret fields require step-up auth (re-enter password). |
| SQL injection | Eloquent / parameterized queries everywhere; raw SQL only in migration bootstrap and audited. |
| Mass assignment | Models use $guarded = ['id'] • explicit fillable lists in form requests. |
3. Command execution rules
- Allowlist governs which commands can run. Anything not in the list is blocked.
- Blocklist is a kill-switch layer: even if accidentally added to the allowlist, blocklist matches refuse the command. Blocklist always wins.
- Destructive markers force a
pre_commandsnapshot.
- Commands run in the project workspace dir, with a clean env (
PATH,LANG,HOME) plus an injectedAGENT_RUN_ID.
- Per-command timeout from
agent_settings.max_command_time.
- Killed commands set
was_killed=true.
Hard-blocked commands
rm -rf /,rm -rf ~,rm -rf ../*, fork bombs.
mkfs,dd if=,parted,fdisk.
chmod /,chown /,chmod -R 777 /, recursive ownership changes on system paths.
cat .env,printenv,env(reading env wholesale).
git push,git push --force,git push --tags.
shutdown,reboot,systemctl.
curl http*,wget http*(unlessclaude_code.allow_web=true).
npm publish,composer publish,pip publish,pip install -e file://.
- Anything containing a redirect to
/etc/,/usr/,/var/,/root/.
4. File system rules
- Workspaces live under
workspace.root(e.g./var/lib/agent-workspaces/{project_slug}) owned by the app's service user.
- The service user has no sudo, no access to system paths.
- File operations are restricted to symlink-resolved paths under the workspace root. Symlinks pointing outside are rejected.
.env,.git/config, and other sensitive files cannot be read byread_file(returnsfile_blockedevent).
- Max file size for read/write controlled by
limits.max_file_size.
5. API security
- All endpoints require Sanctum token unless explicitly public (auth/token only).
- CORS: deny by default; allowlist for the iPhone app and the web console domain.
- HSTS, secure cookies, CSP for the web console.
- Rate limits per group (see
14 — API Endpoints).
- Request body size cap: 1 MB (32 MB for file upload endpoints, when introduced).
- Output is filtered by API resources; secret fields are never exposed.
- Idempotency keys for run-start endpoints (header
Idempotency-Key).
6. Audit logging
All critical actions append a row to agent_events (run-scoped) or a separate audit_log table (system-scoped, not introduced in v1, but planned). Critical actions include:
- Settings change (with key, before/after redacted).
- Provider enabled/disabled.
- Token created/revoked.
- Snapshot created/restored.
- Run approved/rejected.
- Push attempt (allowed or blocked).
7. Role-based abilities
- v1 ships a single-owner model with per-token abilities (above).
- v2 will introduce a role layer:
owner(everything),developer(start/control runs, commit, no push, no settings write),viewer(read only).
- Sanctum abilities are checked in form requests via
$request->user()->tokenCan('git:push').
8. Backup & recovery
- PostgreSQL: managed daily snapshots, point-in-time recovery 7 days minimum.
- Snapshot tarballs in S3 versioned for 30 days.
- Workspaces are reproducible (re-clone), so they are not backed up.
- Disaster recovery target: RPO 24 h, RTO 4 h.
9. Secrets management
- Secrets live in
agent_settings(encrypted) or environment variables that are mounted at boot only.
- Rotation procedure documented per provider (OpenAI, Anthropic) in
docs/SECURITY.md.
- Compromise drill: revoke at provider → update setting → emit
setting_changedevent → restart workers.
10. Compliance posture (informative)
- v1 is intended for single-owner / small-team use; not certified for SOC 2 / GDPR audits yet.
- Logs and events containing user prompts are considered sensitive and stored within the same tenancy as the project workspace.
- A future hardening track will introduce CMK encryption for
agent_events.payload_jsonand per-tenant KMS.