{"repro_id":"REPRO-2026-00303","version":6,"title":"AWS API MCP Server security policy bypass via startup initialization failure","repro_type":"security","status":"published","severity":"high","description":"Primary-source research dossier for CVE-2026-16584 (AWS API MCP Server Security Policy Bypass via Startup Failure). AWS Security Bulletin 2026-063-AWS states that awslabs.aws-api-mcp-server versions >=0.2.13 and <1.3.47 load the read-operations index used by the optional security policy at startup; if initialization fails, the server continues running with per-request policy checks skipped for the lifetime of the process. In vulnerable versions, a configured denyList or elicitList can therefore be bypassed and denied/gated AWS CLI operations may reach the downstream AWS-call boundary even though policy would normally block them. The patched release 1.3.47 fails closed: the server refuses to start when the read-operations index cannot be loaded, and request-time execution is denied when the index is unavailable. Evidence sources: AWS bulletin 2026-063-AWS, PyPI 1.3.47 release page, and awslabs/mcp commit ab1bbebc097d674c1cdd4bd75a8f313be18473bf. Repo-scoped project: awslabs/mcp.","root_cause":"## Summary\n\nCVE-2026-16584 is a fail-open initialization bug in `awslabs.aws-api-mcp-server`. Versions before the fix load the read-operations index at server startup for later per-request security-policy decisions. If that startup load fails, the vulnerable server catches the exception, sets `READ_OPERATIONS_INDEX = None`, and continues serving MCP requests. Later `call_aws` requests skip the configured deny/elicitation policy path when the index is absent, allowing an AWS operation that a local security policy was configured to deny to reach the AWS call boundary.\n\n## Impact\n\n- **Package/component affected:** `awslabs.aws-api-mcp-server` in the `awslabs/mcp` repository, specifically `src/aws-api-mcp-server/awslabs/aws_api_mcp_server/server.py` and the security-policy path using `core/metadata/read_only_operations_list.py` and `core/security/policy.py`.\n- **Affected versions:** Submitted claim states `>=0.2.13` and `<1.3.47`. This run anchored the vulnerable build to fixed commit `ab1bbebc097d674c1cdd4bd75a8f313be18473bf^` (`70d8c4cb82601a0e48328e34bf7a81b5a6fba073`) and the fixed negative control to `ab1bbebc097d674c1cdd4bd75a8f313be18473bf`.\n- **Risk level and consequences:** High. A local actor or MCP client using a server configured with a security policy can cause denied or gated AWS CLI operations to be executed by the server process after the read-operations index fails to initialize. IAM permissions still bound what AWS accepts, but the server-level denyList/elicitList control is bypassed for the process lifetime.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Authorization/policy bypass for configured denyList or elicitList entries, allowing denied/gated AWS CLI operations to reach the AWS-call boundary after startup initialization failure.\n- **Reproduced impact from this run:** Full authorization bypass at the claimed CLI/MCP server boundary. The vulnerable server started via the real `awslabs.aws_api_mcp_server.server` stdio entrypoint, logged read-operations index initialization failure, accepted an MCP `call_aws` request for a command present in `denyList`, and performed an HTTP POST to a local fake STS endpoint. The fixed commit failed closed during startup and never reached the fake AWS endpoint.\n- **Parity:** `full`\n- **Not demonstrated:** No real AWS account mutation was attempted. The proof uses a local fake AWS HTTP endpoint as the downstream AWS-call boundary to avoid external side effects; the boundary request body and botocore-signed headers demonstrate that the real server execution path reached the AWS client call.\n\n## Root Cause\n\nThe root cause is fail-open handling of a required security-policy dependency. In vulnerable commit `70d8c4cb82601a0e48328e34bf7a81b5a6fba073`, `main()` always tries to load `READ_OPERATIONS_INDEX = get_read_only_operations()`, but catches any exception, logs `Failed to load read operations index`, sets `READ_OPERATIONS_INDEX = None`, and continues to `server.run(...)`. Later, `call_aws_helper()` only invokes `check_security_policy(ir, READ_OPERATIONS_INDEX, ctx)` when `READ_OPERATIONS_INDEX is not None`. With the index set to `None`, a normal denyList check is skipped unless the separate read-only or mutation-consent modes are enabled.\n\nThe fix commit is `ab1bbebc097d674c1cdd4bd75a8f313be18473bf` (`fix(aws-api-mcp-server): Security Policy Bypass via Startup Initialization Failure`). It removes the fail-open fallback and changes request-time behavior so an absent `READ_OPERATIONS_INDEX` is treated as a hard denial: `Execution of this operation is denied because the security policy enforcement data failed to initialize.` In the startup path, initialization failure is re-raised, so the stdio MCP server does not finish initialization.\n\n## Reproduction Steps\n\n1. Run `bundle/repro/reproduction_steps.sh`.\n2. The script:\n   - Uses `bundle/project_cache_context.json` when present and checks out/reuses `/pruva/project-cache/repo`.\n   - Resolves vulnerable commit `ab1bbebc097d674c1cdd4bd75a8f313be18473bf^` and fixed commit `ab1bbebc097d674c1cdd4bd75a8f313be18473bf`.\n   - Creates isolated source trees for each commit and installs the real package in a Python virtual environment.\n   - Starts the real server via `python -m awslabs.aws_api_mcp_server.server` over MCP stdio.\n   - Writes a real `~/.aws/aws-api-mcp/mcp-security-policy.json` with `denyList: [\"aws sts get-caller-identity\"]`.\n   - Forces read-operations index initialization failure with an invalid proxy for `servicereference.us-east-1.amazonaws.com` while preserving localhost access.\n   - Sends an MCP `call_aws` tool request for `aws sts get-caller-identity --endpoint-url http://127.0.0.1:<fake-sts-port>`.\n   - Runs two vulnerable attempts and two fixed negative-control attempts.\n3. Expected evidence:\n   - Vulnerable attempts show `mcp_initialized=true`, `startup_failure_observed=true`, `fake_aws_boundary_reached=true`, and `policy_denial_observed=false`.\n   - Fixed attempts show startup failure, `mcp_initialized=false`, and `fake_aws_boundary_reached=false`.\n\n## Evidence\n\n- `bundle/logs/reproduction_steps.log` contains the full script output, resolved commit IDs, patch-presence excerpts, and per-attempt summaries.\n- `bundle/logs/repro/summary.json` contains structured results. In the latest run it recorded:\n  - `vulnerable_all_bypassed: true`\n  - `fixed_all_fail_closed: true`\n- `bundle/logs/repro/vulnerable_attempt_1.json` and `bundle/logs/repro/vulnerable_attempt_2.json` show both vulnerable attempts reached the fake AWS boundary. Each includes a captured POST with body `Action=GetCallerIdentity&Version=2011-06-15`, botocore `Authorization` headers, and a successful MCP tool response for `AWS Security Token Service / GetCallerIdentity`.\n- `bundle/logs/repro/vulnerable_attempt_1_server.log` and `bundle/logs/repro/vulnerable_attempt_2_server.log` show the server logged `Failed to load read operations index`, then continued to start FastMCP over stdio and handled `call_tool call_aws`.\n- `bundle/logs/repro/fixed_attempt_1.json` and `bundle/logs/repro/fixed_attempt_2.json` show no fake AWS calls.\n- `bundle/logs/repro/fixed_attempt_1_server.log` and `bundle/logs/repro/fixed_attempt_2_server.log` show the fixed server raised `RuntimeError: Error retrieving the service reference document...` during startup and closed the MCP connection.\n- `bundle/repro/runtime_manifest.json` records the runtime-backed entrypoint as `cli_command`, with target path reached and proof artifacts listed.\n\n## Recommendations / Next Steps\n\n- Upgrade `awslabs.aws-api-mcp-server` to version `1.3.47` or later, containing fix commit `ab1bbebc097d674c1cdd4bd75a8f313be18473bf`.\n- Keep the security-policy fail-closed invariant: if read-operations metadata cannot be loaded, do not serve requests and do not execute AWS CLI operations.\n- Add regression tests that force `get_read_only_operations()` to fail and assert both startup refusal and request-time denial for absent policy-enforcement data.\n- Ensure operational monitoring alerts on `Failed to load read operations index` or equivalent startup failures.\n\n## Additional Notes\n\n- The reproduction script was run successfully twice consecutively after fixing an initial brittle grep preflight.\n- The proof is idempotent: it creates temporary HOME directories, local fake AWS endpoints, and isolated source archives on each run.\n- The fake AWS endpoint is intentionally local and side-effect free. It proves the downstream AWS-call boundary is reached without using real cloud credentials or changing AWS resources.\n","cve_id":"CVE-2026-16584","cwe_id":"CWE-455","source_url":"https://github.com/advisories/GHSA-29w2-fq35-v728","package":{"name":"awslabs.aws-api-mcp-server","ecosystem":"pip","affected_versions":">=0.2.13 and <1.3.47; equivalently 0.2.13 through 1.3.46.","fixed_version":"1.3.47, released on PyPI on 2026-07-22."},"reproduced_at":"2026-07-27T05:43:09.881263+00:00","duration_secs":1792.0,"tool_calls":213,"handoffs":2,"total_cost_usd":9.357799,"agent_costs":{"claim_matcher":0.016359,"judge":1.010902,"learning_policy":0.013683,"repro":2.148586,"support":0.654131,"vuln_variant":5.514138},"cost_breakdown":{"claim_matcher":{"gpt-5.5-2026-04-23":0.016359},"judge":{"gpt-5.5":0.953544,"gpt-5.5-2026-04-23":0.057358},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.013683},"repro":{"gpt-5.5":2.148586},"support":{"gpt-5.5":0.654131},"vuln_variant":{"gpt-5.5":5.514138}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-27T05:43:10.527420+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":12773,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":7569,"category":"analysis"},{"path":"bundle/repro/proof_summary.json","filename":"proof_summary.json","size":1016,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":955,"category":"other"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":33595,"category":"log"},{"path":"bundle/logs/repro/summary.json","filename":"summary.json","size":29409,"category":"other"},{"path":"bundle/logs/repro/vulnerable_attempt_1.json","filename":"vulnerable_attempt_1.json","size":9125,"category":"other"},{"path":"bundle/logs/repro/vulnerable_attempt_2.json","filename":"vulnerable_attempt_2.json","size":9125,"category":"other"},{"path":"bundle/logs/repro/fixed_attempt_1.json","filename":"fixed_attempt_1.json","size":5235,"category":"other"},{"path":"bundle/logs/repro/fixed_attempt_2.json","filename":"fixed_attempt_2.json","size":5235,"category":"other"},{"path":"bundle/logs/repro/vulnerable_attempt_1_server.log","filename":"vulnerable_attempt_1_server.log","size":4242,"category":"log"},{"path":"bundle/logs/repro/fixed_attempt_1_server.log","filename":"fixed_attempt_1_server.log","size":7861,"category":"log"},{"path":"bundle/logs/repro/patch_presence.log","filename":"patch_presence.log","size":2529,"category":"log"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":764,"category":"other"}]}