# Defense-in-depth score: Eigent

**Repo:** https://github.com/eigent-ai/eigent · **Commit:** `5877598509f64e4c2d33771a1c60bf7dd3666977` (1.0.5) · **Reviewed:** 2026-10-05
**What it is:** Open-source desktop "cowork" app that runs single agents or a multi-agent workforce on the user's machine, built on CAMEL-AI.
**Category:** AI Assistants
**Scored configuration:** Desktop app (Electron plus local Python backend) as installed, default Space permission profile "request approval", default single-agent toolkits, no extra MCP servers.
**Agent surface (default):** code execution yes · filesystem write yes · network egress yes · external credentials yes · persistent memory yes · untrusted input yes · third party extensions opt-in · sub agents yes · external communication yes

## Score: 3.6 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L0 | L1 | L0 | L0 | 0.07 | none | **0.07** | Medium |
| C2 | Approval gates | L3 | L3 | L3 | L2 | 0.70 | none | **0.70** | High |
| C3 | Tool & action scoping | L1 | L1 | L1 | L0 | 0.20 | none | **0.20** | Medium |
| C4 | Code-execution isolation | L1 | L1 | L2 | L0 | 0.25 | none | **0.25** | Medium |
| C5 | Untrusted input blast radius | L2 | L2 | L2 | L1 | 0.45 | none | **0.45** | High |
| C6 | Memory, context & configuration integrity | L2 | L2 | L2 | L1 | 0.45 | none | **0.45** | High |
| C7 | Third-party extensions | L1 | L1 | L1 | L1 | 0.25 | none | **0.25** | Medium |
| C8 | Secrets & sensitive-data protection | L2 | L1 | L0 | L0 | 0.23 | none | **0.23** | Medium |
| C9 | Audit & traceability | L3 | L3 | L2 | L4 | 0.75 | none | **0.75** | High |
| C10 | Limits & kill switch | L1 | L1 | L1 | L1 | 0.25 | none | **0.25** | High |


Eigent puts a careful, deterministic approval gate in front of almost everything: by default every shell command, file write, browser action and MCP or connector call waits for the user to approve the call and its arguments, and the approval is bound to the call that actually runs. Behind that gate there is little containment. Approved commands run on the host as the user, with the user's connector keys in their environment, and web-fetch and search tools run unattended, so injected content can send data out without a prompt. There are no default step or cost limits; the durable run journal is a strong audit trail.

## Critical gaps
- Tools run with the desktop user's full authority and the connector keys saved in ~/.eigent/.env, so a hijacked session reaches the user's files and every connected account. (ASI03, T3; C1). Evidence: [backend/app/agent/toolkit/terminal_toolkit.py:344-366](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L344-L366); [backend/app/component/environment.py:93-103](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/component/environment.py#L93-L103)
- Shell commands run on the host as the desktop user with the user's network access and connector keys; there is no sandbox behind the approval prompt. (ASI05, T11; C4). Evidence: [backend/app/agent/toolkit/terminal_toolkit.py:344-366](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L344-L366); [SECURITY.md:36-41](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/SECURITY.md#L36-L41)

## Criterion details

### C1 Identity & least privilege: 0.07 (medium confidence)

Eigent runs as the desktop user and does not narrow that authority. Shell commands inherit the backend's environment, which includes connector API keys loaded from the user's ~/.eigent/.env file; only Eigent's own control credentials are stripped. Connected services (mail, drive, chat, code hosting) are reached with the user's long-lived keys or tokens. The permission gate decides whether an action may run, but it does not give tools a narrower identity.

- **S L0:** Tools act with the user's ambient OS authority and the connector keys the user saved; no per-tool or per-task credential scoping exists. Evidence: [backend/app/component/environment.py:66-75](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/component/environment.py#L66-L75); [backend/app/component/environment.py:93-103](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/component/environment.py#L93-L103); searched `rg -n -S -e 'token_exchange|downscop|on_behalf_of|assume_role'` in `backend/app` → 0 hits (no credential exchange or downscoping anywhere in the backend) (verified)
  - *To reach the next level:* Use a dedicated, role-scoped identity or per-capability credentials instead of the user's ambient keys.
- **C L1:** The terminal environment starts from the backend's own environment and removes only Eigent control-plane keys (local capability, secret-broker and Authorization variables), so every shell subprocess receives the user's connector keys. Evidence: [backend/app/agent/toolkit/terminal_toolkit.py:344-366](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L344-L366); [backend/app/agent/toolkit/terminal_toolkit.py:161-169](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L161-L169) (inferred)
  - *To reach the next level:* Give tool subprocesses and MCP servers a scoped identity or a minimal environment instead of the user's ambient credentials.
- **D L0:** A fresh install runs every tool with the user's full privilege; there is no narrower default identity to widen. Evidence: [backend/app/agent/factory/toolkit_assembler.py:640-647](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L640-L647); [SECURITY.md:36-41](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/SECURITY.md#L36-L41) (verified)
  - *To reach the next level:* Ship a near-minimal default identity where writes need explicit operator elevation.
- **B L0:** A hijacked session can use the user's whole home directory and every connected account whose key is stored in ~/.eigent/.env, which Electron writes for connectors. Evidence: [electron/main/index.ts:2427-2440](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/electron/main/index.ts#L2427-L2440); [SECURITY.md:38-41](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/SECURITY.md#L38-L41) (verified)
  - *To reach the next level:* Limit what the agent's credentials reach to one project or service, mostly read-only.
- **Cap:** none

### C2 Approval gates: 0.70 (high confidence)

This is Eigent's strongest control. Every tool call is recorded and passed through a deterministic permission engine before it runs; in the default "request approval" profile anything that is not a code-declared read (shell commands, file writes, browser actions, MCP and connector calls, memory changes) waits for the user, who sees the call and its arguments (large ones as a preview). The approval is bound to a digest of the full call and checked again before dispatch, shell approvals can't be saved as standing rules, and the model cannot change its own permission profile. Gaps: web-fetch and search tools are classed as reads and run unattended, unknown tools are prompted rather than refused, full-access mode is a persistent per-space setting rather than a time-bounded one, and external actions such as sending messages can't be undone.

- **S L3:** Per-call approval shows the call's (secret-redacted) arguments with risk tiers, policy rules match operations and resources with deny precedence, and dispatch requires an approved, trusted decision whose digest equals the executed call. Evidence: [backend/app/permission_policy/runtime.py:206-218](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/runtime.py#L206-L218); [backend/app/permission_policy/engine.py:109-135](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/engine.py#L109-L135); [backend/app/permission_policy/tool_actions.py:762-799](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/tool_actions.py#L762-L799); [src/components/ChatBox/BottomBox/useEventNativeHumanControl.ts:312-320](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/src/components/ChatBox/BottomBox/useEventNativeHumanControl.ts#L312-L320) (verified)
  - *To reach the next level:* Show the complete call for large arguments (they are cut to a preview) and ship argument-level allow, deny and escalate rules rather than an empty rule set.
- **C L3:** Both the sync and async tool paths of the shared agent class prepare a checkpoint and call the gate before dispatch, covering MCP tools and sub-agent tool calls; unknown tools default to the prompted write class, and auto-allowed tools are a code-owned list of reads. Evidence: [backend/app/agent/listen_chat_agent.py:1091-1114](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/listen_chat_agent.py#L1091-L1114); [backend/app/agent/listen_chat_agent.py:1376-1384](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/listen_chat_agent.py#L1376-L1384); [backend/app/run_runtime/tool_checkpoint.py:42-65](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_runtime/tool_checkpoint.py#L42-L65); [backend/app/agent/factory/toolkit_assembler.py:68-72](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L68-L72) (verified)
  - *To reach the next level:* Reject unknown tools by default and remove the egress-capable web-fetch and search tools from the auto-allowed read list.
- **D L3:** Approval is the default profile; auto-review and full access are explicit profile choices that are only honoured with a trusted attestation, bundles can only tighten policy, and permission changes are hard-denied to tools. Evidence: [backend/app/permission_policy/service.py:57-60](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/service.py#L57-L60); [backend/app/permission_policy/service.py:184-197](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/service.py#L184-L197); [backend/app/permission_policy/service.py:147-161](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/service.py#L147-L161); [backend/app/permission_policy/engine.py:94-100](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/engine.py#L94-L100); [backend/app/permission_policy/models.py:814-819](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/models.py#L814-L819) (verified)
  - *To reach the next level:* Make elevated profiles session- or time-bounded instead of persistent per space.
- **B L2:** Workspace files sit in a Git-backed workspace with path restore, but browser actions, messages and connector writes are irreversible once approved and there are no rate or quantity limits. Evidence: [backend/app/workspace_git/backend.py:1281-1285](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/workspace_git/backend.py#L1281-L1285); [backend/app/agent/factory/toolkit_assembler.py:122-136](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L122-L136) (verified)
  - *To reach the next level:* Add previews or dry-runs for external actions and checkpoints for all filesystem writes.
- **Cap:** none

### C3 Tool & action scoping: 0.20 (medium confidence)

The default toolset is broad: a host shell, file writes, a browser, web fetch and search, MCP servers and delegation are all enabled. The shell takes an arbitrary command string filtered only by the underlying library's safe mode, and web fetch accepts any URL. Eigent derives risk tags from paths and commands (credential directories, Git hooks, auto-executed project files, paths outside the workspace) but uses them to escalate approval rather than to restrict what tools may do. A read-only profile exists but is not the default.

- **S L1:** Validation is heuristic: a library safe-mode filter on shell strings plus Eigent's risk tagging; the shell and web fetch still accept arbitrary commands and URLs. Evidence: [backend/app/agent/factory/toolkit_assembler.py:640-647](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L640-L647); [backend/app/agent/toolkit/terminal_toolkit.py:379-388](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L379-L388); [backend/app/permission_policy/tool_actions.py:784-799](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/tool_actions.py#L784-L799) (inferred)
  - *To reach the next level:* Validate arguments against allowlists in code (resolved-path containment, host allowlists that block internal addresses).
- **C L1:** Only a few tools add their own checks (file-write overlay containment, the Git preview grammar); web fetch, search and MCP tools pass arguments through. Evidence: [backend/app/utils/space_overlay_client.py:133-145](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/utils/space_overlay_client.py#L133-L145); [backend/app/agent/factory/toolkit_assembler.py:68-72](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L68-L72) (verified)
  - *To reach the next level:* Apply argument validation to most built-in tools.
- **D L1:** Write, exec and network toolkits are all enabled by default and can only be switched off individually through toolkit configuration. Evidence: [backend/app/agent/factory/toolkit_assembler.py:122-136](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L122-L136); [backend/app/agent/factory/toolkit_assembler.py:239-257](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L239-L257) (verified)
  - *To reach the next level:* Make the default tool set read-only and require explicit enabling of write and exec tools.
- **B L0:** The shell tool reaches the whole machine as the desktop user once approved. Evidence: [SECURITY.md:36-41](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/SECURITY.md#L36-L41) (verified)
  - *To reach the next level:* Scope tools to the workspace with quantity bounds.
- **Cap:** none

### C4 Code-execution isolation: 0.25 (medium confidence)

Model-generated shell commands run directly on the host as the desktop user, in their own process group, with a command filter from the CAMEL library's safe mode. There is no container, OS sandbox profile or network restriction, and the project's own security policy says the permission system is not an OS sandbox and that shell access equals full access to the user's files and processes. The approval gate in front of each command is the only barrier.

- **S L1:** The only execution boundary is the library's safe-mode command filter applied to the shell string; commands then run as a same-user host subprocess. Evidence: [backend/app/agent/toolkit/terminal_toolkit.py:379-388](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L379-L388); [backend/app/agent/toolkit/terminal_toolkit.py:592-594](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L592-L594); searched `rg -n -S -e 'bwrap|bubblewrap|landlock|seccomp|sandbox-exec|firejail|nsjail|gvisor|runsc'` in `backend/app electron/main` → 0 hits (no OS-level sandbox primitive in the backend or Electron main process) (inferred)
  - *To reach the next level:* Run commands in a hardened container or an OS sandbox profile limiting writes to the workspace and denying network by default.
- **C L1:** The filter applies to the terminal tool; skill scripts, MCP stdio servers and browser console execution are not covered by it. Evidence: [backend/app/agent/factory/toolkit_assembler.py:640-667](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L640-L667); [backend/app/agent/tools.py:169-172](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/tools.py#L169-L172) (inferred)
  - *To reach the next level:* Route every execution path (skill scripts, MCP servers, package installs) through the same boundary.
- **D L2:** Safe mode is set by default but the terminal options merged from toolkit configuration can override it, without a warning. Evidence: [backend/app/agent/factory/toolkit_assembler.py:643-648](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L643-L648) (verified)
  - *To reach the next level:* Require an explicit, loudly named operator flag to disable the boundary.
- **B L0:** A command that passes the filter runs with the user's home directory, full network access and the backend environment, including connector keys. Evidence: [backend/app/agent/toolkit/terminal_toolkit.py:344-366](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L344-L366); [SECURITY.md:36-41](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/SECURITY.md#L36-L41) (verified)
  - *To reach the next level:* Confine execution to a workspace-only mount with no secrets in the environment and egress off or allowlisted.
- **Cap:** none

### C5 Untrusted input blast radius: 0.45 (high confidence)

Eigent reads web pages, search results, files and MCP output with no marking of what is untrusted. What limits a hijacked session is the approval gate: in the default profile writes, shell commands, browser actions and connector calls all need the user's approval. Web fetch and search are treated as reads and run without approval, so injected instructions can send data out through a URL without the user being asked. Irreversible actions still need a human.

- **S L2:** State-changing tools need approval in the default profile, but egress through web fetch and search is auto-allowed. Evidence: [backend/app/permission_policy/engine.py:145-167](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/engine.py#L145-L167); [backend/app/run_runtime/tool_checkpoint.py:42-65](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_runtime/tool_checkpoint.py#L42-L65) (verified)
  - *To reach the next level:* Once untrusted content enters a session, force every egress-capable tool through approval or disable it.
- **C L2:** The gate applies regardless of where an instruction came from, but nothing distinguishes tool results, MCP output or delegated agents' messages from the principal's request. Evidence: [backend/app/agent/factory/toolkit_assembler.py:283-296](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L283-L296); [backend/app/agent/factory/toolkit_assembler.py:343-385](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L343-L385) (verified)
  - *To reach the next level:* Track untrusted sources (tool results, MCP output, tool descriptions, sub-agent messages) and apply the limit to each.
- **D L2:** The limit is on by default; the user can switch a space to auto-review or full access, which removes it, and there is no taint-aware mode to keep. Evidence: [backend/app/permission_policy/service.py:57-60](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/service.py#L57-L60); [backend/app/permission_policy/engine.py:152-158](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/engine.py#L152-L158) (verified)
  - *To reach the next level:* Warn explicitly when the protection is disabled and keep it out of reach of anything the agent reads.
- **B L1:** Assuming a hijack, workspace files and memory can be read and sent out through auto-allowed fetch tools without a human; irreversible actions need approval. Evidence: [backend/app/agent/factory/toolkit_assembler.py:68-82](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L68-L82); [backend/app/run_runtime/tool_checkpoint.py:42-65](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_runtime/tool_checkpoint.py#L42-L65) (verified)
  - *To reach the next level:* Put the auto-allowed fetch and search tools behind approval so that sending data out, like irreversible actions, needs a human.
- **Cap:** none

### C6 Memory, context & configuration integrity: 0.45 (high confidence)

Persistent memory is well guarded: the agent's memory write, update, forget and promote tools all go through the approval gate and a memory review step, automatic memory extraction only reads the user's own messages, and memory is stored per user, space and project. The weaker part is auto-loaded context: skills in the workspace folder (skills/, .eigent/skills, .agents/skills) and a project skills-config file are discovered silently and take precedence over the user's own skills. Workspace .env files are not loaded.

- **S L2:** Memory writes require approval and review, but skill instructions from the workspace load silently as agent context. Evidence: [backend/app/agent/toolkit/memory_toolkit.py:403-415](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/memory_toolkit.py#L403-L415); [backend/app/agent/toolkit/skill_toolkit.py:388-397](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/skill_toolkit.py#L388-L397) (verified)
  - *To reach the next level:* Require an explicit workspace-trust decision before loading repo-scope skills and project skill configuration.
- **C L2:** The memory store and automatic extraction are controlled; workspace skills and project skill configuration are not. Evidence: [backend/app/lightweight_memory/maintainer.py:158-160](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/lightweight_memory/maintainer.py#L158-L160); [backend/app/agent/toolkit/skill_toolkit.py:109-116](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/skill_toolkit.py#L109-L116) (verified)
  - *To reach the next level:* Cover auto-loaded workspace files with the same control as memory.
- **D L2:** Memory lives under per-user, per-space and per-project directories by default. Evidence: [backend/app/memory/paths.py:15-24](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/memory/paths.py#L15-L24) (verified)
  - *To reach the next level:* Prevent the model from writing outside its namespace and enforce that in the store.
- **B L1:** Skill text from the workspace persists across the user's sessions and can steer tool use, including unattended read and fetch tools. Evidence: [backend/app/agent/toolkit/skill_toolkit.py:376-397](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/skill_toolkit.py#L376-L397) (verified)
  - *To reach the next level:* Limit persistent context to influencing gated actions only.
- **Cap:** none

### C7 Third-party extensions: 0.25 (medium confidence)

Third-party code arrives mainly as MCP servers the user adds (usually unpinned package commands) and as skills, which can include scripts. Workspace bundles are installed through a review step with digests, but plain MCP servers and skills are not pinned or verified, and skills placed in the workspace folder are picked up without an install step. Running a skill script or an MCP tool still needs approval, but MCP servers run as separate processes under the same user account.

- **S L1:** MCP servers are launched from the user's configuration as given, with no version pin or integrity check. Evidence: [backend/app/agent/factory/toolkit_assembler.py:343-385](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L343-L385); [backend/app/agent/tools.py:169-172](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/tools.py#L169-L172) (verified)
  - *To reach the next level:* Pin extension versions.
- **C L1:** Only workspace bundles are checked through a review-first install with manifest digests; MCP servers and skills are not. Evidence: [backend/app/permission_policy/service.py:141-161](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/service.py#L141-L161) (verified)
  - *To reach the next level:* Verify most extension types, including MCP servers and skills.
- **D L1:** Nothing third-party is installed by default, but skills in the workspace folder are discovered without consent. Evidence: [backend/app/agent/toolkit/skill_toolkit.py:388-397](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/skill_toolkit.py#L388-L397) (verified)
  - *To reach the next level:* Show exactly what will run before enabling an extension and never let the workspace add one silently.
- **B L1:** MCP servers are separate processes under the same user; Eigent strips only secret-broker keys from their configured environment and they can read the user's files, including ~/.eigent/.env. Evidence: [backend/app/agent/factory/toolkit_assembler.py:366-383](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/factory/toolkit_assembler.py#L366-L383) (inferred)
  - *To reach the next level:* Run each extension with a scrubbed environment and its own scoped credentials.
- **Cap:** none

### C8 Secrets & sensitive-data protection: 0.23 (medium confidence)

Connector keys are stored in plaintext in ~/.eigent/.env with owner-only permissions and loaded into the backend's environment, where shell commands inherit them. Eigent redacts secrets from persisted tool arguments, permission cards and journal text with a shared redaction helper. Model request and response logging from the CAMEL library is switched on by default and is not redacted. Third-party telemetry only starts when the user supplies Langfuse keys.

- **S L2:** Plaintext key file with 0600 permissions, plus pattern-based redaction of persisted arguments and journal text. Evidence: [backend/app/component/environment.py:43-63](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/component/environment.py#L43-L63); [backend/app/permission_policy/models.py:595-606](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/models.py#L595-L606) (verified)
  - *To reach the next level:* Store keys in the OS keychain and redact before logs and model-bound messages on all major paths.
- **C L1:** Redaction covers the run journal and permission records, but not model-bound messages, library model logs or subprocess environments. Evidence: [backend/app/permission_policy/models.py:595-606](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/models.py#L595-L606); [backend/app/agent/toolkit/terminal_toolkit.py:344-366](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L344-L366) (verified)
  - *To reach the next level:* Extend redaction to logs and saved transcripts, including the library model logs.
- **D L0:** Telemetry is opt-in, but full model payload logging is enabled by default for every run. Evidence: [backend/app/controller/chat_controller.py:138-139](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/controller/chat_controller.py#L138-L139); [backend/app/utils/telemetry/workforce_metrics.py:140-167](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/utils/telemetry/workforce_metrics.py#L140-L167) (verified)
  - *To reach the next level:* Turn verbose payload logging off by default.
- **B L0:** Long-lived connector and model keys sit in the process environment that every shell subprocess inherits. Evidence: [backend/app/agent/toolkit/terminal_toolkit.py:344-366](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L344-L366); [electron/main/index.ts:2427-2440](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/electron/main/index.ts#L2427-L2440) (inferred)
  - *To reach the next level:* Keep long-lived keys out of subprocess environments and prefer scoped keys.
- **Cap:** none

### C9 Audit & traceability: 0.75 (high confidence)

Eigent keeps a durable SQLite run journal outside the workspace. Every tool call is checkpointed with its arguments, agent, step and delegated sub-agent step before it is allowed to run, and permission decisions are written as security audit events with the actor type (system or auto-reviewer). A tool call cannot be dispatched without that checkpoint. The journal is an ordinary local database the same user could edit, with no hash chain or signing; the gate blocks shell commands that name the journal files, but the project itself says that holds only within the same-user boundary.

- **S L3:** Structured per-call records with agent, step and delegation chain, plus security audit events with actor attribution and action digests. Evidence: [backend/app/run_runtime/tool_checkpoint.py:801-830](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_runtime/tool_checkpoint.py#L801-L830); [backend/app/run_journal/store.py:15903-15916](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_journal/store.py#L15903-L15916); [backend/app/run_journal/store.py:1093-1104](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_journal/store.py#L1093-L1104); searched `rg -n -S -e 'prev_hash|hash_chain|previous_hash|chain_digest'` in `backend/app/run_journal` → 0 hits (no hash chaining of journal records) (verified)
  - *To reach the next level:* Make the record tamper-evident (hash chain, signing or off-host shipping) with a standard export.
- **C L3:** All tool calls, including MCP and sub-agent calls, plus approval and denial decisions are recorded. Evidence: [backend/app/permission_policy/service.py:225-241](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/service.py#L225-L241); [backend/app/agent/listen_chat_agent.py:1091-1104](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/listen_chat_agent.py#L1091-L1104) (verified)
  - *To reach the next level:* Also record configuration changes, memory writes and credential use in the same audit trail.
- **D L2:** On by default at ~/.eigent/run-journal.sqlite3, outside the workspace, but writable by the agent's own user and process. Evidence: [backend/app/run_journal/paths.py:20-23](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_journal/paths.py#L20-L23); [SECURITY.md:46-49](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/SECURITY.md#L46-L49) (verified)
  - *To reach the next level:* Write the record from a component the model cannot control.
- **B L4:** Dispatch fails closed: without a durable checkpoint and admitted run context a tool call is rejected before it runs. Evidence: [backend/app/permission_policy/runtime.py:51-59](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/permission_policy/runtime.py#L51-L59); [backend/app/run_runtime/tool_checkpoint.py:715-730](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_runtime/tool_checkpoint.py#L715-L730) (verified)
- **Cap:** none

### C10 Limits & kill switch: 0.25 (high confidence)

In the default execution path there is no step cap and no token or cost budget. A hard per-step timeout exists but is off unless an environment variable sets it; what is on by default is a 30-minute no-progress timeout for agents and workforce tasks. Delegation depth is capped at one level, shell commands run in their own process group that can be killed, and the stop button asks the workforce to stop gracefully.

- **S L1:** Only a sliding no-progress timeout and a delegation-depth cap are on by default; step and cost limits are absent and the hard step timeout is opt-in. Evidence: [backend/app/agent/listen_chat_agent.py:98-110](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/listen_chat_agent.py#L98-L110); searched `rg -n -S -e 'max_cost|cost_limit|token_budget_total|spend_limit'` in `backend/app` → 0 hits (no cost or spend cap) (verified)
  - *To reach the next level:* Add an enforced iteration cap plus a run time limit or token/cost budget.
- **C L1:** The stall timeout covers agent activities and workforce tasks; sub-agents are depth-limited but do not share a budget. Evidence: [backend/app/agent/toolkit/depth_limited_agent_toolkit.py:44-49](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/depth_limited_agent_toolkit.py#L44-L49); [backend/app/utils/workforce.py:169-175](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/utils/workforce.py#L169-L175) (verified)
  - *To reach the next level:* Add per-tool timeouts alongside the loop limits.
- **D L1:** The defaults that exist are large (30 minutes of no progress) and the hard caps are unset. Evidence: [backend/app/run_runtime/timeout_config.py:32-39](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/run_runtime/timeout_config.py#L32-L39) (verified)
  - *To reach the next level:* Ship sensible step and time defaults.
- **B L1:** A runaway run is bounded only by the stall timeout and spend is unbounded; stop is cooperative, though terminal process groups are killed on cleanup. Evidence: [backend/app/controller/chat_controller.py:2747-2752](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/controller/chat_controller.py#L2747-L2752); [backend/app/agent/toolkit/terminal_toolkit.py:698](https://github.com/eigent-ai/eigent/blob/5877598509f64e4c2d33771a1c60bf7dd3666977/backend/app/agent/toolkit/terminal_toolkit.py#L698) (verified)
  - *To reach the next level:* Set tight per-run time and cost ceilings and cancel pending calls on stop.
- **Cap:** none
- **Notes:** An opt-in managed execution path sets an iteration cap of 64 and a step timeout (backend/app/agent/agent_model.py 461-470), but it is only admitted for spaces in the full-access profile and was not scored as an alternative.

## Rule-of-Two check
[A] untrusted input: Web search, web fetch and browser pages enabled by default (backend/app/agent/factory/toolkit_assembler.py:122-136) · [B] sensitive data/systems: Workspace files via auto-allowed read tools and connector keys loaded into the process environment (backend/app/component/environment.py:93-103) · [C] state change / egress: Auto-allowed web fetch to any URL (backend/app/run_runtime/tool_checkpoint.py:42-65); shell and connector writes behind approval · Same default session? Yes

## Highest-impact improvements
1. Run shell commands and skill scripts inside an OS sandbox or container limited to the workspace, with network off or allowlisted. (C4 S L1→L3, +0.150 before caps; Playbook 3)
2. Require approval for web fetch and search once a session has read untrusted content, instead of treating them as reads. (C5 S L2→L3, +0.075 before caps; Playbook 1)
3. Turn library model payload logging off by default. (C8 D L0→L2, +0.100 before caps; Playbook 4)
4. Start tool subprocesses and MCP servers with a minimal environment instead of the backend's full environment. (C1 C L1→L2, +0.075 before caps; Playbook 4)
5. Set default iteration and token budgets for every run, shared with delegated sub-agents. (C10 S L1→L2, +0.075 before caps; Playbook 3 step 3)

## Re-audit log
- C2 S: L4 → L3. Large tool arguments are shown to the approver only as a bounded preview (models.py persistence_payload), and no argument-level rules ship by default, so the L4 element of exact display plus argument policy is not fully met.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- Behaviour of the third-party CAMEL library (camel-ai 0.2.91a7) was not read: the terminal safe-mode filter, the WebFetchToolkit and the MCP stdio environment are inferred from how Eigent calls them.
- The Electron main process and renderer were reviewed only where they touch approvals, rendering and secret storage; the separate server/ deployment and Eigent's cloud services were not scored.
- The opt-in managed execution path and the Workforce coordinator's planning prompts were not scored separately.
- Model behaviour (refusals, alignment) is out of scope; only deterministic controls were credited.
