When Cognito Self-Sign-Up Is Enabled, Anyone Can Obtain Temporary AWS Credentials: The Case of the AI Attack Tool ARTEX and Three Settings You Should Check

When Cognito Self-Sign-Up Is Enabled, Anyone Can Obtain Temporary AWS Credentials: The Case of the AI Attack Tool ARTEX and Three Settings You Should Check

Datadog reported its attack agent ARTEX self-signing up to an exposed Cognito user pool and exploring AWS with identity pool temporary credentials. This covers why it works and how to check your own user pools and authenticated roles.
2026.11.14

This page has been translated by machine translation. View original

Hi there! I'm Yuji Nishimura from the Ops team!

You put the frontend on S3, let users sign in with a user pool in Amazon Cognito, and hand out temporary AWS credentials through an identity pool. This is a common setup when building an SPA on AWS. Datadog Security Labs reported how an attack AI agent used this setup in its November 9, 2026 article "On the hunt for ARTEX: understanding agentic attacks in AWS environments".

The agent tried to sign up to an exposed user pool (one with self-sign-up enabled and an app client ID visible from the frontend), and kept trying even after being rejected until it registered. It exchanged the tokens it obtained for temporary AWS credentials and tried to explore the account. This flow works through a combination of settings, not a vulnerability, so if your environment uses self-sign-up together with an identity pool, you should check your settings.

Conclusion first: what to check and what to do

What to check Countermeasure
Whether self-sign-up is enabled on the user pool (AllowAdminCreateUserOnly) For a user pool that doesn't need registration by general users, set it to true so that only administrators can create users
Whether users can write to the attributes used for authorization (such as custom:isAdmin) themselves (WriteAttributes of the app client) Remove attributes used for authorization from WriteAttributes of all app clients
The permissions of the identity pool's authenticated role and the role assignment settings (RoleMappings) Restrict permissions on the assumption that "anyone who signs up can assume it". Don't assign a high-privilege role based on an attribute users can write
Whether you can notice a surge in SignUp or many permission-denied errors in identity pool temporary credential sessions (role session name CognitoIdentityCredentials) Monitor CloudTrail management events. If you also want to track identity pool operations and S3 object operations, enable data events

What was observed

According to Datadog's article, ARTEX is described as an AI-native penetration framework. When an operator gives it a target and an objective, multiple LLM agents make a plan and carry out the attack. ARTEX was published on GitHub by a developer in China in July 2026, and is said to have become closed source on October 8, after its fork count exceeded 12,000. The article also mentions that Microsoft links it to the threat actor Storm-0940.

In October 2026, Datadog observed traces of ARTEX running in "several" AWS environments. It is said to have focused on access to financial data, but the article does not state the number of affected organizations or a breakdown by industry. The observed activity was as follows.

  • All the sessions that could be confirmed started with unauthenticated S3 reads (GetObject, ListObjects, etc.). In CloudTrail they are recorded with userIdentity.type as AWSAccount and accountId as anonymous. The agent was often seen reading the frontend of a web app hosted on S3 and looking for build manifests and runtime configuration
  • From there it split into two lines. One uses leaked long-term access keys; in one case Datadog showed, the most frequently executed operation was a CloudWatch Logs search with FilterLogEvents. This article does not cover that line. The other is the Cognito line, which this article covers
  • In the Cognito line, as one example, on October 26 and 27 SignUp calls to an exposed user pool surged from a single IP address. In some calls the User-Agent contained the tool name, such as Mozilla/5.0 ARTeX, and the others were curl or browser strings

The flow from sign-up to exploration with temporary credentials

The following diagram is a conceptual diagram I made based on Datadog's article. Some registrations were rejected by the entry validation, but what stopped the exploration was the lack of permissions on the authenticated role.

  1. Prerequisites: Self-sign-up must be enabled on the user pool. In addition, the agent needs to know the app client ID of the user pool. To get as far as temporary credentials, there must also be an identity pool that accepts this user pool as an authentication provider. Datadog's article does not say where the agent obtained these IDs. However, an SPA calls SignUp and similar operations directly from the browser, and a SignUp call requires the app client ID (ClientId), so the app client ID is included in the frontend code or configuration. AWS's documentation on user creation policies also explains that for a user pool that accepts sign-ups, anyone on the internet who knows a public, discoverable app client ID can create users. Combined with the observation that it was looking at the S3 frontend, it may have obtained them from there
  2. Abuse: The agent called SignUp. This user pool had a PreSignUp Lambda trigger that verifies the tenant's domain, and some of the attempts were rejected by this trigger (invalid tenant domain, malformed domain format, tenant mismatch). There was also a schema error saying the type of custom:isAdmin could not be determined. The agent kept trying anyway. In the attempts where registration succeeded, it called SignUp, InitiateAuth, RespondToAuthChallenge, GetUser, and GetTokensFromRefreshToken in that order and obtained tokens. It then also tried Get* calls and ListUserPools. The article does not say how the registration confirmation step (ConfirmSignUp, etc.) was passed
  3. Impact: The agent exchanged the tokens for temporary AWS credentials through the identity pool. CloudTrail records the session using these credentials with the role session name CognitoIdentityCredentials. The agent tried almost the full range of read-only exploration: listing S3 buckets (ListBuckets), listing Secrets Manager secrets (ListSecrets), Systems Manager parameters (DescribeParameters), CloudWatch Logs log groups (DescribeLogGroups), and listing Lambda functions (ListFunctions20150331). All of them failed because the identity pool's authenticated role did not have the necessary permissions

