-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
531 lines (483 loc) · 25.7 KB
/
Copy pathindex.html
File metadata and controls
531 lines (483 loc) · 25.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
---
layout: default
title: "Sandlock - Kernel-Enforced Sandboxing for Untrusted Code"
description: "Sandlock confines untrusted code on Linux using Landlock, seccomp-bpf, and seccomp user notification. No root, no containers, no hypervisor. 5 ms startup, 97% of bare-metal throughput."
permalink: /
---
<style>
/* Homepage-only composition */
.hero-terminal {
max-width: 780px;
margin: var(--space-10) auto 0;
text-align: left;
}
.hero-terminal .code-block {
margin: 0;
background: rgba(0, 0, 0, 0.32);
border-color: rgba(255, 255, 255, 0.1);
}
.interfaces-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: var(--space-4);
}
.interface-card {
display: block;
padding: var(--space-6);
background: white;
border: 1px solid var(--gray-200);
border-left: 3px solid var(--gray-300);
border-radius: var(--radius-lg);
text-decoration: none;
transition: border-color var(--transition-base), box-shadow var(--transition-base), transform var(--transition-base);
}
.interface-card:hover {
border-left-color: var(--accent-500);
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.interface-card .name {
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: 600;
color: var(--gray-900);
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-2);
}
.interface-card .name i {
width: 16px;
height: 16px;
color: var(--accent-600);
}
.interface-card p {
font-size: var(--text-sm);
color: var(--gray-600);
line-height: var(--leading-relaxed);
}
.pillars {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-5);
margin-top: var(--space-10);
}
.pillar {
padding: var(--space-6);
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.07);
border-top: 2px solid var(--accent-500);
border-radius: var(--radius-md);
}
.pillar h3 {
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: 600;
color: white;
margin-bottom: var(--space-3);
}
.pillar p {
font-size: var(--text-sm);
line-height: var(--leading-relaxed);
color: rgba(255, 255, 255, 0.55);
}
@media (max-width: 900px) {
.pillars {
grid-template-columns: 1fr;
}
}
</style>
<section class="hero hero-tall">
<div class="hero-container">
<div class="hero-badge">
<i data-lucide="shield"></i>
<span>Landlock · seccomp-bpf · seccomp notify</span>
</div>
<h1>Run Untrusted Code Without Handing It Your Machine</h1>
<p class="subtitle">Sandlock is a lightweight Linux process sandbox. It confines a command's filesystem, network, syscalls, and resources with kernel-enforced policy, in about 5 milliseconds. No root. No image build. No container runtime. No hypervisor.</p>
<div class="button-group">
<a href="/docs/getting-started.html" class="btn btn-primary">
<i data-lucide="rocket"></i>
Get Started
</a>
<a href="https://github.com/multikernel/sandlock" target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
<i data-lucide="github"></i>
View on GitHub
</a>
</div>
<div class="hero-terminal">
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">Confine a Python agent</span>
<button class="code-copy" type="button" aria-label="Copy command">
<span class="code-copy-label">Copy</span>
</button>
</div>
<pre><code><span class="c"># Read-only system dirs, one writable dir, 512 MB cap,</span>
<span class="c"># and exactly one host reachable on exactly one port.</span>
<span class="p">$</span> <span class="k">sandlock run</span> -r /usr -r /lib -r /etc -w /tmp \
-m 512M -P 20 -t 300 \
--net-allow <span class="s">api.openai.com:443</span> \
-- python3 agent.py</code></pre>
</div>
</div>
<div class="hero-stats">
<div class="hero-stat">
<span class="hero-stat-number">5 ms</span>
<span class="hero-stat-label">Startup overhead</span>
<span class="hero-stat-detail">44× faster to start than Docker</span>
</div>
<div class="hero-stat">
<span class="hero-stat-number">0</span>
<span class="hero-stat-label">Privileges required</span>
<span class="hero-stat-detail">No root, no setuid, no daemon</span>
</div>
<div class="hero-stat">
<span class="hero-stat-number">97%</span>
<span class="hero-stat-label">Of bare-metal throughput</span>
<span class="hero-stat-detail">Measured on Redis SET and GET</span>
</div>
</div>
<div class="hero-trust">
<p class="trust-text">Open source, Apache-2.0, written in Rust</p>
<div class="trust-features">
<div class="trust-feature">
<i data-lucide="lock"></i>
<span>Kernel-enforced policy</span>
</div>
<div class="trust-feature">
<i data-lucide="layers"></i>
<span>No namespaces or cgroups</span>
</div>
<div class="trust-feature">
<i data-lucide="terminal"></i>
<span>CLI, Python, Rust, Go</span>
</div>
<div class="trust-feature">
<i data-lucide="container"></i>
<span>OCI runtime for Kubernetes</span>
</div>
<div class="trust-feature">
<i data-lucide="blocks"></i>
<span>Programmable interception</span>
</div>
</div>
</div>
</div>
</section>
<!-- The gap -->
<section class="section section-alt">
<div class="section-container">
<div class="section-header">
<div class="section-badge">
<i data-lucide="git-compare"></i>
<span>The gap</span>
</div>
<h2 class="section-title">Strict Confinement Without the Weight</h2>
<p class="section-subtitle">Containers and microVMs are powerful, but both were designed to package and boot a system. When all you need is to run one command you do not trust, they make you pay for a system anyway.</p>
</div>
<div class="cards-grid">
<div class="capability-card">
<div class="capability-icon"><i data-lucide="box"></i></div>
<h3 class="capability-title">Containers</h3>
<p class="capability-description">Require an image build, a runtime daemon, and root or a configured user-namespace setup. Every container shares the host kernel, and the isolation boundary is a namespace, not a policy the kernel evaluates per access.</p>
<div class="capability-features">
<div class="feature-item"><i data-lucide="minus"></i><span>~200 ms to start</span></div>
<div class="feature-item"><i data-lucide="minus"></i><span>Image build in the loop</span></div>
<div class="feature-item"><i data-lucide="minus"></i><span>No HTTP-level control</span></div>
</div>
</div>
<div class="capability-card">
<div class="capability-icon"><i data-lucide="server"></i></div>
<h3 class="capability-title">MicroVMs</h3>
<p class="capability-description">A separate guest kernel gives a strong boundary, but it needs KVM, a guest image, and a device model. Nested virtualization inside a cloud VM adds real overhead, and there is no visibility into what the guest is doing at the syscall level.</p>
<div class="capability-features">
<div class="feature-item"><i data-lucide="minus"></i><span>Requires KVM access</span></div>
<div class="feature-item"><i data-lucide="minus"></i><span>Guest image to maintain</span></div>
<div class="feature-item"><i data-lucide="minus"></i><span>Block-level filesystem only</span></div>
</div>
</div>
<div class="capability-card primary">
<div class="capability-icon"><i data-lucide="shield-check"></i></div>
<h3 class="capability-title">Sandlock</h3>
<p class="capability-description">A policy applied to a process. Landlock and seccomp-bpf enforce filesystem, network, IPC, and syscall rules in the kernel; a userspace supervisor adds resource limits, destination IP checks, HTTP method and path rules, and copy-on-write writes.</p>
<div class="capability-features">
<div class="feature-item"><i data-lucide="check"></i><span>~5 ms to start, no image</span></div>
<div class="feature-item"><i data-lucide="check"></i><span>Runs as an ordinary user</span></div>
<div class="feature-item"><i data-lucide="check"></i><span>Method + host + path ACL</span></div>
</div>
</div>
</div>
</div>
</section>
<!-- Capabilities -->
<section class="section">
<div class="section-container">
<div class="section-header">
<div class="section-badge">
<i data-lucide="sliders-horizontal"></i>
<span>Capabilities</span>
</div>
<h2 class="section-title">One Policy, Six Enforcement Surfaces</h2>
<p class="section-subtitle">Every dimension of the sandbox is described in the same policy object, whether you write it as CLI flags, a TOML profile, or a struct in Python, Rust, or Go.</p>
</div>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon"><i data-lucide="folder-lock"></i></div>
<h3 class="feature-title">Filesystem</h3>
<p class="feature-description">Landlock grants read and write paths as recursive rules the kernel evaluates on every access, so the policy is immune to time-of-check/time-of-use races. Denied paths override broader grants.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i data-lucide="network"></i></div>
<h3 class="feature-title">Network</h3>
<p class="feature-description">Default-deny egress. Allow a hostname, an IP, or a CIDR on specific ports over TCP, UDP, or ICMP, or invert it with a denylist. Bind ports are governed separately, and can be virtualized per sandbox.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i data-lucide="globe-lock"></i></div>
<h3 class="feature-title">HTTP ACL</h3>
<p class="feature-description">Go past <code class="inline">host:443</code>: allow <code class="inline">POST api.openai.com/v1/chat/completions</code> and nothing else. Zero-config HTTPS interception generates an ephemeral CA and splices it into the trust bundles you name.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i data-lucide="key-round"></i></div>
<h3 class="feature-title">Credential injection</h3>
<p class="feature-description">The API key lives in the supervisor and is attached to the request in the proxy, strictly after the ACL check. The sandboxed process never holds the secret and cannot exfiltrate it.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i data-lucide="copy"></i></div>
<h3 class="feature-title">Copy-on-write</h3>
<p class="feature-description">Writes under the working directory are staged in an upper layer and committed on success or discarded on failure. <code class="inline">--dry-run</code> shows exactly which files a command would add, modify, or delete.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i data-lucide="gauge"></i></div>
<h3 class="feature-title">Resources</h3>
<p class="feature-description">Memory, process count, open files, CPU percentage, disk quota, CPU pinning, and GPU device selection, all without cgroups. A given GPU index is a hard Landlock boundary, not an environment variable.</p>
</div>
</div>
</div>
</section>
<!-- How it works -->
<section class="section section-dark">
<div class="section-container">
<div class="section-header">
<div class="section-badge">
<i data-lucide="cpu"></i>
<span>How it works</span>
</div>
<h2 class="section-title">Kernel First, Supervisor Second</h2>
<p class="section-subtitle">Sandlock forks, installs confinement in the child before it executes anything, and only then lets the workload start. What the kernel can enforce, the kernel enforces.</p>
</div>
<div class="pillars">
<div class="pillar">
<h3>Landlock</h3>
<p>Unprivileged, kernel-evaluated access control for filesystem paths, TCP connect and bind ports, and IPC scoping. Applied once and irreversible for the lifetime of the process tree.</p>
</div>
<div class="pillar">
<h3>seccomp-bpf</h3>
<p>A default blocklist removes syscall families a confined workload has no business using. Extra denials compose on top; the blocklist itself is always applied.</p>
</div>
<div class="pillar">
<h3>seccomp notify</h3>
<p>The supervisor sees selected syscalls before they run: destination IP checks, memory and process accounting, COW interception, /proc virtualization, and port remapping.</p>
</div>
</div>
<div class="text-center" style="margin-top: var(--space-10);">
<a href="/how-it-works.html" class="btn btn-secondary">
<i data-lucide="arrow-right"></i>
Read the architecture
</a>
</div>
</div>
</section>
<!-- Programmable -->
<section class="section section-alt" id="programmable">
<div class="section-container">
<div class="value-content">
<div class="value-text">
<div class="section-badge">
<i data-lucide="blocks"></i>
<span>Programmable</span>
</div>
<h2 class="section-title">A Sandbox You Can Program, Not Just Configure</h2>
<p class="section-subtitle">Every policy on this page is declarative. Handlers are the layer underneath: your own code runs inside the supervisor, registered on any syscall you choose, and decides what the workload actually observes.</p>
<div class="capability-features" style="margin-top: var(--space-6);">
<div class="feature-item"><i data-lucide="check"></i><span><strong>Deny, or fabricate a result.</strong> Return an errno, or a value the syscall never produced.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Serve files that do not exist.</strong> Hand the guest a sealed in-memory file: a generated config, a secret, an object fetched from storage. No host filesystem involved.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Do slow work without stalling.</strong> Defer a call to a worker, park the guest, and answer when your backend replies.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Audit that the guest cannot evade.</strong> Interception sits below the language runtime, so <code class="inline">ctypes</code> and raw syscalls do not route around it.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Extend confinement, never weaken it.</strong> Built-ins always run first, and registering on a blocklisted syscall is rejected before fork.</span></div>
</div>
<div class="value-cta">
<a href="/docs/handlers.html" class="btn-link">
Read the handler API
<i data-lucide="arrow-right"></i>
</a>
</div>
</div>
<div class="value-visual" style="align-items: flex-start;">
<div class="code-block" style="width: 100%; margin: 0;">
<div class="code-block-header">
<span class="code-block-title">Audit every open, from your own code</span>
<button class="code-copy" type="button" aria-label="Copy code"><span class="code-copy-label">Copy</span></button>
</div>
<pre><code><span class="k">import</span> sandlock
<span class="k">from</span> sandlock.handler <span class="k">import</span> Handler, NotifAction
<span class="k">class</span> Audit(Handler):
<span class="k">def</span> handle(<span class="k">self</span>, ctx):
<span class="c"># Runs in the supervisor, before the kernel acts.</span>
<span class="k">print</span>(<span class="s">f"open {ctx.read_path()} from pid {ctx.pid}"</span>)
<span class="k">return</span> NotifAction.continue_() <span class="c"># fall through</span>
sb = sandlock.Sandbox(fs_readable=[<span class="s">"/usr"</span>, <span class="s">"/lib"</span>, <span class="s">"/etc"</span>])
sb.run_with_handlers(
cmd=[<span class="s">"python3"</span>, <span class="s">"task.py"</span>],
handlers=[(<span class="s">"openat"</span>, Audit())],
)</code></pre>
</div>
</div>
</div>
</div>
</section>
<!-- Performance -->
<section class="section">
<div class="section-container narrow">
<div class="section-header">
<div class="section-badge">
<i data-lucide="activity"></i>
<span>Performance</span>
</div>
<h2 class="section-title">Confinement You Can Leave On</h2>
<p class="section-subtitle">Landlock and the seccomp blocklist cost nothing per syscall once installed. Only the syscalls the supervisor explicitly registers for take a userspace round trip.</p>
</div>
<div class="data-table-wrap">
<table class="data-table">
<thead>
<tr>
<th scope="col">Workload</th>
<th scope="col">Bare metal</th>
<th scope="col" class="col-highlight">Sandlock</th>
<th scope="col">Docker</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row"><code>/bin/echo</code> startup</th>
<td>2 ms</td>
<td class="col-highlight">7 ms</td>
<td>307 ms</td>
</tr>
<tr>
<th scope="row">Redis SET, 100K ops</th>
<td>82K rps</td>
<td class="col-highlight">80K rps</td>
<td>52K rps</td>
</tr>
<tr>
<th scope="row">Redis GET, 100K ops</th>
<td>79K rps</td>
<td class="col-highlight">77K rps</td>
<td>53K rps</td>
</tr>
<tr>
<th scope="row">Redis p99 latency</th>
<td>0.5 ms</td>
<td class="col-highlight">0.6 ms</td>
<td>1.5 ms</td>
</tr>
<tr>
<th scope="row">COW fork × 1000</th>
<td>No equivalent</td>
<td class="col-highlight">530 ms</td>
<td>No equivalent</td>
</tr>
</tbody>
</table>
</div>
<p class="table-note">Measured on a typical Linux workstation. Redis under Sandlock holds 97.1% of bare-metal throughput. COW fork produces roughly 1,900 forks per second, or 530 µs per clone.</p>
</div>
</section>
<!-- Interfaces -->
<section class="section section-alt">
<div class="section-container">
<div class="section-header">
<div class="section-badge">
<i data-lucide="plug"></i>
<span>Interfaces</span>
</div>
<h2 class="section-title">Reach for It From Where You Already Are</h2>
<p class="section-subtitle">One Rust core, exposed through a CLI, a C ABI, three language SDKs, an OCI runtime, and an MCP server. Every surface takes the same policy.</p>
</div>
<div class="interfaces-grid">
<a href="/docs/cli.html" class="interface-card">
<div class="name"><i data-lucide="terminal"></i>CLI</div>
<p><code class="inline">sandlock run</code>, plus <code class="inline">ps</code>, <code class="inline">inspect</code>, <code class="inline">kill</code>, <code class="inline">learn</code>, and profile management.</p>
</a>
<a href="/docs/python.html" class="interface-card">
<div class="name"><i data-lucide="file-code"></i>Python SDK</div>
<p>A <code class="inline">Sandbox</code> dataclass, pipelines, COW fork/reduce, and Python callbacks for dynamic policy.</p>
</a>
<a href="/docs/rust.html" class="interface-card">
<div class="name"><i data-lucide="file-code-2"></i>Rust API</div>
<p>The core library itself: a typed builder, async execution, and custom seccomp-notify handlers.</p>
</a>
<a href="/docs/go.html" class="interface-card">
<div class="name"><i data-lucide="file-code"></i>Go SDK</div>
<p>cgo bindings over the C ABI, with a plain configuration struct that is safe to share across goroutines.</p>
</a>
<a href="/docs/oci.html" class="interface-card">
<div class="name"><i data-lucide="container"></i>OCI runtime</div>
<p>A drop-in low-level runtime for containerd, CRI-O, and Kubernetes. Namespace-less and cgroup-less.</p>
</a>
<a href="/docs/mcp.html" class="interface-card">
<div class="name"><i data-lucide="bot"></i>MCP server</div>
<p>Sandboxed shell, Python, and file tools for Claude Desktop, Cursor, and any other MCP client.</p>
</a>
</div>
</div>
</section>
<!-- Use cases -->
<section class="section">
<div class="section-container">
<div class="section-header">
<div class="section-badge">
<i data-lucide="target"></i>
<span>Where teams use it</span>
</div>
<h2 class="section-title">Built for Code You Did Not Write</h2>
</div>
<div class="cards-grid">
<div class="capability-card">
<div class="capability-icon"><i data-lucide="bot"></i></div>
<h3 class="capability-title">AI agents and tool use</h3>
<p class="capability-description">Give an agent a workspace, one API endpoint, and nothing else. Credential injection keeps the key out of the agent's reach, and a policy callback can revoke network access the moment the agent finishes starting up.</p>
<a href="/use-cases.html#ai-agents" class="btn-link">Read more <i data-lucide="arrow-right"></i></a>
</div>
<div class="capability-card">
<div class="capability-icon"><i data-lucide="git-branch"></i></div>
<h3 class="capability-title">CI and untrusted builds</h3>
<p class="capability-description">Run a pull request's build steps with the source tree copy-on-write, the network closed, and a memory cap, on a shared runner with no root and no Docker socket to hand out.</p>
<a href="/use-cases.html#ci" class="btn-link">Read more <i data-lucide="arrow-right"></i></a>
</div>
<div class="capability-card">
<div class="capability-icon"><i data-lucide="users"></i></div>
<h3 class="capability-title">Per-request code execution</h3>
<p class="capability-description">Notebook backends, code interpreters, function platforms, and grading systems that start a fresh sandbox per request. At 5 ms there is no warm pool to keep resident and nothing carries over.</p>
<a href="/use-cases.html#per-request" class="btn-link">Read more <i data-lucide="arrow-right"></i></a>
</div>
</div>
</div>
</section>
<section class="cta-band">
<div class="cta-band-inner">
<h2>Start Confining in Under Five Minutes</h2>
<p>Sandlock is Apache-2.0 licensed, free at any scale, and builds from source with Cargo. When you outgrow one machine, the Sandbox HTTP API and Sandbox Scheduler run it as a fleet.</p>
<div class="button-group">
<a href="/docs/getting-started.html" class="btn btn-primary">
<i data-lucide="rocket"></i>
Get Started
</a>
<a href="/enterprise.html" class="btn btn-secondary">
<i data-lucide="cloud"></i>
Run It as a Fleet
</a>
</div>
</div>
</section>