|
| 1 | +# Security |
| 2 | + |
| 3 | +## Threat Model |
| 4 | + |
| 5 | +TeaAgent is a governance-first agent harness that gives an LLM-controlled agent access to |
| 6 | +a workspace directory through registered tools. The primary threats are: |
| 7 | + |
| 8 | +1. **Model misbehavior** — the LLM generates tool calls that escape the workspace, |
| 9 | + execute dangerous shell commands, or exfiltrate data. |
| 10 | +2. **Untrusted MCP clients** — remote MCP clients that attempt unauthorized tool |
| 11 | + execution or credential theft. |
| 12 | +3. **Network attackers** — attackers on the same network that intercept MCP HTTP |
| 13 | + traffic or replay authenticated sessions. |
| 14 | +4. **Multi-tenant workspace collision** — concurrent agent runs on the same workspace |
| 15 | + root corrupting each other's state. |
| 16 | +5. **Prompt injection** — attacker-controlled content in workspace files influencing |
| 17 | + the agent's decision-making loop. |
| 18 | + |
| 19 | +TeaAgent assumes the LLM provider and local process boundary are trusted. It does |
| 20 | +**not** protect against a compromised Python runtime, operating system, or LLM provider |
| 21 | +infrastructure. |
| 22 | + |
| 23 | +## Permission Modes |
| 24 | + |
| 25 | +| Mode | Read | File Write | Shell Mutate | Approval Required | |
| 26 | +|-----------------------|------|------------|-------------|--------------------| |
| 27 | +| `read-only` | Yes | No | No | N/A | |
| 28 | +| `workspace-write` | Yes | Yes | No | N/A | |
| 29 | +| `prompt` | Yes | Conditional| Conditional | Human-in-the-loop | |
| 30 | +| `allow` | Yes | Yes | Yes | Session-scoped | |
| 31 | +| `danger-full-access` | Yes | Yes | Yes | None | |
| 32 | + |
| 33 | +Even in `danger-full-access`, the harness enforces: |
| 34 | +- Workspace path confinement (tools reject `../`, absolute paths, symlink escapes) |
| 35 | +- File size limits (`max_read_bytes`, `max_write_bytes`, `max_shell_output_bytes`) |
| 36 | +- Shell command size limits (`max_shell_command_bytes`) |
| 37 | +- Shell command timeout ceiling (`max_shell_timeout_seconds`) |
| 38 | +- Iteration and tool-call budgets |
| 39 | + |
| 40 | +## Shell Sandbox |
| 41 | + |
| 42 | +Shell commands are classified as **inspect** or **mutate** before execution: |
| 43 | + |
| 44 | +- **Inspect**: `pwd`, `ls`, `find`, `rg`, `grep`, `cat`, `head`, `tail`, `wc`, |
| 45 | + safe `git` subcommands (`status`, `diff`, `log`, `show`, `branch`, `grep`) |
| 46 | +- **Mutate**: everything else |
| 47 | + |
| 48 | +### Classification Algorithm |
| 49 | + |
| 50 | +The classifier (`workspace_tools.py:classify_shell_command_policy`) uses quote-aware |
| 51 | +scanning to detect unquoted shell operators (`>`, `<`, `|`, `&&`, `;`, `` ` ``, `$(`). |
| 52 | +Quoted operators (e.g., `git log --grep='>'`) are correctly classified as inspect |
| 53 | +because the shell treats them as string content, not operators. |
| 54 | + |
| 55 | +**Property tests** (`tests/test_workspace_tools.py:ShellClassifierPropertyTests`) |
| 56 | +verify that: |
| 57 | +- All inspect commands are classified as inspect |
| 58 | +- All mutate commands are classified as mutate |
| 59 | +- Quoted operators do not trigger mutate |
| 60 | +- Actual redirect/pipe/chain operators trigger mutate |
| 61 | +- Command substitution triggers mutate |
| 62 | +- Workspace-escape paths trigger mutate |
| 63 | + |
| 64 | +### Inspect Execution |
| 65 | + |
| 66 | +Inspect commands are executed via `shlex.split()` with `shell=False`, which: |
| 67 | +- Prevents shell metacharacter expansion |
| 68 | +- Prevents command chaining (the `;`, `&&`, `||` operators are rejected by the |
| 69 | + classifier *before* reaching execution) |
| 70 | +- Rejects workspace-escaping arguments (`/`, `~`, `..`) |
| 71 | + |
| 72 | +### Known Limitations |
| 73 | + |
| 74 | +- The classifier uses a heuristic approach. An adversarial prompt may discover edge |
| 75 | + cases. Report findings as described below. |
| 76 | +- `shell_arg_escapes_workspace` only checks literal path prefixes; it does not |
| 77 | + expand environment variables or glob patterns. |
| 78 | +- Commands that match the inspect allowlist but spawn subprocesses (e.g., `git` |
| 79 | + hooks, custom pager configurations) can execute arbitrary code. |
| 80 | + |
| 81 | +## MCP HTTP Security |
| 82 | + |
| 83 | +### Bind Enforcement |
| 84 | + |
| 85 | +The MCP HTTP server enforces authentication at two layers: |
| 86 | + |
| 87 | +1. **CLI layer** (`cli/__init__.py:mcp_serve_command`): refuses to start on a |
| 88 | + non-loopback host (`0.0.0.0`, `::`, external IPs) unless `--auth-token` or |
| 89 | + `--oauth-issuer` is provided. |
| 90 | +2. **Library layer** (`mcp_http.py:build_mcp_http_server`): raises `ValueError` when |
| 91 | + constructed with a non-loopback host and no `auth_token` or `oauth_server`. |
| 92 | + |
| 93 | +### Authentication Options |
| 94 | + |
| 95 | +- **Bearer token**: static shared secret via `--auth-token`. Every request must |
| 96 | + include `Authorization: Bearer <token>`. |
| 97 | +- **OAuth 2.1 with DPoP**: proof-of-possession token binding via |
| 98 | + `--oauth-issuer` + `--oauth-signing-key`. Access tokens are bound to the |
| 99 | + client's DPoP key, preventing token replay by network observers. |
| 100 | + |
| 101 | +### Origin Control |
| 102 | + |
| 103 | +Pass `--allowed-origin` (repeatable) to restrict browser-originated requests. |
| 104 | +Without it, all origins are accepted. |
| 105 | + |
| 106 | +### Transport |
| 107 | + |
| 108 | +The MCP HTTP server does not support TLS natively. When serving on non-loopback |
| 109 | +hosts, place a reverse proxy (nginx, Caddy, Cloudflare Tunnel) with TLS termination |
| 110 | +in front of the server. |
| 111 | + |
| 112 | +## Code Mode |
| 113 | + |
| 114 | +Code Mode executes LLM-generated Python code in a **child process** sandbox with: |
| 115 | + |
| 116 | +- AST allow-list validation (limited node types, restricted builtins) |
| 117 | +- `RLIMIT_CPU` (CPU time limit) |
| 118 | +- Wall-clock timeout (process termination) |
| 119 | +- Best-effort `RLIMIT_AS` (memory limit; silently no-op on macOS) |
| 120 | + |
| 121 | +Code Mode is **not** a production sandbox: |
| 122 | +- No seccomp, cgroups, namespaces, or VM-level isolation |
| 123 | +- `exec()` runs inside the same Python interpreter |
| 124 | +- Memory limits are advisory on macOS |
| 125 | + |
| 126 | +For production use, defer Code Mode code execution to a container, V8 isolate, or |
| 127 | +managed execution service. |
| 128 | + |
| 129 | +## File System Access |
| 130 | + |
| 131 | +All workspace tools enforce path confinement through `resolve_workspace_path()`: |
| 132 | +- Paths are resolved relative to the workspace root |
| 133 | +- `../` escapes, absolute paths, and symlink escapes are rejected |
| 134 | +- `.git` directories are excluded from `list_files` and `search_text` |
| 135 | + |
| 136 | +Write tools (`workspace_write_file`, `workspace_apply_patch`, `workspace_edit_at_hash`) |
| 137 | +enforce `max_write_bytes` before any write occurs. |
| 138 | + |
| 139 | +## Edit Safety |
| 140 | + |
| 141 | +- `workspace_apply_patch` requires the `old` text to appear **exactly once** in the |
| 142 | + target file. If it appears multiple times, the edit is rejected — the caller must |
| 143 | + provide more context or use `workspace_edit_at_hash`. |
| 144 | +- `workspace_edit_at_hash` uses CRC32 line anchors (`LINE#HASH|content`). If the |
| 145 | + hash of the target line has changed (stale read), the edit is rejected. |
| 146 | + |
| 147 | +## Audit Trail |
| 148 | + |
| 149 | +Every tool call, approval decision, iteration, and final result is recorded in the |
| 150 | +audit log with: |
| 151 | +- Per-run JSONL persistence (file mode `0600`) |
| 152 | +- Argument redaction for sensitive keys (API keys, passwords, tokens, secrets) |
| 153 | +- Result content redaction for stdout/stderr |
| 154 | +- Truncation of long strings at 20,000 characters |
| 155 | + |
| 156 | +Audit logs are append-only. TeaAgent does not rotate or expire audit files — set up |
| 157 | +external log rotation for long-running deployments. |
| 158 | + |
| 159 | +## Credential Handling |
| 160 | + |
| 161 | +- LLM API keys are read from environment variables only; never from files or |
| 162 | + command-line arguments |
| 163 | +- MCP `--auth-token` and `--oauth-signing-key` are command-line arguments (visible |
| 164 | + in `ps`). Prefer environment variables or a secrets manager for production |
| 165 | +- Audit logs redact keys matching `api_key`, `authorization`, `credential`, |
| 166 | + `password`, `secret`, `token` in any casing |
| 167 | + |
| 168 | +## LLM Provider Resilience |
| 169 | + |
| 170 | +- The LLM adapter layer includes configurable exponential-backoff retry |
| 171 | + (`LLMRetryConfig`) for transient errors (HTTP 429, 5xx, connection failures). |
| 172 | +- Cost budget pre-flight (`RunBudget.check_cost_preflight`) estimates the maximum |
| 173 | + possible cost of an LLM call before spending money, rejecting calls that would |
| 174 | + exceed the budget. |
| 175 | +- Every run has hard limits on iterations and tool calls, enforced by `AgentRunner`. |
| 176 | + |
| 177 | +## Concurrent Access |
| 178 | + |
| 179 | +`RunStore`, `UltraworkStore`, and `MemoryCatalog` use plain JSONL file append |
| 180 | +without file locking. Concurrent agent runs on the same workspace root may produce |
| 181 | +interleaved log lines. For production multi-worker scenarios: |
| 182 | +- Use separate workspace roots per worker |
| 183 | +- Or replace the JSONL backend with a transactional store (SQLite, PostgreSQL) |
| 184 | + |
| 185 | +## Reporting a Vulnerability |
| 186 | + |
| 187 | +Report security vulnerabilities to the project maintainers. Do not file public |
| 188 | +issues for security-sensitive findings. |
| 189 | + |
| 190 | +**Scope**: vulnerabilities in TeaAgent's harness logic, tool governance, sandbox |
| 191 | +escape vectors, or authentication bypasses. |
| 192 | + |
| 193 | +**Out of scope**: vulnerabilities in LLM providers, the Python standard library, |
| 194 | +the operating system, or upstream dependencies. |
0 commit comments