github ministackorg/ministack v1.5.30

3 hours ago

What's Changed

New Contributors

[1.5.30] — 2026-10-20

Added

  • Auth — more calls are authorized under AUTH=true — RDS Data calls are authorized as rds-data:<operation> on the cluster its resourceArn names, S3 Control ListTagsForResource, TagResource and UntagResource as s3:<operation> on the bucket, and SES v2 calls as ses:<operation> on the resource the path or create body names (* otherwise), each with AWS's 403 denial (AccessDeniedException, S3 Control AccessDenied) naming the action and resource. S3 bucket and identity policies now see s3:x-amz-server-side-encryption (a part and the completion take their CreateMultipartUpload's), s3:x-amz-acl, s3:x-amz-grant-* and, on GetObject and HeadObject, s3:ExistingObjectTag/<key>. S3 Tables and Glue Iceberg REST calls at /iceberg are authorized for the catalog that serves them, whatever service they are signed for: on a table bucket as the s3tables actions AWS lists for the operation (loadTable needs GetTableMetadataLocation, then GetTableData) on the bucket or table (table/* for one that does not exist), on the Glue catalog as the route's glue action on the catalog, then the database, then the table, and transactions/commit as updateTable on each table it changes; a denial answers 403 in the Iceberg error shape naming the action and resource ("Access Denied" for a later S3 Tables action, no body for HEAD), a {prefix} that is neither a table bucket ARN nor <account>:s3tablescatalog/<bucket> answers 400 bad_request, and a call that names a key is served from its own region only. Contributed by @iot-rocket.
  • IoT — MQTT over WebSocket follows the signer's IAM policies under AUTH=true — a session signed with an IAM user or STS key could connect under any client id and publish, subscribe and receive anywhere; its CONNECT, will, PUBLISH, SUBSCRIBE and deliveries now need iot:Connect, iot:Publish (and iot:RetainPublish), iot:Subscribe and iot:Receive on the client, topic and topic filter ARNs, with a certificate session's refusals. The handshake (and an AWS_IAM WebSocket $connect) has its signature, date and session token verified: IoT answers 403 Forbidden and expects the token after the signed part of the URL, API Gateway names the canonical string it expected, and the session runs in the signer's account. Contributed by @iot-rocket.
  • Cognito — user pool feature plans — CreateUserPool, UpdateUserPool and AWS::Cognito::UserPool ignored UserPoolTier; a pool now stores and describes its tier (ESSENTIALS by default, PLUS when created with threat protection on), keeps it when an update omits it, and refuses threat protection on ESSENTIALS and password history on LITE with FeatureUnavailableInTierException or, on a tier change, TierChangeNotAllowedException. Contributed by @iot-rocket.

Fixed

  • Auth — unverifiable credentials under AUTH=true — a request naming an IAM user's key in a SigV2 header, another SigV4 algorithm (AWS4-HMAC-SHA512), a header without Credential= or a query X-Amz-Credential the signature does not cover was served as that user; it now answers IncompleteSignature (S3: InvalidRequest), and SigV2 header and presigned signatures are verified. A request that maps to no IAM action (SES v2, RDS Data, S3 Control, X-Ray) is authenticated and answers the service's credential error (InvalidSignatureException 403 from SES v2 and RDS Data, SignatureDoesNotMatch 403 from S3 Control), and an SES v2 call selected by X-Amz-Target on a path without /v2/email is authorized as the operation it performs. A SigV4 header or presigned query that names no access key ran as the account root; it now answers IncompleteSignature naming every missing part (Lambda: MissingAuthenticationTokenException for a part left out), and an empty key the unknown-key error. Contributed by @iot-rocket.
  • API Gateway — credentials under AUTH=true — a REST method with NONE or COGNITO_USER_POOLS authorization, or an HTTP API route without authorization, refused a signed caller its policies or signature failed; such a call now reaches the method and its own authorizer whatever it is signed with, and a REST API policy judges a NONE method's caller as anonymous (ACCESS_DENIED unless the policy allows everyone). A correctly presigned call (X-Amz-Signature) to an AWS_IAM method is served instead of 403 MissingAuthenticationTokenException. CreateRestApi, GetRestApi, GetRestApis and UpdateRestApi now return apiStatus AVAILABLE and rootResourceId, which Terraform AWS provider 6.x waits on. Contributed by @iot-rocket. Reported by @kharabasz.
  • AppSync — GraphQL authorization — under AUTH=true a GraphQL call was authorized as appsync:UpdateGraphqlApi whatever the API's authentication type, and an AWS_IAM API served any AWS4-HMAC-SHA256 header. On /v1/apis/{apiId}/graphql and on the API's own host an API key request now ignores its signature, and an AWS_IAM API needs a verified signature and authorizes appsync:GraphQL per top-level field (401 Permission denied when none is allowed, a field error per denied field, 400 BadRequestException for a wrong signature). Contributed by @iot-rocket.
  • AppSync — data-plane errors — Event API POST /event refusals answered {"__type", "message"}, and GraphQL answered 500 for a body that is not a JSON object or a non-string query or non-object variables, a message-only 400 for a missing query and 200 for an unparseable one; both now answer AWS's {"errors": [{"errorType", "message"}]} envelope and x-amzn-ErrorType (SerializationException, MalformedHttpRequestException, ResourceNotFoundException for an unknown API or key, UnknownOperationException for another path on an Event API host), and publish answers application/json. Contributed by @iot-rocket.
  • Cognito — tokens under AUTH=true — /oauth2/userInfo returned the user's attributes for any JWT with a user's sub; it now refuses an ID token, an altered, revoked or expired token, a disabled or deleted user and a token without the openid scope with 401 invalid_token, AWS's error_description and a WWW-Authenticate realm of the pool's issuer. AdminDisableUser revokes the user's tokens (User is disabled., then Access Token has been revoked / Refresh Token has been revoked and invalid_grant after AdminEnableUser), a deleted user's access token gets UserNotFoundException: User does not exist., and refresh tokens are signature-checked and refused for a disabled user or a deleted user's successor. ID tokens carry email_verified and phone_number_verified as JSON booleans, as AWS does; userInfo and the attributes keep them as strings. Contributed by @iot-rocket and @SAY-5. Reported by @proeben.
  • Cognito — identity Logins and OAuth scopes — under AUTH=true GetId, GetCredentialsForIdentity and GetOpenIdToken took any token in Logins; they now refuse an unsupported provider, a token that is no ID token of a trusted client, a bad signature, one expired over five minutes or another user's login with NotAuthorizedException (Access to Identity '<id>' is forbidden. without Logins), GetId returns the identity that holds the login, and the first login is linked. A call on an identity without a login answers for the identity that holds its login, an identity with a login takes the logins of the identities holding the others, and a merged-away identity is listed with ["DISABLED", "<region>"] (ListIdentities HideDisabled leaves it out). /oauth2/authorize with an identity_provider redirects with error_description=invalid_scope for a scope outside AllowedOAuthScopes, and client_credentials ignores undefined scopes once a defined one is requested (api/read bogus/x gets api/read). Contributed by @iot-rocket.
  • IoT — reserved MQTT topics — a client could subscribe to any $ filter and publish to the replies AWS IoT writes (fleet provisioning, Jobs, Device Defender, file delivery, Device Location, commands); the broker now refuses them before the policy check (SUBACK 0x8F, PUBACK 0x90 or DISCONNECT 0x90 at QoS 0, a closed connection on 3.1.1), judges a $share/<name>/<filter> subscription by its filter, and HTTPS Publish answers Topic can't start with $ for fleet provisioning and Device Location replies. Contributed by @iot-rocket.
  • IoT — Jobs — a stale expectedVersion answered 409 and now answers 400 InvalidRequestException (VersionMismatch over MQTT); a status a device may not set is refused first with InvalidStateTransitionException; a statusDetails that is not a string map answers 400 instead of 500, an execution has none until a device reports some, and an empty map replaces it; MQTT get and update answer ResourceNotFound for another executionNumber; describe and list calls time out expired executions; documents that are not JSON or nest over 1000 levels are refused; and Jobs MQTT messages are compact UTF-8 in AWS's member order, clientToken first, characters outside the BMP escaped, "jobDocument":null kept. Contributed by @iot-rocket.
  • IoT — shadows, topic rules, sessions and authorization — NaN and Infinity in job documents and shadows are refused (Payload contains invalid json), and a number beyond a double is stored as "Infinity"; unknown topic rules answer 401 UnauthorizedException (Access to topic rule '<name>' was denied) and bad names, including a trailing newline, AWS's validation message; unacknowledged QoS 1 messages survive a persistent session reconnect without reusing an in-flight packet id. Under AUTH=true the data plane answers 403 ForbiddenException, aws:ResourceTag conditions read the resource's tags, tagging calls are checked on the resourceArn, and denials name the resource. Contributed by @iot-rocket.
  • S3 — ACLs and Block Public Access — x-amz-grant-* headers were refused with MissingSecurityHeader or dropped; on buckets with ACLs enabled PutBucketAcl, PutObjectAcl, CreateBucket, PutObject and CopyObject now set exactly the named grants and refuse what S3 refuses, and a BucketOwnerEnforced bucket answers 400 AccessControlListNotSupported. BlockPublicAcls refuses setting a public ACL (403 AccessDenied), IgnorePublicAcls drops public grants, RestrictPublicBuckets the policy, BlockPublicPolicy refuses a public bucket policy, and a bucket ACL grants only what its permission covers. Contributed by @iot-rocket.
  • S3 — copy and policy checks under AUTH=true — a copy source in another account's bucket is checked for read access, x-amz-* headers sent in the query string are checked like headers, bucket policies for anonymous and cross-account callers use the request's own action and condition keys, and s3:ExistingObjectTag resolves through an access point alias. A header-signed PutObject checks the bucket name, regional endpoint and signing region before credentials and a missing bucket after them, answering InvalidBucketName, PermanentRedirect, AuthorizationHeaderMalformed, InvalidAccessKeyId, SignatureDoesNotMatch or NoSuchBucket with S3's XML fields. Contributed by @iot-rocket and @gakuto-cw21.
  • S3 — bucket configuration validation — out-of-enum values in PutBucketVersioning, PutBucketLifecycleConfiguration and PutBucketEncryption now answer 400 MalformedXML (The XML you provided was not well-formed or did not validate against our published schema), and a non-positive Days or NoncurrentDays 400 InvalidArgument ('Days' for Expiration action must be a positive integer).
  • CloudWatch — composite alarms follow their rule — a composite alarm stayed in INSUFFICIENT_DATA; it now evaluates its AlarmRule on create, update and every child state change, with AWS's StateReason, StateReasonData and history. PutCompositeAlarm refuses an unknown child or a malformed rule (also one nested over 256 levels) with ValidationError, a long AND/OR chain no longer answers 500, DeleteAlarms and stack deletes refuse an alarm a composite depends on and DeleteAlarms more than one composite, and DescribeAlarmHistory returns composite items only for AlarmTypes=["CompositeAlarm"]. Contributed by @iot-rocket.
  • IAM — trust policies with NotPrincipal — CreateRole, UpdateAssumeRolePolicy and AWS::IAM::Role stored them; they now answer MalformedPolicyDocument (Allow with NotPrincipal is not allowed., AssumeRole policy must not contain NotPrincipal field.). Contributed by @iot-rocket.
  • CloudTrail — RecursiveLogging on trails — CreateTrail and UpdateTrail ignored it; trails now store it (default true, kept by an UpdateTrail without it) and return it from every trail response. Contributed by @iot-rocket.
  • CloudFormation — callback URLs under USE_SSL=1 — the custom resource ResponseURL and the WaitConditionHandle URL were always http://, which the TLS-only gateway drops, so a handler's urllib PUT never arrived and the stack waited out ServiceTimeout; both are now https:// when USE_SSL is on, and MiniStack's Lambdas get SSL_CERT_FILE set to the gateway's CA bundle, which Python's ssl reads. Contributed by @iot-rocket.

Don't miss a new ministack release

NewReleases is sending notifications on new releases.