# MCP Filesystem Symlink Containment Escape Artefacts

- **Severity:** Medium
- **Signature ID:** `7b3f2a41-6c58-4d9e-9a12-0f4d8c1e5b73`
- **OWASP:** LLM06 (Excessive Agency), LLM02 (Sensitive Information Disclosure)

## Summary

This rule spots early warning signs that someone is trying to break out of the folder an AI agent's filesystem tool is supposed to be confined to. It does not see the actual breakout — only the setup moves that typically precede it.

## How the attack works

Some MCP filesystem or git servers decide whether a file path is 'inside' the allowed workspace by resolving symlinks rather than by checking the path string itself. An attacker can plant a symbolic link, junction, or bind mount that points from inside the workspace to something outside it — like a credential store or a system-control file — so that a later, innocent-looking read follows the link out of the sandbox. Separately, attackers probe these servers with directory-traversal strings disguised through percent-encoding, double-encoding, overlong UTF-8, fullwidth dots, or repeated dots, trying to find an input the sanitiser doesn't catch. This rule watches tool calls, LLM tool calls, and process executions for exactly those two things: suspicious symlink/junction/mount creation pointing at sensitive targets, and these obfuscated traversal strings in path arguments. It deliberately ignores plain '../../..' traversal because that pattern is extremely common in legitimate monorepo and package-manager tooling.

## Why it matters

If the underlying containment bypass succeeds, an agent's filesystem tool can read or write files well outside its intended workspace — including credentials or system files — leading to sensitive information disclosure and actions beyond what the agent was meant to be able to do. This rule catches only the precursor steps, not confirmation that the escape worked.

## What you can do

- Treat any alert as an investigation trigger, not proof of compromise — correlate it with subsequent file reads/writes from the same session.
- Restrict what your MCP filesystem/git server can symlink to, and prefer path-containment logic based on the resolved real path rather than symlink-following string checks.
- Review flagged symlinks, junctions, and bind mounts by hand, especially any pointing at SSH keys, credential stores, docker sockets, or system directories.
- Exclude known-legitimate sources of noise, such as dotfile-manager bootstrap scripts and CI container setup, but do not blanket-exclude the whole home or /var/run tree.

## Known benign look-alikes

- Dotfile managers (GNU Stow, chezmoi, yadm) symlinking shell/SSH config from a repo into the home directory. Mitigated by matching only key material (id_*) and authorized_keys under .ssh rather than the whole directory, but a bootstrap script that links a key file will still fire.
- Container and dev-environment bootstrap scripts that bind-mount or symlink /var/run/docker.sock or /root/.cache into a build workspace. Legitimate in some CI images.
- The MCP server''s own path-containment regression tests, which necessarily contain encoded traversal payloads. Suppressed by filter_test_context when standard assertion syntax is present.
- Vulnerability write-ups, advisories and WAF-tuning discussions quoting %2e%2e / ....// payloads. Suppressed by filter_documentation.
- Kubernetes or cloud onboarding runbooks that symlink ~/.kube/config to a shared profile.

## References

- https://owasp.org/www-project-top-10-for-large-language-model-applications/
- https://cwe.mitre.org/data/definitions/59.html
- https://cwe.mitre.org/data/definitions/22.html

---
Source: https://www.netzilo.com/threats/mcp-symlink-containment-bypass
