What's Changed
[1.3.22] — 2026-04-30
Added
- Cognito PreTokenGeneration Lambda trigger —
LambdaConfig.PreTokenGenerationConfig(V2_0) and the legacyLambdaConfig.PreTokenGeneration(V1_0) are now round-tripped throughCreateUserPool/UpdateUserPool/DescribeUserPooland invoked at token-mint time. Before signing an access or id token, ministack synchronously invokes the configured Lambda with the AWS-shaped event (triggerSource,userPoolId,request.userAttributes,request.groupConfiguration,request.scopesfor V2+,callerContext.clientId, etc.) and applies the Lambda'sresponse.claimsAndScopeOverrideDetails.{accessTokenGeneration,idTokenGeneration}(V2_0:claimsToAddOrOverride,claimsToSuppress,scopesToAdd,scopesToSuppress,groupOverrideDetails) — or the legacyresponse.claimsOverrideDetails(V1_0, id token only). Refresh tokens are opaque in AWS and skip the trigger. Lambda errors fail open (token issued without overrides + warning logged); setMINISTACK_COGNITO_PRETOKEN_STRICT=1to fail closed the way real AWS does. Invocation reuses the existing_resolve_name_and_qualifier→_get_func_record_for_qualifier→_execute_functionchain inlambda_svc.py— no new handlers added. Reported by @aahoughton (#533). - S3 PostObject (browser-based form upload) —
POST /<bucket>/withmultipart/form-datais now handled. Honourskey(with${filename}substitution from the file part),Content-Type,x-amz-meta-*,x-amz-storage-class,x-amz-tagging, the object-lock headers,success_action_status(200/201/204; default 204), andsuccess_action_redirect(303 withbucket=&key=&etag=appended). On 201 returns the<PostResponse>XML withLocation/Bucket/Key/ETag. Versioning, persistence, multi-tenancy, S3 event notifications all flow through the same path asPutObject. Thecontent-length-rangepolicy condition is enforced — uploads under the minimum returnEntityTooSmall400 and uploads over the maximum returnEntityTooLarge400 (matches AWS error codes). Other policy conditions and the signature field are accepted but not validated — same lenient stance as ministack's presigned-URL handling. boto3'sgenerate_presigned_postworks end-to-end. Requested by @mattburton (#535). - Init / ready scripts: expose
MINISTACK_INIT_SCRIPT_DIRandMINISTACK_INIT_SCRIPT_PATHto each script — every.sh/.pyrun from/docker-entrypoint-initaws.d[/ready.d](or/etc/localstack/init/{boot,ready}.d) now sees its own directory and absolute path in the environment, so scripts can reference sibling files (aws s3 cp "${MINISTACK_INIT_SCRIPT_DIR}/data.json" s3://bucket/) without hardcoding the mount path or computingdirname "${BASH_SOURCE[0]}". Phase-levelMINISTACK_INIT_BOOT_DIR/MINISTACK_INIT_READY_DIRare also set when those directories exist. Requested by @andreluiznsilva (#520). - EC2 Instance Metadata Service (IMDS) emulator — new
imdsservice responds on the gateway port at/latest/api/token(IMDSv2) and/latest/meta-data/...//latest/dynamic/instance-identity/document. Returns a credentials document under roleministack-instance-roleso SDKs that fall through to the IMDS step of the default credential chain (boto3, aws-sdk-go-v2, AWS SDK Java v2) get a validASIA*session key + token. Both IMDSv1 (token-less) and IMDSv2 (PUT /token → GET withX-aws-ec2-metadata-token) supported; setMINISTACK_IMDS_V2_REQUIRED=1to reject token-less requests, matching AWS hop-limit-1 IMDSv2-only instances. Point SDKs at ministack viaAWS_EC2_METADATA_SERVICE_ENDPOINT=http://localhost:4566(orec2_metadata_service_endpointin~/.aws/config); we don't bind the link-local 169.254.169.254 IP — that's a per-container network-alias concern, not portable from inside ministack. Reported by @bimargulies
Fixed
- S3 PutObject
StorageClasswas dropped on the floor — objects written withStorageClass=GLACIER/INTELLIGENT_TIERING/ etc. came back fromGetObject,HeadObject,ListObjects(V2), andListObjectVersionsasSTANDARD. The header is now stored on the object record and emitted on the wire (header omitted for the defaultSTANDARD, matching AWS). Same propagation throughCopyObject(with optional override viax-amz-storage-class) andCreateMultipartUpload→CompleteMultipartUpload. Unknown storage class values now returnInvalidStorageClass(400). Verified againstbotocore/data/s3/2006-03-01/service-2.json. Reported by @JoeHale (#534).