Test Results

Total
102
Passed
102
Failed
0
Skipped
0
Run details
Started
9/30/2026, 7:35:51 PM
Duration
4.03 s
Version
0.4.0
Branch
main
Commit
f94610b0

API key authorizer

Featuretest/api-key-authorizer.test.ts

7 passed0 failed

Clients call the MCP endpoint with an x-api-key header. The Lambda authorizer hashes the key, looks it up in API_KEYS, and returns a short-lived scoped identity. Anything unexpected fails closed.

missing, unknown, short, whitespace and multiple keys fail closed

Status: Passed
  • api-key
  • auth
  1. Givena key registered for "team-bot"
  2. Whenmalformed or unknown credentials are presented
    Rejected credentials
    Case
    missing
    empty
    too short
    unknown 64 chars
    leading space
    two keys
  3. Thennone of them are authorized

valid keys become scoped clients without exposing the key or its hash

Status: Passed
  • api-key
  • auth
  1. Givena key registered for client "team-bot" with scope "echo"
  2. Whenthe key is authorized
  3. Thenthe identity carries the client, scopes and a 60 second expiry
    Identity
    {
      "sub": "team-bot",
      "scopes": "echo",
      "exp": 1800000060
    }
  4. Butneither the key nor its hash leak into the identity

each key grants only its own client and scopes

Status: Passed
  • api-key
  • auth
  1. Giventwo keys: "team-bot" with "echo" and "reader" with "read"
  2. Whenthe reader key is authorized
  3. Thenthe identity is "reader" with only "read"

expired keys are refused and identity lifetime does not exceed key expiry

Status: Passed
  • api-key
  • auth
  1. Givena key that has already expired
  2. Thenit is refused
  3. Anda key expiring in 10 seconds
  4. Andthe identity expires with the key, not after 60 seconds

configuration rejects empty keys, invalid hashes, duplicate hashes and ambiguous scopes

Status: Passed
  • api-key
  • auth
  1. WhenAPI_KEYS is empty, has a bad hash, a duplicate hash, or a scope with a space
  2. Thenthe schema rejects it
  3. Anduppercase hashes are normalised to lowercase

Lambda reads x-api-key, refuses missing keys, and honors removal on the next request

Status: Passed
  • api-key
  • auth
  1. GivenAPI_KEYS contains the "team-bot" key
  2. Whena request sends the key in x-api-key
  3. Thenit is authorized as "team-bot"
  4. Anda request sends no key
  5. Andit is refused
  6. Andthe key is removed from API_KEYS
  7. Andthe next request with it is refused

Lambda fails closed on malformed configuration

Status: Passed
  • api-key
  • auth
  1. GivenAPI_KEYS is not valid JSON
  2. Whena request sends a key
  3. Thenit is refused

Bundling

Featuretest/bundling.test.ts

7 passed0 failed

The construct and its runtime handlers must bundle without Autotel or OpenTelemetry, so consumers who do not opt in pay nothing for tracing. Instrumentation lives in a separate entry point.

src/index.ts bundles without Autotel or OpenTelemetry

Status: Passed
  • bundling
  1. Giventhe entry point src/index.ts
  2. Whenit is bundled with esbuild
  3. Thenno Autotel or OpenTelemetry module is among its inputs
  4. Andthe output does not require autotel

src/runtime/api-key-authorizer.ts bundles without Autotel or OpenTelemetry

Status: Passed
  • bundling
  1. Giventhe entry point src/runtime/api-key-authorizer.ts
  2. Whenit is bundled with esbuild
  3. Thenno Autotel or OpenTelemetry module is among its inputs
  4. Andthe output does not require autotel

src/runtime/google-authorizer.ts bundles without Autotel or OpenTelemetry

Status: Passed
  • bundling
  1. Giventhe entry point src/runtime/google-authorizer.ts
  2. Whenit is bundled with esbuild
  3. Thenno Autotel or OpenTelemetry module is among its inputs
  4. Andthe output does not require autotel

example/mcp.ts bundles without Autotel or OpenTelemetry

Status: Passed
  • bundling
  1. Giventhe entry point example/mcp.ts
  2. Whenit is bundled with esbuild
  3. Thenno Autotel or OpenTelemetry module is among its inputs
  4. Andthe output does not require autotel

the separate Autotel entry point bundles its Lambda and MCP instrumentation

