Skip to main content

Container Isolation Model

NanoClaw Pro runs all agents in isolated Linux containers (lightweight VMs). Each group’s agent can only access explicitly mounted directories.
Containers provide true OS-level isolation, not just application-level permissions. Bash commands run safely inside the container, not on your host.

Default Mounts

Every agent container has these mounts:
Host PathContainer PathPurposeRead-only
groups/{name}//workspace/groupGroup-specific memory and filesNo
groups/global//workspace/global/Shared global memory (non-main groups)Yes
data/sessions/{group}/.claude//home/node/.claude/Session transcriptsNo
data/env/env/workspace/env-dir/envEnvironment variablesYes
data/ipc//workspace/ipc/Host communication (tasks, messages)No
The global memory mount (groups/CLAUDE.md) is read-only for non-main groups. Only the main channel can write to global memory.

Additional Mounts

Give specific groups access to external directories using containerConfig in the group registration.

Mount Configuration Structure

interface ContainerConfig {
  additionalMounts?: Array<{
    hostPath: string;        // Absolute path or ~/ for home
    containerPath: string;   // Relative to /workspace/extra/
    readonly?: boolean;      // Default: false
  }>;
  timeout?: number;          // Override CONTAINER_TIMEOUT for this group
}

Example: Mount a Project Directory

Register a group with access to a codebase:
import { registerGroup } from './src/db.js';

registerGroup("[email protected]", {
  name: "Dev Team",
  folder: "whatsapp_dev-team",
  trigger: "@Andy",
  added_at: new Date().toISOString(),
  containerConfig: {
    additionalMounts: [
      {
        hostPath: "~/projects/webapp",
        containerPath: "webapp",
        readonly: false,
      },
    ],
  },
});
Now agents in that group can access the project at /workspace/extra/webapp/:
# Inside container
cd /workspace/extra/webapp
ls -la

Multiple Mounts

containerConfig: {
  additionalMounts: [
    {
      hostPath: "~/Obsidian/Personal",
      containerPath: "notes",
      readonly: true,  // Agent can read but not modify
    },
    {
      hostPath: "~/Downloads",
      containerPath: "downloads",
      readonly: false,
    },
  ],
}
Mounted at:
  • /workspace/extra/notes/ (read-only)
  • /workspace/extra/downloads/ (read-write)

Mount Security

Allowlist

All additional mounts must be in the mount allowlist. The allowlist is stored outside the project root to prevent agents from modifying it. Location: ~/.config/nanoclaw/mount-allowlist.json
mount-allowlist.json
{
  "allowedRoots": [
    "/Users/yourname/projects",
    "/Users/yourname/Obsidian",
    "/Users/yourname/Downloads"
  ],
  "blockedPatterns": [
    ".*\\.env$",
    ".*credentials\\.json$",
    ".*/\\.ssh/.*"
  ],
  "nonMainReadOnly": true
}
allowedRoots
string[]
Absolute directory paths that can be mountedMounts must be within these roots (subdirectories allowed).
blockedPatterns
string[]
Regex patterns for paths that can never be mountedEven if inside an allowed root, paths matching these patterns are rejected.
nonMainReadOnly
boolean
default:"true"
Force all additional mounts to read-only for non-main groupsOnly the main channel can have read-write access to additional mounts.

Setting Up the Allowlist

During setup (/setup), you’ll be asked to configure the allowlist:
npx tsx setup/index.ts --step mounts -- --json '{"allowedRoots":["/Users/yourname/projects"],"blockedPatterns":[],"nonMainReadOnly":true}'
Or create it manually:
mkdir -p ~/.config/nanoclaw
cat > ~/.config/nanoclaw/mount-allowlist.json << 'EOF'
{
  "allowedRoots": ["/Users/yourname/projects"],
  "blockedPatterns": [".*\\.env$"],
  "nonMainReadOnly": true
}
EOF

Mount Validation

Before spawning a container, NanoClaw validates all mounts against the allowlist in src/mount-security.ts:
src/mount-security.ts
export function validateMount(
  mount: Mount,
  allowlist: MountAllowlist,
  isMain: boolean,
): { allowed: boolean; reason?: string } {
  // 1. Expand ~ to absolute path
  const absoluteHost = expandPath(mount.hostPath);
  
  // 2. Check blocked patterns
  for (const pattern of allowlist.blockedPatterns) {
    if (new RegExp(pattern).test(absoluteHost)) {
      return { allowed: false, reason: "Path matches blocked pattern" };
    }
  }
  
  // 3. Check allowed roots
  const inAllowedRoot = allowlist.allowedRoots.some((root) =>
    absoluteHost.startsWith(expandPath(root))
  );
  if (!inAllowedRoot) {
    return { allowed: false, reason: "Path not in allowed roots" };
  }
  
  // 4. Enforce read-only for non-main
  if (allowlist.nonMainReadOnly && !isMain && !mount.readonly) {
    return { allowed: false, reason: "Non-main groups cannot mount read-write" };
  }
  
  return { allowed: true };
}
Never mount sensitive directories like:
  • .ssh/ (SSH keys)
  • .aws/ (AWS credentials)
  • .env files (secrets)
