github ministackorg/ministack v1.5.11

3 hours ago

What's Changed

New Contributors

[1.5.11] — 2026-09-13

Added

  • CloudFormation — ValidateTemplate reports capabilities and transforms — it answered Description and Parameters only, so a caller checking a template before a deploy could not see that it needs CAPABILITY_IAM or that it declares a transform. It now returns Capabilities, CapabilitiesReason and DeclaredTransforms, on the rule GetTemplateSummary already used. Contributed by @iot-rocket.
  • CloudFormation — a stack and a change set report their capabilitiesDescribeStacks and DescribeChangeSet left the Capabilities member out, so a client could not see what a deploy had acknowledged. A stack reports what its last CreateStack or UpdateStack acknowledged, a change set what it was created with, and executing one hands its set to the stack. Contributed by @iot-rocket.

Changed

  • Lambda — warm local custom runtimesprovided.* bootstraps reuse the subprocess worker pool instead of restarting on every invocation. Each invocation gets its request metadata through the Lambda Runtime API, concurrent calls lease separate workers, and a failed environment is discarded before reuse. Durable provided.* invocations keep the one-shot executor, since a bootstrap reads its environment once, at spawn. Contributed by @jayjanssen.

Fixed

  • S3 — the s3:TestEvent no longer reaches Lambda targetsPutBucketNotificationConfiguration fanned the test event out to every destination, but AWS sends it to SQS and SNS only and verifies a Lambda destination through its function permissions. The payload has no Records array, so every S3-triggered function raised on it, and the async path then retried it to MaximumRetryAttempts and dropped it in the function's DLQ, where an event AWS never sends looked like a lost message. Queue and topic destinations still receive it. Contributed by @ppettitau.
  • S3 — a presigned upload is no longer rejected for the checksum in its query string — current SDKs sign x-amz-checksum-crc32 into every presigned PutObject, computed over the empty body, because whoever holds the URL picks the body later. MiniStack hoisted that parameter into the request headers and verified it against the real body, so a stock getSignedUrl upload failed with 400 BadDigest. The checksum value parameters are no longer hoisted: they still take part in signature verification, so rewriting one is still 403, and a checksum sent as a real header is verified as before. Contributed by @bognari.
  • S3 — a presigned URL is verified against the credentials that signed it — the signature was recomputed with the server's own secret, so a URL signed with an IAM user's or an STS session's key verified against the wrong one, and a deactivated key, an expired session or a session token that was never issued were all accepted. The signing key is resolved to its owner and the URL verified with that key's secret, with the exact session token required for temporary credentials: InvalidAccessKeyId (403) for an unknown or inactive key, ExpiredToken or InvalidToken (400) for a bad token. Under AUTH=true an HTTP request also resolves its key to the owning account and GetSessionToken refuses temporary credentials. Contributed by @Areson.
  • Lambda — an SDK call from a function runs under its execution role — under AUTH=true every runtime received account-root credentials, so a call from a handler bypassed the role the function declares and a policy that should have denied it did nothing. Each invocation now gets temporary credentials for its Role on the warm workers, the Docker and provided runtimes and the one-shot executor, and the IAM layer resolves the assumed role's account, a table's index ARNs and projection keys, and an EventBridge PutEvents bus. Contributed by @adamkeener.
  • Lambda — a Node custom resource signals its stack in the docker executor — the response submitters the CDK bundles into its custom-resource handlers build the ResponseURL PUT from the URL's hostname and path only and hand it to https.request, so under LAMBDA_EXECUTOR=docker the callback went out over TLS to port 443, nothing answered, and the resource hung its stack until ServiceTimeout. The shim the executor injects now turns https to the gateway hosts, on the https default port or the gateway port, into plain http on the gateway port; any other host or port keeps TLS. Contributed by @iot-rocket.
  • Lambda — a durable invocation runs on the warm pool — durable Python and Node.js invocations went to the one-shot executor, because the three AWS_LAMBDA_DURABLE_* variables change per call while a pooled worker's environment is fixed at spawn. Each paid a fresh interpreter start inside the function's Timeout, whose default is 3 seconds, so on a loaded machine every durable invocation timed out. The values travel beside the payload now, and both bootstraps apply them before the handler runs and drop them when the next invocation is not durable. Contributed by @iot-rocket.
  • Lambda — two durable executions of one function no longer share a callback — the CallbackId was the operation id, which the durable SDK derives from the workflow position, so two concurrent executions registered the same id: the second overwrote the first, an answer sent for one completed the other, and the abandoned execution's own callback then answered CallbackTimeoutException. The id is unique per execution and stable across replays and restarts. Reported by @Nhollas.
  • ECS — RunTask returns before the image is pulled — the call blocked while Docker pulled the image and started the container. A task is registered and returned immediately, and walks PENDINGACTIVATING (the pull, with pullStartedAt stamped) → RUNNINGSTOPPED, with a failed start reported as TaskFailedToStart. StopTask and a reset no longer race the starter, and pendingCount and runningCount keep counting PENDING and RUNNING only. Contributed by @po-luka-miletic.
  • Transcribe — StartTranscriptionJob returns a job already IN_PROGRESS — the start response reported QUEUED with no StartTime and the job reached IN_PROGRESS only from the background worker, so a caller that reads the status off the response and then waits for the terminal event never saw an in-progress state. Jobs start IN_PROGRESS with StartTime set, and TRANSCRIBE_JOB_RUN_SECONDS covers the whole run. QUEUED, which AWS reaches only through JobExecutionSettings.AllowDeferredExecution at the concurrent job limit, is not modelled. Contributed by @ppettitau.
  • Cognito — SetIdentityPoolRoles keeps the role mappings it is given — the call stored Roles only and GetIdentityPoolRoles answered a hard-coded RoleMappings: {}, so a mapping set through the API or declared on a CloudFormation attachment was accepted and dropped. Both members are stored on the pool and served back; the call sets the whole configuration, so an omitted RoleMappings clears what was there. The map is stored as sent and takes no part in credential vending. Contributed by @iot-rocket.
  • API Gateway v2 — UpdateApi keeps the description and the API key selection expression — the call applied five members and discarded Description and ApiKeySelectionExpression, so an update lost what the create had stored. Both are applied now, and a CorsConfiguration on the call replaces the stored one; removing one is DeleteCorsConfiguration, which DELETE /v2/apis/{apiId}/cors serves. Contributed by @iot-rocket.
  • EC2 — a security group's IPv6 and prefix-list rules survive the readDescribeSecurityGroups rendered ipv6Ranges and prefixListIds as empty elements and dropped every range description, so a rule authorized with an IPv6 CIDR or a prefix list was invisible on the next read and Terraform planned the same egress change on every run. All three families are reported, each with its description. Reported by @edersonbrilhante.
  • EC2 — a launch template keeps its metadata options and shutdown behaviourCreateLaunchTemplate parsed neither MetadataOptions nor InstanceInitiatedShutdownBehavior, so both were discarded at creation and every refresh reported them as newly added. The IMDS settings and the shutdown behaviour are stored and reported on the version, with the response-only State the API carries. Reported by @edersonbrilhante.
  • SSM — a SecureString parameter reports its KeyId — the key was stored and never served: DescribeParameters and GetParameterHistory left the member out, so a caller read an empty key and updated the parameter on every run. Both report it for SecureString parameters, the only ones it applies to, and an empty KeyId on the request means the account default. GetParameter is unchanged, since its response shape has no such member. Reported by @edersonbrilhante.
  • CloudWatch Logs — a log group reports its class and its KMS keyDescribeLogGroups omitted logGroupClass and kmsKeyId and CreateLogGroup discarded both, so a consumer reading the class back saw an empty value and planned a replacement of a group that had not changed. Both are stored and reported, with the API's default of STANDARD and an unknown class refused, and AWS::Logs::LogGroup carries LogGroupClass and KmsKeyId through the stack. Reported by @edersonbrilhante.
  • CloudFormation — UpdateReplacePolicy: Retain holds for every handler-side replacement — 24 update handlers that replace a resource themselves deleted the predecessor inline, so a template that retains it lost the resource while the stack still recorded DELETE_SKIPPED; only the handlers on the shared rename helper honoured the policy. Every inline delete goes through one helper that reads the policy the engine publishes, and the three handlers that deleted before creating create first. Contributed by @iot-rocket.
  • CloudFormation — a resource inside a nested stack reads its own UpdateReplacePolicy — the nested-stack deploy loop never published the policy, so a child resource's handler saw whatever the parent's loop had left behind: a retained parent kept every child's predecessor, an unretained one deleted a retained child's. Each child resource's policy is now resolved the way the engine resolves it, intrinsics included, and published around that resource's update only. Contributed by @iot-rocket.
  • CloudFormation — a nested stack's IAM resources need the parent's capabilities — the check under AUTH=true read the parent template only, so a parent deployed without --capabilities provisioned a child full of IAM roles through AWS::CloudFormation::Stack. The child template is checked against the set the parent acknowledged, and a child's child reads the same set; a missing one fails the nested-stack resource with Requires capabilities : [CAPABILITY_IAM] and the parent rolls back. Without AUTH nothing changes. Contributed by @iot-rocket.
  • CloudFormation — an unknown Capabilities value is refusedCreateStack, UpdateStack and CreateChangeSet accepted any string, so a typo or another tool's capability was kept without effect and the deploy behaved as if it had been acknowledged. The member is checked against the three documented values, joined with the other request-level problems into one message. Contributed by @iot-rocket.
  • CloudFormation — the SAM transform reads the transforms the response reportsValidateTemplate normalises the Transform section in every form it takes, while the deploy path had its own normalisation that left the macro object (Transform: {Name: AWS::Serverless-2016-10-31}) unmatched, so such a template was reported as declaring the SAM transform and then deployed untransformed, failing on AWS::Serverless::Function. Both read one function now. Contributed by @iot-rocket.
  • CloudFormation — an AWS::ApiGateway::Method's MethodResponses and IntegrationResponses are provisioned — the provisioner read the method and integration properties and discarded both response lists, so a REST API deployed from a template lost its mapped response headers. The visible casualty was the CDK's defaultCorsPreflightOptions, whose generated OPTIONS method returns the Access-Control-Allow-* headers through a MOCK integration: the preflight answered without a single CORS header and the browser blocked the request. Both lists are provisioned onto the method now, and a stack update reprovisions them. Contributed by @ppettitau.
  • CloudFormation — Fn::GetAtt on a user pool client's ClientSecret resolves — the provisioner returned ClientId alone, so a template reading ClientSecret or Name failed with Requested attribute ClientSecret does not exist in schema and rolled the stack back, on the create and on the update that follows it. All three attributes the type declares are returned, on both paths; a client created without GenerateSecret reads empty. Reported by @JoshuaSmeda.
  • CloudFormation — AWS::Cognito::IdentityPoolRoleAttachment updates in place — the type had no update handler, so a stack update fell through to the create. Roles and RoleMappings are re-applied to the pool the attachment sits on and a dropped property reverts to its create default, since SetIdentityPoolRoles takes the whole configuration; a changed IdentityPoolId replaces, configuring the new pool before clearing the old. Contributed by @iot-rocket.
  • CloudFormation — AWS::Cognito::UserPoolResourceServer updates in place, and a move between pools is refused — the physical id is the Identifier alone while the record is keyed by pool and identifier, so a template that repointed UserPoolId left two live resource servers vending the same scopes. Name and Scopes go through UpdateResourceServer on the existing server, a changed Identifier replaces, and a changed UserPoolId gets CloudFormation's refusal to replace a custom-named resource. Contributed by @iot-rocket.
  • CloudFormation — AWS::SQS::QueuePolicy and AWS::SNS::TopicPolicy update in place — neither had an update handler, so a changed policy fell through to create under a fresh physical id; the engine recorded a replacement and the cleanup delete then stripped the policy the create had just written, leaving the queue or topic with none. Both properties are No interruption, so the resource keeps its id, writes the document on every queue or topic the template names and removes it from one the template dropped. Contributed by @iot-rocket.
  • CloudFormation — AWS::SNS::Subscription updates in place — a changed FilterPolicy or RawMessageDelivery re-subscribed under a fresh SubscriptionArn, so Ref moved on every update. The No-interruption attributes go through SetSubscriptionAttributes on the existing subscription and a dropped one reverts to the create default, while TopicArn, Protocol and Endpoint replace. The create also stores DeliveryPolicy, RedrivePolicy and SubscriptionRoleArn, which it dropped. Contributed by @iot-rocket.
  • CloudFormation — AWS::Lambda::Alias updates in place, and ProvisionedConcurrencyConfig reaches the service — the create ignored ProvisionedConcurrencyConfig and stored RoutingConfig in the template's list shape, on which boto3's GetAlias fails. The No-interruption properties go through UpdateAlias and the provisioned-concurrency put and delete on the existing alias, Name or FunctionName replaces, and the routing weights are stored as the {version: weight} map GetAlias returns. Contributed by @iot-rocket.
  • CloudFormation — AWS::Logs::SubscriptionFilter updates in place, and a group change moves it — a changed LogGroupName fell through to create, which wrote the filter on the new group under its unchanged name, so the engine saw no replacement and the old group kept its copy. The No-interruption properties go through PutSubscriptionFilter on the existing filter, and a changed FilterName or LogGroupName creates the new filter before removing the old. Contributed by @iot-rocket.
  • CloudFormation — AWS::IAM::InstanceProfile updates in place, and a named profile refuses a Path change — a Roles change fell through to create, which rebuilt the record without its tags and stored whole role records, on which GetInstanceProfile answered 500. Roles is applied to the existing record, which keeps its ARN, id and tags; InstanceProfileName and Path replace, so a custom-named profile whose Path changes fails the update as CloudFormation fails it. Contributed by @iot-rocket.
  • CloudFormation — the four HTTP API types update in placeAWS::ApiGatewayV2::Api, Integration, Route and Stage had create handlers only, so a stack update minted a new id for each: the API re-created every child under a new ApiId and ApiEndpoint (and rolled the stack back when the id was pinned with ms-custom-id), an integration or route left its predecessor on the API while Ref moved, and a stage lost its CreatedDate and its tags. Each now updates through the service's own call and keeps its id, a dropped property reverts to the create's default, and only ProtocolType, ApiId or StageName replaces. Contributed by @iot-rocket.
  • CloudFormation — AWS::CloudFront::Distribution updates in place, and a provisioned distribution is readable — a stack update minted a new Id and DomainName and the engine deleted the old distribution, though both of the type's properties are No interruption; and the create stored an empty configuration, so GetDistribution and GetDistributionConfig answered 500 on every CloudFormation distribution. The create renders the template's DistributionConfig into the API's shape, and the update keeps Id, ARN, DomainName and the invalidation history, rolls the ETag and reconciles tags, as UpdateDistribution does. Contributed by @iot-rocket.
  • CloudFormation — the five CloudFront policy, OAC and function types update in placeCachePolicy, OriginRequestPolicy, ResponseHeadersPolicy, OriginAccessControl and Function had create handlers only, so an update fell through to a create that found the record it had made before and returned it untouched: the stack reported UPDATE_COMPLETE while the service kept serving the old configuration. Each updates its record now, keeping the id a distribution Refs, and a rename onto a taken name is refused. Contributed by @iot-rocket.

Don't miss a new ministack release

NewReleases is sending notifications on new releases.