Security Model
How CoreLayer decides what an agent may do, how it asks, and how it records the answer.
CoreLayer assumes that a prompted model will eventually be wrong, an MCP server may be hostile, and a coding agent may run an unintended command. Security is therefore enforced below the model, in code that does not read the prompt.
Principles
- Local-first — state lives on your machine unless you explicitly enable cloud storage.
- Deny by default at the top —
criticalrisk is denied unless an approval flow says otherwise. - Declared risk — a tool's risk level is set at registration time; it is never inferred from the tool name or its arguments.
- Monotonic delegation — a child run may narrow its parent's permissions, never widen them.
- Everything is attributable — every execution writes an audit entry and every run writes events.
Enforcement layers
| Layer | Enforces |
|---|---|
| Permission Guard | risk → policy (auto / notify / confirm / deny) |
| Approval service | suspends a run, persists the request, resumes after a decision |
| Delegation bounds | path scope, tool set, test commands, timeout, executor and model pinning |
| Path policy | which files a coding run may touch, resolved against the worktree |
| Worktree isolation | every coding run writes in its own git worktree, not your checkout |
| Quality gates / judge | refuses to deliver a run whose evidence does not support "done" |
| Audit + event log | records who did what, with which risk, and what came back |
Risk levels and default policy
The four levels are low, medium, high, critical. The default policy is:
| Risk | Default action | Meaning |
|---|---|---|
low | auto | Execute silently |
medium | notify | Execute and surface a notification |
high | confirm | Suspend and wait for a human decision |
critical | deny | Block unless an explicit grant exists |
Per-app overrides can downgrade or tighten a single level (for example, write: false turns medium into confirm and high into deny).
Tools that spawn work declare high explicitly: dispatch_agent, run_task_graph, and destructive conversation cleanup. A name like dispatch_agent contains no risky-sounding word, which is exactly why risk is not inferred.
Human-in-the-loop approvals
Tool call or executor request
→ approval request persisted (with action digest, risk, scope)
→ run suspended
→ UI shows the request; user approves / denies / remembers
→ decision recorded; run resumed or failed- Approvals are stored, so a pending decision survives a daemon restart.
- Batch approve / deny and stale-expiry are supported for unattended queues.
- "Remember" creates a permission memory scoped to the action digest, so the same action is not asked twice.
See Permissions for policy configuration and Approvals API for the endpoints.
Data security
Local state is split across six zones under the CoreLayer home directory:
| Zone | Contents | Sensitivity |
|---|---|---|
config/ | config.json, routing.json | No secrets, safe to sync |
security/ | credentials.json | Secrets; 0600 on POSIX |
memory/ | USER.md, MEMORY.md, journal/, dreams/ | Personal |
storage/ | SQLite databases | Personal |
worktrees/ | active/, snapshots/ | Code under execution |
cache/ | symbol graphs, MCP schemas, delegation pipes | Disposable |
API keys are resolved in this order: environment variable for the provider → security/credentials.json → provider default with no key. A key is only ever sent to the provider you configured.
When cloud storage (Supabase) or self-hosted PostgreSQL is enabled, data leaves the machine. Transport is HTTPS for Supabase; you own the project and its access policies.
Network surface
- The daemon listens on loopback.
/api/runtime/shutdownrejects non-loopback peers. - Outbound connections are only to configured model providers, configured MCP servers, and your configured database.
- No telemetry, analytics, or phone-home.
Threat model
| Threat | Mitigation |
|---|---|
| Model requests a destructive tool | Declared risk + policy; high requires confirmation, critical denied |
| Prompt injection via tool output | Tool results are data, not instructions; approval still gates side effects |
| Malicious or compromised MCP server | Every tool is registered with a risk level; risky calls are gated and audited |
| Coding agent escapes its scope | Worktree isolation + path policy + narrowed delegation bounds |
| Agent delegates to bypass its limits | Bounds only narrow: paths intersect, tools intersect, checks are additive, executor/model cannot change |
| Child reports success without evidence | Completion policy + quality gates; the adversarial judge is evidence-gated |
| Silent daemon access from another host | Loopback-only binding and peer checks on shutdown |
Known limits
- Computer control is not active. Its execute path is a stub and it is not registered; do not rely on screen or input automation.
- Delegation bounds are only as strong as the executor. Executors that do not honor a path policy rely on the worktree and the post-hoc diff as the real boundary.
mcp-servers.jsonand the SQLite path still resolve from their legacy locations, so the six-zone table above applies toconfig.json, credentials, memory, worktrees, and cache — not to every file.
Next Steps
- Permission Guard — the risk model in depth
- Executor Governance — containment for coding agents
- Permissions configuration — configure policies