DeepConcepts

Agentic Security / agent / enforcement point / kernel

Kernel Enforcement Cannot See Intent

The misconception

That putting Tetragon or Falco under an agent contains it, because eBPF sees everything and blocks in-kernel at microsecond latency. Coverage really is that good and the block really does hold. The gap is semantic: the hook sees an outbound connection to an allowed address, not that a prompt injection caused it or that the bytes are rows from a table the agent was never meant to query. Because a legitimate agent's paths, hosts and processes drift prompt to prompt, a learned allowlist is either loose enough to contain the attacker's primitives too or tight enough to break the agent — and the settings in between shrink to nothing as that drift grows. The designs that work concede this by construction: they use eBPF to see and to redirect, and put the decision at a layer that has the nouns — an L7 proxy, a per-agent identity, a network policy, an admission rule. The cost of not knowing that is a control that is trusted, correct, and produces no security.

16 min

An eBPF policy under your agent sees every system call it makes and can refuse any of them from inside the kernel. That is true, it is not marketing, and it is not enough. eBPF — the extended Berkeley Packet Filter, the in-kernel virtual machine Linux uses to run small verified programs at kernel hook points — decides on the arguments the kernel has. A path. A binary. A five-tuple. It does not have the one argument that separates the agent's own work from an injected instruction, because that argument was never a syscall argument.

This is a different question from the one in which enforcement point sits astride which action. There the failure is coverage: the layer you configured is simply not in the path of the thing that happened. Here coverage is perfect. Every action passes the hook, every hook fires, every refusal holds. The question is what a hook that is squarely in the path is able to decide once it gets there.

Concretely: an agent working on a ticket connects to db.internal:5432. So does the same agent after it reads a poisoned line in a runbook — same destination, same port, same process, same syscall, different SELECT. A policy written on the five-tuple cannot separate those two, and no amount of kernel-side speed changes that. A policy written on the destination host can only separate them when the destinations differ. So the whole design problem becomes: how much of what a legitimate agent does is distinguishable, by argument alone, from what an attacker wants it to do?

Below, an agent runs 120 tasks against a Kubernetes cluster with a kernel-level policy under it. The policy was built the way real ones are built: run the agent in observe-only mode, record everything it touched, keep what it touched often enough. Two sliders. Policy tightness is how much of that observed tail you keep. Behavioural variance is how far a fresh prompt's paths and destinations drift from the agent's usual working set. Watch the two readouts that move against each other, and watch the strip of eleven bars, which is the same simulation run at all eleven tightness settings at once.

the stream

Tightness is a threshold on how often a target appeared during the audit: at 0 anything seen even once during the 2,000 observed tasks stays on the list, at 10 only targets seen in every single one survive. A target the audit never saw is never allowed. Variance 0 is a nearly deterministic agent that still reaches a new path in about one task in ten; variance 10 is one whose working set turns over constantly. DNS is the Domain Name System, the lookup that turns a hostname into the address a connection then uses.

tasks finished with the injected chain stopped
tightness settings that stop the chain and still finish 9 tasks in 10
agent task success
tasks the policy broke
actions the hook observed
blocks that definitely prevented the operation
egress routes still open to the injected chain
allowlist entries kept
action classes matched on a user-memory pointer
verdict at this setting
The same run at all eleven tightness settings

Bar height is the agent's task success rate at that tightness; the current setting is outlined. the chain is stopped and the agent still finishes at least 9 tasks in 10 · the chain is stopped but the policy has broken the agent · the injected chain reaches its objective. Nine tasks in ten is this page's bar for "still working", not anyone's standard; the success readout is there so you can pick your own. Bars are floored at a sliver so a 0% column still shows its colour.