Status: Passed
  • bundling
  1. Giventhe example/mcp-autotel.ts entry point
  2. Whenit is bundled with esbuild
  3. Thenautotel-aws is bundled for the Lambda
  4. Andautotel-mcp-instrumentation is bundled for the MCP server

src/runtime/identify.ts bundles without aws-cdk-lib, so handlers can import it

Status: Passed
  • bundling
  1. Giventhe runtime entry point consumers import as aws-cdk-mcp/runtime
  2. Whenit is bundled with esbuild
  3. Thenno aws-cdk-lib or constructs module is among its inputs

package.json exports aws-cdk-mcp/runtime

Status: Passed
  • bundling
  1. Giventhe published package.json
  2. Then"./runtime" points at the compiled identify module

cdk-nag

Featuretest/cdk-nag.test.ts

6 passed0 failed

A consumer running AwsSolutions or Serverless checks inherits no findings from this construct, in any auth mode. What is deliberate is acknowledged inside the construct, with the reason.

auth 'iam' synthesizes clean under AwsSolutions and Serverless checks

Status: Passed
  • cdk-nag
  • compliance
  1. Givena StatelessMcpServer with auth 'iam'
  2. WhenAwsSolutions and Serverless checks are added
  3. Thensynth raises no findings

auth 'none' synthesizes clean under AwsSolutions and Serverless checks

Status: Passed
  • cdk-nag
  • compliance
  1. Givena StatelessMcpServer with auth 'none'
  2. WhenAwsSolutions and Serverless checks are added
  3. Thensynth raises no findings

auth 'apiKey' synthesizes clean under AwsSolutions and Serverless checks

Status: Passed
  • cdk-nag
  • compliance
  1. Givena StatelessMcpServer with auth 'apiKey'
  2. WhenAwsSolutions and Serverless checks are added
  3. Thensynth raises no findings

auth 'jwt' synthesizes clean under AwsSolutions and Serverless checks

Status: Passed
  • cdk-nag
  • compliance
  1. Givena StatelessMcpServer with auth 'jwt'
  2. WhenAwsSolutions and Serverless checks are added
  3. Thensynth raises no findings

auth 'lambda' synthesizes clean under AwsSolutions and Serverless checks

Status: Passed
  • cdk-nag
  • compliance
  1. Givena StatelessMcpServer with auth 'lambda'
  2. WhenAwsSolutions and Serverless checks are added
  3. Thensynth raises no findings

auth 'googleWorkspace' synthesizes clean under AwsSolutions and Serverless checks

Status: Passed
  • cdk-nag
  • compliance
  1. Givena StatelessMcpServer with auth 'googleWorkspace'
  2. WhenAwsSolutions and Serverless checks are added
  3. Thensynth raises no findings

Example MCP Lambda

Featuretest/example-handler.test.ts

27 passed0 failed

The example Lambda answers stateless 2026-07-28 MCP requests exactly as API Gateway delivers them. Identity comes from whichever authorizer ran (Lambda, JWT or IAM), scopes gate each tool, and anything it cannot trust is refused.

a traced request past the SDK's byte limit still gets its 413

Status: Passed
  • example
  • handler
  • tracing
  1. Givena traced tools/call of about 4.5 MB on the wire
  2. Whenthe traced handler receives it
  3. Thenit answers 413

one trace, one chain: caller → Lambda invocation → tool

Status: Passed
  • example
  • handler
  • tracing
  1. Givena caller whose request carries a W3C traceparent
    traceparent
    00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
  2. Whenthe traced handler runs the whoami tool
  3. Thenthe Lambda invocation joins the caller's trace as its child
  4. Andthe tool span is a child of the invocation, not a sibling

API key clients use sub as identity and receive only their granted scopes

Status: Passed
  • api-key
  • example
  • handler
  1. Givenan API key client "team-bot" granted "echo"
  2. Whenit calls whoami
  3. Thenit is identified by sub with its scopes
  4. Andit can call echo
  5. Buta "reader" client granted only "read" cannot call echo

answers a first-message tools/call with no handshake and no session

Status: Passed
  • example
  • handler
  1. Givenbob, authorized with "read echo", and no prior initialize
  2. Whenthe very first message is a tools/call for whoami
  3. Thenit answers 200 with bob and his scopes

Lambda authorizer

