Adopted principles: architecture.md.
Security review and implementation items: GitHub Issue #33, #46.
逸灵风设计为单人本地 / 内网部署:
- Habitat RPC REST (
/rpc/v1/* except health probe/CORS/echo) requires a
Service API Token (Authorization: Bearer fa_at_…); create with
anima token create. Binding 127.0.0.1 limits network exposure but does
not replace token auth — any local process that can reach the port still
needs a valid token for business routes.
- 默认 bind 为
127.0.0.1;若需局域网访问,请自行评估 CORS 与网络隔离。
- Do not expose the service to the public internet without TLS and
token-protected clients (see
remote-access.md)。可选
http.tls.mode: acme(Let’s Encrypt HTTP-01,公网 :80 challenge + :2659
HTTPS)仍须 Token;不替代防火墙 / 反向代理 harden。
| Rule | Description |
|---|
| Sole authoritative store | Vault (ECS vault_item in User + Agent libraries); legacy pass (~/.password-store) is read-only on disk after migration — runtime no longer uses pass CLI |
| Versioned updates | Substantive vault item updates keep up to 10 prior snapshots (entities.revisions); restore via Shell /vault history. See entity-revisions |
| Never commit secrets | Do not write API keys, tokens, DB passwords into git. Bootstrap config.yaml: use env() (or keep secrets out of the file). Runtime LLM/MCP settings live in PG — use vault() / env() there |
| Runtime directory | ~/.anima/ (FREEANIMA_HOME overridable) holds config, agent machine key (vault/agent-machine.key), conversations, memory—recommend chmod 700 |
| User master password | Set only in Shell /vault、bundled Chat unlock,或 Vault 浏览器扩展选项解锁;never sent as a chat message or stored in PG messages |
| Chat User vault unlock | v1 bundled Chat only(src/portal/app/web / desktop / mobile);Discord / WeChat gateways cannot unlock User library |
| Browser extension | 浏览器形态入口(src/portal/extension);Service API Token 存 chrome.storage.local;主密码仅扩展内存(默认 15min);RPC 走 background host_permissions。见 portal.md、vault.md |
config.yaml is bootstrap only (read before PostgreSQL is up). Secrets
there support plaintext or env("KEY") — not vault() (Vault items
live in PG). After Habitat is connected, runtime config in PG may use
vault("item_id", "field") and env("KEY"). Agent tools that need CLI
credentials pass per-call secrets[] on terminal_run / code_execute
(child env only). Browser form fields use browser_type secret (typed
into the page; never echoed in tool results). User-library resolution still
requires an unlocked Chat session on the client.
| Surface | User library | Agent library |
|---|
| Habitat PG | Ciphertext + verifier only | Ciphertext + machine key file on disk |
| LLM | Metadata only; subprocess secrets[] / browser_type secret | Metadata only; subprocess secrets[] / browser_type secret |
| Shell | Client master key in memory | Habitat decrypt over loopback SAP |
| 路径 | 内容 | 加密 |
|---|
| PostgreSQL 对话存档 | conversations / messages | 无应用层加密 |
~/.anima/vault/agent-machine.key | Agent 保险库机读密钥 | 文件权限(chmod 600) |
~/.anima/weixin/ | 微信同步状态 | 无 |
磁盘备份 = 数据访问。请妥善保护备份介质。
| Capability | Risk |
|---|
terminal_run | Default shell: false (argv spawn). Explicit shell: true enables pipes/redirection. Optional secrets[] decrypts Vault into that subprocess env only (not Habitat process.env). Always-on hard deny for catastrophic targets—not an OS sandbox; bypass via code_execute / interpreters remains |
file_read / file_write / file_delete / file_search | Path deny: /etc, /proc, /sys, dangerous /dev, ~/.ssh private keys, FREEANIMA_HOME/vault. Heuristic deny ≠ writable-root sandbox |
code_execute | No shell (Bun/Node argv). Optional secrets[] same as terminal_run. Arbitrary JS can still use node:fs—not a container sandbox |
browser_type | Optional secret decrypts one Vault field and types it into the page; tool result redacts typed as *** (never plaintext) |
| MCP tools | Capabilities entirely determined by external Server; stdio default, SSE auth scheme not fully defined |
| Capability mask (Mask) | Conversation-level tool whitelist; deny overrides allow; LLM cannot see policy details; see src/host/core/mask/ |
| ACP (Cursor) | Default auto-approve all session/request_permission (allow-once) |
vault_list / vault_search / vault_get_meta | Vault metadata only; no secret values. Habitat-only (not MCP) |
vault_create / vault_update / vault_delete | Habitat-only (not MCP). create/update seal plaintext into Agent library only; tool results are metadata only. User library writes stay in Vault UI |
- Discover —
vault_list / vault_search / vault_get_meta (metadata
only; never plaintext in tool results).
- Write (Agent library) —
vault_create / vault_update /
vault_delete in Habitat chat only. create/update accept plaintext
secrets to seal on Habitat; results return metadata only. User library:
Vault UI. (Entire vault ToolSet is not MCP-exposed.)
- Use — pass
secrets: [{ id, env_name, field?, subject_kind? }] on
the same terminal_run or code_execute call that needs the credential
(e.g. GH_TOKEN for gh); or secret: { id, field } on browser_type
for form fields. For field: "totp", the resolved value is the current
TOTP code (RFC 6238), not the stored Base32 secret.
- Scope — plaintext is decrypted for that call only (
secrets[] →
child env; browser_type secret → Camofox type payload). It is
not written to Habitat process.env and not returned in tool
results. Default shell=false: use argv form (printenv GH_TOKEN, gh …), not echo $VAR.
| Measure | Description |
|---|
| Same-origin RPC | TanStack Start server functions same-origin by default, no CORS whitelist needed |
| Config API secrets | Habitat config GET returns secrets in cleartext (api_key, MCP env / headers, etc.). Legacy "***" on write-back is restored via restoreMaskedSecrets |
| MCP config | sanitizeConfigForApi keeps MCP env / headers plaintext so Habitat MCP page can edit round-trip |
| File path policy | Shared path-policy for file_* tools: /etc, vault, ssh private keys, /proc//sys, blocked devices |
| Terminal shell default off | terminal_run default shell=false; pass shell=true only when pipes/redirection are needed (friction, not a sandbox) |
| Terminal command hard deny | Always-on catastrophic command policy (terminal-command-policy); cannot be disabled via env |
| Slash commands | Whitelist routing; every command must produce user-visible feedback; long-running commands send an immediate ack then the final result |
| MCP default stdio | Reduces port exposure |
| Vault isolation | LLM sees vault item metadata only, not decrypted fields |
| Service API Token | Habitat RPC REST /rpc/v1/* routes require Authorization: Bearer fa_at_… (service_api_tokens PG table); health probe/CORS/echo exempt. |
| CI secret scanning | .github/workflows/security.yml (Gitleaks); GitHub Secret scanning + Push protection (free for public repos) |
.gitignore | .env.*, config.yaml, private key suffixes |
以下在代码或文档中规划,部署者请勿假设已实现:
| Priority | Item | Status |
|---|
| P0 | file_* path deny (/etc/, vault, ssh, …) | Implemented (path-policy) |
| P0 | terminal_run default shell=false | Implemented |
| P0 | Terminal catastrophic command hard deny | Implemented (heuristic; ≠ sandbox) |
| P0 | code_execute no shell | Implemented (JS FS still open) |
| P1 | Runtime Unix socket chmod 600 + handshake token | Not implemented |
| P1 | FREEANIMA_WRITE_SAFE_ROOT / READ_SAFE_ROOT | Not implemented |
| P2 | (retired) Config API field redaction maintenance | Dropped — secrets not masked (incl. MCP env/headers) |
| P3 | IPC / LLM rate limiting | None |
| P3 | Session disk encryption | None |
| 代号 | 名称 | 说明 |
|---|
| A | 外网攻击 | 未授权访问、端口暴露 |
| B | LLM 层注入 | Prompt 注入、工具参数操纵、命令注入 |
| C | Agent 过失 | 误执行危险操作 |
| D | 依赖链 | 三方库 / MCP / ACP 被攻陷 |
| E | 数据安全 | 对话泄露、密钥泄露、记忆篡改 |
| Module | A External | B LLM injection | C Agent error | D Dependencies | E Data |
|---|
| Runtime | Default 127.0.0.1 bind | MaxTurnsExceeded | Gap: rate limiting | llm client vulns | PG unencrypted |
| Gateway | Token in Vault / env | Malicious messages | Reply with sensitive info | SDK vulns | — |
| CLI / Tools | Local shell compromised | path deny + terminal hard deny (bypass via code_execute possible) | Reduced catastrophic rm | — | Logs may contain conversations |
| HTTP / Habitat | service_api_tokens Bearer token(所有来源,含 loopback) | BFF does not touch LLM params directly | config display | Vue/axios | SSE plaintext |
| MCP / ACP | SSE auth undefined | Malicious params | Wrong delegation | Server compromised | Context may contain sensitive data |
| Vault | Agent machine key file permissions | Metadata-only tools; per-call secrets[] / browser_type secret | Wrong item / env_name | Web Crypto | User MP never in PG messages |
- Read/write/delete/search deny:
/etc/, /proc/, ~/.ssh/ private keys,
vault under FREEANIMA_HOME
- Optional
FREEANIMA_READ_SAFE_ROOT still P1
terminal_run default shell=false; pass shell=true only for
pipes/redirection (not a sandbox)
- Always-on deny for catastrophic
rm/rmdir, mkfs*, dd of=/dev/…,
fork bombs, power commands, recursive chmod/chown on / or $HOME,
destructive find on system/home roots
code_execute remains argv-only (no shell); not a process sandbox
- Unix socket
chmod 600 + 可选握手 token
- 写安全根默认 cwd(
FREEANIMA_WRITE_SAFE_ROOT)
- When adding secret fields, sync config sanitization in platform
- Copy
config.example.yaml →
~/.anima/config.yaml; use env() for bootstrap secrets — do not
write plaintext secrets in config; vault() is for PG runtime config
after Habitat is up
- Open Shell
/vault; set User master password; migrate secrets from
legacy pass if needed
chmod 700 ~/.anima (includes vault/agent-machine.key)
- 仅 bind
127.0.0.1,或确保内网隔离
- Review inbound MCP / ACP in Habitat UI (
/habitat/mcp, ACP page); set
enabled: false for untrusted external Servers
- 定期备份
~/.anima/(若保留则含遗留 ~/.password-store);备份介质加密存储
- 勿将
.env、config.yaml 提交到 git
- Do not pass
shell=true on terminal_run unless you intentionally need
pipes/redirection