O
OOMeta
← Back to Insights

September 2026 · 6 min read

Trust handoff: marked safe, acted on anyway

{meta.title}

Key Definitions

Harness The code around the model — tool permissions, command validation, sandboxing, output handling — that turns the model's intent into shell commands and API calls. In unattended automation, the harness is the security boundary.

Exfiltration Oracle A side channel that recovers a secret character by character by observing a public, read-only counter (such as model download counts): the agent is directed to fetch the repo matching each character, and the attacker polls the counters to reassemble the full key — with no outbound traffic to attacker-controlled servers.

A GitHub issue anyone can file — zero privileges, zero account trust — pulled secrets out of the default CI workflows behind Anthropic's, Google's, and OpenAI's own repositories. In Novee Security's Black Hat USA 2026 demonstration, the models behaved entirely as designed. What was exploited was not the intelligence but the code wrapped around it: the harness. The flaw is not in the model; it is in the trust handoff — one layer marks a value "safe," a later layer acts on that mark with more authority, and nothing re-verifies it in between.

One GitHub Issue, Three Vendors' Default Configurations

Novee's method was to test each vendor's own agent, on the vendor's own repository, with the vendor's default workflow. The entry point is the text of a GitHub issue or PR — submittable by anyone. Three vendors, three codebases, one class of failure (Source: Novee Security original disclosure).

Claude Code (CVE-2026-54316, fixed in 2.1.163): the command validator strips single-quoted text before running its 23 safety checks — correct bash behavior — so a payload hidden in a git push --receive-pack='sh -c …' quoted value passed the validator and executed on the host. Then tac, rev, and fold were hardcoded as read-only but never path-checked, allowing reads of /proc/self/environ, with results surfacing in the public GitHub Actions Step Summary (reverse the string to defeat secret masking). The final round needed no "write" action at all: huggingface.co sat on the pre-approved WebFetch list, so 64 attacker-created model repositories turned its public download counter into a character-by-character exfiltration channel. Three rounds of patch-and-bypass, each quieter than the last (Source: GitHub Advisory GHSA-fg94-h982-f3mm).

Gemini CLI (CVE-2026-12537, CVSS 10.0, fixed in 0.39.1 / run-gemini-cli 0.1.22): in headless CI mode the CLI auto-trusted the workspace and loaded configuration and environment variables from .gemini/.env — a malicious .env injected OS commands before the sandbox started, executing with host privileges. Google's "process isolation" was actually environment sanitization: the child's environment is filtered, but the parent process still holds every secret, and cat /proc/$PPID/environ reads them out. Separately, under --yolo the fine-grained tool allowlist in settings.json was never enforced at runtime — an allowlist that is parsed and not applied is worse than no allowlist, because it documents a protection nobody gets (Source: GitHub Advisory GHSA-wpqr-6v78-jr5g).

Codex (no CVE; a design problem): openai/codex's automation ran two review passes inside one job sharing a single checkout. AGENTS.md is the instruction file Codex reads at the start of every invocation. Pass one — controlled via injected issue content — wrote a malicious AGENTS.md; pass two read it and executed the attacker's instructions with full job permissions. OpenAI's response was structural: the two passes now run as separate jobs with separate checkouts, Codex operates in a read-only sandbox with drop-sudo, and AGENTS.md is formally documented as an untrusted input surface (Source: Novee Security original disclosure).

The Exfiltration Oracle: Not Bypassing the Boundary, Using It

The step worth internalizing most is the last one in the Claude Code chain. Hugging Face's download counter increments on every HTTP request — including GET and HEAD — and is publicly readable. The attacker creates 64 repositories, one per possible character of the key; via injection, directs the agent to read position N of the secret and then fetch the matching repository (e.g., attacker/char-a/resolve/main/config.json). Exactly one repository's counter increments. The attacker polls all 64 counters and orders them by timestamp, reassembling the full API key character by character. No connection to an attacker-controlled server, no writes, no log anomalies — a read-only GET walks the secret out.

The boundary lesson matters more than the technique: an allowlist answers "which services may the agent reach," not "what may the agent send to each service." Meged did not bypass the allowlist — he used it. An approved domain can be directed to carry any content. Restricting which services an agent can reach is necessary but not sufficient; the boundary must be defined at the action and content level.

Our Judgment: Trust Must Be Re-Verified at the Point of Consumption

