Security-first personal AI coding assistant with zero-trust architecture.
SafeClaw is an AI coding assistant with multi-provider LLM support (GitHub Copilot, OpenAI, Anthropic), mandatory OS-level sandboxing, capability-based access control, encrypted secret storage, and signed skill verification. Every tool execution is sandboxed via Linux kernel features (Landlock, seccomp-BPF, namespaces). There is no way to disable security enforcement -- it is structural.
The target user is an individual developer who wants AI-assisted coding with strong guarantees against prompt injection, malicious tool calls, and data exfiltration.
Linux and macOS. Node.js >= 22. pnpm 9+.
safeclaw/
├── packages/ # pnpm monorepo workspace packages
│ ├── vault/ # @safeclaw/vault -- AES-256-GCM encrypted key-value store
│ ├── sandbox/ # @safeclaw/sandbox -- OS-level process sandboxing wrapper
│ ├── core/ # @safeclaw/core -- agent runtime, capabilities, tools, sessions, copilot client
│ ├── gateway/ # @safeclaw/gateway -- HTTP server with auth + rate limiting
│ ├── webchat/ # @safeclaw/webchat -- browser chat SPA + static file server
│ └── cli/ # @safeclaw/cli -- CLI entry point (top of dependency tree)
├── native/ # C11 sandbox helper binary (musl-gcc, statically linked)
├── skills/ # Builtin skill manifests (Ed25519-signed)
├── test/ # Cross-cutting security tests
├── docs/ # Architecture, security model, sandboxing, skills docs
│ └── plans/ # Design documents and implementation plans
└── scripts/ # Build/release scripts
vault (standalone) sandbox (standalone)
\ |
\ v
+-----> core <-----+
/ | \
/ | \
v v v
gateway webchat cli (depends on all)
packages/core/src/agent/agent.ts -- Multi-round tool-calling loop. Sends messages to the configured model provider, receives tool call requests, executes them through the ToolOrchestrator, feeds results back. Continues until the model produces a final text response.
packages/core/src/providers/types.ts--ModelProviderinterface (commonchat()andchatStream()methods)packages/core/src/providers/copilot.ts--CopilotProviderwraps existingCopilotClientpackages/core/src/providers/openai.ts--OpenAIProvideruses nativefetchagainst OpenAI APIpackages/core/src/providers/anthropic.ts--AnthropicProvidertranslates OpenAI wire format to/from Anthropic Messages APIpackages/core/src/providers/registry.ts--ProviderRegistrymanages available providers- Provider selection is vault-driven:
vault.get("provider")returns"copilot"(default),"openai", or"anthropic"
packages/core/src/capabilities/registry.ts-- Tracks which skills have which capabilitiespackages/core/src/capabilities/enforcer.ts-- Checks every tool call against granted capabilities at runtimepackages/core/src/capabilities/verifier.ts-- Ed25519 signature verification for skill manifests- 8 capability types:
fs:read,fs:write,net:http,net:https,process:spawn,env:read,secret:read,secret:write - Capabilities have constraints (e.g., allowed paths, allowed hosts)
packages/core/src/tools/orchestrator.ts -- Central tool execution pipeline:
- Capability check (enforcer)
- Sandbox execution (if available) or direct execution
- Audit logging (timestamp, duration, result, sandbox status)
Located in packages/core/src/tools/builtin/:
read.ts,write.ts,edit.ts-- File operationsbash.ts-- Shell command execution;createBashTool(options?)factory acceptsallowedCommandPathsfor advisory command validation (warns when a binary is not under an allowed directory; Landlock is the real enforcement)web-fetch.ts-- HTTP fetchingweb-search.ts-- Web search via Brave Search API (conditionally included whenbrave_api_keyexists in vault)process.ts-- Background process management (start/status/log/kill/list)apply-patch.ts-- Multi-file unified diff patching with atomic writes and fuzzy matching (parser inpatch-parser.ts, applier inpatch-applier.ts)
Each tool declares requiredCapabilities and implements a ToolHandler interface.
packages/core/src/tools/process-manager.ts -- Tracks spawned child processes by UUID. Features: ring buffer output capture (1MB max per process), automatic cleanup after 1 hour, maximum 8 concurrent processes. Used by the process builtin tool.
packages/sandbox/src/sandbox.ts-- Wraps commands via@anthropic-ai/sandbox-runtime(SandboxManager.wrapWithSandbox()) as the outer layer; injects the C helper as the inner process via--policy-file <tmp>when foundpackages/sandbox/src/policy-builder.ts--PolicyBuilderclass with fluent API;PolicyBuilder.forDevelopment(cwd, options?)creates a development-ready policy with allowlisted system paths, compiler toolchains (JVM, GCC), an expanded ~120 syscall allowlist, and support forextraExecutePaths/extraReadWritePathsviaDevelopmentPolicyOptions;PolicyBuilder.toRuntimeConfig(policy)translatesSandboxPolicytoSandboxRuntimeConfigfor sandbox-runtime (write allowlist + credential dir denylist)native/src/main.c-- C helper binary that applies: Landlock filesystem rules, seccomp-BPF syscall filtering, capability dropping,PR_SET_NO_NEW_PRIVS- Policy sent to helper via
--policy-file <tmp>(JSON written to a temp file at mode 0o600; cleaned up after each execution)
packages/vault/src/vault.ts -- AES-256-GCM encrypted JSON file store. Keys derived via scrypt from passphrase or fetched from OS keyring (GNOME secret-tool). File permissions enforced at 0o600.
packages/core/src/channels/types.ts defines ChannelAdapter interface (connect, disconnect, onMessage, send). Two implementations:
packages/cli/src/adapter.ts-- readline-based terminalpackages/webchat/src/adapter.ts-- HTTP/SSE-based browser SPA
packages/gateway/src/server.ts -- HTTP server with:
- Bearer token auth (timing-safe comparison, min 32 chars)
- Token bucket rate limiting per client IP
- Single endpoint:
POST /api/chat - Localhost-only binding
The main entry point is packages/cli/src/cli.ts (registered as safeclaw binary).
Bootstrap flow (packages/cli/src/commands/bootstrap.ts):
- Open vault (keyring or passphrase)
- Read provider config from vault (
providerkey, defaults to"copilot") - Create appropriate
ModelProvider(CopilotProvider, OpenAIProvider, or AnthropicProvider) - Load builtin skill manifest
- Read
brave_api_keyfrom vault; if present, include web_search tool in tool registry - Create ProcessManager for background process tracking
- Initialize
SandboxManagernetwork proxy (viaPolicyBuilder.toRuntimeConfig()) - Create: CapabilityRegistry -> CapabilityEnforcer -> ToolRegistry -> Sandbox -> ToolOrchestrator -> ContextCompactor -> Agent
- Return
{ agent, sessionManager, capabilityRegistry, auditLog }
CLI commands: chat (default), onboard, audit, serve/server, doctor, help, version
| Aspect | Choice |
|---|---|
| Language | TypeScript (strict, ES2024 target) |
| Runtime | Node.js >= 22 |
| Modules | ESM ("type": "module", "module": "Node16") |
| Package manager | pnpm 9+ with workspaces |
| Build | tsc with project references (composite builds) |
| Tests | Vitest 4.x with v8 coverage |
| Linter | OxLint 1.50+ |
| Native code | C11, statically linked with musl-gcc |
| LLM API | Multi-provider: GitHub Copilot (device flow OAuth), OpenAI, Anthropic |
| Crypto | Node.js crypto -- AES-256-GCM, scrypt, Ed25519 |
| CI | GitHub Actions |
pnpm install # Install dependencies
pnpm build # Build all packages (tsc --build)
pnpm test # Run all tests (vitest run)
pnpm lint # Lint with oxlint
pnpm typecheck # Type-check without emitting (tsc --build --dry)
pnpm bundle # Create release bundle
# Native sandbox helper
make -C native # Build (requires musl-tools)
make -C native check # Run native tests- Co-located tests:
*.test.tsfiles next to source files - Dependency injection: All external dependencies are injectable via constructor/function parameters for testability
- Mocking:
vi.mock()for module mocking,vi.fn()for function mocks - Security tests: Dedicated
test/security/directory with sandbox-escape, permission-escalation, crypto-validation, and auth-bypass tests - Native tests: Shell scripts + compiled C test binaries in
native/test/ - Vitest config: Module aliases map
@safeclaw/*to source files (vitest.config.ts)
- No database -- all runtime state is in-memory (audit log, capability registry)
- Vault: JSON file on disk (
~/.safeclaw/vault.json), AES-256-GCM encrypted, 0o600 permissions - Config:
~/.safeclaw/directory (global user config) - Sessions: persisted to
<cwd>/.safeclaw/sessions/(directory-scoped, per-project)
Never commit or push changes. When work is complete (or at a logical stopping point), stop and provide ready-to-run git commit commands for all changes made during the session. If changes span multiple logical units, provide multiple commit commands in sequence so each commit is atomic and well-scoped. Always list every file touched and group them by commit. Example:
Ready to commit. Run:
git add packages/sandbox/src/types.ts packages/sandbox/src/detect.ts packages/sandbox/src/detect.test.ts && \
git commit -m "feat(sandbox): extend EnforcementLayers and KernelCapabilities types for bwrap"
git add packages/sandbox/src/policy-builder.ts packages/sandbox/src/policy-builder.test.ts && \
git commit -m "feat(sandbox): add toBwrapArgs() and selective home directory binding"
git add docs/sandboxing.md docs/security-model.md README.md AGENTS.md && \
git commit -m "docs: update documentation for bubblewrap sandbox integration"
If all changes are a single logical unit, a single commit is fine:
Ready to commit. Run:
git add -A && git commit -m "feat(tools): add JSON Schema parameters to all builtin tools"
The user will review and run the commands themselves. Do not run git add, git commit, git push, or any other git write operation.
- Commit style: Conventional Commits --
type(scope): description(e.g.,feat(core): add agent runtime) - Scopes:
core,cli,sandbox,vault,native,gateway,webchat,security,skills,tools,ci - Error handling: Fail-closed (deny by default), custom error classes per domain
- Exports: Each package has
src/index.tsbarrel file re-exporting public API - No classes for data: Use TypeScript interfaces/types for data shapes, classes for stateful components
- Security principle: Zero-trust, mandatory enforcement, no opt-out
- Documentation updates: Whenever a feature is added, modified, or removed, update all relevant documentation files (README.md, AGENTS.md, docs/architecture.md, docs/getting-started.md, etc.) in the same changeset. Never leave documentation out of sync with the implementation.
- Lint errors: All lint errors and warnings must be fixed before considering work complete. The GitHub CI workflow runs
pnpm lintand will fail the build on any lint diagnostic. Never leave lint warnings as "pre-existing" or "to be ignored" -- fix them immediately.
When getting oriented with this codebase, read these files in order:
docs/architecture.md-- Full system architecture with diagramsdocs/security-model.md-- Security philosophy and threat modelpackages/core/src/agent/agent.ts-- The central agent looppackages/core/src/tools/orchestrator.ts-- How tool calls flowpackages/core/src/capabilities/enforcer.ts-- How capabilities are checkedpackages/cli/src/commands/bootstrap.ts-- How everything gets wired togetherpackages/sandbox/src/sandbox.ts-- How sandboxing worksnative/src/main.c-- The native sandbox helper entry point