Skip to content

Back to the catalog

AI Agent Sandbox Flaw in brig: Symlink Traversal (CVSS 8.2) | Blog | Endor Labs
endorlabs.comwriteup

AI Agent Sandbox Flaw in brig: Symlink Traversal (CVSS 8.2) | Blog | Endor Labs

Endor Labs researcher pkennedy details GHSA-wp6x-29qx-fpr7 (ENDOR-VUL-2026-1809, CVSS 8.2, CWE-59), a symlink-traversal flaw in brig, a tool that runs AI coding agents inside microVM/container sandboxes. While brig's shared working directory was carefully guarded against symlink swaps, the user-named project directory was passed to the underlying runtime (Virtualization.framework on macOS, containerd/nerdctl on Linux) as a raw unresolved path; an agent could replace a subfolder it already controls with a symlink to any host directory, which is then mounted read-write into the sandbox on the next run, exposing SSH keys, cloud credentials, and other projects with the operator's privileges. Fixed in brig 0.3.0 by resolving and holding the path open before mounting; the piece includes full PoC commands, root-cause code snippet, and a detailed vendor disclosure/CVSS negotiation narrative.

TL;DR Symlink-traversal bug (CVSS 8.2, GHSA-wp6x-29qx-fpr7) lets an AI agent escape brig's sandbox to read/write arbitrary host files by planting a symlink in its project mount across two runs; fixed in brig 0.3.0.
Document

What it is

brig (an AI-coding-agent sandboxing tool) has a symlink-traversal flaw, GHSA-wp6x-29qx-fpr7 / ENDOR-VUL-2026-1809, CVSS 8.2, CWE-59, fixed in 0.3.0. Confirmed read+write on macOS 15 arm64 with brig 0.2.0 (Homebrew) over the hull/Virtualization.framework microVM backend, and Ubuntu 24.04 arm64 over nerdctl/containerd. The microVM's strongest isolation claim does not change the outcome — brig itself is the confused deputy, not the hypervisor or guest kernel.

What to do

If you can't upgrade to 0.3.0, resolve the project path yourself so no unresolved link reaches the runtime:

brig run claude "$(cd ~/lab/monorepo/frontend && pwd -P)"

Detection/hygiene the author states explicitly: checking only the last path component is not enough (the link can sit at any component), and treat any project whose path passes through a directory a sandbox has mounted read-write as suspect. Note the pre-fix run envelope's PROJECT row showed the typed path, not where it resolved — so operators who checked it were misled; 0.3.0 reports the resolved directory.

The escalation the summary omits

Beyond SSH keys / cloud creds, the author pointed the link at /proc and confirmed a write to /work/<name>/sys/kernel/core_pattern returns success — a host-root command-execution primitive. He marks this inferred, not demonstrated (core_pattern isn't namespaced and his host was nested, so he didn't fire a payload). Write access to ~/.ssh/authorized_keys or a shell rc file is a demonstrated path to code execution as the host user. Neither --network isolated nor --network offline helps — the escape is a filesystem share, not network.

Why the two-run sequence matters

The planted symlink dangles and does nothing in the run that creates it (the host target doesn't exist in the guest namespace). It only resolves on the host during a later, normal-looking run that narrows to that subdirectory — the standard way to point an agent at part of a monorepo. Both operator commands are documented usage; the agent's only action is planting the link with its own in-sandbox privileges (hence PR:L, agreed by both sides).

flowchart TD
    A[Agent has RW on project] --> B[Replace subfolder with symlink to host dir]
    B --> C{Later run names that subfolder?}
    C -->|No| D[Link dangles, harmless]
    C -->|Yes| E[brig passes unresolved path to runtime]
    E --> F[Runtime resolves link on HOST, mounts target RW]
    F --> G[SSH keys, creds, core_pattern reachable]
    H[0.3.0: resolve + hold dir open before mount] -.cuts.-> E

Root cause

The vulnerable code called filepath.EvalSymlinks(abs) only to reject a path resolving to /, then mounted the path as typed (c.Project = abs); both runtime adapters concatenated that string into a mount spec resolved later by another process. The workspace share had full per-component symlink defenses; the project share had one check. 0.3.0 splits the path at the first user-writable directory, descends one component at a time refusing every symlink (including operator-created ones), and holds the directory open across handover.

Organisations named here

  • CWE-59
  • ENDOR-VUL-2026-1809
  • GHSA-wp6x-29qx-fpr7

Technologies named here

  • brig
  • macOS
  • hull
  • Ubuntu
  • nerdctl
  • containerd
  • Endor Labs
  • Linux
  • Claude Code

Techniques named here

  • Project-mount symlink traversal
  • Confused deputy
  • Two-run exploitation sequence
  • core_pattern write primitive