Intelligence

The Object Key Named a File to Download. It Also Named Where root Would Write It.

CVE-2026-81849, published by AWS on 28 August 2026, describes a path traversal in amazon-ssm-agent's aws:downloadContent plugin. A principal whose ssm:SendCommand permission is restricted purely to the AWS-DownloadContent document can only ask the agent to perform one bounded operation: download an S3 object into a destination directory. This record independently cloned aws/amazon-ssm-agent and read the affected and fixed source directly: the destination path for each downloaded file is built by joining the destination directory with the portion of the real S3 object key beyond the caller's configured prefix, using a plain path join with no containment check, while a second, already existing helper in the same package performs the identical join safely and was already in use by five sibling plugins. A crafted S3 object key could carry the join straight past the intended download directory, and the agent, which writes as root, would follow it there.

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

A principal was authorized to invoke exactly one SSM document, one that only downloads an S3 object into a directory. How did that narrow authority reach a root-owned file the document never named?

The authorized operation and the effective write target were never the same question, and nothing in the vulnerable code asked the second one. AWS Systems Manager lets an administrator restrict a principal's ssm:SendCommand permission to a single named document through an IAM condition; AWS-DownloadContent is one such document, and its own contract is narrow by design, fetch a caller named S3 object into a caller named destination directory. This record independently cloned aws/amazon-ssm-agent and confirmed that the plugin implementing that document, aws:downloadContent, resolves the effective local write path for each downloaded file by joining the destination directory with a fragment of the real S3 object key, the part of the key beyond the prefix the caller configured, and that this join, in every version through 3.3.4364.0, used fileutil.BuildPath, a function this record confirmed by direct reading performs a plain filepath.Join with no check that the result stays inside the directory it was joined against. The identical file, agent/fileutil/fileutil.go, already defined fileutil.BuildSafePath, a function this record confirmed cleans the root, joins and cleans each path element in turn, and returns the root itself, unmodified, the moment a joined element would resolve outside it, and this record independently confirmed five other plugins in the same codebase, at the same affected version, already called it. GitHub Security Advisory GHSA-mqvm-jv87-w7rx formally assigns this CVE-2026-81849 on 28 August 2026, CVSS 4.0 base score 8.7, High, and states plainly that an authenticated user whose ssm:SendCommand permission is restricted to the AWS-DownloadContent document could craft S3 object keys to write arbitrary files outside the intended download directory with root privileges, and that overwriting sensitive files this way could enable arbitrary code execution as root. Authority to invoke one bounded document was never authority over every path the document's own key-handling logic could be made to resolve to, and this record's own reading of the source confirms the two were never separately checked: the S3 object key's own content, not the destination directory the document declared, decided where the write actually landed. The fix, shipped in version 3.3.4515.0, replaces the single unsafe call, and a second, adjacent one this record separately found in the plugin's GitHub git-resource directory download path, with the already existing safe helper; no new containment mechanism was written, because one already existed in the same file and was already trusted everywhere else in the codebase except the two call sites the fix touches.

An S3 object key is, formally, just a string. Amazon S3 imposes almost no structure on it: a key can contain forward slashes that look like directory separators without being directories at all, and nothing in S3 itself stops a key from containing the literal characters a filesystem path parser would read as "go up one directory." CVE-2026-81849 is what happens when a privileged local agent reads that string as though it were already a safe, bounded path fragment, joins it onto a destination directory with a function that performs no containment check, and writes the result as root.

What the advisory states

GitHub Security Advisory GHSA-mqvm-jv87-w7rx, fetched directly in this session, assigns CVE-2026-81849, published 28 August 2026 at 18:03:54 UTC, titled "Path traversal in the aws:downloadContent plugin in amazon-ssm-agent." Its structured data carries no CWE id, an empty array; AWS's own bulletin description, corroborated through independently phrased web search since aws.amazon.com is blocked to direct fetch in this session, describes the flaw as "improper limitation of a pathname to a restricted directory," the description CWE reserves for CWE-22. The advisory states the affected package is amazon-ssm-agent, versions before 3.3.4515.0, and credits reporter @wareeqshile for coordinated disclosure. Its own description: "An authenticated user with restricted ssm:SendCommand permissions targeting the AWS-DownloadContent document could exploit crafted S3 object keys to write files outside the intended directory with root privileges, potentially enabling arbitrary code execution through sensitive file overwriting." CVSS 4.0 base score 8.7, High, vector CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N; the same GHSA JSON states no CVSS 3.1 vector was supplied through that structured field. Independently phrased web searches converge on a CVSS 3.1 base score of 8.8, High, attributed to AWS's own bulletin; this record states that figure as search corroborated rather than independently confirmed from a fetched primary source, since aws.amazon.com itself remained unreachable to this session on every attempt.

