May 9, 2026 · v1.3.32
1.3.32 lands six community-driven fixes across Cognito, EC2, and DynamoDB. Two of them turn the Cognito Hosted UI from "works with manual config" into "Amplify auto-discovers it." One adds VPN Connection CRUD. Three are AWS-spec parity bugs that real users hit on production-shaped tooling.
Before 1.3.32, hitting /<poolId>/.well-known/openid-configuration returned endpoints pointing at https://cognito-idp.<region>.amazonaws.com/<poolId>/oauth2/... — URLs that exist on neither real AWS (which serves OAuth2 on the user pool's hosted-UI domain) nor MiniStack (which serves them on the gateway). The result: Amplify, oidc-client-ts, and any other library that auto-configures from discovery couldn't reach the token, authorize, or userInfo endpoints.
Discovery now returns URLs at the MiniStack gateway, where the Cognito Hosted UI endpoints actually live, so OIDC clients auto-wire without manual endpoint config:
{
"issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxx",
"jwks_uri": "http://localhost:4566/us-east-1_xxxx/.well-known/jwks.json",
"authorization_endpoint": "http://localhost:4566/oauth2/authorize",
"token_endpoint": "http://localhost:4566/oauth2/token",
"userinfo_endpoint": "http://localhost:4566/oauth2/userInfo",
"end_session_endpoint": "http://localhost:4566/logout",
"response_types_supported": ["code", "token"],
...
}
The issuer claim still matches the JWT iss claim (real AWS URL) so existing token validation keeps working unchanged. response_types_supported now advertises both code and token, matching real AWS Cognito. Reported by @coezbek.
Even with reachable URLs, browser-based OIDC clients fetching them cross-origin failed with "No 'Access-Control-Allow-Origin' header is present on the requested resource." The Cognito dispatchers in app.py were returning raw response tuples that bypassed _with_data_plane_headers — the helper every other data-plane response uses to set the wildcard CORS header. OPTIONS preflight already worked; the actual response was missing the header.
Fixed at the dispatcher call sites in _handle_pre_body_request and _handle_post_body_shortcuts — both are now wrapped through _with_data_plane_headers so /oauth2/authorize, /oauth2/token, /oauth2/userInfo, /logout, and /.well-known/* return Access-Control-Allow-Origin: * like every other data-plane response. Combined with the discovery URL fix, OIDC discovery and the auth code flow now work end-to-end against MiniStack from any browser app. Contributed by @coezbek.
Five new actions: CreateVpnConnection, DescribeVpnConnections, DeleteVpnConnection, CreateVpnConnectionRoute, DeleteVpnConnectionRoute. Stores Type, CustomerGatewayId, VpnGatewayId, TransitGatewayId, Options.StaticRoutesOnly, and per-connection Routes. Multi-tenant via AccountScopedDict; persists across restarts; cleared on /_ministack/reset.
aws --endpoint-url http://localhost:4566 ec2 create-vpn-connection \ --type ipsec.1 \ --customer-gateway-id cgw-... \ --vpn-gateway-id vgw-... \ --options StaticRoutesOnly=true
The same PR also fixed DescribeRouteTables, which was returning an empty <propagatingVgwSet/> even when EnableVgwRoutePropagation had stored a gateway ID — so any IaC tool that round-trips through Describe lost the propagation. Now serializes the actual stored entries. Contributed by @tmq107.
RunInstances honors PrivateIpAddress and IamInstanceProfileTwo existing parameters were silently dropped:
--private-ip-address was ignored; the auto-generated default IP was malformed (the prefix string "10.0" was missing a trailing dot, so addresses came out as 10.0193.216 — invalid IPv4). The DNS name was derived from the malformed IP and was equally broken.--iam-instance-profile was dropped entirely. The launched instance had no IamInstanceProfile field in the RunInstances response or in DescribeInstances.Both parameters are now stored on the instance record. IamInstanceProfile is surfaced in the XML response with both arn and id, matching the real AWS shape. The default-IP generator now produces well-formed addresses. Reported by @coseym.
When multiple items in a GSI shared the same (GSI_HASH, GSI_RANGE) value — common with single-table designs that use a constant or empty sort key to model "all items of type X for owner Y" — paginating with ExclusiveStartKey would either drop items silently from page 2 onward, or cycle the caller through the same items indefinitely. ElectroDB collections, hand-rolled schemas with sparse GSIs, and hash-only GSIs all hit this.
Real DynamoDB orders GSI results by (INDEX_HASH, INDEX_SORT, BASE_PK, BASE_SK) and uses the base-table primary key as a hidden tiebreaker on the cursor. MiniStack now mirrors that ordering: GSI candidates are sorted by the same tuple, and _apply_exclusive_start_key matches the cursor against all four positions instead of just the index sort key:
def _apply_exclusive_start_key(candidates, esk, pk_name, sk_name, scan_forward, table):
keys = _index_order_keys(table, sk_name) # (INDEX_SORT, BASE_PK, BASE_SK)
cursor = tuple(_index_order_value(esk, n, t) for n, t in keys)
return [it for it in candidates
if (cmp := tuple(_index_order_value(it, n, t) for n, t in keys))
and (cmp > cursor if scan_forward else cmp < cursor)]
Cursors now advance correctly across pages whether the GSI has a range key or not. Reported by @bensont1 and @mspiller.
docker pull ministackorg/ministack:1.3.32 docker run -d -p 4566:4566 ministackorg/ministack:1.3.32
Or pin in compose.yaml:
services:
ministack:
image: ministackorg/ministack:1.3.32
ports:
- "4566:4566"
Shipped by the MiniStack community. Contributions credited throughout. GitHub · r/ministack