## Summary

CVE-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.

## Impact

- **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`.
- **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`.
- **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.

## Impact Parity

- **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.
- **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.
- **Parity:** `full`
- **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.

## Root Cause

The 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.

The 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.

## Reproduction Steps

1. Run `bundle/repro/reproduction_steps.sh`.
2. The script:
   - Uses `bundle/project_cache_context.json` when present and checks out/reuses `/pruva/project-cache/repo`.
   - Resolves vulnerable commit `ab1bbebc097d674c1cdd4bd75a8f313be18473bf^` and fixed commit `ab1bbebc097d674c1cdd4bd75a8f313be18473bf`.
   - Creates isolated source trees for each commit and installs the real package in a Python virtual environment.
   - Starts the real server via `python -m awslabs.aws_api_mcp_server.server` over MCP stdio.
   - Writes a real `~/.aws/aws-api-mcp/mcp-security-policy.json` with `denyList: ["aws sts get-caller-identity"]`.
   - Forces read-operations index initialization failure with an invalid proxy for `servicereference.us-east-1.amazonaws.com` while preserving localhost access.
   - Sends an MCP `call_aws` tool request for `aws sts get-caller-identity --endpoint-url http://127.0.0.1:<fake-sts-port>`.
   - Runs two vulnerable attempts and two fixed negative-control attempts.
3. Expected evidence:
   - Vulnerable attempts show `mcp_initialized=true`, `startup_failure_observed=true`, `fake_aws_boundary_reached=true`, and `policy_denial_observed=false`.
   - Fixed attempts show startup failure, `mcp_initialized=false`, and `fake_aws_boundary_reached=false`.

## Evidence

- `bundle/logs/reproduction_steps.log` contains the full script output, resolved commit IDs, patch-presence excerpts, and per-attempt summaries.
- `bundle/logs/repro/summary.json` contains structured results. In the latest run it recorded:
  - `vulnerable_all_bypassed: true`
  - `fixed_all_fail_closed: true`
- `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`.
- `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`.
- `bundle/logs/repro/fixed_attempt_1.json` and `bundle/logs/repro/fixed_attempt_2.json` show no fake AWS calls.
- `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.
- `bundle/repro/runtime_manifest.json` records the runtime-backed entrypoint as `cli_command`, with target path reached and proof artifacts listed.

## Recommendations / Next Steps

- Upgrade `awslabs.aws-api-mcp-server` to version `1.3.47` or later, containing fix commit `ab1bbebc097d674c1cdd4bd75a8f313be18473bf`.
- 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.
- 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.
- Ensure operational monitoring alerts on `Failed to load read operations index` or equivalent startup failures.

## Additional Notes

- The reproduction script was run successfully twice consecutively after fixing an initial brittle grep preflight.
- The proof is idempotent: it creates temporary HOME directories, local fake AWS endpoints, and isolated source archives on each run.
- 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.
