You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TagResource and ListTagsOfResource accept a stream ARN for a stream that does not exist #452
validate_resource_arn in crates/engine/src/tagging.rs checks only that the table named in a stream ARN exists. TagResource on table/<name>/stream/<label> succeeds for a label the table never had and stores the tags under that ARN, and ListTagsOfResource returns them. ListTagsOfResource on table/<name>/stream/* returns 200.
What DynamoDB does
Measured on 2026-10-22: TagResource and ListTagsOfResource on a stream ARN with a label the table never had return ResourceNotFoundException "Requested resource not found: ResourceArn: not found". On table/<name>/stream/* and table/*/stream/<label> they return ValidationException "Invalid StreamArn".
Effect
Tags can be stored and listed for a stream that does not exist. DynamoDB does not evaluate stream tags for authorization, and with #419 ExtendDB does not either, so no IAM decision depends on them.
Suggested fix
Resolve the label against the table's streams before storing or listing tags. Return ResourceNotFoundException with the service's message for an unknown label, and ValidationException "Invalid StreamArn" for a wildcard table name or label. The answer for a disabled stream's label was not measured.
Two more cases from the same check, measured on a SQLite server built from main at 1d0d8f4, region us-east-1, a principal allowed dynamodb:* on *. Table tagrows-3a565fd5 with a stream, LatestStreamArn.../table/tagrows-3a565fd5/stream/2026-10-31T11:28:11.847.
The check also accepts the issued stream ARN with a trailing space. TagResource on .../stream/2026-10-31T11:28:11.847 with k=trailing space: 200. ListTagsOfResource on that spelling: 200, that tag, as its own row beside the issued ARN's. The service answer for this spelling was not measured. An exact label compare refuses it together with the unknown label.
The rows outlive the table and come back on a new table of the same name. TagResource on .../stream/2000-01-01T00:00:00.000 with k=unknown label: 200. DeleteTable, then wait until DescribeTable answers ResourceNotFoundException: the tags table still holds that row, the trailing-space row and the issued ARN's row. CreateTable with the same name (new label 2026-10-31T11:28:12.956), then ListTagsOfResource on the unknown-label ARN from before the delete: 200, k=unknown label. DeleteTable again: all three rows are still there.
DeleteTable deletes only the rows whose resource_arn is the table ARN, on all four backends (storage-sqlite/src/delete_table.rs:118, storage-postgres/src/delete_table.rs:239 and :617, storage-cassandra/src/delete_table.rs:181, storage-mongodb/src/table_engine.rs:622; SQLite run, the other three read). Keeping the issued ARN's rows matches the service (#607). Rows under a label the table never had have no service counterpart, since the service refuses the write.
So resolving the label, as suggested above, stops new rows and leaves the ones already written. The fix or the upgrade has to say what happens to those.
The check also accepts the issued stream ARN with a trailing line feed, like the trailing space above, and UntagResource accepts both. A Deny on the stream ARN does not stop either spelling.
Measured on a SQLite server built from e3b0f96 (the head of #509), region us-east-1. Table <T> with a stream, LatestStreamArn.../table/<T>/stream/<label>. The caller is allowed dynamodb:* on * and denied dynamodb:* on the table ARN and on that stream ARN.
ResourceArn
TagResource, UntagResource and ListTagsOfResource
.../stream/<label>
AccessDeniedException
.../stream/<label> and a trailing space
200
.../stream/<label> and a trailing line feed
200
A leading space got "ARNs must start with 'arn:'". An ARN with another partition, service or region, or with one of those fields in upper case, got "Invalid TableArn". Each spelling got TagResource, then UntagResource of the same key, then ListTagsOfResource. Afterwards an admin's ListTagsOfResource on .../stream/<label> returned no tags.
Authorization matches the tag operations against an ARN that keeps the label as written. extenddb_core::arn::parse keeps the label (crates/core/src/arn.rs:110), and tag_resource_arn puts it back into the canonical ARN (crates/server/src/authz_resource.rs:154-167), so a Deny on the issued ARN does not match a label with whitespace after it. I read that code on main at 03baa32 and ran the requests only on e3b0f96, where the policies are matched against the ResourceArn as written, which keeps the whitespace as well. The tags then go under the spelling as written, as the comment above shows for the trailing space. The service's answer for these spellings was not measured.
Activity
LeeroyHannigan commentedon Oct 31, 2026
Two more cases from the same check, measured on a SQLite server built from main at 1d0d8f4, region us-east-1, a principal allowed
dynamodb:*on*. Tabletagrows-3a565fd5with a stream,LatestStreamArn.../table/tagrows-3a565fd5/stream/2026-10-31T11:28:11.847.The check also accepts the issued stream ARN with a trailing space. TagResource on
.../stream/2026-10-31T11:28:11.847withk=trailing space: 200. ListTagsOfResource on that spelling: 200, that tag, as its own row beside the issued ARN's. The service answer for this spelling was not measured. An exact label compare refuses it together with the unknown label.The rows outlive the table and come back on a new table of the same name. TagResource on
.../stream/2000-01-01T00:00:00.000withk=unknown label: 200. DeleteTable, then wait until DescribeTable answers ResourceNotFoundException: thetagstable still holds that row, the trailing-space row and the issued ARN's row. CreateTable with the same name (new label2026-10-31T11:28:12.956), then ListTagsOfResource on the unknown-label ARN from before the delete: 200,k=unknown label. DeleteTable again: all three rows are still there.DeleteTable deletes only the rows whose
resource_arnis the table ARN, on all four backends (storage-sqlite/src/delete_table.rs:118,storage-postgres/src/delete_table.rs:239and:617,storage-cassandra/src/delete_table.rs:181,storage-mongodb/src/table_engine.rs:622; SQLite run, the other three read). Keeping the issued ARN's rows matches the service (#607). Rows under a label the table never had have no service counterpart, since the service refuses the write.So resolving the label, as suggested above, stops new rows and leaves the ones already written. The fix or the upgrade has to say what happens to those.
LeeroyHannigan commentedon Nov 3, 2026
The check also accepts the issued stream ARN with a trailing line feed, like the trailing space above, and UntagResource accepts both. A Deny on the stream ARN does not stop either spelling.
Measured on a SQLite server built from e3b0f96 (the head of #509), region us-east-1. Table
<T>with a stream,LatestStreamArn.../table/<T>/stream/<label>. The caller is alloweddynamodb:*on*and denieddynamodb:*on the table ARN and on that stream ARN.ResourceArn.../stream/<label>AccessDeniedException.../stream/<label>and a trailing space.../stream/<label>and a trailing line feedA leading space got "ARNs must start with 'arn:'". An ARN with another partition, service or region, or with one of those fields in upper case, got "Invalid TableArn". Each spelling got TagResource, then UntagResource of the same key, then ListTagsOfResource. Afterwards an admin's ListTagsOfResource on
.../stream/<label>returned no tags.Authorization matches the tag operations against an ARN that keeps the label as written.
extenddb_core::arn::parsekeeps the label (crates/core/src/arn.rs:110), andtag_resource_arnputs it back into the canonical ARN (crates/server/src/authz_resource.rs:154-167), so a Deny on the issued ARN does not match a label with whitespace after it. I read that code on main at 03baa32 and ran the requests only on e3b0f96, where the policies are matched against theResourceArnas written, which keeps the whitespace as well. The tags then go under the spelling as written, as the comment above shows for the trailing space. The service's answer for these spellings was not measured.