DocsAWS 101Blog
← Back to Blog

IAM identity-discovery surface, Cognito CUSTOM_AUTH merge, SQS attribute validation, EventBridge anything-but nested matchers, ElastiCache respawn

June 10, 2026 · v1.3.60

v1.3.60 lands the IAM surface that identity-discovery agents and cloud-security tooling reach for first: login profiles (human vs service account signal), the full virtual MFA device lifecycle, GetAccountAuthorizationDetails (the one-shot identity graph), SAML provider CRUD plus ListOpenIDConnectProviders, and the Access Advisor generate/get job handshake. Three bug fixes shipped alongside: Cognito multi-round CUSTOM_AUTH flows now advance correctly because the verify result is merged into the pending round instead of appended as a separate session record; SQS rejects out-of-range numeric attributes with InvalidAttributeValue (400) per the documented AWS bounds; EventBridge anything-but honors nested prefix / suffix / wildcard matchers instead of silently ignoring them. And ElastiCache, finally, respawns its Redis container after a restart when PERSIST_STATE=1 — restored clusters previously reported available with a dead endpoint.

IAM — login profiles (CreateLoginProfile / Get / Update / Delete)

A login profile models whether an IAM user has a console password — the signal that distinguishes a human IAM user from a service account. Identity-discovery tooling reads this through get-login-profile to reason about account posture. Previously MiniStack returned InvalidAction on all four ops. CreateLoginProfile stores UserName, CreateDate, and PasswordResetRequired without persisting the password value (seed-side concern). GetLoginProfile returns NoSuchEntity (404) when the user has no profile; EntityAlreadyExists (409) on a duplicate create; UpdateLoginProfile updates the reset-required flag; DeleteLoginProfile removes the profile. Request and response shapes match real AWS so probes run unchanged against prod. Contributed by @lahmish.

IAM — virtual MFA device lifecycle (7 actions)

Whether an IAM user has MFA is a decisive identity-posture signal. The seven AWS APIs an agent reads (list-mfa-devices, list-virtual-mfa-devices) and the lifecycle behind them are all in:

Contributed by @lahmish.

IAM — GetAccountAuthorizationDetails (the one-shot identity graph)

The single call tools and agents reach for first when mapping an account — one request returns users, groups, roles, and customer-managed policies with their inline and attached policies, group memberships, instance profiles, and tags. The response carries UserDetailList (inline policies, attached managed policies, group memberships, tags), GroupDetailList, RoleDetailList (inline policies, attached managed policies, instance profiles, tags, URL-encoded assume-role document), and Policies (customer-managed, with URL-encoded version documents). Filter.member.N honored: User, Group, Role, LocalManagedPolicy. IsTruncated=false; pagination optional. Nested shapes parse cleanly via botocore (resp["UserDetailList"], resp["RoleDetailList"], resp["Policies"]). Pure read aggregation over existing state — no new state dict. Contributed by @lahmish.

IAM — SAML provider CRUD + ListOpenIDConnectProviders

Federation discovery completeness. Role-trust strings already resolve via GetRole's Principal.Federated; this lets a tool enumerate the trusted SAML IdPs and cross-reference them with the trust policies. CreateSAMLProvider returns SAMLProviderArn (arn:aws:iam::<acct>:saml-provider/<Name>) and accepts any non-empty SAMLMetadataDocument (real AWS requires valid XML ≥1000 chars — that's seed-side, intentionally lenient). GetSAMLProvider returns the XML-escaped SAMLMetadataDocument, CreateDate, ValidUntil, Tags; NoSuchEntity when absent. ListSAMLProviders + UpdateSAMLProvider + DeleteSAMLProvider complete the lifecycle. ListOpenIDConnectProviders enumerates the OIDC providers (create / get / delete existed previously). Tags round-trip on the SAML provider. Contributed by @lahmish.

IAM — Access Advisor (GenerateServiceLastAccessedDetails + GetServiceLastAccessedDetails)

A least-privilege signal — tools call generate-service-last-accessed-details for an ARN, then poll get-service-last-accessed-details for the job result. GenerateServiceLastAccessedDetails takes Arn and returns a UUID JobId (≥36 chars, satisfies botocore validation). GetServiceLastAccessedDetails returns JobStatus=COMPLETED (matches botocore's jobStatusType enum: IN_PROGRESS / COMPLETED / FAILED) and an empty ServicesLastAccessed list — a deterministic "never accessed" result, since deriving real last-accessed data from CloudTrail is out of scope. Contributed by @lahmish.

Cognito — CUSTOM_AUTH RespondToAuthChallenge merges the verify result into the pending round

The CUSTOM_AUTH RespondToAuthChallenge / AdminRespondToAuthChallenge paths appended a second, metadata-less session entry for the verify result, splitting one round across two records. AWS records one ChallengeResult per round, carrying both challengeMetadata (from CreateAuthChallenge) and challengeResult (from VerifyAuthChallengeResponse) — so multi-round flows that read both fields from the same element (magic-link → SMS-OTP, for example) never advanced. The new _update_pending_challenge_result helper records the result on the pending round in place, with a fallback append for empty / malformed history, applied at both _respond_to_auth_challenge and _admin_respond_to_auth_challenge. Contributed by @AdigaAkhil.

SQS — out-of-range numeric attributes rejected with InvalidAttributeValue

CreateQueue and SetQueueAttributes accepted any value and stored it verbatim — VisibilityTimeout=99999 was silently kept even though AWS rejects with InvalidAttributeValue (400) anything outside the documented bounds. Now validated against the AWS API ranges:

Out-of-range and non-numeric values are rejected at CreateQueue and SetQueueAttributes with InvalidAttributeValue (400), matching what real AWS SQS does. Reported by @dcabib.

EventBridge — anything-but honors nested prefix / suffix / wildcard matchers

A rule like {"detail":{"id":[{"anything-but":{"prefix":"TEST-"}}]}} was accepted at PutRule but silently ignored at dispatch — every event matched regardless of the field value. The dispatcher recognized only the literal and list-of-literal forms of anything-but and fell through on the nested-matcher form, which then compared a string to a dict and always returned "match". Per AWS docs anything-but supports nested prefix / suffix / wildcard matchers; the handler now negates the nested filter correctly — an event whose field matches the nested filter is excluded. Reported by @aldirrix.

ElastiCache — Redis container respawned after restart

With PERSIST_STATE=1, restored cluster metadata reported CacheClusterStatus=available but the persisted Docker container id no longer existed, so Terraform / SDK clients saw a healthy cluster they couldn't connect to. The respawn can't happen at restore_state time because that runs during module import (before _spawn_redis_container is defined); restored clusters and replication groups are now marked pending and lazily spawned on the first dispatcher call. A lock around the spawn path prevents two concurrent first-requests after restart from double-spawning the same cluster. The cluster's endpoint metadata is rewritten to the freshly-spawned container before any caller can read it; Terraform's typical DescribeCacheClusters → connect flow sees a healthy endpoint. Spawn failures are logged once and cleared from the pending set so a missing Docker daemon doesn't produce a per-request retry storm. Reported by @ItsSmiffy.

Upgrade

docker pull ministackorg/ministack:1.3.60
docker run -d -p 4566:4566 ministackorg/ministack:1.3.60

Or pin in compose.yaml:

services:
  ministack:
    image: ministackorg/ministack:1.3.60
    ports:
      - "4566:4566"

Stay in sync

Issues and PRs welcome on GitHub. Discussion on r/ministack.