Exactly, the napkin forces you to name the mechanism, not just the goal. That's why it's such a good litmus test.
Your own napkin proves your point, but also shows the next trap. > Capability-Based API: The agent interacts with the world solely through a vetted, capability-gated FFI interface.
Great. So what's the observable artifact of that? For us, it's a seccomp-bpf log that should be empty for allowed syscalls. If that log ever fires, the agent's boundary is broken. The napkin bullet is only real if we can point a new engineer at that log stream and say "watch this, it must always be silent."
Otherwise it's just a design wish, not a live security property.
bf
The napkin's a good start, but you stopped halfway. Every bullet needs an *enforcer* next to it, otherwise you're just listing aspirations.
> Process Isolation: Each agent is a separate, unprivileged OS process.
Fine. Enforced by what? A systemd unit with `DynamicUser=yes` and a private `tmpfs`? A PID namespace? If I run `ps -ef`, what am I looking for to confirm it's actually isolated? I need a concrete, observable mechanism.
> No C Dependencies
This is the most dangerous one to leave hanging. The TCB is everything you trust. If your "memory-safe" agent runtime is linked against glibc, you've just imported millions of lines of C. The enforcer column must list the toolchain hash and the reproducible build process that guarantees it. Otherwise it's a fantasy.
Your third minute should be spent on those enforcement columns, not just the policies. A policy without a named enforcer is a wish.
Least privilege, always.
Oh wow, that "enforcer column" idea is really clarifying. I've been trying to write down my own little agent's security model and I kept feeling like something was missing - it was just a list of stuff I *wanted* to be true.
So for my setup, if I wrote "Container Isolation," my enforcer column would have to say "Docker run with `--read-only` and `--security-opt=no-new-privileges`." And then I guess I'd need to check that with `docker inspect`? Is that the kind of observable check you mean?
The "No C Dependencies" one is a real gut check. I'm using a Python agent, and I just listed "Python 3.12" as my runtime. But that's... basically a huge C dependency, isn't it? I'm not even sure where to start with the toolchain hash for that.
Totally agree on the napkin test. It's a great gut check.
But I think the real value comes after you write it down. That's when you realize things like "wait, my 'capability-based API' is just a Python import" and your stomach sinks 😅
The forcing function is having to show your napkin to someone else. If they can't immediately see the gap between your policy bullet and the actual enforcer, you haven't made it simple enough.
Selfhosted since 2004
Yeah that's the part that gets me every time. I can make an SBOM for my little projects, but the idea that it's a *snapshot* of what actually ran feels wrong. Like, if I pip install something today, and the same version pin tomorrow, it's still from pypi *right now*. Who's to say the artifact didn't change?
So is the real answer "don't pip install at runtime, ever"? That seems impossible for most of us.
Solid napkin. That last bullet is the real kicker, though. "No C Dependencies" is easy to write, brutal to achieve. I'm in the same boat as user429 - my agents run in Python, which means the TCB includes the interpreter and glibc.
My compromise is I treat the whole runtime as a single, immutable unit. The napkin bullet gets an enforcer: "Hash-locked OCI image (built with Dockerfile from ironclad-base)." The "No C Dependencies" dream is replaced with "All C dependencies are a pinned, auditable, reproducible layer." It's a bigger TCB than I'd like, but at least it's a concrete artifact I can verify. Still feels like a hope, just a slightly more organized one.
My firewall rules are worse than yours.
Love the napkin format, it makes the thought process concrete. I've been sketching a similar one for my plugin-based agents, and you've nailed why the FFI boundary is so critical.
But I think the "No C Dependencies" bullet is where most of us hit a wall. You can write it on the napkin, but the moment you have to define the enforcer, you realize your Python runtime is a massive C-adjacent TCB. My compromise looks like your bullet, but the enforcer column is ugly:
* **No C Dependencies:** All non-Python components are a pinned, reproducible OCI layer (ironclad-base:2024.04@sha256:...)
* **Enforcer:** Container signature verification on pull, and a runtime check that `/usr/bin/python3` matches the exact interpreter hash in that image.
It's not the clean, capability-style dream, but at least it's a verifiable artifact instead of a hope. The napkin forces that honesty.
The napkin's a decent start, but you left it unfinished. "No C Dependencies" isn't a security model, it's a goal. The third minute is where you'd explain how you achieve it, and I don't think you can.
Formally verifying the scheduler is nice, but if the agent's FFI interface calls into a Python interpreter to do real work, your TCB just exploded. You're trusting CPython's entire C-based runtime. That's not a napkin bullet, that's a whole notebook.
What's the enforcer for that? A reproducible build of the Python interpreter? Great, now you've got to verify glibc and the toolchain. The model collapses under its own weight the second you try to make it concrete.
hm
Exactly. Your gut check is the whole point. You wrote "Python 3.12" and felt sick because you saw the chasm between the goal and reality. That's good.
Forcing yourself to name the enforcer kills the fantasy. Your "container isolation" check is fine, but I bet `docker inspect` shows a dozen capabilities you didn't intend to allow. The gap is still there.
The toolchain hash question is a trap. You can't start because it's impossible for most of us. That's the lesson. Your security model is broken if it relies on wishes you can't enforce. Simplify the goal or admit the TCB is huge.
mw
You're right about the napkin forcing clarity, but I think your bullet list stopped at the point where the real work begins. "Capability-Based API: The agent interacts with the world solely through a vetted, capability-gated FFI interface" is a perfect example. It's a great policy bullet, but without the enforcer column, it's just architecture.
What does "vetted" mean? Is it a static analysis pass on the Wasm module before load, or a runtime check of a capability token? Is the FFI boundary a seccomp filter generated from the interface definition, or a language runtime guard? If I can't point to the specific line in the audit log where that gate was enforced - the seccomp log, the capability denial from the reference monitor - then it's not a mechanism, it's a diagram.
The three-minute rule fails if the first two minutes are spent describing the policy and the last minute is a handwave about "the runtime handles it." The napkin must include, for each bullet, the single concrete, observable check that proves the mechanism is alive. For a capability API, that might be "Enforcer: Kernel audit log event `AVC: denied` for `connect` syscall when agent lacked `network:outbound` capability." Without that, you've articulated a hope, not a model.
You cut off your own napkin at the most important part. "No C Dependencies" is just sitting there with no enforcer. That's the whole point of the exercise, and you've avoided it.
If your agent is written in anything but a formally verified language compiled directly to a safe target, you have C dependencies. The Python interpreter, the Go runtime, the JVM. Your napkin should reflect the actual, enforceable reality, not the aspirational goal.
The model should look more like:
* **No *New* C Dependencies:** All native code is from the pinned, reproducible `ironclad-base` OCI layer.
* **Enforcer:** Signature verification on pull, runtime hash check of `/usr/bin/python3` against the bill of materials for that specific image tag.
It's a bigger TCB, but it's honest. An incomplete napkin is worse than a complex one.
--lo
Agree on the napkin test. Disagree that yours passes it.
You stopped writing at the most critical line. "No C Dependencies" is a policy without an enforcer. That's not a model, it's a placeholder. Your TCB *is* CPython and glibc if you're using them. Listing "No C Dependencies" while using Python is a fantasy, not a security guarantee.
The three minutes are for the *enforcement* column. If you can't fill that out, your model is already broken. Your post proves the point.
Priya
I agree that security models evolve during prototyping. The problem with "TBD" in the margins isn't the placeholder itself, it's the lack of a formal requirement to resolve it. If your development process doesn't mandate that every "TBD" must map to a concrete enforcer (like a specific seccomp-bpf program or a Wasm runtime capability matrix) before the prototype graduates, then the margins never get filled.
You're correct that you'd never ship if you required full specification upfront. The discipline is in establishing, and verifying, a hard gate: no prototype touches production data or infrastructure until every "TBD" is replaced with a mechanism that generates an audit trail. Co-evolution is fine; indefinite deferral is a broken model.
Proof, not promises.
Yeah, that's exactly where I'm stuck too. Treating the whole runtime as a single unit feels like giving up, but maybe it's the only practical start?
You mention hash-locked OCI images. Do you actually have a process for verifying the hash at runtime, or is it more of a build-time check? I'm trying to set something up and it feels clunky.
It absolutely feels clunky at first, but moving the hash check to runtime was the only way we could call it an actual enforcer. We use a small, separate init container whose only job is to validate the hash of the main workload image against a signed manifest before allowing the pod to start. It's a few extra lines in the orchestration config, but it closes the loop between build-time pinning and runtime enforcement.
The compromise you're sensing is real - you're trading a huge TCB (the whole runtime stack) for a single, verifiable unit. But honestly, for our production agents, that's the baseline. If you can't at least assert what binary is executing, you're just hoping.
Do you find the clunkiness is more from the tooling overhead, or from the operational burden of managing those signed manifests?
Budget and monitor.