What is real and what is a model. The mechanism is real: a policy learned from an observation window, evaluated per action against the argument the kernel has, with a task failing if any of its actions is refused. The enforcement semantics are taken from Tetragon's documentation and are described below. What is invented is the workload — the pools of paths, binaries and destinations, and the shape of the drift between tasks. The audited frequencies are counted from a simulated 2,000-task observation window, not measured anywhere. Treat the arithmetic of the trade-off as the lesson and the absolute percentages as an illustration. Nothing here is an attack: the injected chain is a defender's inventory of four egress routes, each named at the level of "a connection to a host the audit never saw".

Two numbers that cannot both be good

Reset the panel before reading on. Tightness 1, variance 2, blocking by kprobe with Sigkill, network policy at the DNS query-name allowlist, the injected instruction ticked on, the general HTTP tool ticked off. That is the starting state, and every number below assumes it unless the paragraph says otherwise.

There it reads as a success. The hero number says 111 tasks finished with the chain stopped, task success is 92.5%, all four egress routes are closed, and the window readout says 3 of 11 tightness settings would do the same. The hook observed 3,857 actions and ruled on all of them. That is the state people mean when they say the agent is contained by eBPF, and at variance 2 it is a fair description.

Now drag behavioural variance from 2 to 6 and leave everything else alone. The agent gets better at its job, in the sense that it stops repeating itself: it reaches new packages, new documentation hosts, new files. Task success barely moves, 92.5% to 86.7%. The hero number goes to 0. The window goes to 0 of 11. Route 2 has opened, because a wider-ranging agent legitimately fetched from a public file host often enough during the audit that the audit put it on the allowlist. Nothing about the attack changed. The agent's own repertoire grew, and it grew over the same ground the attacker needed.

The obvious response is to tighten. Leave variance at 6 and drag tightness from 1 to 4. It works: all four routes close again, and the chain is stopped. Task success is 48.3%. Fifty-eight of the agent's 120 tasks now finish, sixty-two die on a refusal of something the agent legitimately needed, and the strip shows every bar from tightness 2 rightward in the warm colour that means "stopped the chain, broke the agent", with only tightness 0 and 1 still in the hot colour that means the chain gets out. The window readout still says 0 of 11, because no setting on the strip does both.

Put tightness back to 1 and variance back to 2 — the panel returns to 111 and 3 of 11, exactly where it started. Then walk variance up one step at a time and watch only the window readout. It goes 3, 2, 2, 1, 0 as variance goes 2, 3, 4, 5, 6. At variance 5 there is exactly one setting left, and it is tightness 1. At variance 6 there is none, and there is none at any higher variance either. That is the crossover, and it does not reopen.

Put variance back to 2 before the next section.

The hook is not the weak part

Everything above happened with the policy working perfectly, and it is worth proving that rather than asserting it, because the natural reading of a failure like this is that the tool is flaky. It is not.

From the reset state — tightness 1, variance 2 — the "actions the hook observed" readout says 3,857 of 3,857. There is no sampling and no miss: a TracingPolicy — a Kubernetes custom resource, defined by a CustomResourceDefinition, or CRD, that Tetragon installs — attaches a BPF program at the hook point, and the program runs on every entry. BPF here is the same in-kernel virtual machine as eBPF; the kernel dropped the "extended" from the name internally and the tooling never fully caught up. Tetragon's documentation on selectors is explicit that the decision happens where the event happens — "Sigkill, Override, Post, TrackSock and UntrackSock are executed directly in the kernel BPF code while GetUrl and DnsLookup are happening in userspace after the reception of events." The whole design goal is that the filtering happens before anything crosses to user space.

That design goal is also the constraint that makes the semantic problem unfixable at this layer, and a Tetragon maintainer says so directly. Issue 971 asks whether a hook can pause while an external service decides what to do. The answer, closing the issue: "No, because everything is done synchronously in the kernel, you can't wait on the event and rely on a userspace source to make the decision." The kernel program cannot call out to anything that knows what a prompt injection is, because calling out means blocking in kernel context. Nor can it simply be given more logic: the verifier has to prove the program terminates and stays in bounds before it will load at all. Speed and semantics are traded against each other here by construction, not by an implementation gap somebody will close.