Status: Passed
  • example
  • handler
  • scopes
  1. Givenalice has only "read" and bob has "echo", via the Lambda authorizer
  2. Whenboth call echo
  3. Thenalice gets a tool error
  4. Andbob gets his echo

JWT authorizer: a read-only token cannot call echo

Status: Passed
  • example
  • handler
  • scopes
  1. Givenone JWT with "read" and another with "echo"
  2. Whenboth call echo
  3. Thenthe read-only token gets a tool error
  4. Andthe echo token gets its echo

no identity is refused, not treated as anonymous

Status: Passed
  • example
  • handler
  1. Givena request with no authorizer context
  2. Whenit calls whoami
  3. Thenit is refused with 401

an identity whose token expired is refused, even if the gateway cached its approval

Status: Passed
  • example
  • handler
  1. Givena Lambda identity and a JWT identity that both expired a second ago
  2. Wheneach calls whoami
  3. Thenboth are refused with 401

a browser Origin that is not allowlisted gets a 403

Status: Passed
  • example
  • handler
  • origin
  1. Givenno ALLOWED_ORIGINS configured
  2. Whena request arrives with Origin https://untrusted.example
  3. Thenit is refused with 403

discovery does not advertise list-changed notifications it cannot deliver

Status: Passed
  • example
  • handler
  1. Whena client calls server/discover
  2. Thenit answers 200
  3. Buttools.listChanged is not advertised

subscriptions/listen is refused in-band, never opened as a stream

Status: Passed
  • example
  • handler
  1. Whena client calls subscriptions/listen for toolsListChanged
  2. Thenno event stream is opened
  3. Andthe response carries a JSON-RPC error

an allowlisted browser Origin gets through

Status: Passed
  • example
  • handler
  • origin
  1. GivenALLOWED_ORIGINS is https://trusted.example
  2. Whena request arrives with that Origin
  3. Thenit answers 200

a refused identity gets a bearer challenge pointing at the metadata

Status: Passed
  • example
  • handler
  1. Givenno RESOURCE_METADATA_URL
  2. Whenan unauthenticated request is refused
  3. AndRESOURCE_METADATA_URL is set
  4. Andthe same request is refused again
  5. Thenthe first challenge is a bare invalid_token
  6. Andthe second points at the protected-resource metadata
    WWW-Authenticate
    Bearer error="invalid_token", resource_metadata="https://abc.example/.well-known/oauth-protected-resource/mcp"

a trusted role acting for a listed user gets that user, and that user's scopes

Status: Passed
  • example
  • handler
  • iam
  1. GivenSlackBot is a trusted caller role, bob has "echo" and alice has "read"
  2. WhenSlackBot calls echo on behalf of bob, then of alice
  3. Thenbob gets his echo
  4. Butalice gets a tool error

a role not in TRUSTED_CALLER_ROLES is refused, however it got past the gateway

Status: Passed
  • example
  • handler
  • iam
  1. Givenonly SlackBot is trusted
  2. WhenSomeAdminRole calls on behalf of bob
  3. Thenit is refused with 403
  4. Andno OAuth bearer challenge is sent to a SigV4 caller

a delegation header outside the signature is refused: it could have been swapped

Status: Passed
  • example
  • handler
  • iam
  1. GivenSlackBot is trusted
  2. Whenit sends mcp-on-behalf-of without signing that header
  3. Thenit is refused with 403

a trusted role must say who it acts for, and for someone in USERS

Status: Passed
  • example
  • handler
  • iam
  1. GivenSlackBot is trusted and carol is not in USERS
  2. WhenSlackBot calls with no mcp-on-behalf-of
  3. Thenit is refused with 403
  4. AndSlackBot calls on behalf of carol
  5. Andit is refused with 403

a trusted role cannot act for "*", the Google everyone entry

Status: Passed
  • example
  • handler
  • iam
  1. GivenSlackBot is trusted and USERS has a "*" entry
  2. WhenSlackBot calls on behalf of "*"
  3. Thenit is refused with 403

reads SignedHeaders from the Authorization header

Status: Passed
  • example
  • handler
  • sigv4
  1. Givena SigV4 Authorization header with mixed-case SignedHeaders
  2. Thenthe signed header names are returned lower-cased

reads X-Amz-SignedHeaders from a presigned URL

Status: Passed
  • example
  • handler
  • sigv4
  1. Givena presigned URL with X-Amz-SignedHeaders
  2. Thenthe signed header names come from the query string

