Skip to content

Security: jasondillingham/leonard

Security

SECURITY.md

Security Policy

Threat model

Leonard runs locally, per-project, against source code the user has chosen to expose to Claude Code. The threat model focuses on:

  • Confused-deputy bugs: a malicious or confused Claude Code session emitting hook payloads or MCP tool calls that get Leonard to act outside its intended scope (e.g. indexing files outside the project root, executing commands in unintended directories).
  • Resource exhaustion: a payload that causes Leonard's helpers to OOM or pegs CPU.
  • Information disclosure: anything that exposes data from one project's .leonard/leonard.db to another, or leaks symbols across project boundaries.

Out of scope:

  • Threats requiring Claude Code itself to be compromised — Leonard treats Claude Code as a trusted-enough caller (its commands are user-initiated).
  • Multi-user / multi-tenant attacks — Leonard is single-user by design.
  • Threats requiring root or shell access to the local machine — attackers with that level of access can already do anything.

Existing guards

The bug-hunt + security-review discipline has produced several concrete guards:

Guard Versions What it protects against
Path-trust sweep (ResolveSafe) v0.8, v0.14 confused-deputy file_path: /etc/hosts payloads; symlink-out escapes; NFC/NFD normalization collisions
Resource caps (per-payload, per-snippet, per-element-count, per-response) v0.9, v0.13 OOM via crafted PreToolUse payloads; runaway MCP response sizes
Custom line reader replacing bufio.Scanner v0.13 busy-spin DoS on oversize stdin lines
MCP stdin filter v0.13 wrong-version / malformed JSON-RPC frames killing the transport
[post_edit.verify].command requires operator trust v0.51, v0.52 command execution. The verifier command no longer auto-executes from .leonard/config.toml. The operator runs leonard config trust interactively, which stores the SHA-256 fingerprint at $XDG_CONFIG_HOME/leonard/trust/<sha256-of-project-root>.sha256 (per-user, outside the project tree — moved out of .leonard/ in v0.52 so .leonard/-write attacks can't poison the fingerprint). The post-edit hook recomputes the fingerprint and refuses to invoke sh -c until they match. Closes the entire Bash-obfuscation bypass class (backslash escapes, command substitution, parameter expansion, glob, base64-decode, …) that v0.46 + v0.50 tried to enumerate.
.leonard/ write guard v0.37, v0.46, v0.50 defense-in-depth for the trust gate. Pre-edit rejects Edit/Write/MultiEdit/NotebookEdit targeting any path under .leonard/. The v0.46 path-segment match was hardened by v0.50 against symlinks (filepath.EvalSymlinks), case-insensitive filesystems (strings.EqualFold — closes the .LEONARD/ bypass on APFS/NTFS/Samba), backslash separators (WSL), and Bash command strings (catches naive echo x > .leonard/config.toml).
.leonard/ symlink rejection at init v0.51 a malicious project that ships .leonard as a symlink to attacker-controlled storage could pre-populate a trust fingerprint matching a malicious command. leonard init now refuses to operate when .leonard is a symlink.
Ledger hygiene (vet_ok-based supersede, no missing-file claims) v0.38, v0.39 ledger pollution / supersede mis-classification
working_dir path-trust validation v0.46 confused-deputy. [post_edit.verify].working_dir values are validated via ResolveSafe against the project root; escapes fall back to the project root with a captured-output note.
idx_symbols_parent and other store indexes v0.7.1, v0.15 DoS via expensive queries

See audits/security-1-review.md for the full security review and the audits/ directory for finding-by-finding history across the five bug-hunt rounds.

Reporting a vulnerability

If you find a security-sensitive issue:

  1. Don't open a public issue. Use GitHub's Private Vulnerability Reporting feature on this repo (the "Security" tab → "Report a vulnerability"). All maintainers receive the report.
  2. Include a reproducer if possible — a minimal config or hook payload that triggers the bad behavior.
  3. Describe the threat model angle: confused-deputy? Resource exhaustion? Cross-project leak? Other?

We aim to acknowledge reports within 7 days. Critical issues get a v0.X.0 release as soon as the fix passes the bug-hunt discipline (regression test + commit message linking the finding).

Disclosure timeline

  • We don't have a formal disclosure window — Leonard is a small-audience local-first tool, not a service with users to coordinate with. We'll fix and release; the CHANGELOG.md entry will describe the issue.
  • If the finding is in a transitive dependency (e.g. a tree-sitter grammar, modernc.org/sqlite, the MCP SDK), we'll coordinate with that upstream and document the workaround until it's fixed.

Bug-hunt-style audits welcome

If you'd like to run a security-focused bug hunt against Leonard, the format is documented in audits/security-1-review.md + the round-N triage files under audits/. Findings filed in that shape are easy to action.

There aren't any published security advisories