The how the policy blocks selector is the other half of "perfect". Leave tightness at 1 and variance at 2, and read the two readouts on the right. With kprobe and Sigkill, "blocks that definitely prevented the operation" says 8 of 9, and "action classes matched on a user-memory pointer" says 3 of 4. Both of those are documented weaknesses and both have fixes:

  • Sigkill is not the same as refusing. Tetragon's enforcement page says a SIGKILL sent inside a write() "does not guarantee that the data will not be written to the file", and recommends combining Signal with Override. Switch the selector to "kprobe on the syscall, Override" and the readout goes to 9 of 9. Override "will run in place of the original probed function and return the value specified in the argError field", so the call never executes. It needs a kernel built with CONFIG_BPF_KPROBE_OVERRIDE, and the target function has to be in /sys/kernel/debug/error_injection/list; syscalls are the main case, and security_ hooks became overridable in 5.7.
  • A kprobe on a syscall reads the caller's memory. The Tetragon hook points page carries this warning: "Hooking a system call can introduce time-of-check to time-of-use (TOCTOU) races when the relevant argument is a pointer to user-space memory. In that case, user space can modify the underlying data after the hook executes but before the kernel consumes it. Hooking a later kernel function, such as an LSM security_ hook, avoids this issue because it operates on kernel-resident state after the data has been copied from user space." LSM is the Linux Security Module framework, the kernel's built-in set of access-control hook points, and BPF LSM lets you attach a BPF program to them. Switch the selector to "BPF LSM hook, Override" and both readouts go clean: 9 of 9 enforced, 0 of 4 classes exposed.

This is the distinction worth carrying away from the mechanism, and which attach point can decide what is a subject in its own right. A kprobe can be attached to any kernel function and is superb for observation; using one for enforcement means either killing the process afterwards or injecting an error return into a function somebody marked as safe to inject errors into. A BPF LSM hook is an access-control decision point that the kernel put there on purpose: the program returns -EPERM, the kernel's permission-denied error, and the operation does not happen. Getting it is a boot question, not a code question. CONFIG_BPF_LSM=y must be set, and bpf must appear in the active list, which you read with cat /sys/kernel/security/lsm. Mainline's default CONFIG_LSM string ends in bpf, so on a stock kernel it is usually already there; Tetragon's docs give the fallback of adding it to GRUB_CMDLINE_LINUX="lsm=lockdown,integrity,apparmor,bpf" because plenty of distribution kernels ship a CONFIG_LSM that omits it.

Now the part that matters. With the selector on BPF LSM and Override — the strongest configuration the panel offers, still at tightness 1 and variance 2 — the hero number is 111, the window is 3 of 11, all four routes are closed. Identical to the Sigkill run. Move variance to 6: the hero number goes to 0 and the window to 0 of 11, exactly as before. Upgrading from a block that might race to a block that cannot buys real correctness and buys zero security against the injected chain, because the injected chain was never blocked by a race. It was allowed by a rule that said yes.

Put variance back to 2 and the selector back to "kprobe on the syscall, Sigkill" before the next section.

What the network layer can and cannot read

Tightness 1, variance 2, Sigkill, network policy at the DNS query-name allowlist, injected instruction on, HTTP tool off. The network policy selector is where most of the real hope lives, because a destination is a much better discriminator than a syscall number, and it is worth being precise about how far it goes.

Set the selector to "addresses and ports only". Route 3 opens, the hero number drops from 111 to 0, and the window goes from 3 of 11 to 0 of 11 — at every variance, because a rule on addresses cannot distinguish a name lookup from a name lookup carrying data. The cluster resolver is in 100% of audited tasks; there is no threshold that removes it and leaves the agent able to resolve anything.

Set it back to "plus a DNS query-name allowlist" and the hero number returns to 111. This is a genuine win and it is worth understanding why it is a win: the name is a layer above the address, and a DNS proxy in the Cilium agent reads it. Cilium's own documentation bounds the win in the same breath — policy "may be applied to DNS traffic, allowing or disallowing specific DNS query names or patterns of names (other DNS fields, such as query type, are not considered)". A name is an argument the policy has. A payload is not.

Now set the selector to "plus HTTP method and path rules" and read the log. The hero number stays at 111, the window stays at 3 of 11, and a line appears saying 0 of the 1,001 egress connections are cleartext. Layer 7 policy in Cilium — L7, the application layer, as against the addresses and ports of layers 3 and 4 — can match on path, method, host and headers, and Cilium's documentation is specific about how: "Layer 7 policies will proxy traffic through a node-local Envoy instance, which will either be deployed as a DaemonSet or embedded in the agent pod." Against an agent that speaks TLS to everything — TLS being Transport Layer Security, the encryption under HTTPS — that proxy has nothing to read. Cilium says so plainly in its TLS-visibility guide: an application accessing a service over HTTPS "would normally mean that network-layer mechanisms like Cilium would not be able to see the HTTP-layer details of the communication, since all application data is encrypted using TLS before that data is sent on the network". The documented remedy is to stand up your own internal certificate authority and have the proxy terminate the connection — which is a real option, and which buys you the HTTP request line, and still not the meaning of the rows in the body.

Put the network policy selector back to "plus a DNS query-name allowlist".

The boundary: one checkbox that closes the window at every setting

Tightness 1, variance 2, Sigkill, DNS query-name allowlist, injected instruction on. The hero number is 111 and the window is 3 of 11.

Tick the agent has a general HTTP tool, so egress needs no new process. The hero number goes to 0. Route 4 is open: the injected chain writes the bytes into a record on api.internal:443, a service the agent talks to on every single task, using a tool that spawns no process. There is no new binary for an exec rule to catch, no new destination for an address rule to catch, and no new syscall class at all. Now sweep the tightness slider across its full range with the box still ticked. The window stays at 0 of 11 the whole way, and it stays at 0 of 11 at every variance too. Put tightness back to 1 when you are done sweeping; the box stays ticked for the next two paragraphs.

That checkbox is the honest boundary of the whole approach. The policy is perfect, the enforcement is complete, and the only reason there was ever a window is that the attacker needed a target that the agent's legitimate work did not already reach. Give the agent one general-purpose outbound tool pointed at a service it is supposed to use, and the intersection of "targets the attacker needs" and "targets the agent needs" stops being empty. This is the exfiltration leg of the lethal trifecta arriving at the kernel and finding that the kernel has no opinion about it.

It is also, precisely, what OWASP tells you to do instead. The excessive agency entry in the OWASP Top 10 for LLM Applications v2.0 — LLM stands for large language model — lists eight preventions for exactly this failure, and the seventh is "Complete mediation: Implement authorization in downstream systems rather than relying on an LLM to decide if an action is allowed or not." The third is "Avoid open-ended extensions", with "fetch a URL" given as the example to avoid. Both of those are instructions to the application. Not one of the eight is something a syscall filter could carry out, because every one of them is phrased in terms of which user, which record, which operation — nouns the kernel does not have.

Untick the HTTP tool box before the next section. With tightness back at 1 and variance at 2, the hero number returns to 111 and the window to 3 of 11.

What a kernel rule is genuinely decisive about

Tightness 1, variance 2, Sigkill, DNS query-name allowlist, injected instruction on, HTTP tool off. Hero 111, window 3 of 11.

Read the route 1 rows in the log. connect collector.example.net:443 is refused, and it stays refused at tightness 0, at tightness 10, at variance 0 and at variance 10 — try all four corners, then return both sliders to tightness 1 and variance 2. The reason the log gives is the reason that matters: no audited task ever connected to that host, so it is off the list at every threshold. There is no legitimate behaviour it collides with, so there is no cost to blocking it, so no amount of behavioural variance erodes it. Route 1 is closed in every reachable state of this panel: all 4,356 combinations of the six controls were checked, and in each of the 2,178 where the injected instruction is present the row reads CLOSED.

That is the shape of a rule worth writing at this layer, and there are more of them than the pessimistic reading suggests:

  • Egress to a destination no legitimate path needs. Default-deny plus a short, hand-written list of destinations the agent's job actually requires. Not a list learned from an audit — a learned list is the thing that swallowed files.example.com at variance 6.
  • Execution of a binary the workload never runs. Watch the exec row for /usr/bin/curl as you move variance with tightness left at 1: it reads BLOCK from variance 0 through 6 and flips to ALLOW at variance 7, because by then a wandering agent had run it often enough during the audit for the 1% threshold to keep it. Put variance back to 2 afterwards. The rule is decisive when your image is small and your agent is typed, and it decays exactly as fast as your agent's process repertoire grows. Tetragon can hook the sched_process_exec tracepoint, which is a clean kernel-resident view of every exec on the box, taking the linux_binprm the kernel has already assembled rather than a path the caller can still rewrite.
  • Reads of paths that are outside the job by construction — a credential file, another tenant's mount, the container runtime socket. These are unambiguous for the same reason: no correct run of the agent touches them, so refusing them costs nothing.
  • DNS query names, as the panel shows, because the name is an argument the policy is given rather than a payload it would have to interpret.

The common property is not "important" or "dangerous". It is unambiguous regardless of intent: the action is wrong no matter why it happened, so the rule never has to know why. Every rule in this class is worth having and eBPF enforces it beautifully. What none of them do is cover the actions where the agent's own legitimate behaviour is the attacker's cover, and for an agent those are most of the interesting ones.

There is a second, quieter cost the panel shows. Untick the injected instruction box, leaving tightness at 1 and variance at 2. The hero number is 111 and the log says 9 of 120 tasks failed on a refusal with no attacker present at all. Now push tightness to 6: 50 tasks fail. That is the price of a policy in a world with no adversary in it, and in a real cluster every one of those failures arrives as a retry, a page, or a human being asked to approve something — which is how this turns into approval fatigue. Re-tick the box and put tightness back to 1.

Where the working designs put the decision

Falco is the other name in this conversation and it makes the opposite trade. Its documentation describes what it does as three steps: "Parsing the Linux syscalls from the kernel at runtime", "Asserting the stream against a powerful rules engine", "Alerting when a rule is violated". The assertion happens in user space and the last step is an alert, not a refusal: "Alerts are configurable downstream actions that can be as simple as logging to stdout or as complex as delivering an HTTP request to an endpoint." That buys richer, more contextual rules than a set of in-kernel selectors can express, and it costs the ability to refuse: by the time the rule fires the syscall has returned. Tetragon buys the refusal and pays with the expressiveness. Neither purchase changes the semantic problem, because both are reading the same arguments.

So what does the layer buy? Three things, and they are worth paying for. First, the decisive rules above: cheap, permanent, and unaffected by behavioural variance. Second, a complete and tamper-resistant record of what the agent actually did, which is the input to every investigation and to every honest attempt at writing an allowlist in the first place. Third, a hard floor under whatever the application layer gets wrong.

What it does not buy is a control that distinguishes an agent following its instructions from an agent following somebody else's. The most convincing evidence for that is not this simulation. It is that the two published architectures which do this well both agree with it, and both solve it the same way: use eBPF to see and to redirect, and put the decision somewhere that has the nouns.

Design one: the kernel redirects, a proxy decides

Dan Finneran, Principal Community Advocate at Isovalent at Cisco, showed a proof of concept at QCon London 2026 that starts from exactly this problem. An eBPF program hooks the socket, and when the agent dials out to its model endpoint the connection is transparently redirected to a userland proxy running beside it — added, neatly, as an ephemeral container rather than a sidecar, because "once the API server has started the workload, you can't add sidecars to it". Everything interesting then happens in the proxy: reading the request, swapping the model, capping tokens, rewriting the prompt, blocking a response on a forbidden keyword.

Read why he attaches where he attaches, because it is this lesson in one paragraph. He could go lower, and says so: at the network card "I'll just be receiving raw frames, which is going to be very hard for me to manipulate and understand that that is basically a bunch of JSON packets come out of order." Traffic control has the same problem. So he goes up to the socket, where "the kernel [takes] care of all of the actual data itself" — and then immediately out of the kernel altogether, into a process that can parse JSON and reason about a prompt. The eBPF half does redirection. A design that needs a proxy in order to read the prompt is a design that has already agreed the kernel cannot.

Note also what he leaves in the kernel: refusing rm, refusing opens of /etc/passwd, refusing a privilege escalation through /proc. "If anybody tries to run rm, just simply don't allow that program to ever actually run." That is the decisive class from the section above — unambiguous regardless of intent — sitting exactly where it belongs.

Two things the talk does not cover, which you need before copying it. The first is that its model endpoint is Ollama, a local model runner, inside the cluster, and the proxy can read the JSON because nothing has encrypted it. The talk's one encryption passage is about adding mutual TLS between internal applications that had none: "If you're running your LLM process internally within the Kubernetes cluster, you typically will find that there'll be no encryption between the two." Point the same setup at a hosted model over HTTPS and the socket hook still redirects perfectly — but the proxy, which "dialed out on behalf of the original process", is now terminating your agent's TLS session, so the pod has to trust a certificate you issue. That is the internal certificate authority from Cilium's TLS-visibility guide quoted earlier, with the key management and the blast radius that implies. The gateway's semantic power is a property of the payload being readable, not a property of eBPF.

The second is the standards claim. He calls the demo "a proof of concept based upon the AI egress working group as part of the Kubernetes project". The body is WG AI Gateway, announced on 9 March 2026, and egress is proposal 10 inside it — and that proposal puts this mechanism out of scope in as many words: "Forward-proxy egress (dynamic routing to arbitrary external hostnames), network-level egress (L3/L4 CIDR-based routing), and mesh-attached egress (sidecar-enforced policy without a Gateway) are not covered by this proposal." What is being standardised is an explicit egress Gateway that workloads are configured to route through, with token management so they never hold provider credentials. Transparent socket capture is a different mechanism that shares a goal. The trade-offs of that mechanism — what your hooks cover, what the proxy costs in the data path, and what reaches a model by a path you never hooked — are a whole subject of their own, and they are the AI gateway's own question rather than this one.

Design two: the kernel is a detection feed

The second design is not a proof of concept. Willem Berroubache, Lead Security Architect at Orange Innovation, has written up the real-time security-operations platform his team is rolling out for Orange's 5G core, a regulated production environment. eBPF is in it, and here is the whole of its job: "Falco with eBPF intercepts every syscall on the workloads we monitor; events flow through Kafka into an Isolation Forest classical anomaly model that pre-filters in front of the LLM-driven agents."

Read that placement carefully. The kernel layer watches the estate the agents defend, and what comes out of it is a feed — and a feed that a classical anomaly model filters before any language model sees it. It is not the thing that governs the agents. That job is split across three layers that each know something the kernel does not:

  • Who the actor is. "cert-manager issues per-agent identities; agents perform mTLS directly at their gRPC/HTTP transport, with no sidecar." An identity is the noun a five-tuple is missing.
  • Which peer that actor may reach. "Cilium provides the network substrate and CiliumNetworkPolicy restricts which agent identities may reach which MCP server" — MCP being the Model Context Protocol, the interface the agents call tools through. A policy keyed on identity rather than address is a policy that survives the pod being rescheduled, and one that says something about who is asking.
  • Whether this specific action is allowed. Their third lesson is headed "Agent safety constraints are policy-as-code, not LLM prompt reasoning", and the mechanism is that the reviewing agent's constraints are "OPA policies and Kyverno admission rules" — the Open Policy Agent for request-time decisions, Kyverno for admission. "The reviewer calls into OPA via MCP, gets a deterministic verdict, and acts."

Their decision to skip a service mesh is theirs, for their threat model, not general advice — they justify it on simplicity: "The combination (cert-manager + agent-level mTLS + CiliumNetworkPolicy) is materially simpler than a mesh and gives us what a mesh would have given us." Take the placement, not the product list.

Put the two together and the shape is the same one twice. eBPF does what it is unmatched at — seeing every syscall, and moving a connection somewhere else without touching the application. The decision about whether an action is legitimate is made one layer up, by something holding an identity, a parsed request, or a policy about a named resource. Neither team argued the kernel was unreliable. They just did not ask it a question it has no words for.

Your agent runs in a pod with a Tetragon TracingPolicy, a default-deny egress policy, and a DNS query-name allowlist. Security asks you to guarantee the agent cannot exfiltrate the customer table. Which answer is honest?

Pick one.

Checking this on a system you actually run

Five things to measure this week, in order of how much they will change your mind.

  1. Measure your agent's behavioural variance before you write a policy. Run Tetragon in observe-only mode for a couple of weeks with a TracingPolicy that has a Post action and no enforcement, then, per action class, count distinct targets and plot how many appeared in more than 1%, 10% and 50% of tasks. If the curve between 1% and 50% is steep, a threshold exists for you. If it is flat, the panel's variance-6 case is your system and no threshold does both jobs. This one measurement decides whether the rest is worth doing.
  2. Check you can actually enforce, not just alert. Run cat /boot/config-$(uname -r) | grep BPF_LSM and cat /sys/kernel/security/lsm. If you do not see CONFIG_BPF_LSM=y and bpf in the active list, an lsmhooks policy will not load and you are on kprobes, with the TOCTOU caveat and the CONFIG_BPF_KPROBE_OVERRIDE requirement that go with them. Grep /sys/kernel/debug/error_injection/list for the function you intended to override before you promise anybody it will be refused.
  3. Audit any policy that uses Signal without Override. A SIGKILL on a write is not a refusal; the documentation says the data may already be in the file. Every such rule in your repository is an alert wearing an enforcement label.
  4. Enumerate your egress allowlist by writability, not by hostname. For each entry, ask one question: can somebody outside your organisation read what a request to this host wrote? A package registry you only GET from is one answer; anything the agent can POST into that a third party can then fetch is another. That column, not the length of the list, tells you whether route 4 in the panel is open in your cluster.
  5. Put the semantic check where the nouns are, and copy the placement from someone who has done it. The agent's database identity should be scoped to the tables its job needs, so that the query the injected instruction wants fails at the database with a permission error rather than succeeding at the kernel with an allowed five-tuple. That is OWASP's "complete mediation", it is a NetworkPolicy and a database grant rather than a TracingPolicy, and it is the only place in the stack that knows which table is which. If you want a worked layout, Orange Innovation's is three moves: a per-agent identity from cert-manager, a network policy keyed on that identity rather than on an address, and the action check itself as Open Policy Agent rules the agent has to call and get a verdict from. Then keep the kernel layer, exactly as it is, for the four decisive rule classes and for the record of what happened.

The framing to retire is "kernel-level enforcement contains the agent". The framing to adopt is narrower and survives contact: kernel-level enforcement is a complete, fast, tamper-resistant decision point about arguments, and it is decisive exactly where a legitimate action and a malicious one differ in their arguments. For an agent, work out how large that set is before you trust it — the panel's window readout is that question, asked once per tightness setting.

And if the honest answer is "not large enough", that is not a reason to take eBPF out. Both designs in the previous section keep it. They just stop asking it the question it has no words for, and spend the effort one layer up instead — on an identity, a parsed request, a policy about a named resource. That is the move, and it is available today.

Why this concept is on the site

Topics are chosen from places engineers visibly get stuck, and the sources are kept with the lesson so the claim is checkable.