Back to resources
Research

One Query on Amazon Athena Returned 43 Other AWS Accounts' SQL

One extra field on an Athena API call returned the full text of SQL queries that other AWS accounts had just run, along with the account IDs that ran them. No query results came back, but statement text carries filter values, inserted rows and table structure. We reported it to AWS on 6 August 2026 and they closed it globally in four days. No customer action is required.

Oren Yomtov
September 6, 2026
Table of Contents

We were mapping how Amazon Athena resolves table names, which is the kind of unglamorous work that turns up most of what we find. Athena refused the query we expected it to refuse. Then we moved one word out of the SQL and into a parameter sitting beside it, sent the same thing again, and got back a result set full of SQL statements. None of them were ours.

The first assumption when that happens is that you have made a mistake, so we checked the account IDs against our own twice before we believed it. They belonged to other AWS accounts, and each row carried the complete statement text, the account ID that submitted it, when it ran and whether it finished. These were accounts we had never heard of, with no shared resource between us and no grant of any kind in either direction. One response carried queries from 43 separate identities. It worked because the check that was supposed to stop us was reading the SQL, and the access did not arrive through the SQL.

We reported it to AWS on 6 August 2026 and they had it closed in four days, in every Region, with nothing for you to do about it.

Trino keeps a table of what just ran

Athena is AWS's serverless SQL service. You point it at data sitting in S3 and query it without running a database of your own, which is most of the appeal. Under the hood, Athena engine v3 is Trino, and Trino brings its own furniture with it.

Part of that furniture is a catalog named system, and inside it a table called system.runtime.queries. It holds what ran recently on the infrastructure you are talking to: query ID, the user who submitted it, state, timing, and the full text of the SQL. On a Trino cluster you own, that table earns its keep, because it is how you find the query eating your workers at four in the afternoon.

Athena is not a cluster you own. Queries on engine v3 do not necessarily run on dedicated, single-tenant infrastructure. Nothing about system.runtime.queries had changed and nothing about it is a bug. What changed is who else is standing in the room when you read it.

The front door was locked

AWS clearly knew about that table, because the obvious way in is closed. Write system into the SQL and Athena refuses. StartQueryExecution answers Queries of this type are not supported and hands back no execution ID at all, so there is no query, no result, and nothing to poll.

That is the correct answer. It is just not the only place a catalog name can arrive.

The same request had a second door

StartQueryExecution takes the SQL text, and beside it a structure called QueryExecutionContext that carries Database and Catalog. That field exists so unqualified table names resolve to something: write SELECT * FROM queries and the context tells the engine where queries live.

So we left the catalog out of the SQL and passed Catalog=system in the context instead. Same table, same target, same intent, expressed in a field the validator was not reading.

The query ran.

‍

A SELECT statement is not metadata

Only the statements leaked, never the results. That sounds like the harmless half, but it isn't.

A SQL statement is a description of what somebody was looking for, written in enough detail for a machine to go and find it. Filter on a customer's email address and that email address is in the query. Filter on an account number, an order ID, a patient identifier, and the identifier is in the query. INSERT statements are worse, because the values sit inside the text. Every field of the row somebody just wrote is spelled out in the statement. A date range in a WHERE clause tells you when something happened without anybody handing over the something.

Then there is the shape of the data, which travels whether or not any single value is sensitive. Table names, column names, joins, and the way somebody chooses to filter tell you how a business models its own operations, what it collects, and where the interesting material sits. If you were planning to spend real effort on a company, an hour of their analysts' queries is a better map than most of their internal documentation.

And every row arrived with the account ID that submitted it, so none of this is a pile of anonymous strings. It is attributable.

We counted rather than read

That is exactly why we did not read any of it. To learn what those statements contained without looking at anyone's data, we counted pattern matches inside the engine and returned only the integers. The text never left AWS, and we never saw another account's statement in a form we could read.

The counting is where the scale showed up. Across 200 query landings we saw 2,794 rows and 2,590 of them belonged to other accounts.
‍

This is the normal case and not a rare race. 102 of 109 landings returned at least one other account's row, and one landing held 43 distinct identities. system.runtime.queries also keeps about an hour of history rather than a moment of it. When we looked at how old the rows were, 41% of the other accounts' rows were already at least 15 minutes old, and the oldest reached back 54 minutes.

Disclosure timeline

6 August. We reported the finding to AWS. They answered the same day and opened a review.

8 August. The call stopped working.

10 August. AWS completed the fix in every Region. We retested in every enabled Region and the block held in all of them.
‍

AWS was fast and professional: they replied within hours, kept us updated, and shipped the fix to every Region in four days. There is no CVE, because AWS assigns one where a customer has to act, and here nobody outside AWS had anything to install or change.

The access paths you do control

This one belonged to AWS and AWS closed it. No setting on your side would have changed the outcome.
The access inside your own account is a different matter, and that is where most of the reachable risk sits, usually as permission somebody granted for a good reason years ago and nobody has taken back.

‍Act Security models what is actually reachable inside your cloud, then ships the least privilege fix as pull requests to the Terraform or CloudFormation you already run.