Skip to content

Security

Adopted principles: architecture.md. Security review and implementation items: GitHub Issue #33, #46.

Trust Model (Required Reading for Open-Source Deployment)

Section titled “Trust Model (Required Reading for Open-Source Deployment)”

FreeAnima is designed for single-user local / intranet deployment:

  • 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.
  • Default bind is 127.0.0.1; for LAN access, assess CORS and network isolation yourself.
  • 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。
RuleDescription
Sole authoritative storeVault (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 updatesSubstantive vault item updates keep up to 10 prior snapshots (entities.revisions); restore via Shell /vault history. See entity-revisions
Never commit secretsDo 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 passwordSet only in Shell /vault、bundled Chat unlock,或 Vault 浏览器扩展选项解锁;never sent as a chat message or stored in PG messages
Chat User vault unlockv1 bundled Chat onlysrc/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.mdvault.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.

SurfaceUser libraryAgent library
Habitat PGCiphertext + verifier onlyCiphertext + machine key file on disk
LLMMetadata only; subprocess secrets[] / browser_type secretMetadata only; subprocess secrets[] / browser_type secret
ShellClient master key in memoryHabitat decrypt over loopback SAP
PathContentEncryption
PostgreSQL conversation archiveconversations / messagesNo application-layer encryption
~/.anima/vault/agent-machine.keyAgent vault machine keyFile permissions (chmod 600)
~/.anima/weixin/WeChat sync stateNone

Disk backup = data access. Protect backup media accordingly.

CapabilityRisk
terminal_runDefault 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_searchPath deny: /etc, /proc, /sys, dangerous /dev, ~/.ssh private keys, FREEANIMA_HOME/vault. Heuristic deny ≠ writable-root sandbox
code_executeNo shell (Bun/Node argv). Optional secrets[] same as terminal_run. Arbitrary JS can still use node:fs—not a container sandbox
browser_typeOptional secret decrypts one Vault field and types it into the page; tool result redacts typed as *** (never plaintext)
MCP toolsCapabilities 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_metaVault metadata only; no secret values. Habitat-only (not MCP)
vault_create / vault_update / vault_deleteHabitat-only (not MCP). create/update seal plaintext into Agent library only; tool results are metadata only. User library writes stay in Vault UI
  1. Discovervault_list / vault_search / vault_get_meta (metadata only; never plaintext in tool results).
  2. 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.)
  3. 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.
  4. 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.
MeasureDescription
Same-origin RPCTanStack Start server functions same-origin by default, no CORS whitelist needed
Config API secretsHabitat config GET returns secrets in cleartext (api_key, MCP env / headers, etc.). Legacy "***" on write-back is restored via restoreMaskedSecrets
MCP configsanitizeConfigForApi keeps MCP env / headers plaintext so Habitat MCP page can edit round-trip
File path policyShared path-policy for file_* tools: /etc, vault, ssh private keys, /proc//sys, blocked devices
Terminal shell default offterminal_run default shell=false; pass shell=true only when pipes/redirection are needed (friction, not a sandbox)
Terminal command hard denyAlways-on catastrophic command policy (terminal-command-policy); cannot be disabled via env
Slash commandsWhitelist routing; every command must produce user-visible feedback; long-running commands send an immediate ack then the final result
MCP default stdioReduces port exposure
Vault isolationLLM sees vault item metadata only, not decrypted fields
Service API TokenHabitat 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

Known Gaps (Documentation ≠ Fully Implemented)

Section titled “Known Gaps (Documentation ≠ Fully Implemented)”

The following are planned in code or docs—deployers must not assume implemented:

PriorityItemStatus
P0file_* path deny (/etc/, vault, ssh, …)Implemented (path-policy)
P0terminal_run default shell=falseImplemented
P0Terminal catastrophic command hard denyImplemented (heuristic; ≠ sandbox)
P0code_execute no shellImplemented (JS FS still open)
P1Runtime Unix socket chmod 600 + handshake tokenNot implemented
P1FREEANIMA_WRITE_SAFE_ROOT / READ_SAFE_ROOTNot implemented
P2(retired) Config API field redaction maintenanceDropped — secrets not masked (incl. MCP env/headers)
P3IPC / LLM rate limitingNone
P3Session disk encryptionNone
CodeNameDescription
AExternal attackUnauthorized access, port exposure
BLLM-layer injectionPrompt injection, tool parameter manipulation, command injection
CAgent errorMistaken dangerous operations
DDependency chainThird-party lib / MCP / ACP compromise
EData securityConversation leak, key leak, memory tampering
ModuleA ExternalB LLM injectionC Agent errorD DependenciesE Data
RuntimeDefault 127.0.0.1 bindMaxTurnsExceededGap: rate limitingllm client vulnsPG unencrypted
GatewayToken in Vault / envMalicious messagesReply with sensitive infoSDK vulns
CLI / ToolsLocal shell compromisedpath deny + terminal hard deny (bypass via code_execute possible)Reduced catastrophic rmLogs may contain conversations
HTTP / Habitatservice_api_tokens Bearer token(所有来源,含 loopback)BFF does not touch LLM params directlyconfig displayVue/axiosSSE plaintext
MCP / ACPSSE auth undefinedMalicious paramsWrong delegationServer compromisedContext may contain sensitive data
VaultAgent machine key file permissionsMetadata-only tools; per-call secrets[] / browser_type secretWrong item / env_nameWeb CryptoUser 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

P0 — Shell execution + command hard deny (landed)

Section titled “P0 — Shell execution + command hard deny (landed)”
  • 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 + optional handshake token
  • Write safe root default cwd (FREEANIMA_WRITE_SAFE_ROOT)
  • When adding secret fields, sync config sanitization in platform
  1. 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
  2. Open Shell /vault; set User master password; migrate secrets from legacy pass if needed
  3. chmod 700 ~/.anima (includes vault/agent-machine.key)
  4. Bind 127.0.0.1 only, or ensure intranet isolation
  5. Review inbound MCP / ACP in Habitat UI (/habitat/mcp, ACP page); set enabled: false for untrusted external Servers
  6. Regularly backup ~/.anima/ (and legacy ~/.password-store if kept); encrypt backup media
  7. Do not commit .env, config.yaml to git
  8. Do not pass shell=true on terminal_run unless you intentionally need pipes/redirection