If the authenticated role had allowed ListBuckets or ListSecrets, the agent could have retrieved the lists of buckets and secrets through the same flow.

Why this works

Self-sign-up is a setting that lets "anyone create a user"

Self-sign-up in a user pool is determined by AllowAdminCreateUserOnly in AdminCreateUserConfigType. According to the API reference, if it is true, only administrators can create users. If it is false, users can register themselves with the SignUp operation.

For an app where general users register, self-sign-up is a necessary feature. On the other hand, if you leave self-sign-up on in an internal or invitation-only app, people you didn't intend can also create users.

"Authenticated" in an identity pool does not mean "authorized"

An identity pool hands temporary credentials for an IAM role to authenticated users. The role-based access control documentation explains that you can configure a default role for authenticated users and a separate role for unauthenticated guests.

"Authenticated" here means being able to sign in to the user pool. If self-sign-up is enabled, even if you validate with PreSignUp, anyone who can prepare values that satisfy the conditions can create a user who can sign in. Therefore, the authenticated role can effectively be assumed by anyone. The permissions of the authenticated role need to be designed not as "permissions used by trusted employees or customers" but as "permissions that anyone on the internet can use".

Users can write to their own attributes

The schema error for custom:isAdmin that appears in Datadog's article indicates that the agent was trying to set this custom attribute. What the article shows is an error where it was rejected because the type of this attribute could not be determined. It does not say whether the agent was able to set it afterward or which app client it tried. Still, if users can write to an attribute used for authorization, they can raise their own privileges by setting a value such as custom:isAdmin themselves.

The user attributes documentation says the following on this point.

  • If you don't specify WriteAttributes for an app client, that app client can write to all standard and custom attributes. Users can change the values of their own attributes with UpdateUserAttributes. When registering with SignUp, an app client with write permission can also put values into attributes. Even for custom attributes made immutable, a value can be set when the user is created
  • Attributes are shared across app clients, so a value written from one app client is also included in tokens issued by other app clients

The same caution applies to the identity pool's rule-based mapping (a setting that selects a role according to token claims, that is, values such as user attributes). According to the best practices in the role-based access control documentation, if you map a claim that users can modify to a role, any user can assume that role.

Attempts continued after rejection

Datadog's article lists the reasons for rejection, such as tenant domain mismatches and schema errors. If such reasons are also returned to the caller in the SignUp response, they could serve as clues for guessing the validation conditions. In this case too, the agent continued trying after being rejected and went as far as registering.

Check whether your environment is affected

You can check the user pool and identity pool settings with the AWS CLI. Replace the parts in <...> with the IDs of your own environment. The region used is from your CLI profile or the AWS_REGION setting, so add --region if necessary. The commands are written based on the API references, and I have not included execution results. If you don't know the IDs, you can list them with aws cognito-idp list-user-pools --max-results 60 and aws cognito-identity list-identity-pools --max-results 60.

Whether self-sign-up is enabled can be checked with AdminCreateUserConfig in DescribeUserPool. If the value is anything other than true (including false or no value returned), proceed with the checks assuming self-sign-up is enabled.

aws cognito-idp describe-user-pool \
  --user-pool-id <ユーザープールID> \
  --query 'UserPool.AdminCreateUserConfig.AllowAdminCreateUserOnly'

Attribute write permissions are determined per app client, so first get the list of app clients with ListUserPoolClients.

aws cognito-idp list-user-pool-clients \
  --user-pool-id <ユーザープールID> \
  --query 'UserPoolClients[].ClientId'

For each app client you obtained, check WriteAttributes with describe-user-pool-client. According to the user attributes documentation, ReadAttributes and WriteAttributes are returned only when you have set non-default permissions. If the --query result is null, it is in the default state where all attributes can be written. Also, according to the same documentation, required attributes can be written by all app clients regardless of the WriteAttributes setting.

aws cognito-idp describe-user-pool-client \
  --user-pool-id <ユーザープールID> \
  --client-id <アプリクライアントID> \
  --query 'UserPoolClient.WriteAttributes'

Which user pools an identity pool accepts can be checked with CognitoIdentityProviders in DescribeIdentityPool. Also check whether guest access (AllowUnauthenticatedIdentities) is enabled. If it is true, temporary credentials for the unauthenticated role can be obtained without signing up, so check the permissions of the unauthenticated role from the same perspective.

aws cognito-identity describe-identity-pool \
  --identity-pool-id <IDプールID> \
  --query '{Providers: CognitoIdentityProviders[].ProviderName, Guest: AllowUnauthenticatedIdentities}'

