Skip to content

Security: ParachuteComputer/paraclaw

Security

docs/SECURITY.md

parachute-agent Security Model

Trust Model

Entity Trust Level Rationale
Main group Trusted Private self-chat, admin control
Non-main groups Untrusted Other users may be malicious
Container agents Sandboxed Isolated execution environment
Incoming messages User input Potential prompt injection

Security Boundaries

1. Container Isolation (Primary Boundary)

Agents execute in containers (lightweight Linux VMs), providing:

  • Process isolation - Container processes cannot affect the host
  • Filesystem isolation - Only explicitly mounted directories are visible
  • Non-root execution - Runs as unprivileged node user (uid 1000)
  • Ephemeral containers - Fresh environment per invocation (--rm)

This is the primary security boundary. Rather than relying on application-level permission checks, the attack surface is limited by what's mounted.

2. Mount Security

External Allowlist - Mount permissions stored at ~/.config/parachute-agent/mount-allowlist.json, which is:

  • Outside project root
  • Never mounted into containers
  • Cannot be modified by agents

(Pre-0.1.0 installs auto-migrate from ~/.config/paraclaw/ on first 0.1.0 boot. Compat read drops in 0.2.0.)

Default Blocked Patterns:

.ssh, .gnupg, .aws, .azure, .gcloud, .kube, .docker,
credentials, .env, .netrc, .npmrc, id_rsa, id_ed25519,
private_key, .secret

Protections:

  • Symlink resolution before validation (prevents traversal attacks)
  • Container path validation (rejects .. and absolute paths)
  • nonMainReadOnly option forces read-only for non-main groups

Read-Only Project Root:

The main group's project root is mounted read-only. Writable paths the agent needs (store, group folder, IPC, .claude/) are mounted separately. This prevents the agent from modifying host application code (src/, dist/, package.json, etc.) which would bypass the sandbox entirely on next restart. The store/ directory is mounted read-write so the main agent can access the SQLite database directly.

3. Session Isolation

Each group has isolated Claude sessions at data/sessions/{group}/.claude/:

  • Groups cannot see other groups' conversation history
  • Session data includes full message history and file contents read
  • Prevents cross-group information disclosure

4. IPC Authorization

Messages and task operations are verified against group identity:

Operation Main Group Non-Main Group
Send message to own chat
Send message to other chats
Schedule task for self
Schedule task for others
View all tasks Own only
Manage other groups

5. Credential Isolation (Local Secret Store)

parachute-agent stores third-party credentials (API keys, OAuth tokens) in a local AES-GCM-encrypted store and injects them into per-agent containers as environment variables at spawn time. There is no proxy in the request path — the credential is plaintext inside the container at runtime, the same posture as any standard process environment.

How it works:

  1. Credentials are written via the /secrets page in the parachute-agent web UI (or the secrets table directly). Ciphertext lives in the central DB; the master key is at ~/.parachute/agent/master.key on the host (mode 0600).
  2. When parachute-agent spawns a container, it resolves the secrets assigned to that agent group, decrypts them on the host, and passes them as -e KEY=VAL Docker flags.
  3. Agents see the credentials as ordinary env vars. The host process is the only thing that ever holds the master key — containers cannot decrypt the ciphertext on their own.

Per-agent assignment: Each agent group has its own assignment list. A "sales" group and a "support" group can hold disjoint sets of credentials. Rotation is a write to the secret row; the next container spawn picks up the new value.

NOT Mounted:

  • Channel auth sessions (store/auth/) — host only
  • Mount allowlist — external, never mounted
  • Any credentials matching blocked patterns
  • .env is shadowed with /dev/null in the project root mount

Privilege Comparison

Capability Main Group Non-Main Group
Project root access /workspace/project (ro) None
Store (SQLite DB) /workspace/project/store (rw) None
Group folder /workspace/group (rw) /workspace/group (rw)
Global memory Implicit via project /workspace/global (ro)
Additional mounts Configurable Read-only unless allowed
Network access Unrestricted Unrestricted
MCP tools All All

Security Architecture Diagram

┌──────────────────────────────────────────────────────────────────┐
│                        UNTRUSTED ZONE                             │
│  Incoming Messages (potentially malicious)                         │
└────────────────────────────────┬─────────────────────────────────┘
                                 │
                                 ▼ Trigger check, input escaping
┌──────────────────────────────────────────────────────────────────┐
│                     HOST PROCESS (TRUSTED)                        │
│  • Message routing                                                │
│  • IPC authorization                                              │
│  • Mount validation (external allowlist)                          │
│  • Container lifecycle                                            │
│  • Local AES-GCM secret store (decrypts + injects as env vars)   │
└────────────────────────────────┬─────────────────────────────────┘
                                 │
                                 ▼ Explicit mounts + assigned secrets as env vars
┌──────────────────────────────────────────────────────────────────┐
│                CONTAINER (ISOLATED/SANDBOXED)                     │
│  • Agent execution                                                │
│  • Bash commands (sandboxed)                                      │
│  • File operations (limited to mounts)                            │
│  • Assigned credentials available as env vars (plaintext)        │
│  • Master key + unassigned credentials never enter the container │
└──────────────────────────────────────────────────────────────────┘

Supply Chain Security (pnpm)

Paraclaw uses pnpm with two supply chain defenses configured in pnpm-workspace.yaml:

Minimum Release Age

minimumReleaseAge: 4320 (3 days). pnpm will refuse to resolve any package version published less than 3 days ago. This defends against typosquatting and compromised maintainer accounts — most malicious publishes are detected and pulled within 72 hours.

Excluding a package from the release age gate (minimumReleaseAgeExclude):

This should be rare. When a zero-day fix or critical dependency requires an immediate update:

  1. The exclusion must be reviewed and approved by a human maintainer
  2. The entry must pin the exact version being excluded — never a range or wildcard
    minimumReleaseAgeExclude:
      some-package: "1.2.3"  # Approved by @user, 2026-04-14 — CVE-XXXX-YYYY fix
  3. The exclusion should be removed once the version ages past the threshold (i.e. after 3 days)
  4. Automated agents (Claude, CI bots) must never add exclusions without human sign-off

Build Script Allowlist

onlyBuiltDependencies restricts which packages can execute install/postinstall scripts. Only packages on this list are permitted to run build scripts during pnpm install. Currently allowed:

  • better-sqlite3 — compiles native SQLite bindings
  • esbuild — downloads platform-specific binary
  • protobufjs — generates protobuf bindings (used by Baileys/libsignal)
  • sharp — downloads platform-specific image processing binary

Adding a package to this list requires human approval — build scripts execute arbitrary code with the installing user's permissions.

.npmrc Safety Net

The .npmrc file contains minReleaseAge=3d as a fallback. The authoritative setting is in pnpm-workspace.yaml, but .npmrc provides defense-in-depth if npm is ever invoked directly (e.g. by a tool that doesn't respect pnpm).

There aren't any published security advisories