no signature visible means nothing is signed

Status: Passed
  • example
  • handler
  • sigv4
  1. Givenno SigV4 signature, or a Bearer Authorization header
  2. Thenno headers are treated as signed

reduces a session and a pathed role to the same role ARN

Status: Passed
  • example
  • handler
  • iam
  1. Givenan assumed-role session ARN and a pathed IAM role ARN for SlackBot
  2. Thenboth reduce to the SlackBot role ARN
    Role ARN
    arn:aws:iam::123456789012:role/SlackBot

another account is another role, and a user is not a role

Status: Passed
  • example
  • handler
  • iam
  1. GivenSlackBot in another account
  2. Thenit is not the trusted SlackBot role
  3. Andan IAM user named SlackBot
  4. Andit has no role ARN

the exact origin passes, and so does no Origin at all (a server-side client)

Status: Passed
  • example
  • handler
  • origin
  1. GivenALLOWED_ORIGINS is https://trusted.example and http://localhost:5173/
  2. Thenexact matches and a missing Origin are allowed
    Allowed
    Origin
    https://trusted.example
    http://localhost:5173
    (none)

another scheme, port or host on the same name is refused

Status: Passed
  • example
  • handler
  • origin
  1. GivenALLOWED_ORIGINS is https://trusted.example and http://localhost:5173/
  2. Thennear misses are refused
    Refused
    OriginDiffers by
    http://trusted.examplescheme
    https://trusted.example:8443port
    https://evil.trusted.examplehost
    nullopaque origin

a bare hostname in the config throws instead of admitting every scheme and port

Status: Passed
  • example
  • handler
  • origin
  1. WhenALLOWED_ORIGINS holds a bare hostname or a non-http(s) scheme
  2. Thenparsing throws

bearerChallenge omits resource_metadata when there is none to point at

Status: Passed
  • example
  • handler
  1. Givenno resource metadata URL
  2. Thenthe challenge is a bare invalid_token

Google authorizer

Featuretest/google-authorizer.test.ts

16 passed0 failed

Clients send a Google access token as a bearer token. The Lambda authorizer asks Google who it belongs to, then admits only verified Workspace users on the allowlist, with the scopes the allowlist gives them. Anything unexpected fails closed.

an allowlisted Workspace user gets their scopes

Status: Passed
  • auth
  • google
  1. GivenAlice is on the allowlist with "read"
  2. WhenGoogle vouches for her token from our Web client
  3. Thenshe is admitted with her lower-cased email and scopes

an expired token is refused, and one without an expiry too

Status: Passed
  • auth
  • google
  1. Givena token that has just expired
  2. Thenit is refused
  3. Anda token with no expiry
  4. Andit is refused

a token from any of our clients is accepted: Claude's Web client or Claude Code's Desktop one

Status: Passed
  • auth
  • google
  1. Givena token minted for Claude Code's Desktop client
  2. ThenAlice is admitted

a token minted for another app is refused

Status: Passed
  • auth
  • google
  1. Givena token whose audience is someone else
  2. Thenit is refused

a matching email outside the Workspace (no hd) is refused

Status: Passed
  • auth
  • google
  1. Givenan allowlisted email with no hosted domain claim
  2. Thenit is refused

an unverified email is refused

Status: Passed
  • auth
  • google
  1. Givenan allowlisted email Google has not verified
  2. Thenit is refused

a Workspace user who is not on the list is refused

Status: Passed
  • auth
  • google
  1. GivenCarol, a verified Workspace user not on the allowlist
  2. Thenshe is refused

"*" admits everyone in the Workspace, and a named entry wins over it

Status: Passed
  • auth
  • google
  1. Givenan allowlist with "*" for everyone and more for Bob
  2. ThenCarol, not named, gets the "*" scopes
  3. AndBob gets his own
  4. Anda named entry with no scopes stays at no scopes
  5. Andsomeone outside the Workspace is still refused

users can be keyed by Google sub, so an email rename keeps access

Status: Passed
  • auth
  • google
  1. Givenan allowlist keyed by Alice's sub, not her email
  2. Whenshe signs in after her email changed
  3. Thenshe keeps her scopes

asks Google about the bearer token and decides on the answer

Status: Passed
  • auth
  • google
  1. GivenGoogle recognises the token as Alice
  2. Whena request arrives with it as a bearer token
  3. ThenAlice is admitted

