How Maka Handles Tool Sandboxing and Security: Architecture and Implementation
Maka isolates tool execution through a layered sandbox architecture that combines a centralized SandboxManager for backend selection with strict permission boundaries that restrict file-system and network access to only what tools explicitly require.
Apache Maka implements a defense-in-depth approach to code execution security. The system ensures that every tool runs with the minimum necessary privileges by enforcing tool sandboxing and security through declarative permission profiles and OS-level isolation mechanisms. This architecture prevents privilege escalation by validating all permission expansions against hard capacity limits and explicit deny rules before spawning any process.
The SandboxManager: Deciding When to Sandbox
The SandboxManager class in packages/runtime/src/sandbox/sandbox-manager.ts serves as the central authority for determining whether a tool requires sandboxing and which platform-specific backend should enforce the isolation.
Permission Profile Evaluation
The manager evaluates the user's sandbox preference—which can be auto, require, or forbid—against the tool's declared permission profile. The decision logic follows this priority:
// packages/runtime/src/sandbox/sandbox-manager.ts
if (preference === 'forbid') return false;
if (preference === 'require') return true;
return profileRequiresSandbox(profile); // see profileRequiresSandbox()
The helper function profileRequiresSandbox() checks whether the profile is managed and whether either the file-system or network access is marked as restricted. If the profile indicates restricted access, the manager proceeds to select an appropriate backend.
Platform-Specific Backend Selection
When sandboxing is required, the manager selects a backend based on the host OS. According to the source code, the selection logic maps platforms to specific backend keys:
- macOS:
macos-seatbelt(checked at lines 68‑78) - Linux:
linux(checked at lines 91‑101) - Windows:
windows(checked at lines 14‑24)
If the appropriate backend is not registered in the manager's available backends set, the transformation fails with a backend_not_available error, preventing execution in an unsecured environment.
Enforcing the Sandbox Boundary
Once a backend is chosen, SandboxManager.transform() hands the request to the backend’s transform method. The backend then launches the tool inside the concrete sandbox implementation—such as macOS Seatbelt, Linux namespaces, or the Windows AppContainer broker—while preserving the original command arguments, environment variables, and working directory.
Defining Permissions: The Sandbox Boundary
A sandbox boundary is a compact, serializable description of allowed capabilities defined in packages/core/src/sandbox-boundary.ts. It serves as the contract between the tool and the operating system:
export interface SandboxBoundaryFilesystemEntry {
readonly path: string;
readonly access: SandboxBoundaryAccess; // 'read' | 'write'
readonly scope: SandboxBoundaryScope; // 'exact' | 'subtree'
}
The boundary may also grant network access through a network.enabled flag. To prevent resource exhaustion attacks, the system enforces strict capacity limits: a maximum of 32 entries, 4 KB total path length, and 64 KB for the serialized payload.
Validating and Expanding Boundaries
When a tool requests additional permissions, validateSandboxBoundaryExpansion parses and validates the request against the current boundary. This function prevents explicit deny conflicts and protects sensitive metadata paths such as workspace roots.
If the expansion is valid, applySandboxBoundaryExpansion merges the new permissions into the existing sandbox profile while preserving deny entries and compacting overlapping paths. This ensures that permissions can only expand within predefined safety margins, and any attempt to override a deny rule is rejected immediately.
Execution Boundaries at Runtime
An execution boundary represents the runtime state of a sandboxed session. Defined in the core package, the type distinguishes between three execution modes:
export type ExecutionBoundary =
| { kind: 'managed'; profile: SandboxProfile; revision: number }
| { kind: 'bypass'; revision: number }
| { kind: 'external'; revision: number };
- Managed: The tool runs under full sandbox restrictions with a specific permission profile.
- Bypass: The tool executes without sandbox restrictions (used when preferences allow and profiles permit).
- External: The tool runs outside the managed environment entirely.
Utility functions like createGenesisExecutionBoundary initialize new sessions, while executionBoundaryDisplayMode translates boundary states into UI-friendly representations for user confirmation prompts.
Practical Implementation Examples
The following example demonstrates how to instantiate a SandboxManager, register platform backends, and transform a tool request into a sandboxed execution:
// 1️⃣ Create a SandboxManager and register backends
import { SandboxManager } from './packages/runtime/src/sandbox/sandbox-manager.js';
import { linuxBackend } from './packages/runtime/src/sandbox/linux-backend.js';
import { windowsBackend } from './packages/runtime/src/sandbox/windows-backend.js';
const manager = new SandboxManager([linuxBackend, windowsBackend]);
// 2️⃣ Prepare a tool request
const request = {
command: {
program: 'node',
args: ['my-tool.js'],
cwd: '/home/user/project',
env: { NODE_ENV: 'production' },
profile: myPermissionProfile, // a PermissionProfile object
},
preference: 'auto',
platform: process.platform,
};
// 3️⃣ Ask the manager to transform the request into a sandboxed launch
const result = manager.transform(request);
if (result.ok) {
// result.exec contains the argv, cwd, env, and sandbox type
console.log('Launching sandboxed tool:', result.exec);
} else {
console.error('Sandbox selection failed:', result.reason, result.message);
}
To work with boundaries directly during a session:
// 4️⃣ Working with sandbox boundaries directly
import { createGenesisExecutionBoundary } from './packages/core/src/sandbox-boundary.ts';
// Start a session with a read‑only boundary
const boundary = createGenesisExecutionBoundary('explore');
console.log('Initial boundary:', boundary);
// Later, expand the boundary (e.g., grant write access to /tmp)
import { assessSandboxBoundaryExpansion } from './packages/core/src/sandbox-boundary.ts';
const expansion = {
filesystem: {
entries: [{ path: '/tmp', access: 'write', scope: 'subtree' }],
},
};
const assessment = assessSandboxBoundaryExpansion(
boundary.profile,
expansion,
);
if (assessment.outcome === 'apply') {
console.log('New profile after expansion:', assessment.profile);
}
Security Guarantees and Containment Mechanisms
Maka's security model rests on several concrete containment mechanisms:
- Platform-specific isolation: Each backend invokes the OS-provided sandbox mechanism (Seatbelt on macOS, namespaces on Linux, AppContainer on Windows) rather than relying on language-level isolation.
- Capability verification: The
canEnforcefunction verifies that the selected backend is actually available and capable of enforcing the requested profile before execution begins. - Capacity limits: Serialized boundaries cannot exceed 64 KB, protecting against resource-exhaustion attacks that attempt to overflow permission buffers.
- Explicit deny protection: Expansions that would write into a path explicitly denied by the profile are rejected at validation time, preventing privilege escalation through boundary manipulation.
Together, these layers guarantee that a tool runs only with the permissions it explicitly needs, and any attempt to over-privilege is detected and blocked before the process spawns.
Summary
- SandboxManager in
packages/runtime/src/sandbox/sandbox-manager.tsdetermines whether sandboxing is required based on user preferences and tool permission profiles. - The system supports platform-specific backends for macOS (Seatbelt), Linux (namespaces), and Windows (AppContainer).
- Sandbox boundaries are strictly limited in size (32 entries, 64 KB serialized) to prevent resource exhaustion.
- Permission expansion is validated against explicit deny rules and metadata path protections before application.
- Execution boundaries track runtime state as managed, bypass, or external modes with revision tracking for auditability.
Frequently Asked Questions
How does Maka decide if a tool needs to run in a sandbox?
The SandboxManager checks the user's sandbox preference (auto, require, or forbid). If set to auto, it calls profileRequiresSandbox() to inspect whether the tool's permission profile declares restricted file-system or network access. Only tools with restricted profiles or explicit user requirements trigger sandboxing.
What happens if the required sandbox backend is not available?
If the appropriate backend for the current platform (e.g., macos-seatbelt, linux, or windows) is not registered in the SandboxManager, the transform() method returns an error object with reason: 'backend_not_available'. This prevents the tool from executing in an unsandboxed environment when sandboxing is required.
How does Maka prevent tools from requesting excessive permissions?
The validateSandboxBoundaryExpansion function enforces hard limits: maximum 32 file-system entries, 4 KB total path length, and 64 KB serialized payload. It also blocks expansions that conflict with explicit deny rules or target protected metadata paths. Valid expansions are merged via applySandboxBoundaryExpansion while preserving all deny entries.
What platforms does Maka support for sandboxing?
Maka currently supports sandboxing on macOS via Seatbelt, Linux via namespaces, and Windows via AppContainer. Each platform has a dedicated backend file (e.g., packages/runtime/src/sandbox/linux-backend.ts) that translates Maka's generic boundary definitions into platform-specific sandbox configurations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →