attenu

Blog · · by , Attenu

We reviewed our own adapters like an attacker would. Here is what broke.

Last week we wired execution binding — the evidence layer that records what happened after a call was authorized — into every adapter attenu-guard ships. Seventeen frameworks in Python, LangGraph in TypeScript. All the test suites were green before any of it was allowed near main. Green suites caught almost none of what follows.

The rule we used: every batch goes to an adversarial reviewer whose brief is to read the pinned framework source, not our adapter's docstrings, and to reproduce every claim before accepting it. The reviewer's verdict gates the merge. Five passes on the first batch, three on the second. What that process found, in the order it hurt:

A shipped core bug, in both languages. A bare v2 check() followed by complete() deadlocked the lifecycle: the guard registered every allow as pending an outcome, including allows whose capture mode honestly promises no outcome will ever come. The verifier already treated those calls as merely unobserved, so runtime and offline verification disagreed with each other. Nobody had hit it because the one wrapper we shipped earlier always records outcomes. The fix gates pending-registration on the capture modes that actually promise an outcome. Finding it took an implementer-grade reading of our own contract. It is D14 in our defect log, fixed in 0.10.0 and 0.5.0.

Correlation that lies under concurrency. Our first CrewAI wiring kept the pending call in a thread-local slot. CrewAI's executor can interleave two dispatches on one thread, so call A could consume call B's identity and the ledger would swap their outcomes — each record internally valid, both wrong. Two rounds later the honest answer turned out to be architectural: where a framework's hook surface cannot guarantee it observes a call's end, the adapter now says so up front (pre_hook_only) instead of promising an outcome it can lose. An honest gap beats a confident lie, in evidence more than anywhere.

Snapshots that were not snapshots. Both languages committed a hash of the parameters as authorized. Both had a fallback that could alias the live object instead of copying it. In Python, a class overriding __deepcopy__ to return itself defeated deepcopy in all six adapters of the first batch. In TypeScript, structuredClone throwing on an unclonable value made the fallback return the raw reference, so a mutation after authorization was visible through the "snapshot". Same disease, different copy protocol. The rule that came out of it, now enforced across both languages: never hand a container to a copy protocol; rebuild it without invoking anything the value controls, and refuse to commit a hash at all for values that cannot be rebuilt without running their code.

Promises the frameworks do not keep. The reviewer reproduced, from pinned sources, paths where hooks we relied on simply do not fire: a later hook rejecting after ours authorized, a plugin registered ahead of ours swallowing the dispatch, a stream closed early, an interrupt that skips the after-hook. Each adapter now documents exactly which paths its strict mode cannot see, with tests that reproduce the loss rather than assert its absence.

Two numbers to keep this honest. First batch: nine findings across five review passes before the merge, four of them critical. Second batch, built with everything the first one taught as the default architecture: seven findings, none of them repeats. The process is not that we write correct code. It is that nothing merges until someone paid to distrust it has failed to break it, and the failures that do get through become named defects with dates in a public log.

The releases carrying all of this are attenu-guard 0.10.0 (PyPI) and attenu-guard-ts 0.5.0 (npm). The defect log, the threat model and the denial contract are in the repository, next to the code they describe: https://github.com/attenu-io/attenu-guard

Rafael Asor is the founder of Attenu and the maintainer of attenu-guard and attenu-derive, open-source Python libraries for AI agent permissions across sub-agent handoffs. He is the author of the IETF Internet-Draft draft-asor-wimse-agent-delegation-chain and is based in Tel Aviv.