Skip to content

Commit 0449a8a

Browse files
anandgupta42claude
andauthored
fix: harden path sandboxing with symlink protection, safe defaults, and sensitive file guards (#209)
* fix: harden path sandboxing with symlink protection, safe defaults, and sensitive file guards - Add `Filesystem.containsReal()` with `realpathSync` to prevent symlink escape attacks (same class of bug as Codex GHSA-w5fx-fh39-j5rw and Claude Code CVE-2025-54794) - Add `isAbsolute(rel)` check to `Filesystem.contains()` for Windows cross-drive bypass - Update `Instance.containsPath()` to use symlink-aware `containsReal()` - Add safe permission defaults: deny `rm -rf`, `git push --force`, `git reset --hard`, `DROP DATABASE`, `TRUNCATE` out of the box - Add `Protected.isSensitiveWrite()` to detect writes to `.git/`, `.ssh/`, `.aws/`, `.env*`, credential files even inside the project boundary - Add `assertSensitiveWrite()` guard to write, edit, and apply_patch tools - Remove resolved TODO comments from `file/index.ts` - Update SECURITY.md, permissions docs, and security FAQ with practical guidance - Add 94 tests including 62 e2e tests covering symlink attacks, path traversal, sensitive file detection, and combined attack scenarios Closes #202 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address code review findings — rule ordering bug, cross-platform paths, TOCTOU docs - Fix critical bug: bash deny defaults had `"*": "ask"` LAST which overrode deny rules due to last-match-wins semantics. Moved `"*": "ask"` to first position so deny rules take precedence. - Fix all doc examples with same ordering bug (security-faq.md, permissions.md) - Fix `isSensitiveWrite` to use regex split `/[/\\]/` for cross-platform path handling - Allow per-path "Always" approval for sensitive file writes (reduces approval fatigue) - Document TOCTOU limitation in `containsReal` JSDoc - Add doc clarification about last-match-wins rule ordering with examples - Add tests: bash deny defaults evaluation, user override merge, Windows backslash paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address multi-model review consensus — movePath guard, case-insensitive matching, expanded patterns Fixes from consensus across GPT 5.2, Kimi K2.5, MiniMax M2.5, and GLM-5 reviews: - Add `assertSensitiveWrite(ctx, movePath)` for move destinations in `apply_patch` (CRITICAL: 3 models flagged that moves to `.ssh/`, `.env` bypassed sensitive check) - Add case-insensitive matching on macOS/Windows for sensitive dirs and files (`.GIT/config`, `.SSH/id_rsa` now correctly detected on case-insensitive FS) - Expand `SENSITIVE_FILES` with `.htpasswd`, `.pgpass` - Add `SENSITIVE_EXTENSIONS` for private keys: `.pem`, `.key`, `.p12`, `.pfx` - Add tests: case-insensitive matching, certificate extensions, credential files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: critical symlink/../ escape via realpathSync lexical normalization divergence Gemini 3.1 Pro found that `realpathSync` and the OS kernel disagree on `symlink/../file.txt`: - `realpathSync("project/link/..")` → `project/` (lexical normalization) - `writeFile("project/link/../f")` → writes to parent of symlink TARGET (kernel) This means `containsReal` would approve a write that the OS places OUTSIDE the project boundary. The fix rejects any unresolved path containing `..` segments, since their behavior through symlinks is fundamentally unpredictable at the application level. Also adds `.github` to `SENSITIVE_DIRS` (workflow injection vector). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: UX evaluation — soften bash defaults, expand FAQ, remove .github from sensitive dirs UX impact evaluation of each change: 1. **Bash defaults softened**: Changed destructive shell/git commands from `deny` (blocked silently) to `ask` (prompted). `rm -rf ./build` and `git push --force` after rebase are legitimate workflows — blocking them without a prompt is poor UX. Database DDL (`DROP DATABASE`, `TRUNCATE`) stays `deny` since it's almost never intentional in agent context. 2. **Removed `.github` from sensitive dirs**: Editing CI/CD workflows is a core use case. Prompting on every workflow edit would cause severe approval fatigue. 3. **Expanded FAQ**: Added "Why am I being prompted to edit .env files?" with table of protected patterns and guidance on "Allow always". Added "What commands are blocked or prompted by default?" with clear table showing which commands prompt vs block. Reordered best practices to lead with "work on a branch" (most effective, least friction). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Sentry review — dedicated sensitive_write permission, sticky DDL deny rules Fixes two issues flagged by Sentry automated review: 1. `assertSensitiveWrite` now uses `permission: "sensitive_write"` instead of `"edit"`, preventing agents with `edit: "allow"` from silently bypassing sensitive file prompts for `.env`, `.ssh/`, `.aws/`, etc. 2. Database DDL deny rules (`DROP DATABASE`, `DROP SCHEMA`, `TRUNCATE`) are now merged as a `safetyDenials` layer AFTER user/agent configs via `userWithSafety`. This ensures wildcard `bash: "allow"` in agent configs cannot override these denials (last-match-wins). Users who need to override must use specific patterns like `"DROP DATABASE test_db": "allow"`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: remove debug scripts and temporary files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: CI failures — update agent test for bash ask default, fix marker mismatches - Update `build agent has correct default properties` test: bash now defaults to "ask" (was "allow") per safety defaults - Fix `config/config.ts` marker mismatch: add missing `altimate_change end` after auto-enhance prompt config (9 starts vs 8 ends → 9/9) - Fix `tool/skill.ts` marker mismatch: remove orphaned `altimate_change end` at EOF (6 starts vs 7 ends → 6/6) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3ba6021 commit 0449a8a

16 files changed

Lines changed: 1261 additions & 47 deletions

File tree

SECURITY.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,29 @@ submit one that will be an automatic ban from the project.
1212

1313
Altimate Code is an AI-powered data engineering coding assistant that runs locally on your machine. It provides an agent system with access to powerful tools including shell execution, file operations, and web access.
1414

15-
### No Sandbox
15+
### Permission System
1616

17-
Altimate Code does **not** sandbox the agent. The permission system exists as a UX feature to help users stay aware of what actions the agent is taking - it prompts for confirmation before executing commands, writing files, etc. However, it is not designed to provide security isolation.
17+
Altimate Code includes a permission system that prompts for confirmation before the agent executes commands, writes files, or accesses resources outside your project. You can configure each tool as `"allow"`, `"ask"`, or `"deny"` — and use pattern-based rules to fine-tune behavior (e.g., allow `dbt run` but deny `rm *`).
1818

19-
If you need true isolation, run Altimate Code inside a Docker container or VM.
19+
The permission system is designed to keep you informed and in control of what the agent does. It includes:
20+
21+
- **Per-tool and per-pattern controls** with wildcard matching
22+
- **Per-agent permission overrides** (e.g., restrict `analyst` to read-only)
23+
- **External directory detection** that prompts when the agent accesses files outside your project
24+
- **Path traversal protection** that blocks attempts to escape the project directory
25+
- **Doom loop detection** that alerts you when the agent repeats failed actions
26+
27+
However, the permission system operates at the application level. It does not provide OS-level sandboxing — the process runs with your user permissions. For high-security environments or when working with sensitive production systems, we recommend running Altimate Code inside a Docker container or VM for additional isolation.
2028

2129
### Server Mode
2230

23-
Server mode is opt-in only. When enabled, set `OPENCODE_SERVER_PASSWORD` to require HTTP Basic Auth. Without this, the server runs unauthenticated (with a warning). It is the end user's responsibility to secure the server - any functionality it provides is not a vulnerability.
31+
Server mode is opt-in only. When enabled, set `OPENCODE_SERVER_PASSWORD` to require HTTP Basic Auth. Without this, the server runs unauthenticated (with a warning). It is the end user's responsibility to secure the server any functionality it provides is not a vulnerability.
2432

2533
### Out of Scope
2634

2735
| Category | Rationale |
2836
| ------------------------------- | ----------------------------------------------------------------------- |
2937
| **Server access when opted-in** | If you enable server mode, API access is expected behavior |
30-
| **Sandbox escapes** | The permission system is not a sandbox (see above) |
3138
| **LLM provider data handling** | Data sent to your configured LLM provider is governed by their policies |
3239
| **MCP server behavior** | External MCP servers you configure are outside our trust boundary |
3340
| **Malicious config files** | Users control their own config; modifying it is not an attack vector |

docs/docs/configure/permissions.md

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,20 @@ For tools that accept arguments (like `bash`), use pattern matching:
3838
{
3939
"permission": {
4040
"bash": {
41+
"*": "ask",
4142
"dbt *": "allow",
4243
"git status": "allow",
4344
"git diff *": "allow",
4445
"rm *": "deny",
45-
"DROP *": "deny",
46-
"*": "ask"
46+
"DROP *": "deny"
4747
}
4848
}
4949
}
5050
```
5151

52-
Patterns are matched in order -- first match wins. Use `*` as a wildcard.
52+
Patterns are matched in order — **last matching rule wins**. Use `*` as a wildcard. Place your catch-all `"*"` rule first and more specific rules after it.
53+
54+
For example, with `"*": "ask"` first and `"rm *": "deny"` after it, all `rm` commands are denied while everything else prompts. If you put `"*": "ask"` last, it would override the deny rule.
5355

5456
## Per-Agent Permissions
5557

@@ -104,3 +106,125 @@ Set permissions via environment variable:
104106
export ALTIMATE_CLI_PERMISSION='{"bash":"deny","write":"deny"}'
105107
altimate
106108
```
109+
110+
## Recommended Configurations
111+
112+
### Data Engineering (Default — Balanced)
113+
114+
A good starting point for most data engineering workflows. Allows safe read operations, prompts for writes and commands:
115+
116+
```json
117+
{
118+
"permission": {
119+
"read": "allow",
120+
"glob": "allow",
121+
"grep": "allow",
122+
"list": "allow",
123+
"edit": "ask",
124+
"write": "ask",
125+
"bash": {
126+
"*": "ask",
127+
"dbt *": "allow",
128+
"git status": "allow",
129+
"git diff *": "allow",
130+
"git log *": "allow",
131+
"ls *": "allow",
132+
"cat *": "allow",
133+
"rm *": "deny",
134+
"DROP *": "deny",
135+
"DELETE *": "deny",
136+
"TRUNCATE *": "deny"
137+
},
138+
"external_directory": "ask"
139+
}
140+
}
141+
```
142+
143+
### Strict (Production-Adjacent Work)
144+
145+
When working near production systems. Blocks destructive operations entirely and requires confirmation for everything else:
146+
147+
```json
148+
{
149+
"permission": {
150+
"read": "allow",
151+
"glob": "allow",
152+
"grep": "allow",
153+
"list": "allow",
154+
"edit": "ask",
155+
"write": "ask",
156+
"bash": {
157+
"*": "ask",
158+
"dbt *": "ask",
159+
"git status": "allow",
160+
"rm *": "deny",
161+
"DROP *": "deny",
162+
"DELETE *": "deny",
163+
"TRUNCATE *": "deny",
164+
"ALTER *": "deny",
165+
"git push *": "deny",
166+
"git reset *": "deny"
167+
},
168+
"external_directory": "deny"
169+
}
170+
}
171+
```
172+
173+
### Per-Agent Lockdown
174+
175+
Give each agent only the permissions it needs:
176+
177+
```json
178+
{
179+
"agent": {
180+
"analyst": {
181+
"permission": {
182+
"write": "deny",
183+
"edit": "deny",
184+
"bash": {
185+
"SELECT *": "allow",
186+
"dbt docs *": "allow",
187+
"*": "deny"
188+
}
189+
}
190+
},
191+
"builder": {
192+
"permission": {
193+
"bash": {
194+
"*": "ask",
195+
"dbt *": "allow",
196+
"git *": "ask",
197+
"DROP *": "deny"
198+
}
199+
}
200+
}
201+
}
202+
}
203+
```
204+
205+
## How Permissions Work
206+
207+
When the agent wants to use a tool, the permission system evaluates your rules in order:
208+
209+
1. **Config rules** — from `altimate-code.json`
210+
2. **Agent-level rules** — per-agent overrides
211+
3. **Session approvals** — patterns you've approved with "Allow always" during the current session
212+
213+
If a rule matches, it applies. If no rule matches, the default is `"ask"` — you'll be prompted.
214+
215+
When prompted, you have three choices:
216+
217+
| Choice | Effect |
218+
|--------|--------|
219+
| **Allow once** | Approves this single action |
220+
| **Allow always** | Approves this pattern for the rest of the session |
221+
| **Reject** | Blocks the action (optionally with feedback for the agent) |
222+
223+
"Allow always" approvals persist for your current session only. They reset when you restart Altimate Code.
224+
225+
## Tips
226+
227+
- **Start with `"ask"` and relax as you build confidence.** You can always approve patterns with "Allow always" during a session.
228+
- **Use `"deny"` for truly dangerous commands** like `rm *`, `DROP *`, `git push --force *`, and `git reset --hard *`. These are blocked even if other rules would allow them.
229+
- **Use per-agent permissions** to enforce least-privilege. An analyst doesn't need write access. A builder doesn't need `DROP`.
230+
- **Review the prompt before approving.** The TUI shows you exactly what will run — including diffs for file edits and the full command for bash operations.

docs/docs/security-faq.md

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ By default, destructive operations like `bash`, `write`, and `edit` require conf
3333
{
3434
"permission": {
3535
"bash": {
36+
"*": "ask",
3637
"dbt *": "allow",
3738
"git status": "allow",
3839
"DROP *": "deny",
39-
"rm *": "deny",
40-
"*": "ask"
40+
"rm *": "deny"
4141
}
4242
}
4343
}
@@ -51,11 +51,11 @@ Yes. Use pattern-based permissions to deny destructive SQL:
5151
{
5252
"permission": {
5353
"bash": {
54+
"*": "ask",
5455
"DROP *": "deny",
5556
"DELETE *": "deny",
5657
"TRUNCATE *": "deny",
57-
"ALTER *": "deny",
58-
"*": "ask"
58+
"ALTER *": "deny"
5959
}
6060
}
6161
}
@@ -198,6 +198,89 @@ For additional safety:
198198
- Run against a **staging environment** before production
199199
- Use the `analyst` agent with restricted permissions for ad-hoc queries
200200

201+
## What protections does Altimate Code have for file access?
202+
203+
Altimate Code includes several layers of protection to keep the agent within your project:
204+
205+
- **Project boundary enforcement** — File operations check that paths stay within your project directory (or git worktree for monorepos). Attempts to read or write outside the project trigger an `external_directory` permission prompt.
206+
- **Symlink-aware path resolution** — Symlinks inside the project that point outside are detected and blocked. This prevents an agent from reading or writing outside your project through symlinks.
207+
- **Path traversal blocking** — Paths containing `../` sequences that would escape the project are rejected with an "Access denied" error.
208+
- **Sensitive file protection** — Writing to credential files (`.env`, `.ssh/`, `.aws/`, private keys) triggers a confirmation prompt, even inside the project. See [below](#why-am-i-being-prompted-to-edit-env-files) for details.
209+
- **Bash command analysis** — The bash tool parses commands with tree-sitter to detect file operations (`rm`, `cp`, `mv`, etc.) targeting paths outside your project, and prompts for permission.
210+
- **Non-git project safety** — For projects outside a git repository, the boundary is strictly the working directory (not the entire filesystem).
211+
212+
These protections operate at the application level. For additional isolation, you can run Altimate Code inside a Docker container or VM.
213+
214+
## Why am I being prompted to edit `.env` files?
215+
216+
Altimate Code prompts before modifying files that commonly contain credentials or security-sensitive configuration, even when they're inside your project. This includes:
217+
218+
| Pattern | Examples |
219+
|---------|----------|
220+
| **Environment files** | `.env`, `.env.local`, `.env.production`, `.env.staging` |
221+
| **Credential files** | `credentials.json`, `service-account.json`, `.npmrc`, `.pypirc`, `.netrc`, `.pgpass` |
222+
| **Secret key directories** | `.ssh/`, `.aws/`, `.gnupg/`, `.gcloud/`, `.kube/`, `.docker/` |
223+
| **Private keys** | `*.pem`, `*.key`, `*.p12`, `*.pfx` |
224+
| **Version control** | `.git/config`, `.git/hooks/*` |
225+
226+
When you see this prompt:
227+
228+
- **"Allow once"** — approves this single edit
229+
- **"Allow always"** — approves edits to this specific file for the rest of the session (resets on restart)
230+
231+
If you frequently edit `.env` files and find the prompts disruptive, click "Allow always" on the first prompt for each file — you won't be asked again for that file during your session.
232+
233+
!!! tip
234+
This protection does **not** block reading these files — only writing. The agent can still read your `.env` to understand configuration without prompting.
235+
236+
## What commands are blocked or prompted by default?
237+
238+
Altimate Code applies safe defaults so you don't have to configure anything for common protection:
239+
240+
| Command | Default | Why |
241+
|---------|---------|-----|
242+
| `rm -rf *`, `rm -fr *` | **Prompted** | Recursive deletion can be destructive. You'll see what's being deleted. |
243+
| `git push --force *` | **Prompted** | Force-push can overwrite shared branch history. |
244+
| `git reset --hard *` | **Prompted** | Discards uncommitted changes permanently. |
245+
| `git clean -f *` | **Prompted** | Removes untracked files permanently. |
246+
| `DROP DATABASE *` | **Blocked** | Almost never intentional in an agent context. |
247+
| `DROP SCHEMA *` | **Blocked** | Almost never intentional in an agent context. |
248+
| `TRUNCATE *` | **Blocked** | Irreversible data deletion. |
249+
| All other commands | **Prompted** | You approve each command before it runs. |
250+
251+
**"Prompted"** means you'll see the command and can approve or reject it. **"Blocked"** means the agent cannot run it at all — you must override in config.
252+
253+
To override defaults, add rules in `altimate-code.json`. See [Permissions](configure/permissions.md) for the full configuration reference.
254+
255+
## Best practices for staying safe
256+
257+
1. **Review before approving.** The permission prompt shows you exactly what will happen — diffs for file edits, the full command for bash. Take a moment to read it.
258+
259+
2. **Work on a branch.** Let the agent work on a feature branch so you can review changes before merging. Git gives you a full safety net — this is the single most effective protection.
260+
261+
3. **Use per-agent permissions.** Give each agent only what it needs. The `analyst` agent doesn't need write access. See [Permissions](configure/permissions.md) for examples.
262+
263+
4. **Use read-only database credentials for exploration.** When using the agent for analysis or ad-hoc queries, connect with a read-only database user.
264+
265+
5. **Commit before large operations.** If the agent is about to make sweeping changes, commit your current state first. You can always `git stash` or revert.
266+
267+
6. **Block truly dangerous database operations.** The defaults block `DROP DATABASE`, `DROP SCHEMA`, and `TRUNCATE`. You can extend this:
268+
269+
```json
270+
{
271+
"permission": {
272+
"bash": {
273+
"*": "ask",
274+
"DROP *": "deny",
275+
"DELETE FROM *": "deny",
276+
"TRUNCATE *": "deny"
277+
}
278+
}
279+
}
280+
```
281+
282+
7. **Use Docker for sensitive environments.** If you're working with production systems or sensitive data, running Altimate Code in a container provides OS-level isolation on top of the permission system.
283+
201284
## Where should I report security vulnerabilities?
202285

203286
**Do not open public GitHub issues for security vulnerabilities.** Instead, email **security@altimate.ai** with a description, reproduction steps, and your severity assessment. You'll receive acknowledgment within 48 hours. See the full [Security Policy](https://github.com/AltimateAI/altimate-code/blob/main/SECURITY.md) for details.
Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
fix: address new Sentry findings — regex m flag and off-by-one budget check
1+
fix: UX evaluation — soften bash defaults, expand FAQ, remove .github from sensitive dirs
22

3-
1. formatTrainingEntry regex: remove multiline `m` flag that could
4-
match user content mid-string (memory/prompt.ts:82)
3+
UX impact evaluation of each change:
54

6-
2. Memory block budget check: change `<` to `<=` so blocks that fit
7-
exactly into remaining budget are included (memory/prompt.ts:204)
5+
1. **Bash defaults softened**: Changed destructive shell/git commands from
6+
`deny` (blocked silently) to `ask` (prompted). `rm -rf ./build` and
7+
`git push --force` after rebase are legitimate workflows — blocking them
8+
without a prompt is poor UX. Database DDL (`DROP DATABASE`, `TRUNCATE`)
9+
stays `deny` since it's almost never intentional in agent context.
810

9-
3 prior Sentry findings already fixed in earlier commits:
10-
- projectDir cache (Map keyed by Instance.directory)
11-
- injectTrainingOnly header-only return (itemCount guard)
12-
- orphaned section headers (first-entry pre-check)
11+
2. **Removed `.github` from sensitive dirs**: Editing CI/CD workflows is a
12+
core use case. Prompting on every workflow edit would cause severe
13+
approval fatigue.
14+
15+
3. **Expanded FAQ**: Added "Why am I being prompted to edit .env files?"
16+
with table of protected patterns and guidance on "Allow always".
17+
Added "What commands are blocked or prompted by default?" with clear
18+
table showing which commands prompt vs block. Reordered best practices
19+
to lead with "work on a branch" (most effective, least friction).
1320

1421
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

0 commit comments

Comments
 (0)