What this record independently confirmed by cloning aws/amazon-ssm-agent directly and reading the real, unmodified source, rather than by reading a description of it: a plain, unchecked path join and a containment checked path join already existed side by side in the same file, and the vulnerable call site was the one plugin in the codebase still calling the unchecked one.

One S3 key, trimmed to a fragment, joined without a check

This record read agent/plugins/downloadcontent/s3resource/s3resource.go directly at git tag 3.3.4364.0, the exact upper bound GHSA-mqvm-jv87-w7rx states as affected. The aws:downloadContent plugin's S3Resource.DownloadRemoteResource method calls ListS3Directory against the S3 location the caller's SourceInfo.Path names, s3.s3Object.Key, and receives back every object key reachable under that prefix. For each one, files, it computes subFolderPath := strings.TrimPrefix(files, s3.s3Object.Key), the portion of the real, returned object key beyond the caller's own configured prefix, then builds the local write path with localFilePath = fileutil.BuildPath(destPath, filepath.Dir(subFolderPath)). This record confirmed by direct reading of agent/fileutil/fileutil.go that BuildPath's own implementation is: start from root, and for each element call filepath.Join(fullPath, removeInvalidColon(element)), returning whatever that produces, with no check afterward that the result still sits under root. removeInvalidColon strips only a literal colon; it does nothing to a "..", so an object key returned by ListS3Directory whose portion beyond the caller's configured prefix contained one or more "../" segments would carry those segments straight through TrimPrefix, straight through BuildPath's own unchecked filepath.Join, and out the far side as a localFilePath sitting wherever those segments walked it to, however far above destPath that reached.

The destination this record just described, destPath, is itself bounded correctly by the plugin's own caller, agent/plugins/downloadcontent/downloadcontent.go, which this record also read directly: an absolute input.DestinationPath is used as given, and a relative one is joined under the SSM document run's own orchestration directory, appconfig.OrchestrationDirectory, followed by a fixed "downloads" subdirectory. That upstream confinement is real; this record's own reading found nothing wrong with it. The gap this record confirmed sits entirely downstream of it, inside S3Resource.DownloadRemoteResource itself, in the one join that takes the correctly bounded destPath and recombines it with a fragment of the caller-influenced object key using a function that never checks the result against the root it was supposedly still bounded by.

The safe function already existed, and five plugins already called it

This record confirmed, by direct reading of the identical file, agent/fileutil/fileutil.go, that a second function sits immediately below BuildPath: BuildSafePath. Its own implementation first calls filepath.Clean on root, then for each subsequent element joins it, cleans the result again, and checks strings.HasPrefix(newPath, root); the moment that check fails, the function returns root itself, unmodified, discarding the escaping element rather than propagating it. This record independently confirmed, with a direct source search across the same affected tag, that BuildSafePath was not a newly written or untested function at the time of this vulnerability: five other plugins, runscript, psmodule, configurecontainers, application and dockercontainer, and the shared framework/processor/executer/iohandler package, already called it. The aws:downloadContent plugin's own S3 resource was the outlier, the one call site in a codebase that otherwise already treated this exact containment check as the correct default, still calling the function that skips it.

The fix, read directly against the affected commit

This record read the complete diff of commit 701f15d4481c3e7c9dc40ef12d350583509bba91, titled "Use BuildSafePath instead of BuildPath," directly from the cloned repository. Its own internal code review reference reads CR-273746739; its author date reads 9 May 2026, and this record separately confirmed, through git log against the same commit, that its committer date, the timestamp the repository's own history actually orders it by, reads 21 May 2026, one second before the 3.3.4515.0 release tag itself. The commit changes exactly three lines of substance across two files. In s3resource.go, the single vulnerable call becomes localFilePath = fileutil.BuildSafePath(destPath, filepath.Dir(subFolderPath)), no other logic touched. This record separately confirmed the same commit closes a second, adjacent call this record found independently by reading the diff itself: agent/plugins/downloadcontent/gitresource/github/githubresource.go built a subdirectory destination during a recursive GitHub directory download using the same unchecked filepath.Join pattern, and the commit replaces that call with fileutil.BuildSafePath as well, alongside a second, related change hoisting a trailing path separator check ahead of a call that had been re-cleaning destinationDir in place. GHSA-mqvm-jv87-w7rx's own stated mechanism names only the S3 source type and the AWS-DownloadContent document; this record states the GitHub resource change as adjacent scope the same commit happened to carry, not as a claim the advisory itself makes.

Where the vulnerable pattern came from, read from history rather than assumed from the advisory's own stated lower bound

