flawopen.com/Teardowns/cve-2026-87913-aws-security-agent-mcp-s3-bucket-ownership
CVE-2026-87913: AWS MCP S3 Data Exposure
A predictable AWS Security Agent bucket could send private scan archives to another account. Upgrade to 0.2.0 and verify bucket ownership.[1], [2], [4]
Imagine a courier is told to deliver a confidential package to “the office for account 123 in this region.” The courier checks only that a building with that name exists, not who owns it. Someone else can register the address first and receive the package. The fix makes the storage service confirm the expected owner on the check and the upload.[2], [1]
Core Concepts & Subsystem Terms
MCP server- A local service that exposes operations as tools an AI assistant can request; those operations still use the server process’s permissions.[3]
S3 bucket- A globally named Amazon S3 container for objects. A predictable name does not prove which AWS account owns the bucket.[2], [3]
Framework mappings
- CWE reference for the weakness described in this guide: CWE-283 CWE-341
- OWASP GenAI 2025 — LLM06:2025 Excessive Agency
Root Cause Analysis
The AWS Security Agent MCP server derives its scan-input bucket name from the customer AWS account ID and Region: security-agent-scans-<account-id>-<region>. AWS and the maintainer advisory say the vulnerable version checked whether a bucket by that name existed, but not whether the customer account owned it. S3 bucket names are globally unique, and account IDs are public. A third party that registered the predictable name first could receive the private source archive uploaded by the MCP server; it may contain source code, credentials, and infrastructure state. This is a cloud-resource ownership and predictable-name failure, not an AI-model prompt-injection flaw. Versions 0.1.0 through 0.1.5 are affected; 0.2.0 adds ExpectedBucketOwner to S3 operations and fails if the bucket belongs to another account. Upgrading does not release a name already registered elsewhere: verify the actual owner and do not resume scans if the bucket belongs to a third party.
Bucket name is not bucket ownership
| Boundary | Documented condition | Operator action |
|---|---|---|
| Affected versions | awslabs.security-agent-mcp-server 0.1.0 through 0.1.5 are affected; 0.2.0 adds the ownership check. | Upgrade to 0.2.0 or later and verify the running package version. |
| Predictable bucket name | The scan bucket name is derived from account ID and Region; the name can be registered by another AWS account. | Treat a matching bucket name as an address, not proof of ownership. |
| Cross-account upload | A vulnerable server could accept the named bucket without verifying its owner, then upload a private scan archive. | Verify the bucket belongs to the expected account before scanning or uploading. |
| Fixed ownership boundary | ExpectedBucketOwner makes S3 compare the owner on both the ownership check and the upload. | Upgrade, verify the actual owner, and stop scans if a third party owns the name; upgrading cannot transfer it. |
Step-by-Step Attack Flow
Source Code: Flaw vs. Secure Implementation
This is a schematic Python reduction of the advisory-described boundary, not a verbatim source diff. Existence is insufficient: Amazon S3 must compare the bucket owner with the expected account both during the ownership check and on the write. Derive the expected account from the authenticated AWS caller context, not from an untrusted tool argument. Fail closed on a mismatch. After upgrading, separately audit the bucket owner because an upgrade cannot transfer or release a name already held by another account. AWS documents ExpectedBucketOwner for both HeadBucket and PutObject: a mismatch makes the request fail with 403. Treat that as a fail-closed guard, but investigate access-denied errors through an authorized ownership-check path before concluding a bucket is foreign.[2], [8], [9]
Source excerpt: Advisory-derived AWS SDK example; not a verbatim source diff.
# Simplified vulnerable shape: the name is checked, but its owner is not.
s3.head_bucket(Bucket=bucket_name)
s3.put_object(
Bucket=bucket_name,
Key=object_key,
Body=archive_bytes,
)
# Bind each operation to the account that is expected to own the bucket.
expected_owner = sts.get_caller_identity()["Account"]
s3.head_bucket(
Bucket=bucket_name,
ExpectedBucketOwner=expected_owner,
)
s3.put_object(
Bucket=bucket_name,
Key=object_key,
Body=archive_bytes,
ExpectedBucketOwner=expected_owner,
)
Engineering & System Hardening Checklist
- ✓Upgrade
awslabs.security-agent-mcp-serverto 0.2.0 or later and verify the running version, including plugins, CI images, and forks.[2], [4] - ✓Before scanning, verify
security-agent-scans-<account-id>-<region>is owned by the expected AWS account; finding a bucket by name does not prove ownership.[1], [2] - ✓Pass the expected account ID as
ExpectedBucketOwneron every S3 operation involving the derived bucket, including the upload itself.[8], [9] - ✓Fail closed on owner mismatch or access errors. If a third party already owns the name, stop scans and contact AWS; upgrading does not release or transfer it.[1]
- ✓Where applicable, pre-create the scan bucket in the customer account as AWS recommends; apply least-privilege S3 policies and encryption.[3], [1]
- ✓Review scans performed with affected versions for uploaded archives; assess possible secret exposure and rotate credentials if needed.[2], [1]
Sources
- AWS Security Bulletin 2026-105-AWS — CVE-2026-87913
- Maintainer GHSA-3jxw-vj8m-8x77
- AWS Security Agent IDE integration and scan bucket workflow
- awslabs.security-agent-mcp-server 0.2.0 release
- NVD — CVE-2026-87913
- MITRE CWE-283 — Unverified Ownership
- MITRE CWE-341 — Predictable from Observable State
- Boto3 HeadBucket — ExpectedBucketOwner
- Boto3 PutObject — ExpectedBucketOwner
- Related flaw in the same package (separate CVE): CVE-2026-97662 AWS MCP Git argument injection