no bearer token, no call to Google

Status: Passed
  • auth
  • google
  1. Givena Google that must not be called
  2. Whena request has Basic auth, or no authorization at all
  3. Thenit is refused without asking Google

a socket Google closed while Lambda was frozen is retried, not a refusal

Status: Passed
  • auth
  • google
  1. Giveneach Google endpoint's first request fails on a stale keep-alive socket
  2. WhenAlice presents her token
  3. Thenthe retry succeeds and she is admitted

an expired or revoked token (Google 400) is refused

Status: Passed
  • auth
  • google
  1. GivenGoogle answers 400 for the token
  2. Whena request presents it
  3. Thenit is refused

a response Google would never send is a refusal, not a crash

Status: Passed
  • auth
  • google
  1. GivenGoogle answers with malformed fields
    Response
    {
      "aud": 42,
      "email_verified": "yes"
    }
  2. Thenthe request is refused without throwing

reads what the construct deploys

Status: Passed
  • auth
  • google
  1. Giventhe environment the construct sets on the Lambda
    Environment
    {
      "GOOGLE_CLIENT_IDS": "web, desktop",
      "HOSTED_DOMAIN": "example.com",
      "USERS": "{\"alice@example.com\":[\"read\"]}"
    }
  2. Thenit parses into client ids, hosted domain and users

a malformed allowlist fails at cold start instead of letting anyone through

Status: Passed
  • auth
  • google
  1. WhenUSERS maps an email to a string instead of a list
  2. Thenparsing throws
  3. AndGOOGLE_CLIENT_IDS has no real ids
  4. Andparsing throws

identify

Featuretest/identify.test.ts

4 passed0 failed

The handler's half of auth: turn what the gateway's authorizer attached to the request into the MCP SDK's AuthInfo, or refuse. Expiry is checked again here because API Gateway caches a Lambda authorizer's approval, which can outlive the token.

a Lambda authorizer identity becomes AuthInfo, email preferred over sub

Status: Passed
  • auth
  • runtime
  1. Givena Google identity with two scopes
  2. ThenclientId is the email and scopes are split
  3. Andan API key identity (no email) is known by its sub

API Gateway passes Lambda context values as strings; exp is parsed

Status: Passed
  • auth
  • runtime
  1. Givenexp arrives as a string
  2. Thenit is still honored

a JWT identity uses gateway-parsed scopes, else the scope claim

Status: Passed
  • auth
  • runtime
  1. Givena JWT with authorizationScopes on the route
  2. Anda JWT without them falls back to the scope claim

refuses expired, anonymous, missing and IAM-only callers

Status: Passed
  • auth
  • runtime
  1. Thenan identity at or past exp is refused, however recently the gateway approved it
  2. Andone without exp is refused
  3. Andone without an id is refused
  4. Andno authorizer at all is refused
  5. AndIAM callers are the handler’s own decision: refused here

Protected-resource metadata

Featuretest/protected-resource.test.ts

2 passed0 failed

OAuth clients discover the full public resource URL, including API mapping prefixes.

advertises the configured public URL even when the request uses the execute-api host

Status: Passed
  • oauth
  • runtime
  1. Givenan endpoint mapped at /docs/mcp
  2. Whenthe metadata Lambda receives a request through the gateway host
  3. Thenclients receive the public resource and the configured authorization server

keeps request-domain discovery for deployments without publicUrl

Status: Passed
  • oauth
  • runtime
  1. Giventhe existing configuration with no explicit public URL
  2. Thenthe resource still follows the request domain and MCP route

StatelessMcpServer construct

Featuretest/stateless-mcp-server.test.ts

33 passed0 failed

A stateless MCP server is one HTTP API in front of one Lambda: no sessions, no load balancer, no cache, no table. The auth prop picks who may call /mcp: nobody checked, API keys, a JWT issuer, IAM roles, your own authorizer, or Google Workspace users.

has no session machinery: one API, one stage, no load balancer, no cache, no table

Status: Passed
  • cdk
  • construct
  1. Givena server with auth "none"
  2. Thenthe stack has one HTTP API and no load balancer, cache or tableNo assertion

names the API after the stack, so a prefixed stack gives a prefixed API

Status: Passed
  • cdk
  • construct
  1. Givena server in stack "Test" with id "Mcp"
  2. Thenthe API is named "Test-Mcp"No assertion

