August 16, 2026 · v1.4.18
v1.4.18 scopes the API Gateway Lambda authorizer cache to the method and route ARN (a cached Allow no longer leaks across resources or stages), adds an IoT topic-rule engine with WHERE evaluation and non-Lambda actions plus fleet indexing, and hardens Lambda reliability. On top of that: S3 conditional reads on GetObject / HeadObject, Aurora DSQL expression indexes, ALB response streaming, and SES v2 email templates.
SearchIndex (POST /indices/search) and the indexing-configuration operations (UpdateIndexingConfiguration / GetIndexingConfiguration / DescribeIndex / ListIndices) are implemented. The AWS_Things index is OFF by default; once enabled, SearchIndex answers AND-separated thingName / thingTypeName / thingGroupNames / attributes.* / shadow.desired|reported.* terms (with * / ? wildcards and maxResults / nextToken paging) queried directly against the live registry and classic shadows. Contributed by @iot-rocket.RegisterCertificateWithoutCA and the deprecated principal-policy operations. RegisterCertificateWithoutCA shares the RegisterCertificate store, and AttachPrincipalPolicy / DetachPrincipalPolicy / ListPrincipalPolicies / ListPolicyPrincipals map onto the modern policy-target store. Contributed by @iot-rocket.SendEmail with Content.Template. A templated send previously returned 200 and a MessageId while silently dropping the template; Content.Template is now rendered, and CreateEmailTemplate / GetEmailTemplate / UpdateEmailTemplate / DeleteEmailTemplate / ListEmailTemplates are served over the shared v1 store, so a template created with aws ses create-template is sendable through aws sesv2 send-email. Contributed by @mikolajk0wal.AWSAuthenticationPlugin and calls RDS-specific stored procedures now works against supported MySQL images; the compatibility plugin, Aurora/RDS procedures, and predefined S3 roles install automatically (disable with MINISTACK_MYSQL_IAM_AUTH=off). Contributed by @Areson.Allow for one method or route also authorized another, and a verdict cached on one stage was served for another. They now cache the authorizer's output (the policy document, or the isAuthorized boolean for a simple response) and re-evaluate it against each request's own method/route ARN, with the stage in the key. Malformed authorizer output answers a proper error and is never cached. Contributed by @iot-rocket.AWS) Lambda integrations return the handler's raw output. AWS and AWS_PROXY shared one response path, so a custom-integration handler's plain document was misread as a {statusCode, headers, body} envelope. A non-proxy integration now serializes the return value as the body with the integration response's status (200 by default), passes a standard Lambda error through at 200 (as AWS does with no selectionPattern), and answers 504 for an uninvokable or throttled backend. Contributed by @iot-rocket.WHERE evaluation, SQL functions, and non-Lambda actions. Rules ignored the WHERE clause (every matching-topic publish dispatched), and Lambda was the only wired-up action. The rules engine now evaluates WHERE with AWS's three-valued logic (=/<>/BETWEEN/IN/LIKE/IS NULL/regexp_matches, AND/OR/NOT, arithmetic), implements the clientid / encode / replace / timestamp / topic and other SQL functions, and dispatches republish, dynamoDBv2, and sns actions with the rule's errorAction on failure. Contributed by @iot-rocket.INIT or a timed-out handler is now reported once instead of retried up to the function timeout (a mis-importing function used to pin the emulator for minutes); RecursiveLoop is enforced (a self-invoking chain is dropped past ~16 hops with RecursiveInvocationException unless set to Allow); and a Docker-executor handler's nested SDK calls resolve on native Linux engines by mapping host.docker.internal to host-gateway. Contributed by @iot-rocket.GetObject and HeadObject answer the conditional-read headers. If-Match, If-None-Match, If-Modified-Since, and If-Unmodified-Since were honored on writes but ignored on reads, so every conditional read returned 200 and the whole body. Reads now answer 412 PreconditionFailed or 304 Not Modified with AWS's precedence (an entity tag decides, its date counterpart applies only in its absence), evaluated before Range slicing. Reported by @gaul.CREATE INDEX ASYNC (a volatile function is rejected 42P17), refuses a plain CREATE INDEX (unsupported mode), and aligns USING / CONCURRENTLY / WHERE, the 8-key-column limit, and a primary-key column drop with a live cluster's messages. Contributed by @ry-allan.Content-Encoding: gzip and Content-Encoding: aws-chunked separately lost the caller's gzip. Repeated field lines now combine into one comma-joined value per RFC 9110. Contributed by @gaul.UNSUBSCRIBE removes only the topic filters it names. The MQTT-over-WebSocket bridge 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. Contributed by @iot-rocket.sign_leaf_certificate omitted it, so any client on Python 3.13 (which enables VERIFY_X509_STRICT) rejected what MiniStack minted — CreateKeysAndCertificate certificates included. Contributed by @iot-rocket.x-amzn-errortype. Without the header, restJson1 clients surfaced a bare HTTP status instead of the modelled exception (boto3 reported An error occurred (404) rather than NotFoundException); it is now set on every SES v2 error. Contributed by @mikolajk0wal.docker pull ministackorg/ministack:1.4.18 docker run -d -p 4566:4566 ministackorg/ministack:1.4.18
Or pin in compose.yaml:
services:
ministack:
image: ministackorg/ministack:1.4.18
ports:
- "4566:4566"
Issues and PRs welcome on GitHub. Discussion on r/ministack.