Create a governance-wrapped AnchorBrowser session.
// From YAML file
const leash = createLeash('./leash.yaml')
// From inline config
const leash = createLeash({
allow: ['read*'],
deny: ['*send*'],
default: 'deny',
expire: '60min',
maxActions: 100,
agent: 'my-agent',
})
// With options
const leash = createLeash('./leash.yaml', {
anchorApiKey: 'your-key', // default: process.env.ANCHOR_API_KEY
auditPath: './logs/audit.jsonl', // default: ./leash-audit.jsonl
})Execute a task through the governance layer.
const result = await leash.task('read my inbox')
// → { allowed: true, output: '...', auditId: 'evt-...' }
// → { allowed: false, reason: 'blocked by deny pattern: *send*', auditId: 'evt-...' }Destroy the AnchorBrowser session immediately.
await leash.yank()
// Session destroyed, event logged, done.Return all audit events.
const events = leash.audit()
// → [{ id, timestamp, agent, task, action, reason?, duration? }, ...]Return current session stats.
const status = leash.status()
// → { active: true, agent: 'inbox-assistant', uptime: '47min', allowed: 23, blocked: 3 }agent: inbox-reader
rules:
allow:
- "read*"
- "list*"
- "search*"
deny:
- "*"
default: deny
expire_after: 30minagent: sales-assistant
rules:
deny:
- "*delete*"
- "*password*"
- "*settings*"
- "*admin*"
default: allow
expire_after: 8h
max_actions: 500agent: report-generator
rules:
allow:
- "read*"
- "export*"
- "download*"
deny:
- "*send*"
- "*delete*"
default: deny
expire_after: 15min
max_actions: 20const leash = createLeash({
allow: ['read*', 'list*'],
deny: ['*send*', '*delete*'],
default: 'deny',
expire: '60min',
maxActions: 100,
agent: 'my-agent',
})*matches any characters (glob-style via picomatch)- Matching is case-insensitive on the full task string
- Deny rules checked first — deny always takes priority
- If no match → falls back to
default(denyorallow) - Invisible Unicode characters are stripped before matching (zero-width spaces, combining diacriticals, BiDi controls)
Every action is logged to leash-audit.jsonl:
{"id":"evt-1708300000-x4k2m","timestamp":"2026-02-19T10:00:00.000Z","agent":"inbox-assistant","task":"read my inbox","action":"allowed","duration":2340}
{"id":"evt-1708300003-j9f1p","timestamp":"2026-02-19T10:00:03.000Z","agent":"inbox-assistant","task":"send message to Bob","action":"blocked","reason":"blocked by deny pattern: *send*"}
{"id":"evt-1708300010-m3n7q","timestamp":"2026-02-19T10:00:10.000Z","agent":"inbox-assistant","task":"search emails from Q4","action":"allowed","duration":1890}JSONL format means:
- Append-only — events can't be edited or deleted
- Portable — pipe to jq, import into any SIEM
- Zero infra — just a file on disk
await leash.yank()npx leashed kill
# → Session mock-session-123 killed.npx leashed status
# Agent: inbox-assistant
# Status: active
# Allowed: 23
# Blocked: 3
# Total: 27
npx leashed audit
# Time Action Task
# ─────────────────────────────────────────────
# 2026-02-19 10:00:00 allowed read my inbox
# 2026-02-19 10:00:03 blocked send message to Bob (blocked by deny...)- Deny-first evaluation — deny rules always take priority over allow
- Unicode bypass protection — invisible characters stripped before pattern matching
- YAML type validation — non-string patterns rejected at load time
- Fail-closed — errors during execution are logged and reported as blocked
- Session file permissions — restricted to owner-only (0o600)
- Action budgets — only allowed tasks consume quota (blocked tasks are free)
For vulnerability reports, see SECURITY.md.
61 tests covering the governance boundary: policy evaluation, deny-first ordering, Unicode bypass vectors, YAML validation, audit logging, budget enforcement, concurrent access, timer expiration, kill idempotency, and fail-closed behavior. AnchorBrowser is mocked because leashed's job is policy enforcement, not browser automation — if the browser fails, leashed fails closed.