This record traced the S3 resource plugin's own history directly in the cloned repository rather than accepting GHSA's stated lower bound, 2.0.767.0, without checking it. The plugin was introduced as agent/plugins/executecommand/s3resource by commit 66f719aa1bded7fb7e8353dbfd57fa7a51c2cb44, "Support for pulling resources down from S3," dated 21 July 2017. It was renamed into its current package, agent/plugins/downloadcontent/s3resource, by a later commit, "Changing copycontent plugin name to downloadcontent," dated 16 October 2017; this record confirmed directly that the earliest release tag containing that rename commit is 2.0.767.0 itself, cut 18 January 2018, the exact tag GHSA-mqvm-jv87-w7rx names as the start of the affected range. This record read agent/plugins/downloadcontent/s3resource/s3resource.go at that same tag, 2.0.767.0, directly, and confirmed the identical subFolderPath computation and the identical unchecked fileutil.BuildPath call this record describes above were already present, unchanged in mechanism, at that point, three and a half years before fileutil.BuildSafePath existed to be called instead. This record's own git log search across every intervening commit touching this file found no point at which the join was made safe and later regressed; the mechanism this record confirms at 3.3.4364.0 is, as far as this record's own direct reading establishes, the same mechanism present since the plugin's 2017 introduction, carried forward across every released version in between.

Why the write lands as root, independent of the document's own RunAs setting

The download step this record traces above executes inside the SSM Agent's own core plugin framework, before any RunAs impersonation the document itself might separately configure for a script or command step takes effect; this record's own reading of downloadcontent.go and runCopyContent found no privilege drop or user context switch anywhere between p.filesys.WriteFile and the plugin's own execution as part of the agent process. amazon-ssm-agent is installed and run as a system service; GHSA-mqvm-jv87-w7rx's own stated consequence, "write arbitrary files outside the intended download directory with root privileges," matches directly what this record's own reading of the execution path would produce, since nothing this record found in the download plugin's own code path narrows the write to any privilege short of whatever the agent process itself holds. This record treats the advisory's own stated consequence, root-privileged file write and, through overwriting sensitive files, potential arbitrary code execution as root, as the primary evidence for that consequence, corroborated by, not derived independently from, this record's own reading of the execution path.

The Authority Provenance ledger

Source principal. An authenticated caller whose ssm:SendCommand permission an administrator has restricted, through an IAM condition, to the single named document AWS-DownloadContent, and who supplies that document's SourceInfo.Path, an S3 location the object key traversal this record describes above is influenced by, confirmed by this record's own reading of the source as needing no broader ssm:SendCommand authority, no filesystem authority on the target instance, and no authority over the S3 bucket's own object naming beyond whatever access already lets an object exist at the source location the document names.

Interpreting action. amazon-ssm-agent's own aws:downloadContent plugin, specifically S3Resource.DownloadRemoteResource in agent/plugins/downloadcontent/s3resource/s3resource.go, confirmed directly by this record's own reading of the source as the exact code path that trims a returned S3 object key against the caller's configured prefix and joins the remainder onto the destination directory.

Semantic role before the crossing. A path fragment identifying which sub-object, if any, a listed S3 key represents beneath the caller's own configured prefix, confirmed by this record as intended only to preserve a source side directory structure inside an already bounded destination.

Semantic role after the crossing. A literal filesystem path segment, joined with no containment check, confirmed directly by this record's own reading of the pre fix fileutil.BuildPath as reaching filepath.Join unmodified, with nothing downstream re-verifying the joined result still sat under the destination directory the document declared.

Execution principal and environment. The amazon-ssm-agent process itself, running with whatever privileges the installed agent service holds on the target instance, root on Linux per the advisory's own stated consequence, a principal this record's own reading of the source finds no requirement to match the authority of the caller who invoked SendCommand, only the document AWS-DownloadContent the caller was restricted to.

Failed limit. One, confirmed directly: nothing in the pre fix source re-checked, at the point the local write path was resolved, that the join of the destination directory with the S3 key's own trimmed fragment still resolved inside that destination directory, even though the exact function that would have performed that check, fileutil.BuildSafePath, already existed in the same file and was already the default five other plugins in the same codebase called.

Recovery path. Replacing the one unchecked fileutil.BuildPath call in s3resource.go, and a second, adjacent one this record separately confirmed in the plugin's GitHub git-resource directory download path, with the already existing fileutil.BuildSafePath, confirmed directly by this record's own reading of commit 701f15d4481c3e7c9dc40ef12d350583509bba91. No new containment mechanism was written; the fix is calling the function the rest of the codebase already trusted.

