NanoClaw Pro runs every agent invocation inside an isolated Linux container (lightweight VM). This provides OS-level security that’s impossible with application-level permission checks.
Unlike other AI assistants that rely on allowlists and permission checks, NanoClaw Pro uses true filesystem and process isolation. Agents literally cannot access files or processes outside their container.
Secure by isolation. Agents run in Linux containers and they can only see what’s explicitly mounted. Bash access is safe because commands run inside the container, not on your host.
Traditional AI assistants run in the same process as your application, relying on:
Allowlists (“agent can only access these files”)
Permission checks (“is this action allowed?”)
Trust (“agent won’t do anything malicious”)
The problem: These are all bypassable via prompt injection or bugs.NanoClaw Pro uses OS-level isolation:
Agents run in separate Linux VMs
Filesystem is isolated (only mounted directories are visible)
Process tree is isolated (can’t see or kill host processes)
function buildVolumeMounts(group: RegisteredGroup, isMain: boolean) { const mounts = []; if (isMain) { // Main gets the project root read-only mounts.push({ hostPath: projectRoot, containerPath: '/workspace/project', readonly: true, }); // Shadow .env to prevent secret access mounts.push({ hostPath: '/dev/null', containerPath: '/workspace/project/.env', readonly: true, }); // Main also gets its group folder mounts.push({ hostPath: groupDir, // groups/whatsapp_main/ containerPath: '/workspace/group', readonly: false, }); }}
Main channel sees:
Project root at /workspace/project (read-only)
Its group folder at /workspace/group (read-write)
.env shadowed with /dev/null (secrets passed via stdin instead)
Main channel has read-only access to the NanoClaw codebase. This is intentional — it allows the agent to modify its own code, but changes only take effect after restart (giving you time to review).
Purpose: Container communicates with the host via files in /workspace/ipc. Each group has its own IPC directory to prevent cross-group privilege escalation.
for (const mount of mounts) { if (mount.readonly) { args.push(...readonlyMountArgs(mount.hostPath, mount.containerPath)); } else { args.push('-v', `${mount.hostPath}:${mount.containerPath}`); }}
const configTimeout = group.containerConfig?.timeout || CONTAINER_TIMEOUT;// Grace period: hard timeout must be at least IDLE_TIMEOUT + 30sconst timeoutMs = Math.max(configTimeout, IDLE_TIMEOUT + 30_000);const killOnTimeout = () => { timedOut = true; logger.error( { group: group.name, containerName }, 'Container timeout, stopping gracefully', ); exec(stopContainer(containerName), { timeout: 15000 }, (err) => { if (err) { container.kill('SIGKILL'); } });};let timeout = setTimeout(killOnTimeout, timeoutMs);
Default timeout: 30 minutes (configurable via CONTAINER_TIMEOUT or group.containerConfig.timeout)Idle timeout: Container stays alive for 30 minutes after last output (allows piping new messages to the same session)Grace period: Hard timeout is at least IDLE_TIMEOUT + 30s to allow graceful shutdown
Symptom: “Permission denied” inside containerCause: Container runs as uid 1000 but host files are owned by different userSolution: Run container as host user:From src/container-runner.ts:236: