Use this document before deploying the gateway and before merging security-sensitive changes. It consolidates the read-only safety contract into verifiable checklists without replacing detailed tool contracts, deployment steps, or contributor workflow rules.
Related documentation:
- README.md — project overview and security model summary
- AGENTS.md — contributor workflow and test integrity rules
- docs/mcp-tools.md — per-tool input, output, and limit contracts
- docs/chatgpt-connector-production-acceptance.md — final ChatGPT Connector production acceptance gate
- docs/aws-iam-setup.md — IAM user and credential setup
- docs/aws-tools.md — generic direct-input AWS tools (no profiles required)
- docs/application-profiles.md — optional KV-backed operational context
- docs/iam-cross-account.md — multi-account AssumeRole IAM pattern
- docs/deployment.md — deployment and verification steps
- docs/post-mvp-boundaries.md — requirements for future expansion
If you discover a security vulnerability in this project, open a private GitHub security advisory or contact the repository maintainer directly. Do not open a public issue with exploit details, credentials, or live account identifiers.
The gateway is a read-only, public-facing MCP endpoint for explicit AWS tools. The current read-only scope is:
- Each MCP tool is named, typed, and allowlisted — AWS access is mediated through project code, not a generic proxy.
- AWS credentials and MCP auth tokens live only in Cloudflare secrets, never in Git.
- Tool inputs are validated (region allowlist, date ranges, result-size limits) before any AWS call.
- Tool output is normalized; raw AWS response bodies are never returned.
- Optional KV caching stores normalized tool output only.
Read-only guarantees (current):
- The gateway remains read-only in the current scope — no write or management AWS actions are exposed as MCP tools.
- No generic AWS CLI execution tool (for example,
run_aws_cli) exists in the codebase. - No arbitrary AWS API proxy tool (for example,
call_any_aws_api) exists in the codebase. - Every AWS-backed tool maps to a narrow, reviewed action with validated inputs and normalized outputs (see docs/mcp-tools.md).
Out of scope for this document:
- Full deployment instructions — see docs/deployment.md.
- Write-operation policy — see docs/post-mvp-boundaries.md.
- Automated dependency or SAST vulnerability scanning — not required for the current read-only scope.
CI secret scanning (in scope on every PR and main push):
- Gitleaks —
.github/workflows/secret-scan.yml. - Repository safety —
pnpm run repo:safetyin.github/workflows/ci.yml.
OAuth (implemented):
- ChatGPT connector OAuth is documented in docs/auth-chatgpt-oauth.md.
- Contract: docs/specs/oauth-chatgpt-connector.md.
- ChatGPT action visibility requires authenticated
tools/listwith valid descriptors for every enabled tool; disabled or pack-gated tools are omitted fromtools/listand do not appear as ChatGPT Actions.search/fetchare catalog helpers only. - OAuth mode requires the
AUTH_RATE_LIMITERDurable Object binding so/mcprequest throttling happens before the MCP runtime or AWS-backed tools execute.
This repository is intended to be public-safe. Before pushing or opening a PR:
- No AWS access keys, secret access keys, or session tokens appear in commits, issues, or PR descriptions.
- No MCP bearer tokens, Cloudflare API tokens, or OAuth client secrets appear in commits.
-
.env,.dev.vars, and.wrangler/are not tracked (see .gitignore). - CI runs
pnpm run repo:safetyon every pull request and push tomainvia.github/workflows/ci.yml. - CI runs Gitleaks secret-pattern scanning on every pull request and push to
mainvia.github/workflows/secret-scan.yml. - CI runs
pnpm run output:guardrailon every pull request and push tomainvia.github/workflows/ci.yml. - Only
.env.exampledocuments secret names — it contains no real values. -
wrangler.jsonc[vars]contains operational configuration only (regions, app name), not credentials. - Documentation examples use placeholders, not real account IDs, ARNs, log group names, or worker URLs tied to a live deployment.
Configure secrets with wrangler secret put and non-secret vars in wrangler.jsonc. See README.md for the full list.
-
AWS_ACCESS_KEY_IDis set as a Cloudflare secret (not in Git or[vars]). -
AWS_SECRET_ACCESS_KEYis set as a Cloudflare secret (not in Git or[vars]). -
MCP_AUTH_TOKENis set as a Cloudflare secret with a strong, unique value. -
AWS_REGIONis set in[vars]and matches the primary region for regional tools. -
AWS_ALLOWED_REGIONSis set in[vars]as a non-empty comma-separated allowlist. -
AWS_REGIONis included inAWS_ALLOWED_REGIONS. -
AUTH_RATE_LIMITERDurable Object binding and migration are configured for OAuth deployments. -
RATE_LIMIT_MAX_REQUESTSandRATE_LIMIT_WINDOW_SECONDSare set (or intentionally left at documented defaults). - Optional KV binding
AWS_MCP_CACHEis configured in production if caching is desired (see docs/deployment.md). - Optional KV binding
AWS_MCP_APP_CONFIGis used only for application profiles — a separate namespace fromAWS_MCP_CACHE(see docs/application-profiles.md). - Missing or invalid required bindings return a normalized
configuration_errorresponse — not raw stack traces or binding dumps to unauthenticated callers.
The canonical read-only policy template is infra/aws/iam-readonly-policy.json. Setup walkthrough: docs/aws-iam-setup.md.
- The gateway IAM principal uses the project read-only policy — not
AdministratorAccessor broad AWS-managed policies. - IAM policy actions are limited to read-only APIs required by current MCP tools (Cost Explorer, EC2 describe, CloudWatch, CloudWatch Logs).
- No write, create, delete, modify, or
*actions are granted for the current read-only deployment. - IAM credentials used by the gateway are dedicated to this service — not shared personal admin keys.
- Access keys are rotatable without changing application code (update Cloudflare secrets only).
- With valid runtime configuration,
/mcprejects missing or invalid authentication with HTTP 401 before creating an MCP server instance. - Unauthorized failures return the normalized error contract (
code: unauthorized). -
/healthresponds without authentication and returns only{ ok, service }— no credentials, tokens, region config, cache state, or AWS metadata. - Configuration errors on
/mcpdo not expose missing binding names to unauthenticated callers.
Tools are registered explicitly via manifest-backed definitions in src/mcp/tools/. There is no dynamic or runtime tool discovery. ChatGPT action visibility depends on authenticated tools/list returning valid descriptors for every enabled tool.
- The registry defines 38 public tools (see docs/mcp-tools.md).
- Default deployments expose 21 tools via packs
core,cost,inventory,observability, anddatabase(see docs/aws-tools.md). - Three opt-in aggregate tools (
aws_account_overview,aws_cost_overview,aws_observability_overview) require theaggregatespack inAWS_MCP_ENABLED_TOOL_PACKS. - Three opt-in security tools (
check_ssm_parameter_inventory,get_s3_bucket_posture,get_ses_configuration_status,get_sns_topic_status,get_eventbridge_rules_status) require thesecuritypack. - Nine opt-in application-ops tools require the
application-opspack and optionalAWS_MCP_APP_CONFIGKV — profiles are not required for generic tools. - Authenticated
tools/listreturns only enabled tools with validtitle,description,inputSchema,outputSchema(where applicable), read-only annotations, and OAuthsecuritySchemes. -
searchandfetchare catalog helpers — they do not replacetools/listfor ChatGPT action discovery. - New tools are added only through manifest registration, policy/capability/cost-control metadata, and documented contracts in docs/mcp-tools.md.
- No tool accepts arbitrary AWS service names, actions, or CLI commands as input.
-
get_gateway_statusmakes no AWS calls and reportsmode: "read-only". -
searchandfetchdo not call AWS directly (exceptfetchmay embed liveget_gateway_statusJSON for that catalog entry).
Limits are enforced in src/security/ before downstream AWS calls. Constants live in src/security/limits.ts.
- Regional tools reject regions not present in
AWS_ALLOWED_REGIONS. - An empty
AWS_ALLOWED_REGIONSlist causes validation failure at startup or tool invocation. - Cost Explorer date ranges are bounded (maximum 90 days —
COST_MAX_DATE_RANGE_DAYS). - Cost-by-service result rows are capped (maximum 25 —
COST_MAX_SERVICE_ROWS). - CloudWatch Logs lookback is bounded (maximum 24 hours —
LOGS_MAX_HOURS). - CloudWatch Logs event count is bounded (maximum 50 —
LOGS_MAX_EVENTS). - CloudWatch Logs message length is truncated (maximum 1,000 characters —
LOGS_MAX_MESSAGE_LENGTH). - Invalid inputs fail with
validation_errorbefore any AWS API call.
Manifest-backed tools declare explicit costControl metadata in src/mcp/tools/manifest.ts. The central policy gate in src/mcp/tools/policy.ts evaluates cost-control constraints before handler execution and fails closed on missing or invalid metadata.
- Every AWS-backed tool manifest declares
costControlwith a non-freeclass,requiresCache: true, andminCacheTtlSecondsfor paid, volume-sensitive, or fanout-sensitive tools. - Non-AWS tools (
search,fetch,get_gateway_status) declarecostControl.class: "free"andrequiresCache: false. - Cost-control policy denials return generic
validation_errormessages without exposing internal numeric limits. - Cost-control denials happen before handler execution and before AWS calls.
- Paid cost tools declare
maxDateRangeDays(90 days) and cache TTL metadata (1800 seconds). - Fanout-sensitive tools declare
maxRegionsbounded byAWS_ALLOWED_REGIONS. - Volume-sensitive log tools declare
maxLookbackHours(24) andmaxResultCount(50). - New AWS-backed tools must declare appropriate
costControlmetadata before merge (see docs/tooling-conventions.md).
Recommended deployment defaults:
- Configure
AWS_MCP_CACHEin production for expensive repeated reads (cost tools: 30-minute TTL; EC2, CloudWatch, and Logs: 5-minute TTL). - Configure OAuth rate limiting via
AUTH_RATE_LIMITERwithRATE_LIMIT_MAX_REQUESTSandRATE_LIMIT_WINDOW_SECONDSbefore exposing/mcppublicly.
See also docs/specs/secure-tool-platform.md for the cost-control model.
Every public tool must have exactly one manifest factory in src/mcp/tools/definitions/*.ts, registered via src/mcp/tools/registry.ts. Shared manifest types and conversion live in src/mcp/tools/manifest.ts.
- Each manifest declares
name,title,description,pack,lifecycle,visibility,auth,aws,safety,costControl,audit,descriptorKind, andhandler. - AWS-backed manifests declare
aws.services,aws.actions,aws.capabilities,aws.regionMode, andaws.readonly: true. - Non-AWS tools (
search,fetch,get_gateway_status) declare empty AWS metadata andcostControl.class: "free". - Tools with structured output declare
outputSchema. - Non-discovery tools declare ChatGPT
catalogmetadata (keywords,docsAnchor,inputSummary). - Contract tests pass:
src/mcp/tools/manifest-contract.test.ts.
The central policy gate in src/mcp/tools/policy.ts evaluates evaluateToolPolicy() before handler execution.
- Disabled tools, disabled packs, and non-read-only risk levels are denied before handler or AWS work.
- AWS-backed tools with missing or non-allowlisted service/action metadata fail closed.
- Cost-control manifest and request limits are enforced before handler execution.
- Region allowlist enforcement remains in effect for regional tools.
- Policy denials return normalized
validation_errorMCP responses — not raw stack traces or AWS payloads. - Policy denials emit sanitized audit metadata and do not call AWS.
- Contract tests pass:
src/mcp/tools/policy.test.ts,src/mcp/tools/cost-control-policy.test.ts.
AWS capability metadata links tools to IAM actions and read-only posture.
- Every AWS-backed manifest declares
aws.capabilitiesaligned withsrc/aws/capabilities.ts. -
docs/aws-capability-matrix.mdis updated when tools or capabilities change. - IAM policy template
infra/aws/iam-readonly-policy.jsonexactly matches declared capabilities — no undocumented actions. - Aggregate overview tools compose existing APIs — they do not require new IAM actions beyond the read-only policy.
- Contract tests pass:
src/mcp/tools/capability-contract.test.ts,src/mcp/tools/capability-matrix.test.ts,src/aws/iam-readonly-policy.test.ts.
Tool exposure is configured via environment variables (see README.md).
-
AWS_MCP_ENABLED_TOOL_PACKScontrols which packs are exposed (default:core,cost,inventory,observability,database). -
AWS_MCP_DISABLED_TOOLSand optionalAWS_MCP_ENABLED_TOOLSfurther restrict exposure. - Disabled or pack-gated tools are omitted from
tools/listand denied on direct invocation. -
AWS_MCP_MAX_RISK_LEVELisread-only(only supported value today). - Contract tests pass:
src/mcp/tools/exposure.test.ts,src/mcp/tools/list-integration.test.ts.
Caching is optional via the AWS_MCP_CACHE KV binding. See README.md.
- Cache keys are SHA-256 hashes of tool name and normalized input parameters (
src/cache/keys.ts) — not raw credentials or auth headers. - Cached values contain normalized tool output only — never raw AWS response bodies.
- Cache keys and values do not include
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY, orMCP_AUTH_TOKEN. - Cache TTLs match documented values: 30 minutes for cost tools, 5 minutes for EC2, CloudWatch, and Logs tools.
- KV read/write failures degrade gracefully (fall through to AWS) without exposing secrets in logs.
-
AWS_MCP_APP_CONFIGis never used for tool response caching or credential storage — only profile documents and index metadata.
Application profiles are optional operational context. See docs/application-profiles.md.
- Profiles store resource names, regions, display metadata, and optional
auth.strategyrole ARNs — not credentials or secret values. - Profiles are not authorization — OAuth scopes, tool packs, IAM, and region allowlists remain the security boundary.
- Missing or invalid
AWS_MCP_APP_CONFIGdoes not break/mcpor generic AWS tools. - Profile JSON and KV documents must never contain
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,DATABASE_URL,JWT_SECRET, passwords, bearer tokens, connection strings, or SSM parameter values. - Assumed-role temporary credentials are never written to KV — only held in Worker memory via the credential resolver.
- Profile management scripts (
pnpm run app-profile:*) output metadata only and never print secret-looking values.
SSM inventory tools verify parameter existence and metadata only — never parameter values.
-
check_ssm_parameter_inventorycallsssm:DescribeParametersonly — notGetParameterorGetParameters. -
get_application_secret_inventoryuses configured parameter names from profiles — metadata-only, same boundary. - Tool output does not include SSM parameter values, SecureString payloads, or decrypted content.
- IAM policies for SSM inventory do not grant
ssm:GetParameter*unless a future explicit tool requires it with a separate security review.
CloudWatch Logs tools return bounded, truncated event messages that may still contain sensitive application data.
-
get_cloudwatch_logsandget_recent_log_errorsenforce lookback, event count, and message length limits (src/security/limits.ts). - Log message bodies are truncated — they are not guaranteed free of secrets, tokens, or PII.
- Operators treat log tool output and related audit summaries as potentially sensitive in ChatGPT sessions.
- Audit and application logs do not echo full raw log message bodies when avoidable.
- Production deployments recommend
OAUTH_REQUIRED_SCOPES=aws:read(see wrangler.example.jsonc). -
/mcpvalidates tokens againstOAUTH_REQUIRED_SCOPESbefore tool execution. - Per-tool
auth.requiredScopesin manifests are enforced by the policy gate (src/mcp/tools/policy.ts) — future domain scopes (for exampleaws:cost) can be required per tool without listing every domain scope globally inOAUTH_REQUIRED_SCOPES. - ChatGPT connector setup uses scope
aws:readunless a deployment explicitly documents additional required scopes.
Execution metadata on AWS-backed tool results (structuredContent.execution) is defined in docs/specs/tool-execution-metadata.md.
- Metadata includes only sanitized cache status, AWS service/action names, optional region names, request counts, and gateway-side cost estimates.
- Metadata never includes secrets, bearer or OAuth tokens, cache keys, raw AWS response bodies, request payloads, billing account identifiers, or stack traces.
-
billing.estimatedCostUsdand visible billing notes are labeled as estimates — not final AWS invoice totals. - Execution metadata is not a durable audit ledger; structured audit events remain in
src/observability/audit.ts. - Cloudflare Worker, KV, Durable Object, and bandwidth costs are not included in AWS billing estimates.
- Contract tests pass:
src/mcp/tools/execution-contract.test.ts,src/mcp/tools/manifest-contract.test.ts.
Audit events are emitted from src/observability/audit.ts via safeEmitAuditEvent in src/mcp/audit/log.ts and tool handlers via safeMcpHandler.
- Successful and failed tool calls emit structured JSON audit events with tool name, outcome, and duration.
- Audit
inputfields use sanitized summaries (for example, region counts, date-range flags) — not full raw tool arguments when sensitive. - Audit events do not include bearer tokens, AWS credentials, signed headers, or raw AWS response payloads.
- Audit logging failures are swallowed and do not change public MCP tool behavior.
- Application logs use
src/observability/logging.tsand do not printMCP_AUTH_TOKEN, AWS secret keys, or raw provider error bodies. - Production source does not call
console.*outsidesrc/observability/.
Public errors use the normalized contract in src/errors/public-error.ts.
- HTTP errors return
{ error: { code, message, retryable } }— never raw stack traces. - MCP tool errors return
mcpErrorResultwithstructuredContent.error.codeandretryable— not internal exception details. - Unexpected exceptions are mapped to
internal_errorwith a generic message. - AWS client modules normalize provider failures before they reach MCP handlers.
- Tool responses expose documented normalized fields in
structuredContent— not unmodified AWS JSON.
Before merging any PR:
-
pnpm run typecheckpasses locally or in CI. -
pnpm testpasses — unit tests do not call live AWS or unmocked external services. -
pnpm run test:integritypasses — no committed.onlymarkers or unjustified skipped tests. -
pnpm run output:guardrailpasses — production source uses centralized observability sinks only. - GitHub Actions CI workflow (
.github/workflows/ci.yml) passes on the PR. - GitHub Actions secret-scan workflow (
.github/workflows/secret-scan.yml) passes on the PR. -
pnpm run repo:safetypasses — no secrets, maintainer defaults, or forbidden paths in tracked files. - New production dependencies are justified and reviewed — the project avoids unnecessary packages.
- Security, validation, redaction, authentication, region allowlist, and read-only contract tests were not weakened to make unrelated changes pass.
Complete this immediately before pnpm deploy or promoting a Worker version:
- All sections above relevant to this release are checked.
- Secrets are configured in the target Cloudflare environment (
wrangler secret put). -
AUTH_MODEis set appropriately:oauthfor ChatGPT production,local-bearerfor local/single-token deployments. - In
oauthmode: OAuth vars are set andMCP_AUTH_TOKENis not required;GET /.well-known/oauth-protected-resourcereturns expected metadata. - In
local-bearermode:MCP_AUTH_TOKENsecret is configured. -
AWS_ALLOWED_REGIONSreflects only regions this deployment should serve. - IAM policy attached to the gateway principal matches
infra/aws/iam-readonly-policy.json(or a narrower custom variant). - Full pre-deploy validation passes on the commit being deployed (
pnpm run repo:safety,pnpm run output:guardrail,pnpm run verify:connector-contract,pnpm run typecheck,pnpm test,pnpm run test:integrity— see docs/deployment.md). -
GET /healthreturns{ "ok": true, "service": "aws-mcp-gateway" }without authentication. - With valid runtime configuration,
POST /mcpwithout authentication returns HTTP 401 (withWWW-Authenticateinoauthmode). - Authenticated MCP access works (local bearer token or ChatGPT OAuth flow — see docs/mcp-testing.md).
- For ChatGPT production connectors, complete the production acceptance checklist in docs/chatgpt-connector-production-acceptance.md — including authenticated
tools/listvalidation for enabled tools only, OAuth login, Actions visible,get_gateway_status, optionalsearch/fetch, and a bounded AWS tool. Default deployments expect 21 tools; enablingaggregatesadds three; enablingsecurityadds five; enablingapplication-opsadds nine. Detailed step-by-step responses: docs/chatgpt-connector-smoke-test.md. - A smoke test confirms at least one AWS-backed tool returns normalized output in an allowed region.
The following post-MVP capabilities require a separate issue, spec, and security review before implementation:
- Write or management AWS operations (start/stop instances, modify alarms, etc.).
- Broader read-only inventory tools (RDS, Lambda, budgets) — allowed only as explicit new tools with IAM and contract updates.
OAuth for ChatGPT is implemented — see docs/auth-chatgpt-oauth.md. Local bearer mode remains available for local development.
The following patterns remain permanently forbidden and must not be routed through the post-MVP process:
- Generic AWS CLI execution.
- Arbitrary AWS API proxying.
See docs/post-mvp-boundaries.md for acceptance expectations before any post-MVP work begins.
When reviewing a PR, ask: Does this change preserve read-only, allowlisted, normalized-access behavior? If not, stop and route the work through the post-MVP process.