Check the identity pool's roles with GetIdentityPoolRoles. Roles contains the authenticated and unauthenticated roles. RoleMappings contains the role assignment settings for each identity provider. If Type is Rules, the role is determined by the rules; if it is Token, the role included in the token (for a user pool, the role set on the group) is used.

aws cognito-identity get-identity-pool-roles \
  --identity-pool-id <IDプールID>

Check the role's permissions in IAM using the role name at the end of the ARN. Include the roles assigned through RoleMappings in addition to the roles in Roles.

aws iam list-attached-role-policies --role-name <ロール名>
aws iam list-role-policies --role-name <ロール名>

If the results match any of the following, a review is needed.

  • For a user pool that doesn't need self-sign-up, AllowAdminCreateUserOnly is not true
  • An attribute used for authorization is included in the WriteAttributes of one of the app clients (or WriteAttributes is left at the null default)
  • The authenticated role allows operations the app doesn't use (S3 bucket listing, Secrets Manager, Systems Manager parameters, etc.)
  • A RoleMappings rule assigns a high-privilege role based on an attribute users can write
  • AllowUnauthenticatedIdentities is true even though guest access isn't needed

Defense: decide whether self-sign-up is needed, and stop it with attributes and roles

Review the need for self-sign-up and attribute write permissions

If self-sign-up is not needed, set AllowAdminCreateUserOnly to true. If self-sign-up is needed in an app where general users register, don't rely only on entry validation such as PreSignUp; restrict attribute write permissions and the permissions of the authenticated role.

Remove attributes used for authorization (such as custom:isAdmin and custom:tenantId) from the WriteAttributes of all app clients, as the user attributes documentation mentioned earlier requires. The same documentation states that the best practice is to specify read and write permissions explicitly when creating an app client, and to allow only the minimum attributes the app needs.

You can also consider not including content in PreSignUp error messages that would let someone guess the validation conditions. This is an operational measure I came up with from Datadog's observations, and it is not a recommendation explicitly stated in the AWS documentation.

Restrict the identity pool's authenticated role

Allow the authenticated role only the operations and resources the app needs. When using rule-based mapping, as in the best practices of the role-based access control documentation, map high-privilege roles only to claims that users cannot set directly.

As in the example in the same documentation, attach two conditions to the role's trust policy: a condition that restricts it to your own identity pool with cognito-identity.amazonaws.com:aud, and a condition that requires cognito-identity.amazonaws.com:amr to be authenticated. In the JSON, put the identity pool ID in the <リージョン>:<GUID> format into <IDプールID>.

trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "<IDプールID>"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}

These conditions cannot exclude users who signed up themselves. Therefore, use the trust policy conditions together with restrictions on the permissions policy side.

Clues for detection with CloudTrail

User pool APIs (SignUp, InitiateAuth, etc.) are recorded in CloudTrail as management events. If you send the trail to CloudWatch Logs, you can use a query like the following from the documentation to check the count for each combination of source IP address and operation. Run it over bounded time periods and compare whether SignUp has suddenly increased from a specific IP address.

CloudWatch Logs Insights
filter eventSource = "cognito-idp.amazonaws.com"
| stats count(*) by sourceIPAddress, eventName

On the other hand, the identity pool's GetId and GetCredentialsForIdentity are CloudTrail data events and are not recorded by default. To track them, you need to enable data events on the trail and add an event selector for AWS::Cognito::IdentityPool. The Cognito CloudTrail documentation states that data events incur additional charges. APIs such as ListBuckets called with the exchanged temporary credentials are recorded as management events, so you can identify them by the role session name CognitoIdentityCredentials.

S3 GetObject and ListObjects are also data events and, according to the S3 documentation, are not recorded by default. In an environment where data events are not enabled, you cannot trace from CloudTrail the anonymous S3 reads (accountId is anonymous) that Datadog saw.

In addition, Datadog's article lists the following clues.

  • A large number of permission-denied errors. In this observation, they occurred in sessions with the role session name CognitoIdentityCredentials. The notation of errorCode differs by service, such as AccessDenied and AccessDeniedException
  • Enumeration of S3 objects, and instance role credentials being used from IP addresses outside AWS. The latter is a clue that is not limited to the Cognito line
  • The User-Agents (10, including Mozilla/5.0 ARTeX and artex-readonly/1.0) and 10 IP addresses in the appendix of the original article. However, the original article itself cautions that User-Agents can be changed

Summary

  • In Datadog's observations, the attack agent tried to sign up to an exposed Cognito user pool, and kept trying even after being rejected until it registered. It then went on to explore AWS with the identity pool's temporary credentials. The exploration failed because the authenticated role did not have the necessary permissions
  • There are three things to check in your settings
    • Whether self-sign-up is really needed (AllowAdminCreateUserOnly)
    • Whether users can write to the attributes used for authorization (WriteAttributes)
    • Whether the authenticated role's permissions and the role assignments are restricted on the assumption that "anyone can use them"
  • For detection, CloudTrail management events (a surge in SignUp) can be used. To also track identity pool operations and S3 object operations, you need to enable data events

I hope this helps someone.


References:

Share this article