You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SES v2 — email template CRUD and SendEmail with Content.Template — the v2 SendEmail handler read only Content.Simple and Content.Raw, so a templated send was accepted with a 200 and a MessageId while the template was silently dropped, delivering an empty subject and body. Content.Template is now rendered through the same {{placeholder}} substitution v1 uses, from a stored template or from TemplateContent supplied inline, and a named template that doesn't exist fails the send with NotFoundException instead of delivering an empty email. CreateEmailTemplate, GetEmailTemplate, UpdateEmailTemplate, DeleteEmailTemplate, and ListEmailTemplates are served at /v2/email/templates[/{TemplateName}] over the shared v1 store, so a template created with aws ses create-template is sendable through aws sesv2 send-email. ListEmailTemplates paginates: PageSize accepts the documented 1–100 and defaults to 10, with an opaque NextToken returned only while further templates remain. Contributed by @mikolajk0wal.
IoT Core — fleet indexing — SearchIndex (POST /indices/search) and the indexing-configuration operations (UpdateIndexingConfiguration / GetIndexingConfiguration / DescribeIndex / ListIndices) were unimplemented. The AWS_Things index is OFF by default; enabling it with UpdateIndexingConfiguration (as Terraform's aws_iot_indexing_configuration does) lets SearchIndex answer AND-separated thingName / thingTypeName / thingGroupNames / attributes.* / shadow.desired|reported.* terms — */? wildcards, numeric shadow compare, maxResults/nextToken paging with the documented ceiling of 100 — queried directly against the live registry and classic shadows. Searching while thingIndexingMode is OFF returns ResourceNotFoundException, and REGISTRY vs REGISTRY_AND_SHADOW gates the shadow terms. Contributed by @iot-rocket.
IoT Core — RegisterCertificateWithoutCA and the deprecated principal-policy operations — RegisterCertificateWithoutCA (POST /certificate/register-no-ca) shares the RegisterCertificate store, returning ResourceAlreadyExistsException with resourceId/resourceArn on a duplicate PEM. The deprecated AttachPrincipalPolicy / DetachPrincipalPolicy / ListPrincipalPolicies / ListPolicyPrincipals map onto the modern policy-target store, with the principal in the x-amzn-iot-principal header and the policy name in x-amzn-iot-policy. Contributed by @iot-rocket.
RDS — Aurora MySQL compatibility objects — Aurora MySQL provisioning that creates users with AWSAuthenticationPlugin and calls RDS-specific stored procedures failed against upstream MySQL images. MiniStack now builds ABI-matched MySQL 8.0/8.4 AWSAuthenticationPlugin artifacts (amd64/arm64), installs a reject-all compatibility plugin without changing password authentication, and adds the Aurora/RDS procedures, configuration surface, and predefined S3 roles those workflows expect. The path is automatic for supported MySQL images and can be disabled with MINISTACK_MYSQL_IAM_AUTH=off; the plugin deliberately rejects authentication, so the existing non-enforcement stance is unchanged. Contributed by @Areson.
Fixed
Request headers — a field repeated across lines is no longer reduced to its last line — the ASGI header dict was built with plain assignment, so when a client sent the same field twice the earlier line was discarded. The AWS SDK for Java v2 uploads exactly that way, emitting Content-Encoding: gzip and Content-Encoding: aws-chunked as separate lines: MiniStack saw only aws-chunked, stripped it as the chunk-framing marker it is, and stored the object with no content encoding at all — silently losing gzip for every Java SDK v2 caller, S3Proxy's aws-s3 backend among them. Repeated field lines now combine into one comma-joined value as RFC 9110 5.2 requires (Cookie rejoins with "; " per RFC 9113 8.2.3), so aws-chunked is stripped from the joined list and the caller's encoding survives. Sending the header once, in either order, was already correct and is unchanged. Contributed by @gaul.
SES v2 — error responses carry x-amzn-errortype — restJson1 resolves the error shape from that header, so without it SDKs surfaced a bare HTTP status instead of the modelled exception: boto3 reported An error occurred (404) rather than NotFoundException, and typed handling never matched on any SESv2 operation. The 1.3.24 sweep that added the header centrally in error_response_json reached ses but not ses_v2, which builds its error bodies itself; it is now set on all of them. Contributed by @mikolajk0wal.
API Gateway — non-proxy (AWS) Lambda integrations return the handler's raw output — AWS and AWS_PROXY shared one response path, so a custom-integration handler returning a plain document had it read as a {statusCode, headers, body} proxy envelope: a statusCode key in the data became the HTTP status and the rest was dropped. A non-proxy integration now serializes the return value as the response body with the integration response's status (200 by default); a standard Lambda error is passed through as the body at the default 200 status (as AWS does when no selectionPattern is configured), and an uninvokable or throttled backend is a 504 integration failure. AWS_PROXY is unchanged. Contributed by @iot-rocket.
IoT — topic-rule WHERE evaluation, SQL functions, and non-Lambda actions — rules ignored the WHERE clause, so every publish matching the topic filter dispatched, and Lambda was the only action wired up. The rules engine now evaluates WHERE with AWS's three-valued (Undefined) logic — =/<>/</>/BETWEEN/IN/LIKE/IS NULL/regexp_matches, AND/OR/NOT, and arithmetic — implements the clientid / encode / isundefined / newuuid / regexp_matches / replace / timestamp / topic functions (an unimplemented function resolves to Undefined and warns once), and dispatches the republish, dynamoDBv2, and sns actions, running the rule's errorAction on a delivery failure. Contributed by @iot-rocket.
API Gateway (REST / v1) — Lambda authorizer cache is scoped to the method ARN and stage — the cache stored the allow/deny decision keyed on the identity source alone, so a cached Allow for GET /alpha also authorized GET /beta, and a verdict cached on one stage was served for another. It now caches the authorizer's policy document and re-evaluates it against each request's own method ARN, with the stage and API in the key; malformed authorizer output (missing policyDocument or principalId) answers 500 and is never cached. Contributed by @iot-rocket.
API Gateway (HTTP API / v2) — REQUEST-authorizer cache is scoped to the route ARN — the v2 cache stored the verdict rather than the authorizer's response, so a cached Allow leaked across routes. It now caches the output (the policy document for an IAM-policy response, the isAuthorized boolean for a simple response) and re-evaluates the policy against each request's own route ARN, keyed on the identity source values and stage; caching requires at least one identity source and the cache is bounded. Contributed by @iot-rocket.
Lambda — a Docker-executor handler's callbacks resolve on native Linux engines — AWS_ENDPOINT_URL inside a Lambda container is rewritten to host.docker.internal, which only resolves on Docker Desktop, so on a native Linux engine every nested SDK call a handler made died on DNS. Lambda containers now map host.docker.internal to host-gateway (as the ECS and EKS paths already do); an explicit --add-host still wins. Contributed by @iot-rocket.
IoT — MQTT UNSUBSCRIBE removes only the topic filters it names — the MQTT-over-WebSocket bridge never read the filter list in the packet and unsubscribed every subscription the session held, so a client unsubscribing from one topic stopped receiving all the others. It now removes only the named filters (matched character-by-character per MQTT 3.1.1 §3.10.4, so a wildcard matches its own text), popping the id from every per-session map so a preserved cleanSession=0 session cannot resurrect a removed filter, and a truncated payload unsubscribes what parsed rather than tearing the session down. Contributed by @iot-rocket.
TLS — minted leaf certificates carry an Authority Key Identifier — sign_leaf_certificate omitted it, and RFC 5280 requires it on every certificate that is not self-issued. Python 3.13 enables ssl.VERIFY_X509_STRICT in ssl.create_default_context(), so any client on a current Python rejected what MiniStack minted with "Missing Authority Key Identifier" — CreateKeysAndCertificate certificates included. Contributed by @iot-rocket.
Lambda (Docker executor) — a failed init or a timed-out handler is reported once, not retried — the RIE invoke loop's single connection-retry arm swallowed two terminal cases that both look like OSError: a failed INIT (RIE answers 502, raised as HTTPError) and a read timeout (bare TimeoutError). A function whose module fails to import re-ran INIT ~10×/second up to its Timeout — a 900-second function pinned the emulator ~15 minutes — and the caller got a generic error instead of the real Runtime.ImportModuleError. Both are now terminal: an init failure returns 200 with X-Amz-Function-Error: Unhandled and the runtime's own error payload, a timeout answers Runtime.ExitError and recycles the container, and only genuine connection failures still retry within a bounded cold-start window. Contributed by @iot-rocket.
Lambda — RecursiveLoop is enforced — PutFunctionRecursionConfig stored the setting and nothing read it, so a self-invoking function looped unchecked (an async self-invoke returns 202, releases its worker, and never throttles). A per-request lineage depth now travels with direct Lambda → Lambda invocations; past ~16 hops the invocation is dropped with RecursiveInvocationException and a RecursiveInvocationsDropped metric, unless the function's RecursiveLoop is Allow. Loops that leave through SQS, SNS or S3 and back are not yet detected. Contributed by @iot-rocket.
Aurora DSQL — index and DDL error parity, including expression index keys — the wire proxy now allows expression index keys on CREATE INDEX ASYNC (a volatile function is rejected 42P17: functions in index expression must be marked IMMUTABLE, an expression in an INCLUDE column 0A000) and aligns the surrounding errors with a live cluster: plain CREATE INDEX is always refused (unsupported mode. please use CREATE INDEX ASYNC.), USING / CONCURRENTLY / WHERE return DSQL's own messages, 9+ key columns 54011, a nameless IF NOT EXISTS42601, and a primary-key column drop reports cannot drop primary key column <name>. Contributed by @ry-allan.
ALB — target responses are streamed instead of buffered — the data plane read a target's entire response before returning, so a streaming or chunked target reached the client only once it had finished producing (measured time-to-first-byte for a 700 KB stream: 11.28s → 0.004s). It now relays the body as the target produces it, keeping the target's framing (Content-Length or chunked) and bounding an established connection by an idle timeout (default 60s) distinct from the 10s connect deadline; a target that dies mid-body surfaces as a truncated response rather than a clean one. Contributed by @uttom-akash.
S3 — GetObject and HeadObject answer the conditional-read headers — If-Match, If-None-Match, If-Modified-Since and If-Unmodified-Since were honoured on PutObject, CopyObject and DeleteObject but ignored on the reads, so every conditional read returned 200 and the whole body: a cache revalidating an unchanged object refetched it in full, and a read guarded against a concurrent overwrite never noticed one. Reads now answer 412 PreconditionFailed or 304 Not Modified, with the precedence RFC 9110 13.2.2 and the AWS GetObject reference define — an entity tag decides, and its date counterpart applies only in its absence — and a 412 preempts a Range rather than slicing a representation the caller rejected. The 304 keeps the validators (ETag, Last-Modified) and drops the headers describing a payload it cannot carry, x-amz-checksum-* included, which boto3 asks for by default and would otherwise try to validate against the empty body. Reported by @gaul.