Locus is an identity plane for coding agents: it pins sessions to a Binding and makes wrong-account action mechanically hard. This document summarizes the threat model and how to report issues.
Full design detail lives in DESIGN.md §9 (assets, adversaries, non-goals, invariants).
| Version | Supported |
|---|---|
0.1.x (main) |
Yes — active development |
| pre-0.1 tags / unreleased spikes | Best effort |
Please do not open a public GitHub issue for security bugs.
- Email security@ashlr.ai (preferred), or open a private security advisory on the ashlrai/locus repository if you have access.
- Include:
- Affected commit / version / platform
- Reproduction steps (minimal, if possible)
- Impact (e.g. cross-binding credential leak, seal forgery, scope freeze bypass)
- Whether you plan a public write-up and preferred timeline
- We will acknowledge receipt as soon as practical and coordinate fix + disclosure.
We appreciate coordinated disclosure. Credit is given unless you ask otherwise.
| Asset | Intent |
|---|---|
| Provider credentials | Resolved only into worker/child env; never returned by MCP tools |
| Tenant / account selection | Session sealed to one Binding; tools catalog is exclusive |
| Scope freezes | project_ref, team_id, org/repo allowlists cannot be overridden by the model |
| Session integrity | V3 HMAC seals plus a live broker subject/generation bind the exact record, backing, expiry, and authority |
| Workspace boundaries | .locus.toml allowed_bindings blocks wrong-tenant pins (unless audited --force) |
| Threat | Mitigation (design) |
|---|---|
| Confused deputy (agent on A affects B) | Separate pin/catalog; no cross-binding tools |
| Prompt injection → re-pin | Agents cannot pin; locus_request_pin only |
Arg smuggling (project_ref swap) |
Adapter scope freeze |
Ambient CLI race (gh auth switch) |
Private GH_CONFIG_DIR / scrubbed env in locus exec |
| Ambient credential inheritance | Scrub known identity env vars; inject only resolved refs for the pin |
| Seal forgery / replay | HMAC covers session fields; an in-memory supervised broker independently binds their digest to a monotonic generation and rejects stale records |
| Approval id path traversal | Ids constrained to safe charset; joined path must stay under approvals/ |
| Confirm / approval_id injection into digests | args_digest strips control + secret-like keys before hashing |
Destructive tools remain blocked until Locus receives independently authenticated external authorization. Local CLI/dashboard principal strings are advisory review labels only and never become execution authority.
| Policy field | Effect |
|---|---|
require_approval = ["*.delete*", …] |
Tool blocked pending one valid external authorization envelope |
dual_control = ["vercel.deploy.prod", …] |
Matching tools require two externally authenticated approvers |
dual_control_all_approvals = true |
Every require_approval match requires two external approvers |
Flow:
- Agent hits a gated tool → Locus creates
$LOCUS_HOME/approvals/appr_….jsonwithstatus=pendingand anargs_digest(raw args never stored). - Operators may record local review evidence with
locus approve grant appr_… --as alice; the record remains pending. - A second local label is still advisory and cannot satisfy dual-control.
- Only a closed external envelope, issued through a non-agent-accessible capability and verified against an independent trust root, may authorize the exact request. No such verifier ships in this release, so provider execution remains blocked and agents must not retry after local labels.
Secrets never appear in approval files, audit JSONL, or MCP tool results — only digests, ids, tool names, and principal labels. Principal names are restricted to [A-Za-z0-9_-] (no path separators).
Rate limiting: Locus does not yet enforce request rate limits on advisory-label or pending-record creation in process. Operators should treat approval files as sensitive control-plane state. External envelope replay protection, expiry, identity binding, and OS attestation are required before authoritative approvals can be enabled.
Be honest about what LOCUS_CONTROL_CAPABILITY does and does not protect.
What it gates. The control capability authenticates control-plane commands
to the live authority broker: init, enter, pin, leave, binding and
config mutation. Holding it means "this process may change who Locus acts as."
It is not a provider secret and never reaches workers.
Persistence is a deliberate trade. By default locus init / locus quickstart mint the capability and persist it 0600 at
$LOCUS_HOME/control_capability so eval "$(locus hook zsh)" and fresh shells
just work. The cost is stated plainly: any process running as your user can
read that file and run control commands — including a terminal agent with
shell access (locus pin …). Persistence trades strictness for onboarding; it
does not create a new class of exposure, because same-user code can already
read everything in $LOCUS_HOME (the session seal key and daemon.key live
there, mode 0600, which never isolates same-UID code — see non-goals below).
Strict posture. Operators who want control authority to exist only in shells they opened can opt out:
locus init --no-persist-capability # mint to process env only; prints the
# export line for your shell profile
locus capability unpersist # remove an already-persisted file
# (prints the export line — keep it)
locus capability status # env-only / persisted / absent — never
# prints the value
locus capability persist # return to the onboarding defaultlocus doctor reports an INFO-level control_capability_persisted finding
while the file exists — informational, not a warning, because persistence is
the supported default. The strict posture narrows durable same-user access;
it does not stop same-UID code from reading another process's environment or
memory through OS debugging interfaces (also a stated non-goal).
The real agent boundary is the MCP surface, not the CLI. locus-mcp runs
executor-restricted and deliberately without the control capability: agents
speaking MCP cannot pin, re-pin, or elevate — locus_request_pin only asks a
human. An agent granted arbitrary shell access as your user was never inside
Locus's threat model for capability secrecy; choose the strict posture to
raise the bar, and keep truly untrusted agents behind the MCP surface.
- Root / malware on the developer machine — if the OS is fully compromised, all local tools are in scope for the attacker.
- Same-UID code that can inspect or modify the trusted operator/broker process memory — filesystem mode
0600does not isolatedaemon.keyfrom code already running as the same user. The broker prevents possession of that file key alone from forging current authority, but it cannot survive theft of the in-memory control capability through an OS debugging/process-memory boundary. - A human who deliberately pins the wrong client (
locus pin personal --force) — Locus does not second-guess intentional human pin switches beyond workspace allowlists and audit hooks. - Replacing cloud IAM / SSO — Locus complements provider IAM; it does not replace org SSO policies.
- Malicious code inside a worker that already holds that binding’s credentials — blast radius is intentionally one binding. Optional OS sandbox (
LOCUS_WORKER_SANDBOX=1) is additive best-effort (macOS Seatbelt; Linux bubblewrap or path-only fallback taggedpath— not a VM). Network stays allowed by default for MCP; opt in to deny withLOCUS_WORKER_SANDBOX_NO_NETWORK=1/upstream.sandbox_no_network(bwrap--unshare-net; Seatbelt best-effort). See docs/workers.md. - Phase 1 synthetic adapters — current Supabase/GitHub/Vercel tools are identity/scope stubs and policy demos; they do not yet fan out to real upstream MCP APIs. Remote-call isolation for live workers is roadmap (see PLAN / DESIGN).
From DESIGN.md:
tools/callsucceeds only with a valid seal and tool provider ∈ binding- Worker/exec env for binding A must not contain credential material for B
- Unbound session ⇒ tool catalog is only control tools (
locus_*) - Agent-initiated pin must not change state without human action
See also the Security model section in README.md.
Use public GitHub issues for:
- Crash bugs that do not leak secrets across bindings
- DX / docs / adapter feature requests
- CI or packaging problems
When in doubt whether an issue is security-sensitive, use the private channel above.
- Store secrets as explicit CredentialRefs (
phm:NAMEorenv:VAR), never bare names or raw tokens in binding TOML. Production binaries rejecttest:regardless of environment. - Prefer workspace
require_pin = trueand tightallowed_bindingsin client repos. - Treat malformed
.locus.tomlas anUNSAFEdoctor finding; pin and autopin refuse to continue, including with--force. - Use
locus doctorandlocus whoamibefore destructive agent work. - Keep Phantom (or your vault) and Locus updated together when using
phm:refs. - For production-like deploys, set
dual_controlglobs (ordual_control_all_approvals = true) to declare the required external authority threshold. Local laptop labels do not satisfy it. - Keep
$LOCUS_HOMEmode-restricted to block other OS users and treatapprovals/andaudit/as sensitive. Do not treatdaemon.keymode0600as protection from same-UID code; delegated mutation/provider execution also requires a live broker capability. - Review
locus approve listregularly;--ttlis reserved for future externally authenticated grants and does not make local labels authoritative.