October 23, 2026 · v1.5.32
v1.5.32 adds the EC2 Verified Access control plane, Step Functions Distributed Map runs with child executions, SSM service settings, and SigV4a signature checks. CloudFront now serves origins that name a load balancer or a Lambda Function URL in the same MiniStack, and ALB targets and Function URL events get the forwarded headers AWS sets. Under AUTH=true, requests without credentials are anonymous in every service, and S3 enforces access point policies, ACLs on BucketOwnerEnforced buckets, Block Public Access on new buckets and bucket CORS rules. It also enforces ElastiCache transit encryption and AUTH tokens, keeps ECS capacity provider strategies, and brings DynamoDB's PartiQL and validation order in line with DynamoDB.
oidc, and jamf, crowdstrike and jumpcloud devices), groups, load-balancer and network-interface endpoints, trust provider attachments, instance logging configuration, and group and endpoint policies, as the aws_verifiedaccess_* Terraform resources call them, with AWS's codes, messages and check order. Under AUTH=true each call is authorized on every resource AWS checks it against. No access evaluation or Cedar parsing; rds and cidr endpoints and NativeApplicationOidcOptions answer UnsupportedOperation. Contributed by @kharabasz.ProcessorConfig mode is DISTRIBUTED ran inline, with no map run and its ItemReader ignored; it now creates a Map Run (ListMapRuns, DescribeMapRun, UpdateMapRun, ListExecutions with mapRunArn) whose items run as child executions under <state machine>/<label>, reads its items from S3 (s3:getObject as JSON, JSONL or CSV, s3:listObjectsV2 object metadata, MaxItems), and fails with States.ExceedToleratedFailureThreshold past its tolerated failures. Reported by @cupidchan.GetServiceSetting, UpdateServiceSetting and ResetServiceSetting, per account and region, persisted. SettingValue is validated for the ids whose accepted values AWS documents, and /ssm/parameter-store/default-parameter-tier sets the Tier of a PutParameter that names none. Unblocks Terraform's aws_ssm_service_setting. Contributed by @skialpine.AUTH=true. requests signed with AWS4-ECDSA-P256-SHA256, which SDKs use for Multi-Region Access Points, were refused for every IAM user. Header and presigned S3 signatures are now checked with the P-256 key SigV4a derives from the secret, with their date or expiry, and an X-Amz-Region-Set that leaves out the serving region is refused (RegionSetMismatch on S3, the unknown-key error elsewhere). Contributed by @iot-rocket.AUTH=true a request with no Authorization header and no query signature ran as the account root everywhere but S3; it is now refused in the service's own error, as on AWS: MissingAuthenticationToken 403 from Query and REST-XML services, MissingAuthenticationTokenException from JSON services, AccessDenied 403 from SQS, UnrecognizedClientException from Secrets Manager. Operations the SDKs send unsigned (Cognito sign-in, GetId, AssumeRoleWithWebIdentity), SNS ConfirmSubscription and Unsubscribe, API Gateway and Function URL invokes and S3 keep their behaviour. Contributed by @iot-rocket.s3 is refused by other services. under AUTH=true a SigV4 presigned query with an s3 credential scope skipped the signature check when another service served it; it is now refused with SignatureDoesNotMatch "Credential should be scoped to correct service: '<service>'. ", as AWS answers, while S3 keeps verifying its own presigned URLs. Reported by @iot-rocket.AUTH=true. an access point policy was stored but never applied, and unsigned requests, SigV2 Authorization headers and PutBucketPolicy/DeleteBucketPolicy through an access point were served; a request through an access point alias or ARN is now allowed on the access point by an identity policy or its policy, an unsigned or SigV2-header one gets 400 InvalidRequest, bucket policy writes get 403 AccessDenied, and SigV2 presigned URLs for an access point ARN verify. MRAP requests are authorized on the member bucket and then on the MRAP. Contributed by @iot-rocket.BucketOwnerEnforced bucket ACL writes answered 200 and a stored grant still opened the bucket; PutBucketAcl and PutObjectAcl now answer 400 AccessControlListNotSupported, uploads refuse any ACL but an owner-only one, and CreateBucket or a switch to BucketOwnerEnforced refuses a granting ACL with InvalidBucketAclWithObjectOwnership. Under AUTH=true a new bucket has all four Block Public Access settings on, as on AWS, so a public policy or ACL answers 403 AccessDenied and CreateBucket with a public ACL InvalidBucketAclWithBlockPublicAccessError; RestrictPublicBuckets drops a bucket policy only when it is public. Contributed by @iot-rocket.AUTH=true. every preflight answered 200 with Access-Control-Allow-Origin: *, whatever the bucket's CORS configuration; a preflight now has to match one of the bucket's rules (403 AccessForbidden without a configuration or a match, 400 BadRequest without Origin) and gets that rule's headers. Contributed by @iot-rocket.AUTH=true a presigned URL without X-Amz-Expires never expired; it now answers 400 AuthorizationQueryParametersError "Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.", and a negative value "X-Amz-Expires must be non-negative". CompleteMultipartUpload checks x-amz-mp-object-size: a size other than the parts' total answers 400 InvalidRequest "The provided 'x-amz-mp-object-size' header value 11 does not match what was computed: 10", a non-numeric one "Value for x-amz-mp-object-size header is invalid: 'abc'". Reported by @adzcodemi.DNSName or a Function URL's <url-id>.lambda-url.<region>.on.aws answered 502, as for any AWS hostname. A load balancer is now reached on its HTTP listener at the origin's HTTPPort and a Function URL over HTTPS, with the origin's custom headers, so a target that checks a secret header works; OriginReadTimeout applies (504). A Function URL refuses a forwarded viewer Host with 403 {"Message":null} and an unsigned AWS_IAM URL answers 403 {"Message":"Forbidden"}, as on AWS. Reported by @kinoh and @smiley-uriux.?flag reached the origin as ?flag=; it is now rebuilt only when the returned querystring differs. CreateFunction keeps its Tags (deleted with the function), answers invalid base64 code with MalformedInput "Invalid Base64 encoding", and refuses a missing code, configuration, runtime or name and invalid tags in one MalformedXML before the name lookup, then InvalidTagging for repeated, aws: or more than 50 keys. Contributed by @jeffreyheller and @kharabasz.X-Forwarded-* and X-Amzn-Trace-Id as the load balancer sets them. targets got an incoming X-Forwarded-For unchanged and a client-sent X-Forwarded-Proto/X-Forwarded-Port, and Lambda targets none of them; all targets now get X-Forwarded-For with the client address appended (kept or dropped under routing.http.xff_header_processing.mode preserve/remove), X-Forwarded-Proto and X-Forwarded-Port from the listener, and an X-Amzn-Trace-Id with a new Root, or a Self added to or updated in the one sent. Reported by @kinoh.host, x-forwarded-for (the first address sent, or the caller), x-forwarded-port 443, x-forwarded-proto https, x-amzn-trace-id and the TLS fields; requestContext.accountId is anonymous for an AuthType NONE URL; and responses carry x-amzn-RequestId and X-Amzn-Trace-Id, with x-amzn-ErrorType AccessDeniedException on a 403. Reported by @smiley-uriux.Publish: true on create or code update overwrote $LATEST's Version, so $LATEST disappeared from ListVersionsByFunction; it now stays $LATEST. A layer MiniStack doesn't hold from another account, such as Datadog's, resolves as an empty layer instead of AccessDeniedException. Function containers no longer publish port 8080 on every host address: none with DOCKER_NETWORK, 127.0.0.1 otherwise. Contributed by @skialpine and @kharabasz. Reported by @cupidchan.AWS_IAM headers. an HTTP API's responses carried x-amzn-requestid and x-amz-request-id; they now carry apigw-requestid, 16 base64 characters, as AWS does. Under AUTH=true a REST AWS_IAM method served a bearer token or a lower-case algorithm; it now answers 403 IncompleteSignatureException with API Gateway's message. Contributed by @jeffreyheller and @iot-rocket.DeleteConnection answered 204 but left the socket open; it now closes it, GetConnection and PostToConnection answer 410 GoneException, and $disconnect runs after the close. A route whose Lambda is a container image or another runtime now runs it, PostToConnection sends a text frame, and routes and stages keep RouteResponseSelectionExpression, ModelSelectionExpression, AccessLogSettings, ClientCertificateId and DeploymentId. Contributed by @toby6578. Reported by @cupidchan.AUTH=true checks the Event API's authorization. an HTTP publish, a WebSocket connection or a subscription was accepted with any API key or none unless APPSYNC_EVENTS_ENFORCE_AUTH was set; it now needs what the API or channel namespace accepts (an API key, a SigV4 signature, a Cognito or OIDC token), and a refused publish answers 401 UnauthorizedException "You are not authorized to make this call.". Contributed by @iot-rocket.kid ministack-key-1); each pool now has its own access and ID token keys at its JWKS URI. Managed login refuses PKCE plain, an unregistered redirect_uri and an unknown grant_type (400 unsupported_grant_type); a federated user joins <user pool id>_<provider name> and carries identities; an OIDC provider with only oidc_issuer takes its endpoints from the issuer's /.well-known/openid-configuration. Contributed by @kinoh. Reported by @cupidchan.client_credentials. /oauth2/token issued a token to a client without client_credentials in AllowedOAuthFlows; it now answers invalid_grant (invalid_scope first for an unknown scope), and the pre token generation trigger runs for this grant only when LambdaVersion is V3_0. Contributed by @iot-rocket.AddThingToThingGroup and RemoveThingFromThingGroup ignored thingGroupArn and thingArn and were authorized on the request path; they now act on the group and thing the ARNs name, refuse a mismatched or foreign ARN with 400 InvalidRequestException, and under AUTH=true are checked on the group and then the thing. Contributed by @iot-rocket.DatapointsToAlarm. PutMetricAlarm and PutCompositeAlarm dropped Tags on create; they are now stored over CBOR, JSON 1.0 and Query and ignored on update, as on AWS, with AWS's ValidationError, MalformedInput and aws:-key refusals. An alarm without DatapointsToAlarm no longer reports it as EvaluationPeriods. Contributed by @kharabasz.Statement wasn't well formed, can't be processed: Unexpected term, Missing right-hand side expression of infix operator, Aliasing is not supported, positions as at <line>:<column>:<length>); it skips comments, reads every number literal form, and supports ORDER BY, attribute_exists, attribute_not_exists, size, attribute_type, IS, set_add, set_delete, FROM ... WHERE ... SET and EXISTS(SELECT ...) in ExecuteTransaction. Reported by @hicksy (paritysuite.org).Number overflow) and duplicate set members reported in byte order; legacy Expected conditions are validated; UpdateItem's 400KB check charges the request clause by clause before the condition; SET l[n] past the end appends instead of padding with NULL. Reported by @hicksy (paritysuite.org).TransitEncryptionEnabled and an AuthToken served plaintext without a password; it now serves TLS only, signed by the CA at /_ministack/elasticache/ca.pem, requires the token, and ModifyReplicationGroup applies ROTATE, SET and DELETE. CreateCacheSubnetGroup now keeps its subnets, VPC and tags, and answers InvalidSubnet, CacheSubnetGroupAlreadyExists and CacheSubnetGroupInUse. Reported by @aditya-drizz.capacityProviderStrategy dropped it and reported launchType EC2; the strategy is now kept without a launchType, the cluster's default applies when neither is given, both together are refused with InvalidParameterException "Specifying both a launch type and capacity provider strategy is not supported. Remove one and try again.", and AWS::ECS::Service passes it through. Reported by @aditya-drizz.MaxAllocatedStorage only with storage autoscaling. every DB instance reported MaxAllocatedStorage equal to its allocated storage; it is now present only when storage autoscaling was turned on. Reported by @aditya-drizz./_ministack/reset no longer removes another MiniStack's containers. the OpenSearch, OpenSearch Serverless, DSQL, ECS and EKS resets removed every container with the service's label on the Docker daemon; they now remove only this instance's. Contributed by @jeffreyheller.docker pull ministackorg/ministack:1.5.32 docker run -d -p 4566:4566 ministackorg/ministack:1.5.32
Or pin in compose.yaml:
services:
ministack:
image: ministackorg/ministack:1.5.32
ports:
- "4566:4566"
Issues and PRs welcome on GitHub. Discussion on r/ministack.