Even though agents run in containers, prompt injection could cause them to leak data.

Container Timeout

Global Timeout

Set via environment variable (applies to all groups):
.env
CONTAINER_TIMEOUT=1800000  # 30 minutes (default)
Agents that exceed this timeout are terminated.

Per-Group Timeout

Override timeout for specific groups:
registerGroup("[email protected]", {
  name: "Long Tasks",
  folder: "whatsapp_long-tasks",
  trigger: "@Andy",
  added_at: new Date().toISOString(),
  containerConfig: {
    timeout: 3600000,  // 1 hour (overrides global CONTAINER_TIMEOUT)
  },
});
Use longer timeouts for groups that run complex agent swarms or scheduled tasks that process large datasets.

Container Resource Limits

By default, containers have no CPU or memory limits. For production use, consider adding limits:

Docker

Modify src/container-runner.ts to add resource flags:
src/container-runner.ts
const spawnArgs = [
  'run',
  '--rm',
  '-i',
  '--cpus', '2',           // Limit to 2 CPU cores
  '--memory', '4g',        // Limit to 4GB RAM
  '--memory-swap', '4g',   // No swap
  // ... existing args
];

Apple Container

Apple Container uses macOS resource management by default. For explicit limits:
src/container-runner.ts
const spawnArgs = [
  'run',
  '--rm',
  '-i',
  '--cpu-limit', '2',      // Limit to 2 CPU cores
  '--memory-limit', '4096', // 4GB in MB
  // ... existing args
];

Practical Examples

Example 1: Sales Team with CRM Access

registerGroup("[email protected]", {
  name: "Sales Team",
  folder: "whatsapp_sales",
  trigger: "@Andy",
  added_at: new Date().toISOString(),
  containerConfig: {
    additionalMounts: [
      {
        hostPath: "~/Dropbox/Sales",
        containerPath: "sales-data",
        readonly: true,  // Agents can read but not modify
      },
    ],
  },
});
Agent can read sales data:
@Andy summarize this month's pipeline from sales-data/pipeline.csv

Example 2: Dev Group with Code Access

registerGroup("[email protected]", {
  name: "Dev Team",
  folder: "whatsapp_dev",
  trigger: "@Andy",
  added_at: new Date().toISOString(),
  containerConfig: {
    additionalMounts: [
      {
        hostPath: "~/repos/backend",
        containerPath: "backend",
        readonly: false,  // Agent can modify code
      },
      {
        hostPath: "~/repos/frontend",
        containerPath: "frontend",
        readonly: false,
      },
    ],
    timeout: 3600000,  // 1 hour for complex tasks
  },
});
Agent can modify code:
@Andy refactor the auth module in backend/src/auth.ts

Example 3: Personal Assistant with Document Access

registerGroup("[email protected]", {
  name: "Main",
  folder: "whatsapp_main",
  trigger: "@Andy",
  isMain: true,
  triggerRequired: false,
  added_at: new Date().toISOString(),
  containerConfig: {
    additionalMounts: [
      {
        hostPath: "~/Obsidian/Vault",
        containerPath: "notes",
        readonly: false,
      },
      {
        hostPath: "~/Documents",
        containerPath: "docs",
        readonly: true,
      },
    ],
  },
});
Agent can manage notes and read documents:
Create a summary of docs/quarterly-report.pdf and save it to notes/summaries/q4-2024.md

Mount Syntax Reference

Different container runtimes use different mount syntax. NanoClaw Pro handles this automatically in src/container-runner.ts:

Docker

-v /host/path:/container/path
Docker’s :ro suffix may not work on all runtimes. Use --mount with readonly flag for cross-platform compatibility.

Apple Container

-v /host/path:/container/path

Debugging Container Mounts

To see what’s mounted in a running container:
# List running containers
docker ps  # or: container list

# Inspect mounts
docker inspect <container-id> | jq '.[0].Mounts'

# Execute shell inside container
docker exec -it <container-id> /bin/sh
ls -la /workspace/extra/
Container logs are in:
tail -f groups/{folder}/logs/container-*.log

Best Practices

1

Use read-only mounts for sensitive data

If agents only need to read files (documents, notes, reports), mount read-only to prevent accidental modification.
2

Restrict non-main groups

Set nonMainReadOnly: true in the allowlist to ensure only your main channel can modify external files.
3

Avoid mounting credentials

Never mount .ssh/, .aws/, .env, or other credential directories. Prompt injection could leak secrets.
4

Use specific paths, not home directory

Mount ~/projects/webapp instead of ~/. Narrower mounts reduce attack surface.
5

Test mounts in dev mode first

Before modifying production service:
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
npm run dev
# Send test message
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist

Next Steps

Configuration Reference

Environment variables and runtime settings

Security Model

Understanding container isolation and security

Build docs developers (and LLMs) love