Intelligence

The User Chose a Recipe. Goose Treated That as Permission to Run Its Commands.

A Goose recipe can declare a stdio extension command or a retry check shell command, and running the recipe spawns that command as the local user during ordinary extension initialization, independent of whether the MCP handshake that follows ever succeeds. Moona Intelligence verifies the mechanism directly against the Goose source at its latest released version and against the primary issue that reported it, and states plainly what CVE-2026-85623 shows: selecting a shared recipe is a decision to trust an artifact, and it is not, by itself, authorization to run every command embedded inside it.

Event analysed: . This analysis was published on 5 September 2026.

Can a Goose recipe's stdio extension or retry check execute an arbitrary host command before Goose's own recipe security scan or a user consent decision ever applies?

Yes. A shared Goose recipe can declare a stdio extension with an arbitrary cmd and args, or a retry check or retry failure command run through a shell, and Goose's own extension manager spawns that stdio command as part of ordinary extension initialization, independent of whether the MCP handshake that follows ever succeeds. Recipe::check_for_security_warnings, in the recipe module, examines only the recipe's instructions, prompt and activities fields for hidden Unicode tag content, and the CLI's goose run recipe path calls no scan and shows no command preview or consent prompt before spawning. CVE-2026-85623, published 4 September 2026 and classified under CWE-94, covers the version range through 1.49.0, the latest released version at the time of this record, and no fixed release has shipped. A proposed fix, pull request 10337, would have extended the scan to these fields and added a CLI consent prompt. It was closed without merging after automated review raised compatibility concerns, and no superseding fix has been identified.

Goose recipes are portable agent configurations: a file, a shareable link, or an entry in a GitHub recipe repository or a recipe library that another person can load and run without rebuilding the setup themselves. That portability is the feature. Issue 10325, filed against the aaif-goose/goose repository, describes what runs when someone accepts it: a recipe can carry a stdio extension whose cmd and args Goose spawns as an ordinary local process, and a retry configuration whose checks and failure handling run a shell command drawn straight from the recipe file. Neither field is inspected by the recipe's own security scan. Neither is gated by anything in the command line path that actually runs recipes.

Moona Intelligence verified the reported mechanism directly against the Goose source, at the v1.49.0 tag (the latest released version) and again on the current main branch, rather than relying on the issue's own account alone. Both examinations found the same gap. This record states what CVE-2026-85623 establishes and keeps separate what remains unconfirmed: whether Goose Desktop's own recipe trust dialog can be reached before a stdio command has already run is not something this record could verify, and it does not claim that path either way.

How the command runs

A Goose recipe's extensions field accepts a list of extension configurations, and one accepted shape is a stdio extension: a name, a cmd, and an args array. When a recipe enables that extension, Goose's extension manager resolves the command and constructs a child process from it, then hands that process to the code that establishes an MCP client connection over its standard input and output. The process spawns first. The MCP initialize handshake happens afterward, over the transport the spawned process already provides. Should that handshake fail for any reason, the spawned process is not torn down as a consequence: the failure is returned to the caller as an error, and the command that already ran has already run. Whatever the recipe's stdio extension named, it executes with the privileges of the account running Goose, and it executes whether or not Goose ever exchanges a single MCP message with it.

The retry mechanism runs shell commands too

A separate recipe field, retry, carries its own execution path. Its checks field accepts a SuccessCheck::Shell entry naming a command, which Goose runs through sh on Unix systems and through cmd on Windows, taking the command text directly from the recipe. A further field, on_failure, names a second command that runs through the identical mechanism when a retry attempt does not succeed. Both are plain shell execution of recipe supplied text, and neither is described anywhere in Goose's own recipe schema as requiring any different handling than the stdio extension path above.

What the security scan actually inspects

Goose recipes carry a stated safeguard: Recipe::check_for_security_warnings, in the recipe module. Read directly, the function checks exactly three fields, instructions, prompt and activities, for hidden Unicode Tags Block characters, the kind of invisible steering text a malicious recipe author might hide in a field the user would otherwise read. It does not examine extensions at all, so a stdio extension's cmd and args are invisible to it. It does not examine retry at all, so a shell check or failure command is equally invisible. A recipe that passes this scan cleanly, reporting no warning, may still spawn an arbitrary local process and run an arbitrary shell command the moment its extensions initialize. The scan's own scope was never built to answer the question a user would actually want answered before running a shared recipe: what will this thing execute.

