Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
172 lines (162 loc) · 11.7 KB
/
Copy pathindex.html
File metadata and controls
172 lines (162 loc) · 11.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
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Approvals as a First-Class Control</title>
<link rel="stylesheet" href="../vendor/reveal/reset.css">
<link rel="stylesheet" href="../vendor/fonts/fonts.css">
<link rel="stylesheet" href="../vendor/reveal/reveal.css">
<link rel="stylesheet" href="../vendor/reveal/plugin/highlight/monokai.css">
<link rel="stylesheet" href="../shared/theme.css">
</head>
<body>
<div class="reveal">
<img class="brand-mark" src="../assets/png/last9-icon-secondary.png" alt="Last9">
<div class="deck-foot"><span><b>Last9</b> · approvals as a first-class control</span></div>
<div class="slides">
<section class="title" data-background-color="#0e0f13">
<div class="kicker">Human-in-the-loop & agent safety</div>
<h1>Approvals as a<br><span style="color:var(--lime)">first-class control</span></h1>
<p class="sub">Human-in-the-loop that scales past 200 prompts a day.</p>
<p class="byline">Shekhar Patil · Last9</p>
</section>
<!-- MANDATORY · what is an agent -->
<section data-background-color="#0e0f13">
<div class="kicker">Required baseline</div>
<h2>What <em>is</em> an agent?</h2>
<p class="dim" style="font-size:.74em;margin-bottom:.4em">An <strong style="color:var(--ink)">LLM in a loop</strong> that takes <strong style="color:var(--ink)">actions</strong>, keeps <strong style="color:var(--ink)">state</strong>, and acts in the world, not a chatbot, not RPA.</p>
<svg class="diagram" viewBox="0 0 1120 312" fill="none" style="max-height:218px;margin:0 auto">
<defs>
<marker id="a" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7.5" markerHeight="7.5" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#6b6b78"/></marker>
<marker id="ad" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7.5" markerHeight="7.5" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#ff6b5e"/></marker>
<marker id="as" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7.5" markerHeight="7.5" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#c4f000"/></marker>
</defs>
<rect x="28" y="104" width="216" height="92" rx="14" fill="#1c1c22" stroke="#33333c" stroke-width="2"/>
<text x="136" y="146" fill="#fafafa" font-size="22" font-weight="600" text-anchor="middle">user · event</text>
<text x="136" y="174" fill="#a3a3ad" font-size="14" text-anchor="middle">slack · cron · webhook</text>
<circle cx="560" cy="150" r="82" fill="#14160a" stroke="#c4f000" stroke-width="2.5"/>
<text x="560" y="145" fill="#c4f000" font-size="34" font-weight="700" text-anchor="middle">LLM</text>
<text x="560" y="173" fill="#a3a3ad" font-size="15" text-anchor="middle">reason → decide</text>
<rect x="900" y="100" width="200" height="100" rx="14" fill="#2a140f" stroke="#ff6b5e" stroke-width="2.5"/>
<text x="1000" y="138" fill="#ff6b5e" font-size="20" font-weight="700" text-anchor="middle">TOOL CALL</text>
<text x="1000" y="164" fill="#fafafa" font-size="14" text-anchor="middle">shell · cloud API</text>
<text x="1000" y="186" fill="#fafafa" font-size="14" text-anchor="middle">db · message · $$</text>
<ellipse cx="560" cy="298" rx="66" ry="14" fill="#1c1c22" stroke="#33333c" stroke-width="2"/>
<path d="M494 298 v-50 a66 14 0 0 0 132 0 v50" fill="#1c1c22" stroke="#33333c" stroke-width="2"/>
<ellipse cx="560" cy="248" rx="66" ry="14" fill="#16161b" stroke="#33333c" stroke-width="2"/>
<text x="560" y="278" fill="#a3a3ad" font-size="14" text-anchor="middle">memory · state</text>
<path d="M560 246 V 240" stroke="#6b6b78" stroke-width="2" marker-end="url(#a)"/>
<path d="M248 150 H 472" stroke="#6b6b78" stroke-width="2.5" marker-end="url(#a)"/>
<text x="360" y="136" fill="#a3a3ad" font-size="14" text-anchor="middle">prompt + state</text>
<path d="M648 134 H 718" stroke="#ff6b5e" stroke-width="3" marker-end="url(#ad)"/>
<text x="683" y="120" fill="#ff6b5e" font-size="12" font-weight="600" text-anchor="middle">proposes</text>
<rect x="722" y="115" width="98" height="38" rx="10" fill="#14160a" stroke="#c4f000" stroke-width="2"/>
<text x="771" y="139" fill="#c4f000" font-size="13" font-weight="600" text-anchor="middle">approve?</text>
<path d="M824 134 H 896" stroke="#ff6b5e" stroke-width="3" marker-end="url(#ad)"/>
<text x="860" y="120" fill="#ff6b5e" font-size="12" font-weight="600" text-anchor="middle">execute</text>
<path d="M900 178 C 800 234, 662 222, 642 170" stroke="#c4f000" stroke-width="2.5" marker-end="url(#as)"/>
<text x="778" y="240" fill="#c4f000" font-size="14" text-anchor="middle">result</text>
<path d="M514 66 C 534 32, 586 32, 606 66" stroke="#6b6b78" stroke-width="2" stroke-dasharray="4 5" marker-end="url(#a)"/>
<text x="560" y="26" fill="#6b6b78" font-size="13" text-anchor="middle">repeat</text>
</svg>
<div class="row" style="gap:.7em;background:var(--lime-tint);border:1px solid var(--lime);border-radius:12px;padding:.45em .8em;margin-top:.3em">
<span class="chip safe">the gate</span>
<span style="margin:0;font-size:.95em">That <span class="id">approve?</span> step is today's talk: how to make it precise and durable.</span>
</div>
</section>
<!-- THESIS -->
<section class="divider" data-background-color="#0b0c10">
<div class="actno">THE ONE IDEA</div>
<p class="thesis">Approval is a <span class="struct">declared policy</span>, not <span class="conv">ad-hoc code</span>.</p>
<p class="lead dim" style="max-width:32ch;margin-top:.5em">Each tool declares how much human it needs. The
engine enforces it. The agent can't talk its way past it.</p>
<p class="hand-note" style="font-size:var(--step-0);margin-top:.5em">OWASP calls this <em>least agency</em>: bound what each tool can do, how often, and where.</p>
<aside class="notes">
The principle has a name now: OWASP's "least agency", which extends least privilege to agents,
it constrains not just what an agent can access but what each tool can do, how often, and where.
The key design choice: approval is declared on the tool, enforced by the engine, so the agent
can't argue its way around it. Transition: why bother gating at all? Because the agent will be tricked.
</aside>
</section>
<!-- why: confused deputy + injection -->
<section class="attack" data-background-color="#0e0f13">
<div class="kicker">Why a gate at all</div>
<h2>It obeys, even attackers.</h2>
<p class="lead">Meta's account-recovery bot changed emails and passwords on request. Attackers asked,
and took over accounts: a White House one, Sephora, a Space Force official.</p>
<p>That's the <strong>confused deputy</strong>: a trusted agent using power it shouldn't wield alone.
And via prompt injection, one poisoned GitHub issue is enough to do the asking.</p>
<aside class="notes">
Two ways a gate-less agent burns you, and they compound. One, the confused deputy: Meta's
account-recovery bot would change an account's email and password on request, attackers just asked,
and took over high-profile accounts. The bot had power it should never have wielded on its own.
Two, prompt injection: one poisoned GitHub issue can be the thing doing the asking, no human needed.
Simon Willison's lethal trifecta, private data + untrusted content + an exfil path, means agents are
exposed by design. Transition: so "just add a human" should work, except the naive version backfires.
</aside>
</section>
<!-- backfire -->
<section class="attack" data-background-color="#0e0f13">
<div class="kicker">But the naive fix backfires</div>
<h2>"Approve everything" trains people to click yes.</h2>
<p class="lead">200 prompts a day and your team rubber-stamps them. Approval fatigue is its own vulnerability.</p>
<p class="hand-note" style="font-size:var(--step-0)">a gate everyone ignores is not a gate.</p>
</section>
<!-- defense: declarative levels -->
<section class="defense" data-background-color="#0e0f13">
<div class="kicker">Structural control · per-tool approval levels</div>
<h3>The tool declares how much human it needs.</h3>
<pre><code class="language-python" data-trim data-noescape>
TOOLS = [
{"name": "get_metrics", "human": "none"}, # read-only: auto
{"name": "update_ticket", "human": "approve"}, # write: one click
{"name": "change_instance_state","human": "confirm"}, # destructive: type to confirm
{"name": "run_command", "human": "dynamic"}, # decide from the args
]
</code></pre>
<p>Irreversible ops use <em>static</em> <span class="id">confirm</span>, never <span class="id">dynamic</span>:
a bug in your resolver can't downgrade a delete.</p>
<p class="hand-note" style="font-size:.7em;margin-top:.3em">approvals gate the <em>action</em>. input spotlighting hardens the <em>prompt</em>, a complementary control we don't do yet.</p>
</section>
<!-- defense: scale -->
<section class="defense" data-background-color="#0e0f13">
<div class="kicker">Structural control · scale without fatigue</div>
<h3>Memoize decisions; defend in depth.</h3>
<div class="cols">
<div class="card safe"><h3>Learned approvals</h3>
<p>Decisions are memoized by a hash of the tool input. Approve the same call 3 times with zero
denials (<span class="id">approval_auto_promote: 3</span>) and it stops asking. Review or revoke any pattern.</p></div>
<div class="card safe"><h3>Two locks, not one</h3>
<p>engineer9 can stop an EC2 instance (approval) but the IAM role <strong>omits</strong>
<span class="id">TerminateInstances</span> and is tag-scoped. If the gate fails, IAM denies. If IAM is loose, the gate asks.</p></div>
</div>
</section>
<!-- identity incident -->
<section data-background-color="#0e0f13">
<div class="kicker">Identity is a boundary too</div>
<h2>The day the agent said "I'm mithai."</h2>
<p class="lead">engineer9's prompt never named it, so onboarding introduced it to a customer by the
<em>framework's</em> name.</p>
<p>Not a break-out, the model followed the prompt exactly. Fix: explicit identity + an <span class="id">llm_judge</span>
eval that fails if the agent ever claims the wrong name.</p>
</section>
<!-- demo + close -->
<section class="divider" data-background-color="#0b0c10">
<div class="actno">DEMO</div>
<h2>One destructive tool. One memoized one.</h2>
<p class="lead dim" style="max-width:34ch">A <span class="id">confirm</span>-level delete blocks for type-to-confirm.
A read repeated 3× auto-promotes and stops asking.</p>
</section>
</div>
</div>
<script src="../vendor/reveal/reveal.js"></script>
<script src="../vendor/reveal/plugin/highlight/highlight.js"></script>
<script src="../vendor/reveal/plugin/notes/notes.js"></script>
<script>
Reveal.initialize({ hash:true, slideNumber:'c/t', transition:'fade', transitionSpeed:'fast',
width:1280, height:720, margin:0.04, center:false, controlsTutorial:false,
plugins:[ RevealHighlight, RevealNotes ] });
</script>
</body>
</html>