Recovery timing. Authored 9 May 2026 per the commit's own author date and this record's own reading of its internal review reference, CR-273746739; its committer date, the point this record confirms the repository's own history actually places it at, reads 21 May 2026, one second ahead of the 3.3.4515.0 release tag this record separately confirmed carries it, roughly twelve weeks before this CVE's own 28 August 2026 publication date.

Tracking state. AWS's own bulletin page, 2026-091-AWS, 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 converging on matching wording for the affected package, version range and remediation, though this record does not independently confirm the bulletin's own CVSS 3.1 figure from a fetched primary source.

Exploitation. Unknown. No source available to this record states this mechanism was exploited against a real instance or a real S3 source location.

Provenance evidence quality. Strong for the mechanism, its multi-year presence and its fix: this record cloned aws/amazon-ssm-agent directly and read the exact vulnerable call, the exact already existing safe alternative already used by five sibling plugins, the exact fix commit, and the plugin's own 2017 introduction and 2018 rename, confirming each 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 structured record, read directly, and on independently phrased web searches for the bulletin's own stated content, and does not independently confirm the bulletin's exact wording or its own separately reported CVSS 3.1 figure.

Where this sits in the pattern

AgentScope's own add_skill method already showed one half of a two sided transfer operation confined correctly while the other, the source a caller could name, carried no location check of its own at all. This record's own mechanism inverts which side fails: here the destination root, destPath, is confined correctly by the plugin's own upstream caller, and the source, the S3 object key's own returned content, is not an unchecked read location but an unchecked write-path ingredient, joined onto the already bounded destination by a function that discards the very check its own sibling function next to it in the same file would have applied. It is closer in shape to AshAi's own identity_filter, where an operation correctly authorized against a resource type still let a caller supplied argument's own shape decide which specific record the operation actually reached, except the argument reshaping itself here is not caller supplied syntax evaluated in one request; it is a real, already stored object key's own content, resolved through a listing call, joined by a function this record confirmed a working, already deployed sibling function next to it would have rejected. 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 a form this desk has not yet recorded: authority restricted to one named document, one bounded S3 download, was never authority over every path a real S3 object's own key could be made to resolve to once trimmed and joined by a function that trusted it.

What this record does not establish

This record does not claim CVE-2026-81849 has been exploited against a real instance, that every principal whose ssm:SendCommand permission is restricted to AWS-DownloadContent can independently place or rename objects inside the S3 bucket the document's SourceInfo.Path names, 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 fixed function, fileutil.BuildSafePath, has been independently tested by this record against every path shape an S3 key could carry rather than read directly, as source, for the containment logic it implements. 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.

[1]
Path traversal in the aws:downloadContent plugin in amazon-ssm-agent (CVE-2026-81849)
aws/amazon-ssm-agent (GitHub Security Advisory GHSA-mqvm-jv87-w7rx) · 28 August 2026 · Primary source
[2]
CVE-2026-81849, Path traversal in the aws:downloadContent plugin in amazon-ssm-agent
AWS Security Bulletin 2026-091-AWS · 28 August 2026 · Technical documentation
[3]
Commit 701f15d4: Use BuildSafePath instead of BuildPath
aws/amazon-ssm-agent (GitHub, commit) · 21 May 2026 · Primary source
[4]
fileutil.go: BuildPath and BuildSafePath, and their callers, at tag 3.3.4364.0
aws/amazon-ssm-agent (GitHub, source) · 4 May 2026 · Primary source
[5]
s3resource.go and downloadcontent.go at tag 2.0.767.0, and the plugin's 2017 introduction and rename
aws/amazon-ssm-agent (GitHub, source and commit history) · 21 July 2017 · Primary source

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-040 Destination confinement assumed from an unsafe join, not enforced by one

  • No protocol change

    Agent Control Standard (ACS)

    OWASP GenAI Security Project, originally Zenity

    ACS's own conformance document addresses whether a consequential action reaches an independent mediation point at all; amazon-ssm-agent's own aws:downloadContent mechanism is not that kind of gap. The document boundary itself was correctly enforced, an IAM condition genuinely restricted the principal to one named document, and this weakness's own evidence is a codebase level implementation defect inside that already authorized operation, one call site joining a destination directory with caller influenced content through an unchecked function instead of the containment checked sibling already defined in the same file and already called elsewhere in the same codebase. Nothing in the material available to this session states or implies a requirement, of ACS's or of any other protocol draft this registry currently holds, that would have been engaged differently had it applied here: the fix is calling an existing, already correct function, not adopting a new mediation, delegation or containment requirement this dataset's protocol material does not already cover in the abstract. Recorded as no change rather than supporting or missing evidence for any specific protocol requirement.

    This record is the cited evidence for this relationship.

    View protocol evidence

Related Intelligence

All Intelligence Records →