The gap is not only in what the scan inspects. It is in whether the scan runs at all. The command line path that actually starts a recipe, building a session from it and loading its extensions, calls no security scan and shows no command preview before spawning. A user who runs goose run against a recipe file, a recipe fetched by deeplink, or a recipe cloned from a GitHub repository sees nothing naming the commands about to execute, in either the case the scan would have flagged or the case it would not have.

This record's own source verification is scoped to the CLI recipe run path, confirmed directly at v1.49.0 and at current main. Goose's own documentation states that Goose Desktop shows a warning dialog the first time a given recipe runs, letting a user review the recipe before choosing to trust and execute it. That documentation could not be fetched directly in this session and is used here only as secondary, search corroborated material; it also does not state, in any material available to this session, whether a stdio extension or retry command can execute before that dialog is shown or only after. This record does not claim Desktop executes before its own trust dialog, and does not claim the opposite. The CLI finding above does not depend on Desktop's behavior either way.

A proposed fix that did not ship

Pull request 10337, opened against the same repository, proposed replacing the narrow Unicode only check with a broader get_security_warnings method covering stdio extensions with a non empty cmd, inline Python extension code, retry shell checks, and retry failure commands, while keeping the original Unicode tag detection intact. It also proposed a CLI consent prompt, shown before a session with these extensions begins, naming the specific commands a user would be approving. Automated review on the pull request raised three concerns before it closed without merging: that folding command warnings into the same check already used to gate recipe saving and scheduling could block previously valid recipes from being saved at all, that Goose Desktop's own interface could not yet distinguish a command warning from a hidden Unicode character warning for display, and that an interactive consent prompt would break unattended recipe runs that have no terminal to prompt through. None of those three concerns were resolved in the pull request before it closed, and this record found no superseding pull request and no other merged commit that closes the gap on current main. The proposed fix is evidence of an available remediation path, not a shipped one.

Distribution turns a shared convenience into an authority question

The primary issue names files, deeplinks, GitHub recipe repositories and a recipe library among Goose's own recipe distribution paths. This record does not claim every recipe arrives from a remote or untrusted source, and most recipes a person runs will be ones they wrote themselves or received from someone they already trust. What the distribution paths change is provenance, not the underlying mechanism: a recipe is a portable artifact precisely because it is meant to be handed to someone else, and the fields that spawn a process read identically whether the recipe arrived as a local file the user wrote an hour ago or as a link forwarded from a stranger. Selecting a recipe is a decision about the artifact. It is a separate decision, one Goose does not currently ask, about the specific commands that artifact will run.

What no cataloged Moona Protocol yet requires

Checked against the protocol evidence this desk already tracks, no cataloged protocol record states a specific graded requirement for consent before an executable field embedded in a shared agent configuration artifact is run. That is not a gap in any one protocol's own claims: each was evaluated for what it states about a model directed action or a gateway mediated tool call, and none claims coverage of a recipe's own declared extension or retry command executing during the recipe's own startup sequence. This record does not manufacture a protocol link to hang the finding on. The corrective that exists at publication is a closed, unmerged pull request, not a shipped mechanism and not an articulated, checkable requirement in this desk's protocol dataset.

The Authority Provenance ledger

Grantor. The user who selects and runs the recipe.

Intended mandate. Run the selected recipe's declared task, using the extensions and behavior the recipe describes.

Workspace trust. Not established through any mechanism verified in the CLI path. Nothing in goose run reviews the recipe or asks the user to confirm it before its extensions initialize.

Executing actor. Goose's own extension manager and its stdio transport, and separately its retry execution path, neither of which is a tool call the model requested.

Configuration principal. The recipe's own extensions and retry fields, specifically a stdio extension's cmd and args, a retry check's shell command, or a retry failure command.

Execution principal. The host user account running Goose, since the spawned process and the shell commands run with that account's own privileges.

Limit that failed. A recipe supplied, executable field ran during extension initialization, before the recipe's own security scan examined it (the scan does not examine it at all) and before any consent mechanism in the CLI path had a chance to apply.