Novee's own summary is precise: "the harness marks a value safe at one layer, and a later layer acts on it with more authority — with nothing re-checking that value's provenance and authorization in between." All three cases share one shape: the validator correctly stripped quotes (marked safe), and the stripped value flowed to a more privileged execution step; the allowlist correctly recorded entries, never enforced at call time; the sandbox ran as documented, and the first pass's output became the second pass's instructions. The detection layers existed; what was missing was the re-verification point between marking and acting. Our judgment: "this value is safe" must be independently re-verified by the executing side before the action lands, not trusted forever because a front-end decision layer marked it once; the boundary lives at the action level, not the service level.

Two consequences follow. First, for every agent integration point, locate the "marked safe → higher-privilege execution" handoffs and insert provenance binding and pre-action authorization verification — ask "where did this value come from and what is it allowed to do," rather than trusting the earlier mark. Second, expand allowlist review from "which domains are reachable" to "what the agent is permitted to send to each service"; public counter-type services (download counters, metric APIs) are themselves latent exfiltration channels whenever an agent can be directed to write to them.

This matches our overall judgment about agent governance: the model is not the security boundary; actions are. Vendors will say "the sandbox behaved exactly as documented" — but documented unsafe behavior is still an attack surface. Buyer checklists should require vendors to state whether values marked "safe" are re-verified at execution time.

What It Means for Buyers

AI App leads: if your team runs coding agents in CI, pin versions first — Claude Code ≥2.1.163, Gemini CLI ≥0.39.1, run-gemini-cli ≥0.1.22; run multi-pass agent jobs with separate checkouts; scope secrets per task instead of granting whole-repo tokens; treat issue/PR bodies, AGENTS.md, and any user-controlled text as untrusted input.

Security teams: when reviewing service allowlists, add "what content may be sent" to the checklist; check agent output channels (Step Summary, logs) for reverse-string and other bypass patterns; assume your own agent workflows have the same handoff gaps and trace every "marked safe → executed" path.

Your Next Move

Now: confirm the agent versions in your CI and pin them above the fix lines; check whether any fork- or issue-triggered workflow can write user-controlled content into instruction files like AGENTS.md. This week: add "what allowlists permit an agent to send" to review; restrict agent actions that use public counter-type services. Long term: build a re-verification mechanism (provenance binding + pre-action authorization) at every "marked safe → action" handoff in your agent integrations, and put "is the safe mark re-verified at execution time" into your agent-tool procurement evaluation.

References: Novee Security: Black Hat 2026 original disclosure (2026-08-06) · GitHub Advisory: CVE-2026-54316 (Claude Code exfiltration channel) · GitHub Advisory: Gemini CLI trust-model update (CVSS 10.0). Note: the CVE attribution for the Google flaw is publicly ambiguous (CSA and The Hacker News associate CVE-2026-12537; the GitHub advisory itself carries no CVE); this article follows Novee's and the vendor advisory's framing.

FAQ

What is the entry point of the attack?+

The text of a zero-privilege GitHub issue is read by the agent. Prompt injection is only the delivery mechanism; the real flaw is in the harness's trust decisions — one layer marks input as 'safe' and a later layer acts on that mark with more authority.

What is the CVSS 10.0 Gemini CLI flaw?+

CVE-2026-12537: in headless CI mode Gemini CLI auto-trusted the workspace and loaded .gemini/.env, so a malicious .env injected OS commands before the sandbox started, executing with host privileges; the fine-grained tool allowlist was also never enforced at runtime under --yolo.

What is Claude Code's CVE-2026-54316?+

The command validator strips single-quoted text before its checks, so a payload inside a git push --receive-pack quoted value passed validation and ran on the host; separately, huggingface.co sat on the pre-approved domain list and its public download counter was used to exfiltrate keys character by character.

What is an exfiltration oracle?+

Create 64 repositories, one per possible character of the key; the agent is directed to fetch the repo matching each character, incrementing exactly one public download counter; the attacker polls all 64 counters and orders them by timestamp to reassemble the full API key — pure read operations, no anomalous traffic.

What is the Codex problem?+

A two-pass review ran inside one job sharing a single checkout: pass one (controlled via injected issue content) wrote AGENTS.md — the instruction file Codex loads on every invocation — and pass two read it and executed with full job permissions. OpenAI fixed it structurally: separate jobs and checkouts, read-only sandbox with drop-sudo, and AGENTS.md formally documented as untrusted input.

How should enterprises remediate?+

Pin patched versions (Claude Code ≥2.1.163, Gemini CLI ≥0.39.1, run-gemini-cli ≥0.1.22); use task-scoped secrets; run multi-pass agent jobs with separate checkouts; treat issue/PR bodies and AGENTS.md as untrusted input; and review what your allowlists permit an agent to send to each service, not just which services it may reach.