The Ownership File Named Who Owned a Path. One Line Named a Command Instead.
CVE-2026-85012, published by AWS on 3 September 2026, describes an operating system command injection in Amazon CodeCatalyst's blueprints SDK. A project's .ownership-file states, per path, which blueprint may modify it during resynthesis, using an owner field that in every ordinary case is an identity string matched against a package name. This record independently cloned aws/codecatalyst-blueprints and read the affected and fixed source directly: for exactly one merge strategy identifier, local, that identical owner field is instead read as a shell command pattern and, in every version through 0.3.155, handed to a shell with no validation at all. A repository committer holding nothing beyond ordinary write access to the project could place shell syntax in that one field and have it run with whatever privileges the resynthesis environment itself held.
Event analysed: . This analysis was published on 8 September 2026.
The field never stopped being read as data everywhere except one place. Amazon CodeCatalyst blueprints resynthesize an existing project by consulting a .ownership-file that names, per path, which blueprint owns it and which merge strategy applies when the blueprint's own proposed content and the project's current content disagree. This record independently cloned aws/codecatalyst-blueprints and confirmed that the file's owner field carries two structurally different meanings, decided purely by which merge strategy identifier appears next to it in the same file. For every identifier except the reserved word local, deserialize-strategies.ts reads owner as an identity string and matches it, by wildcard, against the resynthesizing blueprint's own package name, a routing decision with no execution consequence at all. For the identifier local, the identical field position is instead read by constructLocalStrategy as a custom git merge driver command pattern and, in every version through 0.3.155, passed whole to Node's execSync, a full shell invocation, with no validation of its contents. GitHub Security Advisory GHSA-c7rj-fr2j-64w7 formally assigns this CVE-2026-85012 on 3 September 2026, CVSS 3.1 base score 8.0, High, CWE-78, and states plainly that a user with permission to commit to the project repository could place shell metacharacters in that one field and cause arbitrary commands to run in the resynthesis environment with whatever privileges and credentials that environment held. Nothing about holding ordinary commit access to a project repository is authority over the resynthesis environment's own shell, and this record's own reading of the source confirms the two were never separately checked: whatever authority a committer needed to edit the ownership file at all was silently treated as sufficient for the local strategy's very different, and far more consequential, second meaning of the same field. The fix, shipped in version 0.3.156, closes the gap in two independent ways rather than one: local.ts no longer builds a shell command string at all, calling execFileSync with a pre split argument array instead, and deserialize-strategies.ts now rejects, before that function is ever reached, any local owner value that does not match a narrow allow list of executable name and placeholder characters. AWS states directly that the managed Amazon CodeCatalyst service required no customer action, since it runs resynthesis in isolated per project environments and applies its own server side validation rejecting an invalid local merge strategy command, independent of which blueprint version a project's blueprints were authored against. That server side protection is a property of the managed service's own execution boundary, not evidence that the SDK's own validation gap did not exist; this record treats the two as the distinct facts AWS itself states them as, rather than letting one stand in for the other.
A .ownership-file is about as unglamorous as project metadata gets. It is a list of paths, each paired with a merge strategy and an owner, and its entire job during Amazon CodeCatalyst blueprint resynthesis is to answer one question: when a blueprint proposes changing a file the project already has, who gets to decide what the result looks like. Nothing in that description asks for a shell. CVE-2026-85012 is what happens when one specific value inside that file gets one, for exactly one merge strategy, with no separate check standing between the authority to edit the file and the authority the resulting command actually exercised.
What the advisory states
GitHub Security Advisory GHSA-c7rj-fr2j-64w7 formally assigns CVE-2026-85012 on 3 September 2026, CVSS 3.1 base score 8.0, High, vector CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H, and classifies it CWE-78, improper neutralization of special elements used in an OS command, against the npm package @amazon-codecatalyst/blueprints.blueprint. Versions 0.3.155 and earlier are affected; version 0.3.156 fixes it. The advisory's own description states that during resynthesis the framework reads the project's .ownership-file to determine which files a blueprint may modify, that the owner field of a local merge strategy entry was passed to an operating system command through a shell without validation, and that a user with permission to commit to the project repository could place shell metacharacters into that field to execute arbitrary commands in the environment performing resynthesis, with whatever privileges and credentials that environment held. AWS credits Vaibhav Kubade for reporting it through coordinated disclosure. AWS's own bulletin, 2026-095-AWS, is blocked to direct fetch from this session's network egress on every attempt, consistent with every AWS bulletin this desk has cited before; its content is corroborated here through the GitHub Security Advisory, read directly, and through independently phrased web searches converging on the same affected package, the same version range and the same remediation the advisory itself states.
One field, two roles, decided by one word
Every line of a .ownership-file this record read directly, in packages/blueprints/blueprint/src/resynthesis/ownership.ts, takes the shape of an identifier in square brackets followed by an owner value, for example an entry reading a strategy identifier and then, on the same line, something matching the comment the source itself carries, an example of a package name such as a codecatalyst blueprint scope and name. What that owner value means once resynthesis reads it back depends entirely on the identifier next to it, and this record confirmed the branch directly in deserialize-strategies.ts. For any identifier other than the reserved word local, owner is passed to a function named matchesOwner, which wildcard matches it against the resynthesizing blueprint's own package name and, when present, its name and version together. That match decides one thing only: whether this particular blueprint is the one permitted to touch this particular path during resynthesis. It is a routing decision, and nothing about it ever reaches a process boundary.
For the one reserved identifier, local, the same field position means something else entirely. deserialize-strategies.ts passes owner straight to constructLocalStrategy, in packages/blueprints/blueprint/src/resynthesis/merge-strategies/local.ts, as a custom merge driver command pattern, following the same percent placeholder convention git itself defines for a custom merge driver, with %O, %A, %B, %P and %L standing in for the common ancestor, existing, proposed and resolved file paths and a conflict marker length. This record read the pre fix version of that file directly: formatLocalCommand substituted those placeholders into the owner string and returned one flat command string, and runLocalCommand handed that whole string to Node's child_process.execSync, a full shell invocation, with no check anywhere in the path that the string contained only the executable name and placeholders it was supposed to. A .ownership-file with a local entry whose owner value read, for example, a legitimate looking script name immediately followed by shell metacharacters and a second, attacker chosen command would substitute cleanly into that string and execute both, the intended merge tool and whatever else the committer appended, under the resynthesis process's own shell.
The fix, read directly against the affected commit
This record read the complete diff of commit 7d12070947b1bb06683428b9e93b061271434b66, titled fix, harden command execution in synthesis, merged 15 June 2026, directly from the cloned repository. It changes local.ts in two connected ways: formatLocalCommand becomes formatLocalCommandArgs, returning a split argument array instead of one flat string, and runLocalCommand now destructures that array into an executable and its arguments and calls execFileSync directly, a call that never invokes a shell at all, so a metacharacter inside any one argument has nowhere left to be interpreted as anything but a literal character of that argument. The same commit adds a second, independent layer in deserialize-strategies.ts: a constant named ALLOWED_LOCAL_CMD, a regular expression accepting only letters, digits, period, underscore, forward slash and hyphen for the leading executable token, followed optionally by further tokens each either one of git's own five recognized placeholders or an ordinary non whitespace argument. Every local owner value is tested against that expression, and a value that fails throws a BlueprintSynthesisError of type ValidationError, naming the rejected value directly in its message, before constructLocalStrategy is ever called. This record confirmed both changes remain present, unchanged, on the current default branch, where the package's own package.json reads version 0.3.159.
Where the vulnerable code came from, and where the fix landed, read from history rather than from the advisory
This record traced the local merge strategy's own history directly in the cloned repository rather than accepting an unstated lower bound. The strategy, and the exact unvalidated execSync call this record describes above, was introduced by commit e448811a7c86beeef11c2046acca65474d43db62, titled feat, blueprint, add local merge strategy, merged 12 July 2023, at which point the package's own package.json read version 0.2.15. No commit between that introduction and the fix touched local.ts again, so the field carried its unvalidated, shell interpreted meaning for the local identifier across every released version from 0.2.15 through 0.3.155, the exact upper bound GHSA-c7rj-fr2j-64w7 itself states. The fix commit, 7d120709, dated 15 June 2026, is a direct ancestor of commit 53f6ff190525ae5136f8a9d28783694885213924, an automated release commit that changes the package's own package.json from version 0.3.155 to version 0.3.156, confirmed by this record through a direct ancestry check in the cloned repository rather than assumed from adjacent dates. That release commit is the version bump, not the remediation itself; the actual fix landed roughly two and a half months before AWS's own 3 September 2026 publication date, a gap consistent with a coordinated disclosure embargo rather than with any uncertainty about when the fix was ready. This record separately read the current default branch, where package.json reads version 0.3.159, corresponding to a release commit dated 24 August 2026, and confirmed the same argument array construction, the same execFileSync call and the same ALLOWED_LOCAL_CMD allow list remain present, unchanged, in the current source.
What the managed service does and does not tell us
AWS's own advisory language, corroborated by this record's independently phrased web searches converging on consistent wording, states that no action is required for use of the managed Amazon CodeCatalyst service, because it runs resynthesis in isolated per project environments with scoped credentials and applies its own server side validation that rejects an invalid local merge strategy command, for blueprints published before version 0.3.156 as well as after. This record preserves that boundary precisely rather than letting it soften the SDK finding it sits beside: the managed service's own server side check is a separate control, enforced outside the SDK code this record read, and its existence says nothing about whether the SDK's own validation gap was real, only that AWS built a second, independent layer in front of it for its own hosted offering. A direct consumer of the @amazon-codecatalyst/blueprints.blueprint package, running resynthesis outside that managed boundary, whether in a self hosted pipeline, a local development environment or a custom automation, had no equivalent server side check standing between a crafted .ownership-file and the shell this record confirmed the pre fix source invoked directly. This record does not represent the direct SDK vulnerability as a compromise of the managed CodeCatalyst service itself, and it does not represent the managed service's own containment as evidence that the SDK vulnerability did not exist for everyone else running the same code.
The Authority Provenance ledger
Source principal. Whoever holds permission to commit to the project repository whose .ownership-file resynthesis reads, confirmed by this record's own reading of the source as needing no authority over the resynthesis environment itself, only ordinary write access to the project.
Interpreting action. Amazon's own blueprints SDK, specifically deserializeStrategies reading the .ownership-file and, for the local identifier, constructLocalStrategy and runLocalCommand carrying the owner value to execution, confirmed directly by this record's own reading of deserialize-strategies.ts and local.ts as the exact code path.
Semantic role before the crossing. An ownership identity string, for every merge strategy identifier other than local, confirmed by this record as reaching only a wildcard string comparison with no execution consequence.
Semantic role after the crossing. A shell interpreted command pattern, for the local identifier alone, confirmed directly by this record's own reading of the pre fix execSync call as reaching a full shell with the substituted owner value as its entire command line.
Execution principal and environment. Whatever process performs resynthesis, holding whatever privileges and credentials that environment carries, a principal this record's own reading of the source finds no requirement to be the same actor, session or organization as the repository committer who wrote the owner field.
Failed limit. One, confirmed directly: nothing in the pre fix source validated the local owner value's character content before it reached execSync, and nothing separately reconfirmed, at the point of execution, that the authority which let a committer edit the ownership file at all extended to authority over an arbitrary shell command in the resynthesis environment.
Recovery path. An argument array passed to execFileSync with no shell in the path, and a character allow list rejecting a non conforming local owner value before constructLocalStrategy is reached, confirmed directly by this record's own reading of commit 7d120709. For the managed service, a separate, independently confirmed server side validation layer AWS states applies regardless of blueprint version.
Recovery timing. Merged 15 June 2026 and confirmed by this record as a direct ancestor of the release commit that carries the package's own version from 0.3.155 to 0.3.156, roughly two and a half months before this CVE's own 3 September 2026 publication date.
Tracking state. AWS's own bulletin page was blocked to direct fetch in this session on every attempt; its stated content is corroborated here against the GitHub Security Advisory, read directly, and against independently phrased web searches, rather than read from the bulletin's own text.
Exploitation. Unknown. No source available to this record states this mechanism was exploited against a real project or a real resynthesis environment.
Provenance evidence quality. Strong for the mechanism, its introduction and its fix: this record cloned aws/codecatalyst-blueprints directly and read the exact commit that introduced the unvalidated local merge strategy, the exact commit that fixed it, the exact commit that carried the fix into the version the advisory names as fixed, and the current default branch, confirming all four directly rather than inferring any of them from the advisory's own prose. Weaker for AWS's own bulletin: aws.amazon.com was blocked to direct fetch in this session, so this record relies on the GitHub Advisory Database's own record and on independently phrased web searches for the bulletin's own stated content, and does not independently confirm the bulletin's exact wording.
Where this sits in the pattern
Amazon's own DynamoDB MCP Server CDK generator already showed a data model's table and attribute names crossing from data into TypeScript source, with the consequence deferred until a separate, later principal compiled and deployed the generated artifact. This record's own mechanism shares the underlying shift, an authority defining field crossing from a descriptive role into an executable one, without sharing that deferral: nothing here waits for a later build or deploy step. The owner field's crossing and its consequence happen inside the same resynthesis invocation that reads the .ownership-file, resolved at once rather than across a persistence gap, closer in shape to git-mcp-server's own read only tools, where a single caller supplied value changed role from data to control syntax purely by virtue of where it sat in an external process's own argument list, decided immediately inside one function call. This record's own mechanism decides its crossing even earlier than that: not by where the value sits in an argument list handed to an external parser, but by which bracketed word precedes it in a file format the SDK's own code parses directly, so the same field name carries two incompatible meanings the SDK itself assigns, entirely upstream of any external process ever seeing the value at all. This desk's wider argument that execution authority has to be evaluated against the action a system actually takes, not the action a principal was nominally authorized for, holds here in its plainest form: authority to edit a file that describes ownership was never authority to occupy the one field position in that same file the SDK itself, silently and without a separate check, also reads as an operating system command.
What this record does not establish
This record does not claim CVE-2026-85012 has been exploited against a real CodeCatalyst project, that every direct consumer of the @amazon-codecatalyst/blueprints.blueprint package runs resynthesis against untrusted repository content, that AWS's own bulletin carries detail beyond what this record could corroborate through the GitHub Security Advisory and independent web searches, or that the managed Amazon CodeCatalyst service's own server side validation has itself been independently tested by this record rather than stated by AWS. Where the evidence available to this record does not establish a fact, this record states it as unknown rather than inferring it from the pattern this weakness class shows elsewhere.
Sources
This analysis interprets third-party reporting, research and announcements. Moona is not the original reporter of the underlying events.
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-023 A tool's stored response transformation runs with the gateway's own authority
- Supports requirement
Agent Control Standard (ACS)
OWASP GenAI Security Project, originally Zenity
Requirement The specification does not guarantee complete mediation of every consequential action a host can take
ACS's own conformance document states directly that the specification does not guarantee complete mediation of every consequential action a host can take, naming a primitive that bypasses its own toolCallRequest hook as invisible to policy. IBM ContextForge's stored jq filter is concrete evidence for a stage this admission does not itself name: a consequential action, evaluating an executable program against gateway process state, occurring after a tool call has already been authorized and mediated, inside a response transformation step neither the tool's own invocation permission nor a hook fired at the moment of the call was written to examine. This entry's own reading of the pre fix evaluation path supports the requirement that mediation be evaluated against every stage capable of consequential effect, not only the moment a tool call itself is authorized.
- Supports requirement
ARC, Agentic Runtime Control
Britive
Requirement Elevated privilege is documented as minted directly in a target system through its own native API and revoked automatically when a task ends, with credential brokering as a separate path for systems that require one
Britive's own documented model brokers or mints credentials ephemerally and scoped to a task, rather than leaving a standing credential ambiently reachable by whatever process happens to run. IBM ContextForge's pre fix jsonpath_filter is the failure that principle answers, made concrete: a stored program, once triggered, read the gateway's own standing process environment directly, with no brokering step and no scope narrower than the gateway's own full authority in between. The shipped fix's own cleared, sandboxed worker is a narrower, response transformation scoped instance of the same corrective this property already names for a broader class of runtime credential access, supporting the requirement that executed logic reach only a scoped, brokered capability rather than a gateway's own ambient environment.
