FlawOpen

flawopen.com/Teardowns/cve-2026-87913-aws-security-agent-mcp-s3-bucket-ownership

● CVE-2026-87913 · CVSS 5.1 · Medium
FlawOpen Security Research

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]

💡 Plain English Explainer (ELI5)

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]
Bucket ownership verification
A check that the account controlling a bucket is the account the application intended, rather than merely confirming that a bucket by that name responds.[2], [8], [9]
ExpectedBucketOwner
An Amazon S3 request parameter that makes the service verify the expected AWS account ID for the bucket before performing that operation.[8], [9]

Framework mappings

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

BoundaryDocumented conditionOperator action
Affected versionsawslabs.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 nameThe 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 uploadA 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 boundaryExpectedBucketOwner 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

Step 1

The MCP server prepares a source archive

An AI client requests a code scan. The local server packages the selected workspace and uses its configured AWS identity to coordinate the scan.[3], [2]

Step 2

The destination name is predictable

The scan-input bucket name is derived from the customer account ID and Region. AWS account IDs are not secret, and S3 bucket names are globally unique.[1], [2]

Step 3

The vulnerable check proves existence only

The affected server accepted a bucket by name without requiring Amazon S3 to confirm that the bucket owner matched the expected customer account.[2], [1]

Step 4

The archive may reach another account

If another account registered the name first, the server could upload the private scan archive there. It may contain credentials and infrastructure data.[2], [1]

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.

UNPATCHED FLAW
# 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,
)
HARDENED SECURE PATCH
# 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

Sources