# How Harvey‑Labs Prevents Symlink Escape Attacks During Glob and Grep Operations

> Discover how Harvey Labs protects against symlink escape attacks in glob and grep operations. Learn how real path resolution secures your sandbox environment.

- Repository: [Harvey/harvey-labs](https://github.com/harveyai/harvey-labs)
- Tags: how-to-guide
- Published: 2026-08-11

---

**Harvey‑Labs prevents symlink escape attacks by resolving every candidate file's real path and verifying it stays within the sandbox bind‑mount root before returning results from `glob` or `grep` operations.**

When AI agents execute filesystem tools inside a containerized sandbox, malicious symlinks pose a serious security risk. The `harveyai/harvey-labs` repository implements a defense‑in‑depth approach that neutralizes symlink‑escape attacks without requiring agents to understand container boundaries.

## The Threat: Symlink Escape in Containerized Tools

A symlink escape attack occurs when an attacker creates a symbolic link inside a restricted directory that points to sensitive files outside that boundary. For example, a malicious agent could create `output/leak → /etc/passwd` and then use `glob` or `grep` to exfiltrate host system data.

Harvey‑Labs addresses this by treating every file discovery as untrusted until proven otherwise. The implementation in [`harness/tools.py`](https://github.com/harveyai/harvey-labs/blob/main/harness/tools.py) enforces path containment at two critical stages: path translation and result filtering.

## Path Translation: Mapping Sandbox to Host

Before any filesystem traversal begins, the `Tools` class translates sandbox‑relative paths to actual host paths. The method `_sandbox_to_host_path` performs this mapping in [`harness/tools.py`](https://github.com/harveyai/harvey-labs/blob/main/harness/tools.py) at line 90.

```python

# From harness/tools.py - path translation before traversal

host_root = self._sandbox_to_host_path(path)

```

This ensures that glob and grep operations work against the correct bind‑mounted directory, not arbitrary host locations.

## The `_is_under` Guard: Core Protection Mechanism

The critical security primitive is the static method `_is_under`, implemented at lines 32‑44 of [`harness/tools.py`](https://github.com/harveyai/harvey-labs/blob/main/harness/tools.py):

```python
@staticmethod
def _is_under(candidate: Path, root: Path) -> bool:
    """
    Return True if candidate is under root (after resolving both).
    Does NOT require candidate to exist.
    """
    try:
        candidate_resolved = candidate.resolve()
        root_resolved = root.resolve()
        # Attempt to compute relative path - raises ValueError if outside

        candidate_resolved.relative_to(root_resolved)
        return True
    except ValueError:
        return False

```

This method:

- **Resolves both paths** fully (eliminating symlinks, `.`, and `..` components)
- **Uses `relative_to`** which raises `ValueError` when the candidate lies outside the root
- **Requires no file existence checks**, making it safe for glob patterns that match non‑existent paths

## Glob Implementation with Symlink Protection

The `_glob` method in [`harness/tools.py`](https://github.com/harveyai/harvey-labs/blob/main/harness/tools.py) (lines 69‑71) applies this protection to every matched file:

```python
host_root_resolved = host_root.resolve()
result = []
for m in host_root.glob(pattern):
    if self._is_under(m, host_root_resolved):
        result.append(str(m))

```

Only files passing the `_is_under` check are added to results. A symlink pointing outside `host_root_resolved` fails this test and is silently excluded.

## Grep Implementation with Identical Checks

The `_grep` method follows the same pattern at lines 104‑110:

```python
for f in host_root.glob(glob_pattern):
    if not self._is_under(f, host_root_resolved):
        continue  # Skip escaped symlinks

    # Safe to read and search file contents...

```

This prevents the grep tool from following symlinks that escape the sandbox, even when the pattern otherwise matches them.

## Practical Examples

Safe glob execution returning only contained files:

```python
result = tool_executor.execute("glob", '{"pattern": "*.txt"}')

# Returns: ["file1.txt", "file2.txt"]

# Excludes: any .txt file reached via symlink outside /workspace/output

```

Safe grep that ignores escaped symlinks:

```python
result = tool_executor.execute(
    "grep",
    '{"pattern": "API_KEY", "path": "/workspace/output"}'
)

# If output/secret_link → /etc/environment, the symlink is ignored

# Only actual files under the bind-mount are searched

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`harness/tools.py`](https://github.com/harveyai/harvey-labs/blob/main/harness/tools.py) | Implements `_is_under`, `_sandbox_to_host_path`, `_glob`, and `_grep` with symlink protection |
| [`sandbox/sandbox.py`](https://github.com/harveyai/harvey-labs/blob/main/sandbox/sandbox.py) | Defines container mounts at `/workspace`, `/workspace/documents`, `/workspace/output` |
| [`tests/test_sandbox.py`](https://github.com/harveyai/harvey-labs/blob/main/tests/test_sandbox.py) | Validates protection with `test_glob_does_not_list_symlink_target_outside_root` |
| [`tests/test_pipeline.py`](https://github.com/harveyai/harvey-labs/blob/main/tests/test_pipeline.py) | End‑to‑end verification of glob tool behavior |

## Summary

- **Path resolution is the defense**: Harvey‑Labs prevents symlink escapes by resolving all candidate paths with `Path.resolve()` before use
- **Single verification point**: The `_is_under` static method centralizes containment checking for both glob and grep operations
- **Fail‑secure design**: Suspicious files are excluded silently rather than causing errors that might leak information
- **No agent cooperation required**: Protection happens automatically in the tool implementation, not the agent's prompt or reasoning

## Frequently Asked Questions

### What happens if an agent creates a symlink to `/etc/passwd` inside the sandbox?

The symlink is created successfully, but `glob` and `grep` operations will not traverse it. When these tools encounter the symlink during directory walking, `_is_under` resolves it to `/etc/passwd`, detects the escape outside the bind‑mount root, and excludes it from results.

### Does the protection require the symlink target to exist?

No. The `_is_under` method uses `Path.resolve()` which works regardless of whether the target exists. This prevents attacks using dangling symlinks or symlinks to future paths.

### Are there performance implications for checking every file?

The `resolve()` operation adds minimal overhead for typical AI agent workloads involving hundreds or thousands of files. The protection runs once per matched file during glob expansion, not per recursive directory entry.

### Can agents disable or bypass this protection?

No. The `_is_under` check is embedded in the `Tools` class implementation and executes on the host side outside the agent's container. Agents interact only through the `tool_executor` interface and cannot influence the path validation logic.