routes POST, GET and DELETE /mcp to the handler, throttled and access-logged

Status: Passed
  • cdk
  • construct
  1. Givena server with auth "none"
  2. ThenPOST, GET and DELETE /mcp are routed without authorizationNo assertion
  3. Andthe default stage is throttled at 50 rps (burst 100) and access-loggedNo assertion

access logs name the caller whichever authorizer ran

Status: Passed
  • cdk
  • construct
  1. Givena server with auth "none"
  2. Thenthe log format records Lambda, JWT and IAM caller fieldsNo assertion

access logs can go to a log group the consumer owns, retained and longer-lived

Status: Passed
  • cdk
  • construct
  1. Givena log group kept for a year and retained when the stack is deleted
  2. Whenthe server is given it as accessLogs
  3. Thenthe stage logs there and the construct creates no access log group of its ownNo assertion

alarms on gateway 5xx, with no action until the consumer adds one

Status: Passed
  • cdk
  • construct
  1. Givena server with auth "none"
  2. Thenan alarm fires on 5 or more 5xx responses in 5 minutes
  3. Andit has no alarm actions

exposes the metadata URL only when it publishes metadata

Status: Passed
  • cdk
  • construct
  1. Givena server with auth "none"
  2. ThenresourceMetadataUrl is undefined
  3. Anda server with Google Workspace auth
  4. AndresourceMetadataUrl points at /.well-known/oauth-protected-resource/mcp

auth 'none' publishes no protected-resource metadata

Status: Passed
  • cdk
  • construct
  1. Givena server with auth "none"
  2. Thenno route serves oauth-protected-resource metadata

publishes mapped-resource discovery on the domain root API, without its default auth

Status: Passed
  • cdk
  • construct
  1. Givenan MCP endpoint at /docs/mcp and a root API with default JWT auth
  2. Thenthe endpoint and metadata URLs use the full public path
  3. Andthe domain maps discovery at the root and the MCP stage under docs
  4. Andboth discovery routes are public on the root API
  5. AndMCP calls still use the authenticated route on the MCP API
  6. Andmetadata advertises the public resource instead of the gateway path

uses a root-mapped public domain without requiring a separate metadata API

Status: Passed
  • cdk
  • construct
  1. Givena public URL whose path is the MCP route
  2. Thenmetadata stays on the MCP API and advertises the public domain

rejects an OAuth mapping prefix without a root metadata API

Status: Passed
  • cdk
  • construct
  1. Givenan OAuth endpoint under /docs, with no root API for discovery
  2. Thenconstruction fails instead of deploying unreachable discovery

rejects public resource URLs carrying credentials, queries or fragments

Status: Passed
  • cdk
  • construct
  1. Givena URL that cannot be an HTTPS OAuth resource identifier
  2. Thenconstruction refuses the URL without repeating its secrets
  3. Andconstruction refuses the URL without repeating its secrets
  4. Andconstruction refuses the URL without repeating its secrets
  5. Andconstruction refuses the URL without repeating its secrets

rejects a public URL without an MCP path or with a trailing slash

Status: Passed
  • cdk
  • construct
  1. GivenpublicUrl https://docs.example.com
  2. Thenconstruction names the path as the problem
  3. AndpublicUrl https://docs.example.com/mcp/
  4. Andconstruction names the path as the problem

rejects metadataApi when the auth mode publishes no discovery metadata

Status: Passed
  • cdk
  • construct
  1. GivenIAM auth with a root metadata API
  2. Thenconstruction fails instead of ignoring the API

apiKey protects every route using x-api-key, with no cache or OAuth metadata

Status: Passed
  • cdk
  • construct
  1. Givena server with one API key for "team-bot"
  2. Thena REQUEST authorizer reads x-api-key with caching off
  3. Andevery /mcp route uses it
  4. Butno OAuth metadata is published
  5. Andthe authorizer receives the key hashes in API_KEYS

apiKey rejects missing or invalid hashes at synth

Status: Passed
  • cdk
  • construct
  1. WhenapiKey auth is given no keys
  2. Thensynth fails
  3. AndapiKey auth is given a raw key instead of a sha256 hash
  4. Andsynth fails

jwt: API Gateway validates issuer and audience

Status: Passed
  • cdk
  • construct
  1. Givenjwt auth with an issuer, audience "mcp" and required scope "mcp:tools"
  2. Thena JWT authorizer checks the issuer and audienceNo assertion
  3. AndPOST /mcp requires the "mcp:tools" scopeNo assertion

