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 asrds-data:<operation>on the cluster itsresourceArnnames, S3 ControlListTagsForResource,TagResourceandUntagResourceass3:<operation>on the bucket, and SES v2 calls asses:<operation>on the resource the path or create body names (*otherwise), each with AWS's 403 denial (AccessDeniedException, S3 ControlAccessDenied) naming the action and resource. S3 bucket and identity policies now sees3:x-amz-server-side-encryption(a part and the completion take theirCreateMultipartUpload's),s3:x-amz-acl,s3:x-amz-grant-*and, onGetObjectandHeadObject,s3:ExistingObjectTag/<key>. S3 Tables and Glue Iceberg REST calls at/icebergare authorized for the catalog that serves them, whatever service they are signed for: on a table bucket as thes3tablesactions AWS lists for the operation (loadTableneedsGetTableMetadataLocation, thenGetTableData) on the bucket or table (table/*for one that does not exist), on the Glue catalog as the route'sglueaction on the catalog, then the database, then the table, andtransactions/commitasupdateTableon 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 forHEAD), a{prefix}that is neither a table bucket ARN nor<account>:s3tablescatalog/<bucket>answers 400bad_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 neediot:Connect,iot:Publish(andiot:RetainPublish),iot:Subscribeandiot:Receiveon the client, topic and topic filter ARNs, with a certificate session's refusals. The handshake (and anAWS_IAMWebSocket$connect) has its signature, date and session token verified: IoT answers 403Forbiddenand 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,UpdateUserPoolandAWS::Cognito::UserPoolignoredUserPoolTier; a pool now stores and describes its tier (ESSENTIALSby default,PLUSwhen created with threat protection on), keeps it when an update omits it, and refuses threat protection onESSENTIALSand password history onLITEwithFeatureUnavailableInTierExceptionor, 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 withoutCredential=or a queryX-Amz-Credentialthe signature does not cover was served as that user; it now answersIncompleteSignature(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 (InvalidSignatureException403 from SES v2 and RDS Data,SignatureDoesNotMatch403 from S3 Control), and an SES v2 call selected byX-Amz-Targeton a path without/v2/emailis authorized as the operation it performs. A SigV4 header or presigned query that names no access key ran as the account root; it now answersIncompleteSignaturenaming every missing part (Lambda:MissingAuthenticationTokenExceptionfor 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 withNONEorCOGNITO_USER_POOLSauthorization, 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 aNONEmethod's caller as anonymous (ACCESS_DENIEDunless the policy allows everyone). A correctly presigned call (X-Amz-Signature) to anAWS_IAMmethod is served instead of 403MissingAuthenticationTokenException.CreateRestApi,GetRestApi,GetRestApisandUpdateRestApinow returnapiStatusAVAILABLEandrootResourceId, which Terraform AWS provider 6.x waits on. Contributed by @iot-rocket. Reported by @kharabasz. - AppSync — GraphQL authorization — under
AUTH=truea GraphQL call was authorized asappsync:UpdateGraphqlApiwhatever the API's authentication type, and anAWS_IAMAPI served anyAWS4-HMAC-SHA256header. On/v1/apis/{apiId}/graphqland on the API's own host an API key request now ignores its signature, and anAWS_IAMAPI needs a verified signature and authorizesappsync:GraphQLper top-level field (401Permission deniedwhen none is allowed, a field error per denied field, 400BadRequestExceptionfor a wrong signature). Contributed by @iot-rocket. - AppSync — data-plane errors — Event API
POST /eventrefusals answered{"__type", "message"}, and GraphQL answered 500 for a body that is not a JSON object or a non-stringqueryor non-objectvariables, a message-only 400 for a missing query and 200 for an unparseable one; both now answer AWS's{"errors": [{"errorType", "message"}]}envelope andx-amzn-ErrorType(SerializationException,MalformedHttpRequestException,ResourceNotFoundExceptionfor an unknown API or key,UnknownOperationExceptionfor another path on an Event API host), and publish answersapplication/json. Contributed by @iot-rocket. - Cognito — tokens under
AUTH=true—/oauth2/userInforeturned the user's attributes for any JWT with a user'ssub; it now refuses an ID token, an altered, revoked or expired token, a disabled or deleted user and a token without theopenidscope with 401invalid_token, AWS'serror_descriptionand aWWW-Authenticaterealm of the pool's issuer.AdminDisableUserrevokes the user's tokens (User is disabled., thenAccess Token has been revoked/Refresh Token has been revokedandinvalid_grantafterAdminEnableUser), a deleted user's access token getsUserNotFoundException: User does not exist., and refresh tokens are signature-checked and refused for a disabled user or a deleted user's successor. ID tokens carryemail_verifiedandphone_number_verifiedas 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
Loginsand OAuth scopes — underAUTH=trueGetId,GetCredentialsForIdentityandGetOpenIdTokentook any token inLogins; 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 withNotAuthorizedException(Access to Identity '<id>' is forbidden.withoutLogins),GetIdreturns 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>"](ListIdentitiesHideDisabledleaves it out)./oauth2/authorizewith anidentity_providerredirects witherror_description=invalid_scopefor a scope outsideAllowedOAuthScopes, andclient_credentialsignores undefined scopes once a defined one is requested (api/read bogus/xgetsapi/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 HTTPSPublishanswersTopic can't start with $for fleet provisioning and Device Location replies. Contributed by @iot-rocket. - IoT — Jobs — a stale
expectedVersionanswered 409 and now answers 400InvalidRequestException(VersionMismatchover MQTT); a status a device may not set is refused first withInvalidStateTransitionException; astatusDetailsthat 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; MQTTgetandupdateanswerResourceNotFoundfor anotherexecutionNumber; 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,clientTokenfirst, characters outside the BMP escaped,"jobDocument":nullkept. Contributed by @iot-rocket. - IoT — shadows, topic rules, sessions and authorization —
NaNandInfinityin job documents and shadows are refused (Payload contains invalid json), and a number beyond a double is stored as"Infinity"; unknown topic rules answer 401UnauthorizedException(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. UnderAUTH=truethe data plane answers 403ForbiddenException,aws:ResourceTagconditions read the resource's tags, tagging calls are checked on theresourceArn, and denials name the resource. Contributed by @iot-rocket. - S3 — ACLs and Block Public Access —
x-amz-grant-*headers were refused withMissingSecurityHeaderor dropped; on buckets with ACLs enabledPutBucketAcl,PutObjectAcl,CreateBucket,PutObjectandCopyObjectnow set exactly the named grants and refuse what S3 refuses, and aBucketOwnerEnforcedbucket answers 400AccessControlListNotSupported.BlockPublicAclsrefuses setting a public ACL (403AccessDenied),IgnorePublicAclsdrops public grants,RestrictPublicBucketsthe policy,BlockPublicPolicyrefuses 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, ands3:ExistingObjectTagresolves through an access point alias. A header-signedPutObjectchecks the bucket name, regional endpoint and signing region before credentials and a missing bucket after them, answeringInvalidBucketName,PermanentRedirect,AuthorizationHeaderMalformed,InvalidAccessKeyId,SignatureDoesNotMatchorNoSuchBucketwith S3's XML fields. Contributed by @iot-rocket and @gakuto-cw21. - S3 — bucket configuration validation — out-of-enum values in
PutBucketVersioning,PutBucketLifecycleConfigurationandPutBucketEncryptionnow answer 400MalformedXML(The XML you provided was not well-formed or did not validate against our published schema), and a non-positiveDaysorNoncurrentDays400InvalidArgument('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 itsAlarmRuleon create, update and every child state change, with AWS'sStateReason,StateReasonDataand history.PutCompositeAlarmrefuses an unknown child or a malformed rule (also one nested over 256 levels) withValidationError, a longAND/ORchain no longer answers 500,DeleteAlarmsand stack deletes refuse an alarm a composite depends on andDeleteAlarmsmore than one composite, andDescribeAlarmHistoryreturns composite items only forAlarmTypes=["CompositeAlarm"]. Contributed by @iot-rocket. - IAM — trust policies with
NotPrincipal—CreateRole,UpdateAssumeRolePolicyandAWS::IAM::Rolestored them; they now answerMalformedPolicyDocument(Allow with NotPrincipal is not allowed.,AssumeRole policy must not contain NotPrincipal field.). Contributed by @iot-rocket. - CloudTrail —
RecursiveLoggingon trails —CreateTrailandUpdateTrailignored it; trails now store it (defaulttrue, kept by anUpdateTrailwithout it) and return it from every trail response. Contributed by @iot-rocket. - CloudFormation — callback URLs under
USE_SSL=1— the custom resourceResponseURLand theWaitConditionHandleURL were alwayshttp://, which the TLS-only gateway drops, so a handler'surllibPUT never arrived and the stack waited outServiceTimeout; both are nowhttps://whenUSE_SSLis on, and MiniStack's Lambdas getSSL_CERT_FILEset to the gateway's CA bundle, which Python'ssslreads. Contributed by @iot-rocket.