Files
yvgude--lean-ctx/docs/reference/13-security-and-governance.md
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

18 KiB

Journey 13 — Security & Governance

You're putting lean-ctx in front of real code — possibly on a shared machine, in CI, or under a security policy. This journey covers every guardrail lean-ctx applies by default and every dial you can tighten: filesystem jail, shell allowlist, secret redaction, OS sandboxing, harden mode, and role policies.

Source files:

  • rust/src/core/pathjail.rs — filesystem boundary (PathJail)
  • rust/src/core/shell_allowlist.rs — command allowlist
  • rust/src/core/secret_detection.rs — secret scanning & redaction
  • rust/src/core/sandbox.rs, sandbox_seatbelt.rs, sandbox_landlock.rs — OS sandbox
  • rust/src/core/tcc_guard_sandbox.rs — macOS launchd seatbelt wrapper (deny ~/Documents, #356)
  • rust/src/cli/harden.rsharden (soft/hard/undo)
  • rust/src/core/context_policies.rs — role policies
  • rust/src/core/owasp_alignment.rs, audit_trail.rs — alignment & audit

0. The defense-in-depth model

Every file read and shell command flows through layered guardrails, on by default — you don't opt in:

flowchart LR
  R[ctx_read / ctx_shell] --> PJ[PathJail<br/>stay in project root]
  PJ --> AL[Shell allowlist<br/>~200 known binaries]
  AL --> SB[OS sandbox<br/>Seatbelt / Landlock]
  SB --> SD[Secret redaction<br/>before output leaves]
  SD --> OUT[result to the agent]

You can tighten each layer (stricter shell parsing, harden mode, role policies) but you cannot accidentally turn off the baseline.


0.1 See and flip the whole posture — lean-ctx security

lean-ctx's guardrails fall into two independent planes. Keeping them separate is deliberate: a usability-first user can let the agent do anything on a trusted machine without ever leaking secrets to the model provider.

Plane Protects Controls
Containment your machine from the agent PathJail + shell gating
Secret defense your secrets from the LLM provider .env / credential redaction

One command shows the live posture and how to change it:

lean-ctx security status        # posture board: jail, shell, secret redaction

Two master switches flip containment in one step — secret redaction is never touched by them:

lean-ctx yolo                   # OPEN: any path + any command (containment off)
lean-ctx secure                 # STRICT: restore the secure defaults

yolo writes path_jail = false and shell_security = "off" to your global config and takes effect immediately (no restart). It is fully reversible, and you re-enable pieces granularly afterwards — the array/blanket dichotomy works in both directions:

lean-ctx config set shell_security warn   # log violations, block nothing
lean-ctx config set path_jail true        # re-enable the filesystem jail
lean-ctx allow acli                       # permit one extra command (additive)

Secret/.env redaction is a separate toggle — yolo leaves it on:

lean-ctx security secrets off   # stop masking secrets (NOT recommended)
lean-ctx security secrets on    # re-enable masking

lean-ctx security status (and lean-ctx doctor) print a coarse label — STRICT (both planes enforced), RELAXED (partially loosened), or OPEN (containment fully off) — so the active posture can never hide.

Mental model: containment is "what the agent may touch on this machine"; secret defense is "what may leave this machine for the provider". yolo only drops the first.


1. PathJail — stay inside the project

What it does: confines file access to the resolved project root. Absolute paths outside the jail are rejected, so a stray read /etc/passwd or a path-traversal ../../ cannot escape the workspace.

  • Re-rooting to a different project root is off by default (allow_auto_reroot = false) — lean-ctx will not silently follow an absolute path into another tree.
  • Multi-root setups (lean-ctx serve --root a:A --root b:B) jail each root independently (server/multi_path.rs).
  • Widen it (array): add trusted roots with allow_paths / extra_roots (or LEAN_CTX_ALLOW_PATH) — reads/writes resolve under those prefixes too.
  • Disable it (blanket): set path_jail = false to allow any path — the blanket equivalent of allow_paths = ["/"], for containers/sandboxes where the boundary is already external. lean-ctx yolo sets this for you, and lean-ctx doctor flags it loudly while it is active.

This is the foundation every other layer assumes.


1.5 Workspace trust — gate untrusted project-local config

What it does: a cloned repo ships its own .lean-ctx.toml, which is merged over your global config. That merge can raise security-sensitive settings — replace the shell_allowlist, widen the jail via allow_paths / extra_roots, repoint a proxy.*_upstream, define custom_aliases, or flip rules_scope / rules_injection. lean-ctx treats those as trusted-only.

For a workspace you have not trusted, the sensitive overrides are withheld (comfort knobs like compression_level / theme still apply) and a [SECURITY] warning is logged. Grant trust explicitly:

lean-ctx trust              # trust the current project root
lean-ctx trust status       # show trust state + which overrides are gated
lean-ctx trust --list       # list all trusted workspaces
lean-ctx untrust            # revoke trust for the current root

Trust is pinned to both the workspace path and a content hash of .lean-ctx.toml — editing the file after trust re-gates it, so a "trust once, modify later" change can't take effect silently. lean-ctx doctor shows the state. Headless / fleet environments can opt in without a prompt via LEAN_CTX_TRUST_WORKSPACE=1 or a LEAN_CTX_TRUSTED_ROOTS=/path/a,/path/b list.

Review a clone's .lean-ctx.toml before you lean-ctx trust it — trusting is what lets its security-sensitive settings widen lean-ctx's own boundaries.


2. Shell allowlist & strict mode

What it does: the shell hook only compresses/executes commands whose binary is on the allowlist (~200 common dev tools: git, cargo, npm, node, python, …). Anything else passes through untouched rather than being wrapped.

Pipelines & chains (|, &&, ||, ;): a compound command is wrapped as a single lean-ctx -c "<whole>" only when every stage is gate-clean — then the pipe/chain runs natively inside one profile-free shell and just the final output is compressed (so git log | wc -l counts the raw log, not a digest). If any stage is not gate-clean (an interpreter -c, a non-allowlisted sink such as kubectl, …) the whole command is left raw for the agent's shell. lean-ctx never wraps only part of a pipe (which would compress mid-pipe data the next stage still needs) and never relaxes the gate to wrap a tricky sink — the rewrite can only ever route commands the same chokepoint would already allow.

# config.toml
shell_allowlist = ["git", "cargo", "npm", "…"]   # REPLACE the default set
shell_allowlist_extra = ["just", "task"]          # ADD to the default set
shell_strict_mode = false                         # set true to block $() and backticks
excluded_commands = []                            # never intercept these
  • docker / podman are deliberately not in the default allowlist — mount flags can bypass PathJail. Add them explicitly only if you accept that.
  • shell_strict_mode = true blocks command substitution ($(…), backticks) for environments that must forbid dynamic command construction.
  • Replace the whole list via LEAN_CTX_SHELL_ALLOWLIST (comma-separated), or just add a few extras with shell_allowlist_extra. The lean-ctx allow <cmd> CLI edits shell_allowlist_extra for you, and lean-ctx allow --list prints the effective allowlist plus any parse errors so a typo can never silently drop your overrides.

2.1 Shell-security mode (enforce | warn | off)

One switch governs all command gating — the allowlist and the hard blocks (eval/exec/source, $()/backticks at command position, interpreter -c). It is applied at a single chokepoint, so MCP ctx_shell and the CLI (lean-ctx -c / -t) behave identically.

# config.toml
shell_security = "enforce"   # default — secure
# shell_security = "warn"    # run the checks, log violations via tracing, never block
# shell_security = "off"     # skip command gating entirely — compression stays active
Mode Allowlist Hard blocks (eval, $(), …) Compression
enforce (default) enforced blocked on
warn logged only logged only on
off skipped skipped on
  • Resolution: LEAN_CTX_SHELL_SECURITY env → shell_security in config → default enforce. An unknown value falls back to enforce (never fails open).
  • Why enforce is the default, even for beginners: lean-ctx mediates the agent's shell, so a weaker default would silently downgrade security for every install on upgrade. In practice the default allowlist already covers normal workflows (git/cargo/npm/node/python/…), so beginners rarely hit it — the friction is for power users running exotic tools, who opt into off.
  • off is the "YOLO" escape hatch. It disables command gating only; it does not lift the read-only-output doctrine in ctx_shell (no >/tee/heredoc file writes — use the native write tool). Compression is unaffected in every mode. Set it together with path_jail = false in one step via lean-ctx yolo (and revert both with lean-ctx secure).
  • lean-ctx doctor surfaces the active mode whenever it is not enforce, so a relaxed posture can never hide.
  • This supersedes the CLI-only LEAN_CTX_ALLOWLIST_WARN_ONLY (which still works for backward compatibility); shell_security is the canonical, global switch.

3. OS sandboxing for executed code

What it does: when lean-ctx executes code (ctx_execute), it runs under the OS sandbox — Seatbelt on macOS and Landlock on Linux — so the executed process gets a restricted filesystem/network view, not your full user privileges. On platforms without a supported sandbox, execution is gated rather than run unconfined.

This is separate from PathJail (which guards lean-ctx's own reads); the sandbox guards child processes lean-ctx spawns on your behalf.

3.1 launchd seatbelt — no macOS "Documents" prompt (#356)

On macOS the daemon, proxy and auto-updater run as LaunchAgents — i.e. as their own TCC identity (ppid 1), which does not inherit your terminal's or editor's privacy grants. Any access they make under ~/Documents, ~/Desktop or ~/Downloads would pop the "lean-ctx would like to access files in your Documents folder" prompt, and because every release re-signs the binary a granted permission is voided on the next update — so the prompt returns forever.

lean-ctx therefore bakes a deny-~/Documents Seatbelt profile into the LaunchAgent invocation itself (rust/src/core/tcc_guard_sandbox.rs): the plist runs sandbox-exec -p '(allow default) (deny file-read* file-write* …)' lean-ctx …. The kernel refuses any access to the three protected directories silently with EPERM — the TCC subsystem is never consulted, so the prompt can never appear and no "Allow" is ever required. Everything outside those directories stays permitted, so the processes keep full functionality. The path guards (pathutil::may_probe_path) and the optional stable code-signing identity (lean-ctx codesign-setup) remain underneath as defense-in-depth. Verified by rust/tests/tcc_sandbox.sh, which boots the daemon under the production wrapper.


4. Secret redaction — nothing leaks to the model

What it does: before any shell output or file content is returned, lean-ctx scans it for credentials (AWS keys, tokens, etc.) and replaces matches with [REDACTED:<kind>]. It is on by default.

[secret_detection]
enabled = true
redact = true
custom_patterns = ["MYCORP_[A-Z0-9]{20}"]   # add org-specific secret shapes
exclude_patterns = ["LCTX_PUBLIC_\\w+"]     # carve out known-safe matches (subtractive)

Built-in patterns cover common cloud/credential formats; custom_patterns lets you redact organization-specific secret shapes. Matches are reported with a safe preview (e.g. AKIA…) so you know redaction fired without seeing the secret.

exclude_patterns is the subtractive counterpart (#718): a detected match covered by any of these regexes is neither reported nor redacted — carve out a repo's own naming conventions without disabling secret detection wholesale. Since v3.9.2 the detector also skips benign non-values on its own: unquoted identifier/property references (serverEnv.getStripeSecretKey), camelCase subwords (superuserPassword: as a key), and obvious placeholders (ghp_change_me, your_key_here). Quoted string literals and digit-bearing values remain protected.

4.1 Sensitivity policy floor — per-item levels

Where [secret_detection] masks known credential shapes, the sensitivity floor classifies every item by a level and enforces one uniform policy floor just before content reaches the model. It is off by default (fully no-op) and covers both tool outputs and injected knowledge facts.

[sensitivity]
enabled = true            # off by default → no-op
policy_floor = "secret"   # public < internal < confidential < secret
action = "redact"         # "redact" masks the spans, "drop" withholds the item
Level Raised by (high-precision signals only)
secret secret-like paths (.env, .ssh/…, *.pem) or detected credentials
confidential Luhn-validated card numbers, mod-97-validated IBANs
internal reserved for explicit tagging
public default

Anything classified at or above policy_floor is dropped or redacted: with action = "drop" the whole item is replaced by a short notice; with redact only the offending spans are masked. Knowledge facts store their level at creation (KnowledgeFact.sensitivity) and are re-checked at injection time, so a floor change takes effect immediately. The classifier uses only high-precision signals (no speculative heuristics) to avoid false positives; LEAN_CTX_SENSITIVITY=0|1 toggles enforcement for a single run. This section lives in the global ~/.lean-ctx/config.toml only — an untrusted project file cannot lower the floor.


5. Harden mode — force the compressed path

What it does: harden makes the agent use lean-ctx's compressed ctx_* tools by denying native Read/Grep (except immediately after an Edit). Two levels:

lean-ctx harden            # soft: sets LEAN_CTX_HARDEN=1 in MCP configs
lean-ctx harden --hard     # also adds "Bash" to ~/.claude/settings.json deny
lean-ctx harden --undo     # remove both — native tools allowed again

Real output:

lean-ctx harden (level: soft)

  [OK] /Users/you/.cursor/mcp.json
  [OK] /Users/you/.claude.json
  [OK] Set LEAN_CTX_HARDEN=1 in MCP configs

Harden active. Native Read/Grep will be denied (except after Edit).
Undo with: lean-ctx harden --undo

Soft harden is reversible and config-only; --hard additionally blocks the Claude Bash tool. Both are fully undone by --undo (see also Journey 12 → §4).


6. Role policies — least privilege per agent

What it does: role policies (context_policies.rs, set via ctx_session action=role / lean-ctx session role) scope what a session may do — e.g. a reviewer role that cannot write, or limiting privileged search (ctx_search ignore_gitignore=true requires an admin-class role). Use this to give a sub-agent or teammate a least-privilege surface.


7. Audit & alignment

lean-ctx maintains an audit trail (audit_trail.rs) of security-relevant actions and ships an OWASP alignment map (owasp_alignment.rs) documenting which controls address which risks — useful when answering a security review.

For a structured external yardstick, lean-ctx publishes a self-assessment against the 32-control Context Governance Benchmark (v1.0-draft): graded C2 — Managed with declared gaps — see docs/compliance/cgb-self-assessment.md.


Governance checklist

Goal Control
See / flip the whole posture lean-ctx security status · yolo · secure
Disable containment for a trusted machine lean-ctx yolo (keeps secret redaction on)
Keep file access in-project PathJail (on by default)
Gate a cloned repo's .lean-ctx.toml Workspace trust (lean-ctx trust)
Restrict which commands run shell_allowlist + shell_strict_mode
Never wrap docker mount-escapes docker/podman off the default allowlist
Confine executed code Seatbelt (macOS) / Landlock (Linux)
Stop secrets reaching the model [secret_detection] (on by default)
Block whole sensitivity levels (PII/secret) pre-prompt [sensitivity] policy floor (off by default)
Force compressed reads lean-ctx harden [--hard]
Least-privilege agents role policies
Answer a security review audit trail + OWASP alignment

Tuning how much lean-ctx compresses or which tools it exposes lives in Journey 10 — Customization & Governance; this journey is specifically the security surface.