jwt refuses an empty requiredScopes rather than accepting ID tokens

Status: Passed
  • cdk
  • construct
  1. Whenjwt auth is given an empty requiredScopes
  2. Thensynth fails naming requiredScopes

signs every /mcp route with AWS_IAM, and publishes no OAuth metadata

Status: Passed
  • cdk
  • construct
  1. Givena server with IAM auth
  2. Thenevery /mcp route requires AWS_IAM
  3. Butno OAuth metadata is published

grantInvoke gives the role execute-api:Invoke on the /mcp routes

Status: Passed
  • cdk
  • construct
  1. Givena server with IAM auth
  2. WhengrantInvoke is called with a bot role
  3. Thenthe role gets execute-api:Invoke on POST /mcpNo assertion

grantInvoke refuses a server whose callers are people, not roles

Status: Passed
  • cdk
  • construct
  1. Givena server with Google Workspace auth
  2. WhengrantInvoke is called with a role
  3. Thenit throws

puts your authorizer in front of every /mcp route, with your cache TTL

Status: Passed
  • cdk
  • construct
  1. Givenyour own authorizer Lambda with a 30 second cache TTL
  2. Thena REQUEST authorizer caches results for 30 secondsNo assertion
  3. Andevery /mcp route uses itNo assertion

publishes protected-resource metadata only when told where clients sign in

Status: Passed
  • cdk
  • construct
  1. Givenone server without an authorizationServer and one with it
  2. Thenthe first publishes no metadata
  3. Andthe second points clients at its authorization server

puts a cached Lambda authorizer in front of every /mcp route

Status: Passed
  • cdk
  • construct
  1. Givena server with Google Workspace auth
  2. Thena REQUEST authorizer reads Authorization and caches for 5 minutesNo assertion
  3. Andevery /mcp route uses itNo assertion

hands the authorizer the client id, domain and lower-cased users

Status: Passed
  • cdk
  • construct
  1. GivenGoogle auth with two client ids, domain example.com and a mixed-case user
    auth
    {
      "type": "googleWorkspace",
      "clientIds": [
        "client-123.apps.googleusercontent.com",
        "desktop-456.apps.googleusercontent.com"
      ],
      "hostedDomain": "example.com",
      "users": {
        "Alice@example.com": [
          "read"
        ],
        "bob@example.com": [
          "read",
          "refund"
        ]
      }
    }
  2. Thenthe authorizer environment carries them, with emails lower-casedNo assertion

serves unauthenticated protected-resource metadata pointing at Google

Status: Passed
  • cdk
  • construct
  1. Givena server with Google Workspace auth
  2. Thenboth well-known metadata routes are open without authorizationNo assertion
  3. Andthe metadata names accounts.google.com as the authorization serverNo assertion

refuses an empty clientIds, which would refuse every token

Status: Passed
  • cdk
  • construct
  1. WhenGoogle auth is given no client ids
  2. Thensynth fails naming clientIds

refuses an empty hostedDomain, which would fail every cold start

Status: Passed
  • cdk
  • construct
  1. WhenGoogle auth is given an empty hostedDomain
  2. Thensynth fails naming hostedDomain

refuses a users map too big for the Lambda environment

Status: Passed
  • cdk
  • construct
  1. Given200 users, well past the 4 KB Lambda environment limit
  2. Thensynth fails, rather than the deploy

counts the whole authorizer environment against the 4 KB limit, not just users

Status: Passed
  • cdk
  • construct
  1. Given110 users and 12 client ids: users fit alone, the environment does not
  2. Thensynth fails

caches verdicts for cacheTtl when given, so removals bite sooner

Status: Passed
  • cdk
  • construct
  1. GivenGoogle auth with cacheTtl of 30 seconds
  2. Thenthe authorizer caches for 30 secondsNo assertion

refuses an empty allowlist rather than letting the whole domain in

Status: Passed
  • cdk
  • construct
  1. WhenGoogle auth is given no users
  2. Thensynth fails asking for at least one

deploys prebuilt runtime handlers, so consumers need no esbuild or Docker

Status: Passed
  • cdk
  • construct
  1. Whena Google-auth server is synthesized
  2. Thenits authorizer is a plain Lambda running the bundled index.handlerNo assertion