Inherited assumption. That a user choosing to run a recipe is choosing to trust the whole artifact, including whatever commands it names, rather than a decision that still leaves the specific commands to be separately reviewed.

Approval. None applied in the CLI recipe run path. No scan runs, and no command is shown to the user before it executes.

Challenge. None available in the CLI path at the point the command runs. Whatever review mechanism exists elsewhere in Goose is not reached before this specific action.

Revocation. None. No fixed release has shipped as of this record's retrieval date, and the proposed remediation, pull request 10337, closed without merging.

Recovery. Unknown, and dependent on what the executed command actually did. This record does not assert that exploitation has occurred beyond the reporter's own demonstrated proof of concept, a benign command whose output was written to a file, run as the invoking user.

Sources

This analysis interprets third-party reporting, research and announcements. Moona is not the original reporter of the underlying events.

[2]
[3]
recipe/mod.rs at v1.49.0
GitHub, aaif-goose/goose · 3 September 2026 · Technical documentation
[4]
recipe/mod.rs on main
GitHub, aaif-goose/goose · 5 September 2026 · Technical documentation
[5]
agents/extension.rs at v1.49.0
GitHub, aaif-goose/goose · 3 September 2026 · Technical documentation
[6]
agents/extension_manager.rs at v1.49.0
GitHub, aaif-goose/goose · 3 September 2026 · Technical documentation
[7]
agents/retry.rs at v1.49.0
GitHub, aaif-goose/goose · 3 September 2026 · Technical documentation
[8]
goose-cli session/builder.rs on main
GitHub, aaif-goose/goose · 5 September 2026 · Technical documentation
[9]
Releases, aaif-goose/goose
GitHub, aaif-goose/goose · Technical documentation
[10]
CVE-2026-85623 record, CVE Program
CVE Program · 4 September 2026 · Technical documentation

Protocol evidence

This record does not assess these architectures. The connection runs through the Risk Registry requirement each one bears on, and these published authority architectures are what the evidence says about that requirement.

Protocol evidence related through AEW-001 Action without applicable policy

  • Supports requirement

    Agent Action Decision Protocol (AADP)

    Shamik Saha, individual submission to the IETF

    Requirement A Policy Decision Point owns authorization state and evidence; PEPs enforce it

    AADP's separation of a Policy Decision Point that must issue a permit before a Policy Enforcement Point performs an action is a direct response to actions that execute with no applicable policy at all. These failures support the need for that requirement. Claude Code's own current documentation, read directly, extends the same gap one lifecycle stage later than GitSpawn's own known example above: a plugin's bundled hooks fire as a host subprocess with no fresh model decision required, and a marketplace update is applied by version signal with no described mechanism to gate a change to that hooks.json specifically, the same absent decision point AADP's separation exists to supply. A preprint posted to arXiv on 3 September 2026, 2609.03884, states this exact class of update as its own subject; this record could verify only its title and author list, not its own specific claims. a2a-settlement/settlebridge-ai issue 5 extends the same requirement to an alternate lifecycle path rather than a later trust stage: a training run's own completion routine, training_service.complete_run, was itself an unmediated Policy Enforcement Point at the affected revision, performing the approving effect with no Policy Decision Point interposed at all, while the platform's ordinary submission path enforced exactly such a decision point on the identical class of approval. Current main's own remediation, read directly, gates that same completion path behind a new module, training_gates.py, read as response side evidence that the requirement now applies uniformly across both lifecycle paths.

    View protocol evidence

  • Supports requirement

    Identity for AI, Agent IAM Core and Agent Gateway

    Ping Identity

    Requirement Ping's own Agent Gateway sits in front of MCP servers and applies McpValidationFilter, McpProtectionFilter and McpAuditFilter to validate protocol shape, enforce OAuth 2.0 resource server scope checks and audit each tool call before the backend is reached

    Ping's own Agent Gateway sitting in front of MCP servers and running McpValidationFilter, McpProtectionFilter and McpAuditFilter before a backend request runs is a configured pre-resource enforcement point ensuring an applicable authorization decision exists for the MCP traffic it mediates, the corrective for an action that executes with no applicable policy at all.

    View protocol evidence

Related Intelligence

All Intelligence Records →