-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
503 lines (497 loc) · 42.1 KB
/
Copy pathindex.html
File metadata and controls
503 lines (497 loc) · 42.1 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Termaxa — a gate for the shell commands AI coding agents run</title>
<meta name="description" content="The agent's prompt shows you the command; Termaxa shows you the consequence. It previews the blast radius, backs up first, blocks the dangerous ones, and keeps a record the agent cannot rewrite. Claude Code, Codex, Cursor, Copilot. Free and open source. Try it in your browser at play.termaxa.com.">
<meta property="og:title" content="Termaxa — a cooperative gate for the shell commands AI agents run">
<meta property="og:description" content="Preview what will happen. Protect what matters. Roll back when you're wrong. A windshield, not a sandbox.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://termaxa.com">
<meta property="og:image" content="https://termaxa.com/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="theme-color" content="#0a0e0a">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#0a0e0a; --panel:#0d130d; --line:#1c2a1c; --line-2:#243824;
--fg:#c8e6c8; --fg-dim:#6f9b6f; --fg-faint:#4a6b4a;
--green:#4ade80; --amber:#fbbf24; --red:#f87171; --blue:#60a5fa;
--mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
--maxw:1000px;
}*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{background:var(--bg);color:var(--fg);font-family:var(--mono);font-size:14px;line-height:1.7;-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 20px}
/* faint scanline texture — subtle, not gimmicky */
body::before{content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
background:repeating-linear-gradient(0deg,rgba(74,222,128,.015) 0,rgba(74,222,128,.015) 1px,transparent 1px,transparent 3px);opacity:.5}
main{position:relative;z-index:1}
/* top nav as shell tokens */
header{display:flex;align-items:center;justify-content:space-between;max-width:var(--maxw);margin:0 auto;padding:18px 20px}
.brand{color:var(--green);font-weight:700;display:flex;align-items:center;gap:10px}
.brand .c{color:var(--fg-faint)}
.brand .mark{display:flex;align-items:center;gap:5px}
.brand .mark svg{display:block}
nav{display:flex;gap:8px;flex-wrap:wrap}
nav a{color:var(--fg-dim);padding:3px 4px}
nav a:hover{color:var(--green)}
nav a .b{color:var(--fg-faint)}
/* the big terminal frame */
.screen{border:1px solid var(--line-2);border-radius:10px;background:var(--panel);
box-shadow:0 0 0 1px rgba(74,222,128,.04),0 40px 100px -40px rgba(0,0,0,.8);
margin:8px auto 0;overflow:hidden}
.screen-in{padding:26px 30px}
@media(max-width:640px){.screen-in{padding:20px 16px}}
.cmdline{color:var(--fg-dim)}
.cmdline .p{color:var(--green)}
.cmdline .cmd{color:var(--fg)}
/* ASCII wordmark */
.wordmark{margin:10px 0 10px}
.wordmark .wm-svg{width:clamp(210px,30vw,320px);height:auto;display:block}
.tagline{color:var(--fg);font-size:clamp(12px,1.8vw,16px);margin:10px 0 4px}
.tagline .s{color:var(--fg-faint)}
/* hero verdict box (types in) */
.verdict{margin:26px 0 6px;border:1px solid var(--line);border-radius:8px;padding:20px 22px;background:rgba(0,0,0,.2)}
.verdict .row{white-space:pre-wrap}
.g{color:var(--green)}.a{color:var(--amber)}.r{color:var(--red)}.bl{color:var(--blue)}
.dim{color:var(--fg-dim)}.faint{color:var(--fg-faint)}
.impact-box{display:grid;grid-template-columns:1fr 1fr;gap:10px 30px;border:1px solid var(--line);border-radius:6px;padding:16px 18px;margin:12px 0}
@media(max-width:640px){.impact-box{grid-template-columns:1fr}}
.impact-box .h{color:var(--fg-dim);margin-bottom:6px}
.kv{display:flex;justify-content:space-between;gap:16px}
.kv .lbl{color:var(--fg-dim)}
.cur{display:inline-block;width:8px;height:15px;background:var(--green);vertical-align:middle;animation:blink 1.1s steps(1) infinite}
@keyframes blink{50%{opacity:0}}
/* section label */
.lab{color:var(--fg-dim);margin:0 0 14px;display:flex;align-items:center;gap:10px}
.lab::before{content:"#";color:var(--fg-faint)}
.lab::after{content:"";flex:1;height:1px;background:var(--line)}
section{padding:30px 0}
/* works with */
.works{display:grid;grid-template-columns:repeat(4,1fr) auto;gap:0;border:1px solid var(--line);border-radius:8px;overflow:hidden}
@media(max-width:760px){.works{grid-template-columns:1fr 1fr}}
.work{padding:18px 20px;border-right:1px solid var(--line)}
.work:last-child{border-right:none;display:flex;align-items:center;color:var(--fg-faint)}
.work .t{color:var(--fg);display:flex;align-items:center;gap:8px;margin-bottom:4px}
.work .d{color:var(--fg-dim);font-size:12.5px}
.work .ic{width:16px;height:16px}
.work.git .ic{color:#f0623c}.work.pg .ic{color:#5b8fc9}.work.tf .ic{color:#7b42bc}.work.sh .ic{color:var(--green)}
/* live demo trio */
.demos{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
@media(max-width:760px){.demos{grid-template-columns:1fr}}
.demo{border:1px solid var(--line);border-radius:8px;padding:16px 18px;position:relative}
.demo .n{color:var(--fg);margin-bottom:10px}
.demo .n .num{color:var(--green)}
.demo .ln{white-space:pre-wrap;color:var(--fg-dim);font-size:13px}
.demo .ms{position:absolute;right:14px;bottom:12px;color:var(--fg-faint);font-size:11px}
/* numbers */
.nums{display:grid;grid-template-columns:repeat(5,1fr);gap:0;border:1px solid var(--line);border-radius:8px;overflow:hidden}
@media(max-width:760px){.nums{grid-template-columns:1fr 1fr 1fr}}
@media(max-width:440px){.nums{grid-template-columns:1fr 1fr}}
.num-cell{padding:20px 16px;text-align:center;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}
.num-cell .v{color:var(--green);font-size:24px;font-weight:700;line-height:1.2}
.num-cell .l{color:var(--fg-dim);font-size:12px;margin-top:4px}
/* hardened by real use */
.bugs{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
@media(max-width:760px){.bugs{grid-template-columns:1fr}}
.bug{border:1px solid var(--line);border-radius:8px;padding:16px 18px}
.bug .t{color:var(--amber);display:flex;gap:8px;margin-bottom:8px}
.bug .d{color:var(--fg-dim);font-size:13px}
.bug .v{color:var(--green)}
/* field notes */
.notes{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media(max-width:760px){.notes{grid-template-columns:1fr}}
.note{border:1px solid var(--line);border-radius:8px;padding:18px 20px;display:block}
.note:hover{border-color:var(--green)}
.note .d{color:var(--fg-faint);font-size:11.5px;letter-spacing:.04em;margin-bottom:8px}
.note .t{color:var(--fg);font-size:14.5px;line-height:1.5;margin-bottom:8px}
.note:hover .t{color:var(--green)}
.note .x{color:var(--fg-dim);font-size:12.5px}
/* live agent gif band */
.agent{border:1px solid var(--line-2);border-radius:10px;overflow:hidden;background:var(--panel)}
.agent .head{padding:20px 24px 6px}
.agent .head h3{color:var(--fg);font-size:16px;font-weight:700;margin-bottom:6px}
.agent .head p{color:var(--fg-dim);font-size:13px;max-width:600px}
.agent .media{padding:16px 24px 24px}
.agent img{width:100%;border:1px solid var(--line);border-radius:8px;display:block}
.agent .fb{white-space:pre-wrap;border:1px solid var(--line);border-radius:8px;padding:18px;background:rgba(0,0,0,.25);font-size:12.5px;line-height:1.85}
/* whats next + free forever */
.split{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media(max-width:760px){.split{grid-template-columns:1fr}}
.next-box,.free-box{border:1px solid var(--line);border-radius:8px;padding:20px 22px}
.next-box .item{color:var(--fg);margin-bottom:8px}
.next-box .item .box{color:var(--fg-faint)}
.free-box{border-color:rgba(74,222,128,.3);background:linear-gradient(180deg,rgba(74,222,128,.05),transparent);text-align:center}
.free-box .big{color:var(--green);font-size:17px;font-weight:700;margin-bottom:10px}
.free-box p{color:var(--fg-dim);font-size:13px}
/* install */
.install-row{display:flex;align-items:center;gap:16px;flex-wrap:wrap;border:1px solid var(--line);border-radius:8px;padding:16px 20px}
.install-cmd{background:rgba(0,0,0,.3);border:1px solid var(--line);border-radius:6px;padding:10px 14px;color:var(--fg);flex:1;min-width:220px;display:flex;justify-content:space-between;align-items:center;gap:12px}
.install-cmd .p{color:var(--green)}
.install-cmd .copy{color:var(--fg-faint);cursor:pointer;font-size:11px;border:1px solid var(--line);border-radius:5px;padding:3px 8px;background:none;font-family:var(--mono)}
.install-cmd .copy:hover{color:var(--green);border-color:var(--green)}
.os{color:var(--fg-dim);font-size:12.5px}
.chips{display:flex;gap:8px}
.chip{border:1px solid var(--line);border-radius:6px;padding:6px 14px;color:var(--fg-dim)}
.chip:hover{border-color:var(--green);color:var(--green)}
.try-line{color:var(--fg-dim);font-size:12.5px;margin-top:10px}
.try-line .c{color:var(--fg)}
/* footer prompt */
.foot{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;padding:22px 0 8px;color:var(--fg-faint)}
.foot .ver .p{color:var(--green)}
.foot .links{display:flex;gap:20px;flex-wrap:wrap}
.foot .links a:hover{color:var(--green)}
.sibling{color:var(--fg-faint);font-size:12.5px;padding:8px 0 6px}
.sibling a{color:var(--fg-dim);border-bottom:1px dotted var(--line-2)}
.sibling a:hover{color:var(--green);border-color:var(--green)}
.maker{border:1px solid var(--line);border-radius:8px;padding:22px 24px;background:rgba(0,0,0,.15)}
.maker .who{display:flex;align-items:center;gap:12px;margin-bottom:14px}
.maker .who .av{width:34px;height:34px;border-radius:6px;background:var(--green);color:#04120a;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:15px}
.maker .who .nm{color:var(--fg)}
.maker .who .nm .h{color:var(--fg-faint);font-size:12px}
.maker p{color:var(--fg-dim);font-size:13.5px;margin-bottom:12px}
.maker p b{color:var(--fg)}
.maker .sig{color:var(--fg-faint);font-size:12.5px;margin-top:14px;border-top:1px solid var(--line);padding-top:12px}
.maker .sig a{color:var(--fg-dim);border-bottom:1px dotted var(--line-2)}
.maker .sig a:hover{color:var(--green);border-color:var(--green)}
</style>
<style>
/* v0.19.4 rebuild: the hero is the demo */
.live{border:1px solid var(--line-2);border-radius:10px;background:rgba(0,0,0,.35);padding:18px 18px 14px;margin-top:18px}
.live .hint{color:var(--fg-dim);font-size:12.5px;margin-bottom:10px}
.live .chips2{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px}
.live .chip2{border:1px solid var(--line-2);border-radius:6px;padding:5px 10px;font-size:12px;color:var(--fg);cursor:pointer;background:transparent;font-family:var(--mono)}
.live .chip2:hover{border-color:var(--green);color:var(--green)}
.live .row2{display:flex;gap:10px;align-items:stretch}
.live input{flex:1;background:#050805;border:1px solid var(--line-2);border-radius:6px;color:var(--fg);font-family:var(--mono);font-size:14px;padding:10px 12px;outline:none}
.live input:focus{border-color:var(--green)}
.live button.go{background:var(--green);color:#0a0e0a;border:0;border-radius:6px;font-family:var(--mono);font-weight:700;font-size:13px;padding:0 18px;cursor:pointer}
.live button.go:disabled{opacity:.5;cursor:wait}
.live pre{margin-top:12px;white-space:pre-wrap;word-break:break-word;font-size:12.5px;line-height:1.55;color:var(--fg);min-height:3.2em}
.live pre .deny{color:var(--red);font-weight:700}.live pre .ask{color:var(--amber);font-weight:700}.live pre .allow{color:var(--green);font-weight:700}
.live .meta{display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;margin-top:10px;color:var(--fg-faint);font-size:11.5px}
.gif{margin-top:22px;border:1px solid var(--line-2);border-radius:10px;overflow:hidden;background:#000}
.gif img{display:block;width:100%;height:auto}
.gif .cap{padding:10px 14px;color:var(--fg-dim);font-size:12px;border-top:1px solid var(--line)}
.harn{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:12px;margin-top:14px}
.harn .h{border:1px solid var(--line);border-radius:8px;padding:12px 14px;background:var(--panel)}
.harn .h .t{font-weight:700;color:var(--fg)}.harn .h .d{color:var(--fg-dim);font-size:12px;margin-top:4px;line-height:1.55}
.harn .h .t .ic{width:14px;height:14px;vertical-align:-2px;margin-right:4px;color:var(--green)}
.harn .h.harness .t::before{content:"» ";color:var(--green)}
.harn .h.herdr{border-color:rgba(96,165,250,.4)}.harn .h.herdr .t{color:var(--blue)}
.tabs{display:flex;gap:6px;flex-wrap:wrap;margin-top:14px}
.tabs button{background:transparent;border:1px solid var(--line-2);border-bottom:0;border-radius:6px 6px 0 0;color:var(--fg-dim);font-family:var(--mono);font-size:12px;padding:6px 12px;cursor:pointer}
.tabs button.on{color:var(--green);border-color:var(--green)}
.tabpane{border:1px solid var(--line-2);border-radius:0 8px 8px 8px;padding:14px 16px;background:rgba(0,0,0,.3)}
.tabpane .cmd{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap}
.tabpane code{font-size:13px;color:var(--fg);white-space:pre-wrap;word-break:break-all}
.tabpane .note{color:var(--fg-faint);font-size:11.5px;margin-top:8px}
.tabpane button.cp{color:var(--fg-faint);cursor:pointer;font-size:11px;border:1px solid var(--line);border-radius:5px;padding:3px 8px;background:transparent;font-family:var(--mono)}
.then{margin-top:12px;color:var(--fg-dim);font-size:12.5px}.then code{color:var(--fg)}
details.more{margin-top:14px}details.more summary{cursor:pointer;color:var(--green);font-size:12.5px;list-style:none}details.more summary::-webkit-details-marker{display:none}
details.more .bugs{margin-top:12px}
.split{align-items:start}
</style>
</head>
<body>
<main>
<header>
<div class="brand">
<span class="mark" aria-hidden="true">
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><rect width="22" height="22" rx="5" fill="#4ade80"/><path d="M8 6.5 13 11l-5 4.5" fill="none" stroke="#0a0e0a" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<svg width="12" height="22" viewBox="0 0 12 22" xmlns="http://www.w3.org/2000/svg"><rect x="0" y="2" width="2.6" height="18" rx="1.3" fill="#4ade80"/><rect x="4.7" y="2" width="2.6" height="18" rx="1.3" fill="#fbbf24"/><rect x="9.4" y="2" width="2.6" height="18" rx="1.3" fill="#f87171"/></svg>
</span>
<span><span class="c">$</span> termaxa</span>
</div>
<nav>
<a href="#what"><span class="b">[</span>what<span class="b">]</span></a>
<a href="#notes"><span class="b">[</span>blog<span class="b">]</span></a>
<a href="#roadmap"><span class="b">[</span>roadmap<span class="b">]</span></a>
<a href="https://play.termaxa.com"><span class="b">[</span>play<span class="b">]</span></a>
<a href="https://github.com/termaxa/termaxa"><span class="b">[</span>github<span class="b">]</span></a>
<a href="https://github.com/termaxa/termaxa#readme"><span class="b">[</span>docs<span class="b">]</span></a>
</nav>
</header>
<div class="wrap">
<!-- HERO: the gate, live -->
<div class="screen">
<div class="screen-in">
<div class="cmdline"><span class="p">$</span> <span class="cmd">termaxa</span> <span class="faint">— a cooperative gate for the shell commands AI agents run</span></div>
<div class="wordmark"><svg class="wm-svg" viewBox="0 0 400 90" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="termaxa">
<text x="0" y="68" font-family="'JetBrains Mono',monospace" font-weight="700" font-size="82" letter-spacing="-2" fill="#4ade80">termaxa</text>
</svg></div>
<div class="tagline">Preview what will happen.<span class="s"> · </span>Protect what matters.<span class="s"> · </span>Roll back when you're wrong.</div>
<div class="tagline" style="color:var(--fg-dim);font-size:clamp(11px,1.5vw,13.5px);margin-top:10px;line-height:1.6"><span style="color:var(--fg)">The agent's own prompt shows you the command it wants to run. Termaxa shows you the consequence.</span> The files, the rows, the commit a force push would lose: previewed before anything runs, backed up first, the dangerous few blocked, and every decision in a record the agent cannot rewrite. Hooks for Claude Code, Codex, Cursor and Copilot. A windshield, not a sandbox.</div>
<div class="live" id="live">
<div class="hint">This is the real gate, running on a throwaway project with the default policy. Nothing executes — <code>termaxa check</code> reads the command and answers. Try to get a destructive one past it.</div>
<div class="chips2">
<button class="chip2">rm -rf ./scratch</button>
<button class="chip2">git push --force origin main</button>
<button class="chip2">psql -c "DROP TABLE users CASCADE"</button>
<button class="chip2">echo TOKEN=abc123 > .env</button>
<button class="chip2">ls && echo ok && rm -rf ./scratch</button>
<button class="chip2">git -C . push -f origin main</button>
<button class="chip2">ls -la</button>
</div>
<div class="row2">
<input id="livecmd" type="text" spellcheck="false" autocomplete="off" placeholder="type a command an agent might run…" value="rm -rf ./scratch">
<button class="go" id="livego">check it</button>
</div>
<pre id="liveout">$ termaxa check "rm -rf ./scratch"
press check it, or pick a chip</pre>
<div class="meta"><span id="livestats"></span><span>full playground, with what counts as beating it: <a href="https://play.termaxa.com" style="color:var(--green)">play.termaxa.com</a></span></div>
</div>
<div class="gif">
<img src="/termaxa-claude-code.gif" width="1102" height="772" alt="A real Claude Code 2.1.283 session with its own approvals switched off: it inspects scratch/ freely, tries rm -rf ./scratch, and Termaxa's hook stops it with the reason and the 12 files it would have taken; the agent declines to route around it" loading="lazy">
<div class="cap">Inside the agent, not beside it: a real Claude Code session with its own approvals switched off. The inspection runs without a prompt; the <code>rm -rf</code> is stopped with the reason and the 12 files it would have taken, and the agent declines to route around it. Recorded on termaxa 0.19.5.</div>
</div>
</div>
</div>
<!-- WORKS WITH -->
<section id="what">
<div class="lab">works with</div>
<div class="harn">
<div class="h harness"><div class="t">claude code</div><div class="d">hook on Bash and the write tools · live-tested · <code>wrap</code> reaches it too</div></div>
<div class="h harness"><div class="t">codex</div><div class="d">hook, deny only: an ask is a refusal there · apply_patch read as writes</div></div>
<div class="h harness"><div class="t">cursor</div><div class="d">shell events and Write/Delete · the 3.11 hook API captured live</div></div>
<div class="h harness"><div class="t">copilot cli</div><div class="d">an ask arrives as a real prompt with the reason in it</div></div>
<div class="h herdr harness"><div class="t">herdr</div><div class="d">launch an agent under the gate, tail the record, see why a pane went red<br><code>herdr plugin install termaxa/herdr-termaxa</code></div></div>
</div>
<div class="harn" style="margin-top:12px">
<div class="h"><div class="t"><svg class="ic" viewBox="0 0 24 24" fill="currentColor"><path d="M23 11.1 12.9 1a1.6 1.6 0 0 0-2.3 0L8.5 3.1l2.7 2.7a1.9 1.9 0 0 1 2.4 2.4l2.6 2.6a1.9 1.9 0 1 1-1.1 1L12.6 9.4v6.4a1.9 1.9 0 1 1-1.6 0V9.4a1.9 1.9 0 0 1-1-2.5L7.3 4.2 1 10.5a1.6 1.6 0 0 0 0 2.3L11.1 23a1.6 1.6 0 0 0 2.3 0L23 13.4a1.6 1.6 0 0 0 0-2.3z"/></svg> git</div><div class="d">what a force push would destroy, pinned before it runs</div></div>
<div class="h"><div class="t"><svg class="ic" viewBox="0 0 24 24" fill="currentColor"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6" fill="none" stroke="currentColor" stroke-width="1.5"/></svg> postgres</div><div class="d">rows and dependent tables, pg_dump first, rollback after</div></div>
<div class="h"><div class="t"><svg class="ic" viewBox="0 0 24 24" fill="currentColor"><path d="M9 3v6l5 3V6zM15 7v6l5-3V4zM9 11v6l5 3v-6zM3 5v6l5 3V8z"/></svg> terraform</div><div class="d">the plan before the apply</div></div>
<div class="h"><div class="t"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m4 7 5 5-5 5M12 17h8"/></svg> shell</div><div class="d">policy, delete previews, insurance, the record</div></div>
</div>
</section>
<!-- INSTALL -->
<section id="install">
<div class="lab">install</div>
<div class="tabs" id="tabs">
<button class="on" data-cmd="brew install termaxa/tap/termaxa" data-note="macOS and Linux. Prebuilt, sha256-pinned; the formula's CI installs it on both before every release goes out.">brew</button>
<button data-cmd="cargo install termaxa" data-note="Any OS with a Rust toolchain. Builds from the crates.io source.">cargo</button>
<button data-cmd="winget install Termaxa.Termaxa" data-note="Windows. The winget manifest can lag a release by a day or two while Microsoft validates it.">winget</button>
<button data-cmd="scoop bucket add termaxa https://github.com/termaxa/scoop-bucket && scoop install termaxa" data-note="Windows, always the current release.">scoop</button>
<button data-cmd="" data-note="Every asset is attested and checksummed. The Linux one is a static musl build that runs on any distro.">binaries</button>
</div>
<div class="tabpane">
<div class="cmd"><code id="tabcmd"><span class="p">$</span> brew install termaxa/tap/termaxa</code><button class="cp" id="tabcopy">copy</button></div>
<div class="note" id="tabnote">macOS and Linux. Prebuilt, sha256-pinned; the formula's CI installs it on both before every release goes out.</div>
<div class="chips" id="binchips" style="display:none;margin-top:10px">
<a class="chip" href="https://github.com/termaxa/termaxa/releases/latest/download/termaxa-linux-x86_64">linux x86_64</a>
<a class="chip" href="https://github.com/termaxa/termaxa/releases/latest/download/termaxa-macos-arm64">macos arm64</a>
<a class="chip" href="https://github.com/termaxa/termaxa/releases/latest/download/termaxa-macos-x86_64">macos x86_64</a>
<a class="chip" href="https://github.com/termaxa/termaxa/releases/latest/download/termaxa-windows-x86_64.exe">windows</a>
</div>
</div>
<div class="then">then, in a project: <code>termaxa init</code> writes the policy and the hook for whatever harness it finds · <code>termaxa doctor</code> proves it's wired · <code>termaxa replay</code> judges every command your agents have already run, so you know the ask ratio before you commit · <code>termaxa check "rm -rf /"</code> works anywhere, no setup</div>
</section>
<!-- HARDENED -->
<section id="hardened">
<div class="lab">hardened by real use</div>
<div class="nums">
<div class="num-cell"><div class="v">35</div><div class="l">releases</div></div>
<div class="num-cell"><div class="v">~15,900</div><div class="l">lines of Rust <span class="faint">(+16,400 of tests)</span></div></div>
<div class="num-cell"><div class="v">541</div><div class="l">regression tests</div></div>
<div class="num-cell"><div class="v">16</div><div class="l">bugs found in the wild</div></div>
<div class="num-cell"><div class="v">4</div><div class="l">live agents tested</div></div>
</div>
<div class="bugs">
<div class="bug">
<div class="t">⚠ shell segmentation bypass</div>
<div class="d">A live agent rode a destructive command in behind a harmless prefix. Compound commands are now split and judged per-segment. <span class="v">(v0.7)</span></div>
</div>
<div class="bug">
<div class="t">⚠ native tools bypass the shell</div>
<div class="d">The same Cursor agent then used its built-in file tool, deleting files the shell hook never saw. For a year the answer was "non-shell tools need a sandbox". Since v0.19 the gate reads them: a <code>Write</code>, <code>Edit</code>, <code>Delete</code> or a patch's file headers is judged by the same path rules as a shell command, insured and receipted — and a file no rule names is silence, by design. Cursor's <code>Delete</code> passed through by default until a live capture on Sep 19. <span class="v">(v0.19–0.19.2)</span></div>
</div>
<div class="bug">
<div class="t">⚠ an escaped quote walked past the gate</div>
<div class="d">A backslash-escaped quote collapsed a compound command into one segment, so an anchored deny rule never saw the second command: <code>echo \" ; terraform destroy</code> matched <code>echo *</code> and was allowed. Live since v0.7.0. Found by unifying two shell parsers and reading what they disagreed about — the disagreements were the bug list. Advisory published. <span class="v">(GHSA-rv66-7qcx-c45j, v0.16)</span></div>
</div>
<div class="bug">
<div class="t">⚠ the supervisor was never reached</div>
<div class="d">Supervised mode passed 439 unit tests, 18 privilege assertions and three green CI platforms. Then a real agent ran <code>ls -la</code> as a real second user, and the gate decided locally: its hook looked for the supervisor in its <em>own</em> home and found nothing there. The walls held; the door led nowhere. Every automated test had run both halves as the same user. <span class="v">(v0.17)</span></div>
</div>
<div class="bug">
<div class="t">⚠ Codex honoured none of our verdicts</div>
<div class="d">The first live Codex session: the hook fired on every command, and Codex rejected every answer — an explicit allow, an ask, and a deny that exited 2 all failed the hook and fell open to Codex's own prompt. Codex honours exactly one PreToolUse verdict, <code>deny</code>, on stdout with exit 0. Fixed the same weekend; the captured payload is a regression test now, and a hard stop lands in Codex's own UI as "Blocked by hook" with the blast radius. <span class="v">(v0.18)</span></div>
</div>
<div class="bug">
<div class="t">⚠ the wrapper never saw Claude Code</div>
<div class="d"><code>termaxa wrap</code> shipped on the promise that a shell resolved by name reaches the gate. Then <code>strace</code> on a real session: Claude Code runs <code>/bin/bash</code> by absolute path when it finds no zsh, and every command walked past the shims with zero audit lines. It honours <code>CLAUDE_CODE_SHELL</code>, so <code>wrap</code> now points it at the shim. Codex runs its login shell by absolute path and offers no such lever; its hooks are the way in, and the docs say so. <span class="v">(v0.18.5)</span></div>
</div>
</div>
<details class="more">
<summary>+ the other ten, with dates and versions →</summary>
<div class="bugs">
<div class="bug">
<div class="t">⚠ git force-push blind spot</div>
<div class="d">The preview said "nothing to push" while a force push destroyed a commit. Now it shows what the remote will lose. <span class="v">(v0.6.1)</span></div>
</div>
<div class="bug">
<div class="t">⚠ repo-owned audit logs</div>
<div class="d">Logs lived in the repo, so <code>git reset --hard</code> could erase them. State moved to <code>~/.termaxa</code>, out of git's reach. <span class="v">(v0.8)</span></div>
</div>
<div class="bug">
<div class="t">⚠ retry-with-different-syntax</div>
<div class="d">A live Cursor agent, blocked on a delete, switched shells (PowerShell → cmd) to sneak it past. Termaxa now classifies intent, not spelling, and trips a per-session circuit breaker on the repeat. <span class="v">(v0.11)</span></div>
</div>
<div class="bug">
<div class="t">⚠ path syntax decided your backup</div>
<div class="d">Two engines parsed the same command independently. <code>rm -rf C:\Users\x\Desktop</code> was insured; <code>rm -rf /c/Users/x/Desktop</code> — the same directory — silently wasn't. Found by building the delete preview and comparing the two. They now share one implementation. <span class="v">(v0.14)</span></div>
</div>
<div class="bug">
<div class="t">⚠ the agent's API moved under us</div>
<div class="d">Cursor 3.11 renamed its hook events — and Termaxa silently stopped gating it. Every test stayed green (fixtures used the old shape). Caught live via payload capture, fixed the same night; the real 3.11 payloads are regression tests now. <span class="v">(v0.11.4)</span></div>
</div>
<div class="bug">
<div class="t">⚠ a delete with no verb in the list</div>
<div class="d">Cursor's <code>Delete</code> tool arrived at the hook, matched no write verb, and fell through by default: <code>doomed.txt</code> was deleted with no line in the record. Found by a debug capture on Cursor 3.11.25 the day native writes went live. <code>delete</code> and <code>remove</code> are write verbs now, and the captured payload is the regression test. <span class="v">(v0.19.2)</span></div>
</div>
<div class="bug">
<div class="t">⚠ every spelling but one</div>
<div class="d">The hard stop read <code>git push*--force*</code>, so <code>git push -f</code> was an <em>ask</em> while <code>--force</code> was a deny. And <code>git -C /path push --force</code> matched no rule at all, hard stop included, because git's global options sit before the subcommand every rule was written for. Found in one live Herdr session where Claude Code spelled every git call with <code>-C</code>. Git's options are stepped over everywhere git is read; force is denied in every spelling. <span class="v">(v0.19.4)</span></div>
</div>
<div class="bug">
<div class="t">⚠ the insurance followed the link</div>
<div class="d">The preview counts a directory link as one entry and never walks into it. The backup copy branched on <code>is_dir()</code>, which follows links — so a folder holding a junction into a live tree passed the size cap as four files and then copied the whole tree behind it, uncapped, and a rollback would have put the link back as a real directory. That is the mechanism of a public incident that deleted 48,000 files through Windows junctions, inside our own insurance. A link is a link now. <span class="v">(v0.19.4)</span></div>
</div>
<div class="bug">
<div class="t">⚠ a rule that could not read its own spellings</div>
<div class="d">A genomics team published a security-reviewed deny list for the <code>gh</code> CLI with one line marked "genuinely open question, unverified". Answering it meant measuring both sides. Their side: Claude Code's matcher steps over an env prefix but lets <code>sh -c</code>, <code>eval</code> and <code>xargs</code> run. Ours: with a <code>gh repo delete*</code> deny, the plain form denied and five spellings the head resolver already understood came back as asks, because the string rules never saw the resolver's readings. Every spelling the resolver knows now feeds every rule; sudo is transparent to a deny and never to an allow. <span class="v">(v0.19.5)</span></div>
</div>
<div class="bug">
<div class="t">⚠ the option beside the one a rule was written for</div>
<div class="d">Tim Schipper built 0.19.5 from source and found five commands the starter allowed for reading that write: <code>git branch -M</code>, <code>find -fprint</code>, <code>git diff --output</code>, <code>sed -n -i</code> and inline <code>node -e</code>, each allowed with no prompt, no preview and no backup. Reproducing them turned up sed's <code>e</code> command, which runs any shell command under the same allow. And a push that deletes remote branches asked, but its preview said "nothing to push". Now the option that writes or runs turns the allow into an ask that names it, the file it would write is previewed and backed up first, and a push that removes refs says which. Published as <a href="https://github.com/termaxa/termaxa/security/advisories/GHSA-36jf-95xr-37f2">GHSA-36jf-95xr-37f2</a>, credited to him. <span class="v">(v0.19.6)</span></div>
</div>
</div>
</details>
</section>
<!-- FIELD NOTES -->
<section id="notes">
<div class="lab">field notes</div>
<div class="notes">
<a class="note" href="/blog/claude-code-sandbox">
<div class="d">AUG 2026</div>
<div class="t">Claude Code shipped a sandbox. Here's what it protects — and what it doesn't.</div>
<div class="x">Bash-only scope, reads wider than writes, a permissioned escape hatch, fail-open, no native Windows — and why containment isn't consequence isn't recovery.</div>
</a>
<a class="note" href="/blog/cursor-saga">
<div class="d">JUL 2026</div>
<div class="t">I asked an AI agent to delete a folder my tool was guarding.</div>
<div class="x">Four rounds with a live Cursor agent: whack-a-mole retries, a classifier hole, a native-tool escape, and a silently renamed hook API.</div>
</a>
</div>
</section>
<!-- WHATS NEXT + FREE -->
<section id="roadmap">
<div class="lab">what's next</div>
<div class="split">
<div class="next-box">
<div class="item"><span class="box">[x]</span> supervised mode — a daemon under <em>your</em> user decides; the agent runs as an account that cannot read the audit log, edit the backups, or stop it</div>
<div class="item"><span class="box">[x]</span> resolved-target rules — <code>> .env</code> and <code>> ./.env</code> are one file, not two strings</div>
<div class="item"><span class="box">[x]</span> hash-chained audit — an edited or removed entry is detectable</div>
<div class="item"><span class="box">[x]</span> the gate reads what the agent actually runs — <code>sh -c</code> strings, <code>bash -lc</code>, a redirect that isn't a target, a PowerShell assignment</div>
<div class="item"><span class="box">[x]</span> the closed side, by choice — <code>unrecognised: deny</code> and <code>backup_failure: deny</code> for unattended runs</div>
<div class="item"><span class="box">[x]</span> the wrapper reaches the agent — <code>wrap</code> steers Claude Code to its shims through <code>CLAUDE_CODE_SHELL</code>; Codex hardcodes its shell and is gated by its hooks instead</div>
<div class="item"><span class="box">[x]</span> native writes through the gate — <code>Write</code>/<code>Edit</code>/<code>Delete</code> and patch headers judged by path rules, insured, receipted</div>
<div class="item"><span class="box">[x]</span> <code>termaxa replay</code> — every command your agents ever ran, judged from their transcripts; the honest ask ratio before you install</div>
<div class="item"><span class="box">[x]</span> the playground — <a href="https://play.termaxa.com">play.termaxa.com</a>, the real gate in a browser, and the four published advisories as solved challenges</div>
<div class="item"><span class="box">[x]</span> herdr plugin — launch an agent under the gate, tail the record, see why a pane went red (<code>herdr plugin install termaxa/herdr-termaxa</code>)</div>
<div class="item"><span class="box">[x]</span> cursor — live-tested (incl. the 3.11 hook API)</div>
<div class="item"><span class="box">[x]</span> post-execution receipts — approved commands don't trip the breaker</div>
<div class="item"><span class="box">[x]</span> execution report — session & 30-day flight recorder</div>
<div class="item"><span class="box">[x]</span> termaxa doctor — is the gate actually wired up?</div>
<div class="item"><span class="box">[x]</span> delete blast radius — what an <code>rm</code> actually costs</div>
<div class="item"><span class="box">[x]</span> codex — live-tested on Windows; its hooks can only deny, so an ask is a refusal there</div>
<div class="item"><span class="box">[x]</span> copilot — live-tested; an ask arrives as a real prompt with the reason in it</div>
<div class="item"><span class="box">[ ]</span> termaxa cloud <span class="faint">(shared policies & approvals)</span></div>
<div style="color:var(--fg-faint);font-size:12.5px;margin-top:10px">v0.20.1 today · built in the open · honest about what's not done</div>
</div>
<div class="free-box">
<div class="big">this tool is free forever.</div>
<p>open source under MIT / Apache-2.0<br>no feature gates. no license keys. ever.<br><span class="faint">cloud sits on top of the free core, never in front of it.</span></p>
</div>
</div>
</section>
<!-- FROM THE MAKER --><section>
<div class="lab">from the maker</div>
<div class="maker">
<div class="who">
<div class="av">M</div>
<div class="nm">Manoj<br><span class="h">solo maker · builds developer infrastructure</span></div>
</div>
<p>I build with Claude Code every day, and I kept getting nervous handing it commands that touch git history and production databases. The built-in "allow this command?" prompt tells you <b>what</b> it wants to run — not what will actually <b>happen</b>.</p>
<p>So I built the thing I wanted: a gate that shows the real consequence, takes a backup <b>before</b> you approve, and lets you roll back. The first time I pointed a live agent at it, the agent chained a destructive command behind a harmless one and slipped past a naive rule. Watching that happen is why Termaxa now splits compound commands and judges each part — <b>that exact bypass is a regression test today.</b></p>
<div class="sig">— <b>Manoj</b> · also the maker of <a href="https://zerodrop.dev">ZeroDrop</a>, email-verification infrastructure for developers</div>
</div>
</section>
<div class="foot">
<div class="ver"><span class="p">$</span> termaxa --version<br>termaxa 0.20.1</div>
<div class="links">
<a href="https://github.com/termaxa/termaxa">github</a>
<a href="https://github.com/termaxa/termaxa#readme">docs</a>
<a href="https://github.com/termaxa/termaxa/security/policy">security</a>
<a href="mailto:security@termaxa.com">security@termaxa.com</a>
<a href="https://github.com/termaxa/termaxa/blob/main/CHANGELOG.md">changelog</a>
</div>
</div>
<div class="sibling">also from the maker: <a href="https://zerodrop.dev">zerodrop → email verification infrastructure for developers</a></div>
<div style="height:30px"></div>
</div>
</main>
<script>
(function(){
var API="https://play.termaxa.com";
var inp=document.getElementById("livecmd"),go=document.getElementById("livego"),out=document.getElementById("liveout"),st=document.getElementById("livestats");
function esc(x){return x.replace(/[&<>]/g,function(c){return {"&":"&","<":"<",">":">"}[c]})}
function render(text){
out.innerHTML=text.split("\n").map(function(l){
var m=l.match(/^(decision\s+)(allow|ask|deny)(.*)$/);
if(m)return esc(m[1])+'<span class="'+m[2]+'">'+m[2]+'</span>'+esc(m[3]);
return esc(l);
}).join("\n");
}
async function stats(){try{var r=await fetch(API+"/api/stats");if(!r.ok)return;var j=await r.json();st.textContent=j.attempts+" attempts on the playground: "+j.denied+" denied, "+j.asked+" asked, "+j.allowed+" allowed"+(typeof j.bypasses_fixed==="number"?" · bypasses found and fixed: "+j.bypasses_fixed:"");}catch(e){}}
// auto: the check this page runs on load, so the playground can answer it without counting it as an attempt.
function post(c,auto){return fetch(API+"/api/check",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(auto?{command:c,auto:true}:{command:c})})}
async function check(auto){
auto=auto===true;
var c=inp.value.trim();if(!c)return;go.disabled=true;
out.textContent="$ termaxa check "+JSON.stringify(c)+"\n…";
try{
var r=await post(c,auto);
if(r.status===503){out.textContent="$ termaxa check "+JSON.stringify(c)+"\nbusy, retrying…";await new Promise(function(x){setTimeout(x,1500)});r=await post(c,auto);}
if(!r.ok){out.textContent=await r.text();return;}
var j=await r.json();render("$ termaxa check "+JSON.stringify(c)+"\n"+j.output);stats();
}catch(e){out.textContent="the playground didn't answer ("+e+"). It's at play.termaxa.com.";}
finally{go.disabled=false;}
}
go.addEventListener("click",function(){check(false)});inp.addEventListener("keydown",function(e){if(e.key==="Enter")check(false)});
Array.prototype.forEach.call(document.querySelectorAll(".chip2"),function(b){b.addEventListener("click",function(){inp.value=b.textContent;check()})});
stats();
setTimeout(function(){check(true)},150);
var tabs=document.querySelectorAll("#tabs button"),cmd=document.getElementById("tabcmd"),note=document.getElementById("tabnote"),bins=document.getElementById("binchips"),cp=document.getElementById("tabcopy"),cur="brew install termaxa/tap/termaxa";
Array.prototype.forEach.call(tabs,function(t){t.addEventListener("click",function(){
Array.prototype.forEach.call(tabs,function(x){x.classList.remove("on")});t.classList.add("on");
cur=t.getAttribute("data-cmd")||"";note.textContent=t.getAttribute("data-note")||"";
if(cur){cmd.innerHTML='<span class="p">$</span> '+esc(cur);cmd.style.display="";cp.style.display="";bins.style.display="none";}
else{cmd.innerHTML='<span class="p">$</span> <span class="faint">download, checksum, put it on your PATH</span>';cp.style.display="none";bins.style.display="flex";}
cp.textContent="copy";
})});
cp.addEventListener("click",function(){navigator.clipboard.writeText(cur);cp.textContent="copied";});
})();
</script>
</body>
</html>