-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuse-cases.html
More file actions
535 lines (472 loc) · 28.5 KB
/
Copy pathuse-cases.html
File metadata and controls
535 lines (472 loc) · 28.5 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
532
533
534
535
---
layout: default
title: "Use Cases"
description: "How teams use Sandlock: AI agent tool execution, untrusted CI builds, per-request code execution, function-as-a-service, prompt injection defense, and Kubernetes."
permalink: /use-cases.html
---
<style>
.usecase {
padding: var(--space-20) 0;
border-bottom: 1px solid var(--gray-100);
}
.usecase:nth-child(even) {
background: var(--gray-50);
}
.usecase-grid {
display: grid;
grid-template-columns: 1fr 1.05fr;
gap: var(--space-12);
align-items: start;
}
.usecase-body h2 {
font-size: clamp(var(--text-2xl), 3vw, var(--text-3xl));
font-weight: 800;
letter-spacing: -0.03em;
color: var(--gray-900);
line-height: var(--leading-tight);
margin-bottom: var(--space-4);
}
.usecase-body h2::before {
content: '';
display: block;
width: 48px;
height: 3px;
margin-bottom: var(--space-4);
background: var(--accent-500);
}
.usecase-body > p {
font-size: var(--text-base);
line-height: var(--leading-relaxed);
color: var(--gray-600);
margin-bottom: var(--space-4);
}
.usecase-body strong {
color: var(--gray-900);
font-weight: 600;
}
.usecase-points {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin-top: var(--space-6);
}
.usecase-aside {
position: sticky;
top: calc(var(--header-height) + var(--space-8));
}
.usecase-aside .code-block {
margin-top: 0;
}
.who {
margin-top: var(--space-5);
padding: var(--space-4) var(--space-5);
background: white;
border: 1px solid var(--gray-200);
border-left: 3px solid var(--accent-500);
border-radius: var(--radius-md);
}
.usecase:nth-child(even) .who {
background: white;
}
.who .mono-label {
display: block;
margin-bottom: var(--space-2);
}
.who p {
font-size: var(--text-sm);
color: var(--gray-600);
line-height: var(--leading-relaxed);
}
.usecase-body a,
.who a {
color: var(--accent-700);
text-decoration: underline;
text-decoration-color: rgba(201, 161, 62, 0.35);
text-underline-offset: 2px;
}
.usecase-body a:hover,
.who a:hover {
color: var(--accent-600);
text-decoration-color: var(--accent-500);
}
@media (max-width: 1000px) {
.usecase-grid {
grid-template-columns: 1fr;
gap: var(--space-8);
}
.usecase-aside {
position: static;
}
}
</style>
<section class="hero hero-page">
<div class="hero-container">
<div class="hero-badge">
<i data-lucide="target"></i>
<span>Use cases</span>
</div>
<h1>Code You Did Not Write, Running on Your Machine</h1>
<p class="subtitle">Every one of these has the same shape: something needs to execute, you cannot fully vouch for it, and wrapping it in a container is either too slow, too privileged, or not precise enough. These are the policies teams actually deploy.</p>
</div>
</section>
<section class="usecase" id="ai-agents">
<div class="section-container">
<div class="usecase-grid">
<div class="usecase-body">
<h2>AI Agents and Tool Execution</h2>
<p>An agent that runs shell commands is arbitrary code execution with a model choosing the arguments. It needs a workspace and one or two endpoints, not a container's whole worldview.</p>
<p>Sandlock gives it exactly the paths and endpoints you name. <strong>The API key never enters its address space</strong>: the secret stays in the supervisor and is attached in the proxy after the ACL check has passed.</p>
<div class="usecase-points">
<div class="feature-item"><i data-lucide="check"></i><span><strong>HTTP-level rules</strong>, not just <code class="inline">host:443</code>. One method, one path.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Zero-config HTTPS.</strong> An ephemeral CA, private key in memory, spliced into the trust bundles you name.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Credential stripped from the child</strong>, so a compromised agent cannot read its own key back.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>GPU selection is a boundary.</strong> <code class="inline">--gpu 0</code> makes the other device nodes unopenable.</span></div>
</div>
<div class="who">
<span class="mono-label">Also available as</span>
<p>An <a href="/docs/mcp.html">MCP server</a> exposing sandboxed shell, Python, and file tools, with a fresh sandbox per call.</p>
</div>
</div>
<div class="usecase-aside">
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">Agent with one endpoint and no key</span>
<button class="code-copy" type="button" aria-label="Copy code"><span class="code-copy-label">Copy</span></button>
</div>
<pre><code><span class="p">$</span> <span class="k">sandlock run</span> \
<span class="c"># the only endpoint that exists</span>
--http-allow <span class="s">"POST api.openai.com/v1/chat/completions"</span> \
--http-deny <span class="s">"* */admin/*"</span> \
<span class="c"># trust the ephemeral CA, no install step</span>
--http-inject-ca /etc/ssl/certs/ca-certificates.crt \
<span class="c"># the key stays in the supervisor</span>
--credential openai=env:OPENAI_API_KEY \
--http-auth <span class="s">"POST api.openai.com/v1/* bearer openai"</span> \
<span class="c"># filesystem and resource envelope</span>
-r /usr -r /lib -r /etc -w /work \
-m 1G -P 32 -t 600 \
-- python3 agent.py</code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="usecase" id="ci">
<div class="section-container">
<div class="usecase-grid">
<div class="usecase-body">
<h2>Untrusted CI Builds</h2>
<p>A pull request from a fork is untrusted code with a build script attached. A disposable VM per job is slow and expensive; Docker on a shared runner hands that job a socket equivalent to root.</p>
<p>Sandlock runs the build as an ordinary user with the network closed, the toolchain read-only, and the source tree copy-on-write. <strong>Writes commit on success and vanish on failure</strong>, and <code class="inline">--dry-run</code> shows what a build would touch without letting any of it land.</p>
<div class="usecase-points">
<div class="feature-item"><i data-lucide="check"></i><span><strong>No root, no daemon, no Docker socket</strong> on the runner.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>About 5 ms of overhead</strong>, so confining each step is practical.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Reproducible builds.</strong> Freeze the clock, seed the PRNG, sort directory reads, disable ASLR.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Generated policies.</strong> <code class="inline">sandlock learn</code> watches a real build and writes the profile.</span></div>
</div>
<div class="who">
<span class="mono-label">Nesting</span>
<p>A sandboxed job can run Sandlock itself, using <code class="inline">--no-supervisor</code> for the inner one.</p>
</div>
</div>
<div class="usecase-aside">
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">Build a fork's PR, offline and reversible</span>
<button class="code-copy" type="button" aria-label="Copy code"><span class="code-copy-label">Copy</span></button>
</div>
<pre><code><span class="c"># See what the build would change, change nothing</span>
<span class="p">$</span> <span class="k">sandlock run</span> --dry-run --workdir . \
-w . -r /usr -r /lib -r /bin -r /etc \
-- make build
<span class="o">A build/out.o</span>
<span class="o">M Cargo.lock</span>
<span class="c"># Run it for real: no network, capped, COW-committed</span>
<span class="p">$</span> <span class="k">sandlock run</span> --workdir . \
-w . -r /usr -r /lib -r /bin -r /etc \
-m 4G -P 64 -t 1800 \
-- make -j4
<span class="c"># Deterministic: frozen clock, seeded randomness</span>
<span class="p">$</span> <span class="k">sandlock run</span> \
--time-start <span class="s">"2000-01-01T00:00:00Z"</span> \
--random-seed 42 --deterministic-dirs \
-- ./build.sh</code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="usecase" id="per-request">
<div class="section-container">
<div class="usecase-grid">
<div class="usecase-body">
<h2>Per-Request Code Execution</h2>
<p>Notebook backends, interpreters, and autograders run one untrusted snippet per request, thousands of times a day. Container startup dominates the request, and a warm pool trades that latency for state leaking between callers.</p>
<p>At about 5 ms, <strong>a fresh sandbox per request is affordable</strong> and nothing carries over. Port virtualization gives each its own port space, so a hundred can bind 8080 without colliding.</p>
<div class="usecase-points">
<div class="feature-item"><i data-lucide="check"></i><span><strong>Fresh sandbox per request</strong>, with no pool and no reuse.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Named sandboxes</strong> get a stable virtual hostname for a reverse proxy to route by.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Fleet introspection.</strong> <code class="inline">sandlock ps</code> and <code class="inline">sandlock inspect</code> show what is running and under which policy.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Virtualized <code class="inline">/proc</code>.</strong> Set the CPU count and memory a workload believes it has.</span></div>
</div>
<div class="who">
<span class="mono-label">Know the boundary</span>
<p>Sandboxes on one host share a kernel. That stops a snippet reading another's files; it does not stop a kernel exploit. For mutually hostile tenants you want a separate kernel. See the <a href="/security.html">security model</a>.</p>
</div>
</div>
<div class="usecase-aside">
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">Two servers, same port, no collision</span>
<button class="code-copy" type="button" aria-label="Copy code"><span class="code-copy-label">Copy</span></button>
</div>
<pre><code><span class="p">$</span> <span class="k">sandlock run</span> --name api.local --port-remap \
--net-allow-bind 8080 \
-r /usr -r /lib -r /etc -- python3 server.py &
<span class="p">$</span> <span class="k">sandlock run</span> --name web.local --port-remap \
--net-allow-bind 8080 \
-r /usr -r /lib -r /etc -- python3 server.py &
<span class="p">$</span> <span class="k">sandlock ps</span>
<span class="o">NAME PID UPTIME CMD</span>
<span class="o">api.local 12345 5m python3 server.py</span>
<span class="o">web.local 12346 3m python3 server.py</span>
<span class="p">$</span> <span class="k">sandlock inspect</span> api.local --toml
<span class="p">$</span> <span class="k">sandlock kill</span> web.local</code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="usecase" id="faas">
<div class="section-container">
<div class="usecase-grid">
<div class="usecase-body">
<h2>Function as a Service</h2>
<p>A function platform takes an event, runs someone else's code against it, returns a response, and releases the resources. The code is not yours, and the request should pay for nothing beyond the time it ran.</p>
<p>A policy per function answers the first. A 5 ms start answers the second: <strong>every invocation gets its own sandbox</strong>, so there is no pool to keep warm, nothing held between requests, and ten concurrent events are ten sandboxes.</p>
<div class="usecase-points">
<div class="feature-item"><i data-lucide="check"></i><span><strong>Stateless by construction.</strong> A fresh sandbox carries nothing from the last invocation.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Policy per function.</strong> A resizer gets scratch space and no network; a notifier gets one endpoint and no data.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Bounded without a supervisor.</strong> Memory, processes, CPU share, and a timeout are part of the policy.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>No warm pool</strong>, so idle functions cost nothing.</span></div>
</div>
<div class="who">
<span class="mono-label">Beyond one host</span>
<p>The <a href="/enterprise.html#http-api">Sandbox HTTP API</a> makes invocations remote calls, and the <a href="/enterprise.html#scheduler">Sandbox Scheduler</a> spreads them across a fleet.</p>
</div>
</div>
<div class="usecase-aside">
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">Event in, response out, sandbox gone</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">from</span> concurrent.futures <span class="k">import</span> ThreadPoolExecutor
<span class="k">from</span> sandlock <span class="k">import</span> Sandbox, StdioMode
<span class="c"># What each deployed function is allowed to do. The resizer</span>
<span class="c"># gets scratch space and no network; the notifier gets one</span>
<span class="c"># endpoint and no data.</span>
FUNCTIONS = {
<span class="s">"resize"</span>: <span class="k">dict</span>(
fs_readable=[<span class="s">"/usr"</span>, <span class="s">"/lib"</span>, <span class="s">"/etc"</span>, <span class="s">"/srv/fn/resize"</span>],
fs_writable=[<span class="s">"/work"</span>], max_memory=<span class="s">"256M"</span>, max_processes=4,
),
<span class="s">"notify"</span>: <span class="k">dict</span>(
fs_readable=[<span class="s">"/usr"</span>, <span class="s">"/lib"</span>, <span class="s">"/etc"</span>, <span class="s">"/srv/fn/notify"</span>],
http_allow=[<span class="s">"POST api.internal/v1/notify"</span>], max_memory=<span class="s">"128M"</span>,
),
}
<span class="k">def</span> invoke(name: <span class="k">str</span>, event: <span class="k">bytes</span>) -> <span class="k">bytes</span>:
<span class="s">"""One invocation: a sandbox of its own, torn down on return."""</span>
proc = Sandbox(**FUNCTIONS[name]).popen(
[<span class="s">"python3"</span>, <span class="s">f"/srv/fn/{name}/handler.py"</span>],
stdin=StdioMode.PIPED, stdout=StdioMode.PIPED,
)
proc.stdin.write(event)
proc.stdin.close() <span class="c"># EOF, so the handler runs</span>
response = proc.stdout.read() <span class="c"># drain before wait</span>
<span class="k">if not</span> proc.wait(timeout=30).success:
<span class="k">raise</span> RuntimeError(<span class="s">f"{name} failed"</span>)
<span class="k">return</span> response
<span class="c"># Concurrent events are concurrent sandboxes, one policy each.</span>
<span class="k">with</span> ThreadPoolExecutor() <span class="k">as</span> pool:
responses = <span class="k">list</span>(pool.map(<span class="k">lambda</span> e: invoke(<span class="s">"resize"</span>, e), events))</code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="usecase" id="prompt-injection">
<div class="section-container">
<div class="usecase-grid">
<div class="usecase-body">
<h2>Prompt Injection Defense</h2>
<p>An agent reading a web page, a document, or a tool result is reading text an attacker may have written, and no amount of prompt engineering reliably stops a model being talked into something.</p>
<p>So stop trying, and make being convinced worthless. <strong>Split the agent so whatever reads untrusted text holds no capability, and whatever holds capability never reads untrusted text.</strong> Each stage keeps its own policy and data passes between them through kernel pipes.</p>
<div class="usecase-points">
<div class="feature-item"><i data-lucide="check"></i><span><strong>The reader has nothing to give away.</strong> No network, no credentials, no data paths.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>The actor never sees the text</strong>, only the structured result, so nothing in its input can carry an instruction.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Exfiltration fails at the syscall.</strong> "Send this to my server" hits a <code class="inline">connect()</code> the policy never allowed.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>"Print your API key" returns nothing</strong>, because <a href="/docs/http-acl.html#credential-injection">credential injection</a> kept it out of the agent's memory.</span></div>
</div>
<div class="who">
<span class="mono-label">What this does not do</span>
<p>It does not stop the model producing a hostile answer. What the policy bounds is what the agent can <em>do</em>, which is the part an attacker wants.</p>
</div>
</div>
<div class="usecase-aside">
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">The reader is powerless, the actor is blind</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">from</span> sandlock <span class="k">import</span> Sandbox
<span class="c"># Reads the untrusted page and emits structured JSON.</span>
<span class="c"># No network and no secrets, so an instruction buried in</span>
<span class="c"># that page has nothing to reach for.</span>
reader = Sandbox(
fs_readable=[<span class="s">"/usr"</span>, <span class="s">"/lib"</span>, <span class="s">"/bin"</span>, <span class="s">"/etc"</span>,
<span class="s">"/work/fetched"</span>],
max_memory=<span class="s">"512M"</span>,
)
<span class="c"># Holds the one endpoint that may be called. It sees the</span>
<span class="c"># reader's JSON, never the prose it came from.</span>
actor = Sandbox(
fs_readable=[<span class="s">"/usr"</span>, <span class="s">"/lib"</span>, <span class="s">"/bin"</span>, <span class="s">"/etc"</span>],
http_allow=[<span class="s">"POST api.internal/v1/tickets"</span>],
)
result = (
reader.cmd([<span class="s">"python3"</span>, <span class="s">"extract.py"</span>, <span class="s">"page.html"</span>])
| actor.cmd([<span class="s">"python3"</span>, <span class="s">"file_ticket.py"</span>])
).run()</code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="usecase" id="programmable">
<div class="section-container">
<div class="usecase-grid">
<div class="usecase-body">
<h2>Compliance Audit and Virtual Filesystems</h2>
<p>Some requirements no declarative policy can meet: a guaranteed record of every file the guest touched, or artifacts streamed to object storage as they are written rather than collected afterwards.</p>
<p>Handlers run your code inside the supervisor, on whichever syscalls you name, before the kernel acts. <strong>Because interception sits below the language runtime, an audit trail built this way cannot be routed around by <code class="inline">ctypes</code> or a raw syscall</strong>, and unlike eBPF tracing it needs no <code class="inline">CAP_BPF</code>.</p>
<div class="usecase-points">
<div class="feature-item"><i data-lucide="check"></i><span><strong>Files with no host backing.</strong> Intercept <code class="inline">openat</code> and return a sealed in-memory file.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Slow work without stalling.</strong> A handler doing a network round trip defers to a worker.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Confinement is never weakened.</strong> Built-ins run first, and a handler on a blocklisted syscall is rejected before fork.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Rust, Python, or C</strong>, through the same model.</span></div>
</div>
<div class="who">
<span class="mono-label">Why it lives here</span>
<p>The kernel allows one seccomp notification listener per process, so extra interception must run inside the same supervisor loop. That is what the <a href="/docs/handlers.html">handler API</a> provides.</p>
</div>
</div>
<div class="usecase-aside">
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">Python: deny by pattern, audit the rest</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.presets <span class="k">import</span> (
AuditPathsHandler, PathDenyHandler, COMMON_PATH_SYSCALLS,
)
audit = AuditPathsHandler(
callback=<span class="k">lambda</span> path, _ctx: log.info(<span class="s">"open %s"</span>, path)
)
deny = PathDenyHandler(deny=[<span class="s">"*/.ssh/*"</span>, <span class="s">"*/.aws/*"</span>])
sb = sandlock.Sandbox(
fs_readable=[<span class="s">"/usr"</span>, <span class="s">"/lib"</span>, <span class="s">"/etc"</span>],
fs_writable=[<span class="s">"/work"</span>],
)
sb.run_with_handlers(
cmd=[<span class="s">"python3"</span>, <span class="s">"task.py"</span>],
handlers=[(s, deny) <span class="k">for</span> s <span class="k">in</span> COMMON_PATH_SYSCALLS]
+ [(s, audit) <span class="k">for</span> s <span class="k">in</span> COMMON_PATH_SYSCALLS],
)</code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="usecase" id="kubernetes">
<div class="section-container">
<div class="usecase-grid">
<div class="usecase-body">
<h2>More Pods per Node on Kubernetes</h2>
<p>A container charges you before your workload runs an instruction: namespaces to construct, cgroups to wire, a privileged runtime to mediate it. That cost is per pod, paid on every start.</p>
<p><code class="inline">sandlock-oci</code> implements the same OCI runtime interface, so containerd, CRI-O, and the kubelet drive it in place of <code class="inline">runc</code> with your images unchanged. What it produces is <strong>namespace-less and cgroup-less, confined by Landlock and seccomp</strong>, so a pod costs roughly what the process inside it costs.</p>
<div class="usecase-points">
<div class="feature-item"><i data-lucide="check"></i><span><strong>About 5 ms to start</strong>, against roughly 200 ms for a container.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>97% of bare-metal throughput</strong> on the Redis benchmark, where Docker held 63%.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>No namespaces, no cgroups, no privileged daemon.</strong></span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Policy rides on pod annotations.</strong> Network and HTTP rules the OCI spec cannot carry travel as <code class="inline">io.sandlock.*</code> keys.</span></div>
<div class="feature-item"><i data-lucide="check"></i><span><strong>Checkpoint and restore</strong>, so an idle pod can give its memory back.</span></div>
</div>
<div class="who">
<span class="mono-label">What you give up</span>
<p>The parts of a container that <em>are</em> namespaces: a pod-private PID space and network stack. A workload that needs those should stay on <code class="inline">runc</code>.</p>
</div>
</div>
<div class="usecase-aside">
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">Register the runtime, once</span>
<button class="code-copy" type="button" aria-label="Copy code"><span class="code-copy-label">Copy</span></button>
</div>
<pre><code><span class="c"># containerd. pod_annotations is what forwards the</span>
<span class="c"># io.sandlock.* keys through to the runtime.</span>
[plugins.<span class="s">"io.containerd.grpc.v1.cri"</span>.containerd.runtimes.sandlock]
runtime_type = <span class="s">"io.containerd.runc.v2"</span>
pod_annotations = [<span class="s">"io.sandlock.*"</span>]
[plugins.<span class="s">"io.containerd.grpc.v1.cri"</span>.containerd.runtimes.sandlock.options]
BinaryName = <span class="s">"/usr/local/bin/sandlock-oci"</span>
<span class="c"># ---</span>
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: sandlock
handler: sandlock</code></pre>
</div>
<div class="code-block">
<div class="code-block-header">
<span class="code-block-title">Then policy travels in annotations</span>
<button class="code-copy" type="button" aria-label="Copy code"><span class="code-copy-label">Copy</span></button>
</div>
<pre><code>apiVersion: v1
kind: Pod
metadata:
name: agent
annotations:
<span class="c"># ';' separates entries, because ',' is already</span>
<span class="c"># meaningful inside a network spec.</span>
io.sandlock.network.allow: <span class="s">"api.internal:443;10.0.0.0/8:5432"</span>
io.sandlock.http.allow: <span class="s">"POST api.internal/v1/*"</span>
io.sandlock.config.http_inject_ca: <span class="s">"/etc/ssl/certs/ca-certificates.crt"</span>
spec:
runtimeClassName: sandlock
containers:
- name: agent
image: python:3.12-slim <span class="c"># unchanged</span></code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="cta-band">
<div class="cta-band-inner">
<h2>Which One Is Yours?</h2>
<p>If your situation is close to one of these but not quite it, we would like to hear about it. Running these at fleet scale is what the Sandbox HTTP API and Sandbox Scheduler are for.</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="building-2"></i>
Talk to Us
</a>
</div>
</div>
</section>