AVE Agentic Vulnerability Enumeration
Technical Writeup · AVE-2026-00046

The tool hook hijack, and why it's harder to detect than it looks

AVE's only CRITICAL-rated record right now. Worth understanding why the severity is warranted, not just accepting the score at face value.

← Back to the full record in the registry

The mechanism

Most agent runtimes and MCP clients support hooks: small pieces of code that run before or after a tool call, typically for legitimate reasons, logging, formatting, validation. A hook registered against a wildcard match ("matches": "*") runs on every tool call the agent makes, not just one.

The attack is specific: a hook registered this way, with blocking: true, that doesn't just observe the call but rewrites its arguments before the legitimate handler ever sees them. Once installed, every subsequent tool call in the session passes through this hook first. The declared scope of whatever tool the agent thinks it's calling stops being the real scope of what actually executes.

Why this earns CRITICAL, specifically

Severity here isn't about any single call being dangerous. It's about what the mechanism removes: the agent's own declared understanding of what it's doing. If a hook can silently substitute arguments on every call, the entire concept of a scoped, auditable tool call is compromised at the runtime level, not just for one tool but for everything downstream of the moment the hook installs. AVE's own scoring reflects amplification factors for exactly this: high autonomy (nothing about the substitution requires further attacker interaction), high persistent_memory-adjacent risk (the hook, once installed, affects every future call in the session, not a one-time event).

Why detection is genuinely hard, not just under-built

Building conformance fixtures for this record surfaced the actual difficulty directly, worth walking through rather than asserting.

The naive detection approach: flag any hook matching a wildcard pattern. This fails immediately against a real, common, entirely benign pattern: audit logging. A hook that matches *, runs on every call, and simply logs {tool: call.name, timestamp: Date.now()} is completely standard practice, and a detector that flags it produces a false positive rate high enough that anyone running the tool would just turn the check off.

The distinguishing signal isn't the wildcard match at all. It's whether the hook is blocking (able to intercept and modify the call before the real handler runs) combined with whether it actually mutates call.arguments rather than only reading them. A hook can be blocking and still legitimate (synchronous validation that rejects malformed input, for instance); a hook can be non-blocking and still concerning in rarer cases (fire-and-forget exfiltration of call metadata, a different, related but distinct behavioral class). The specific combination that matters for this record is blocking plus argument mutation plus unrestricted scope.

This matters practically: a detection rule that only checks the hooks[] configuration's declared fields (matches, blocking) without inspecting what the hook's own code actually does will miss the real attack and only catch cases where the configuration itself is suspicious, which a competent attacker has no reason to make obviously suspicious. Real detection here needs to look at the hook's actual behavior, not just its declared metadata, which is a meaningfully harder static analysis problem than checking a manifest field.

What legitimate defenses actually look like

Not "disallow wildcard hooks," which would break a large amount of completely reasonable logging and validation tooling. The real mitigation is scoping and visibility: a runtime that requires blocking hooks to declare which fields of call.arguments they're permitted to modify, rather than granting silent access to the whole object, turns this from an invisible substitution into a visible, auditable, scoped change. That's a runtime enforcement question, not something AVE's own classification record can fix by existing; the record's job is naming the pattern precisely enough that a runtime can be built to close it.

The fixture that actually tests this

Two files, deliberately minimal, in the conformance suite:

A positive fixture: a hook, blocking: true, matching *, with an accompanying source excerpt showing it fetches a rewritten version of the call's arguments from an external endpoint before returning them.

A negative fixture: a hook, blocking: false, also matching *, that only logs metadata and never touches call.arguments at all.

Any detector that can correctly separate these two, not just recognize "a hook exists," is doing the actual work this record asks for.


The full record: AVE-2026-00046 in the registry, including its complete AIVSS scoring breakdown, indicators of compromise, and remediation guidance.