You spent months on authentication, permissions and audit logging. Then you shipped an export button, and any user with read access can download the entire dataset they can see, in one click, with no limit and no alert.
Export is the shortest path from "has an account" to "has your data on a laptop". It is built early because customers ask for it, and it is almost never reviewed as a data egress channel, because it is a feature rather than a vulnerability.
This post is what to check about yours. For whoever owns a product that lets people download their data, which is most products.
Why it gets missed
It is a feature request, not a security change. It arrives through the product backlog, gets built in a sprint, and never passes a security review, because nobody classified "let users download their data" as a risk.
It looks like a read. The permission check asks whether the user may view this data. They may. The check does not consider that viewing one record and extracting fifty thousand are different acts with different consequences.
It is often the only unbounded operation in the product. Every other endpoint is paginated. Export deliberately is not.
The five questions
Who can export, and is that the same as who can read?
Frequently the export control reuses the read permission, so a read-only user, a support account, an integration token or a trial user can extract everything. Ask whether export deserves its own permission. In most products it does, and separating it is a small change.
Is there a volume limit, and does anyone see it?
A support engineer exporting one customer's records is normal. The same account exporting every customer is not, and if nothing distinguishes them you have no control and no signal. Even a crude threshold, with an alert above it, converts an invisible act into a visible one.
What is actually in the file?
Exports frequently include more than the interface shows: internal identifiers, soft-deleted rows, fields hidden in the UI but present in the underlying query, and joined data from related tables. The CSV is generated from a query, and the query often selects more than the screen renders.
Export one, open it, and read every column. This takes ten minutes and regularly finds something.
Is the export scoped to the tenant?
The classic failure. The list endpoint filters by tenant; the export path was written separately, against a different query, and the filter was applied in the controller rather than the repository. Test it directly, as a user of tenant A, and confirm no row from tenant B appears.
Where does the file go, and for how long?
Large exports are usually generated asynchronously and stored somewhere for download. Then: is that location authenticated, is the link guessable, does it expire, and is the file deleted afterwards. A bucket of historical exports with predictable object names is a data breach waiting to be found, and it is outside every control you built for the primary datastore.
What to build
A separate export permission, not granted by default with read.
Logging of every export, with the requester, the scope, the row count and a timestamp. This belongs in your audit log, not only in application logs, because it is precisely the record you will want during an investigation. If you cannot answer "who exported what last month", that is the gap.
A threshold with an alert. Not a block, necessarily. A notification to someone when an export exceeds a size that is unusual for that account. Most insider incidents involve a volume that would have looked odd if anyone were looking.
Expiring, authenticated download links. Signed URLs with a short lifetime, and deletion of the generated file after a retention period measured in hours or days.
Rate limiting on export specifically. It is expensive to generate and it is the operation you least want repeated in a loop.
Notify the account. For large exports, an email to the account owner or administrator saying an export occurred. This is how compromised-account exfiltration gets caught, and it costs one message.
Test it like an attacker would
# Does export respect tenant scoping? Run as a user of tenant A.
curl -s -H "Authorization: Bearer $TENANT_A_TOKEN" \
"https://api.example.com/export/records?format=csv" -o a.csv
grep -c "tenant_b_identifier" a.csv # want 0
# Does a read-only user get the same file?
curl -s -H "Authorization: Bearer $READONLY_TOKEN" \
"https://api.example.com/export/records?format=csv" -o ro.csv
diff <(head -1 a.csv) <(head -1 ro.csv)
# Is the generated file reachable without authentication?
curl -s -o /dev/null -w "%{http_code}\n" "$DOWNLOAD_URL_WITHOUT_TOKEN"
# Does anything stop fifty exports in a row?
for i in $(seq 1 50); do
curl -s -o /dev/null -w "%{http_code} " \
-H "Authorization: Bearer $TOKEN" "https://api.example.com/export/records"
done
If that last loop returns fifty 200s and nobody hears about it, you have an unbounded, unmonitored extraction endpoint.
The regulatory angle
Under GDPR, users have a right to their data in a portable format, so export is not optional. What is required alongside it is that the export is scoped to that person, that it is delivered securely, and that you can demonstrate the process.
The failure mode here is a data subject access request fulfilled by exporting a broad dataset and sending it over email. That is a disclosure incident wearing the costume of a compliance activity, and it happens.
The concession
Every control here adds friction to a feature customers genuinely want, and some of it will be unwelcome. A customer who exports their data monthly for their own reporting does not want a threshold alert, and an enterprise buyer moving twenty gigabytes will not accept a row limit.
So the answer is not restriction, it is visibility with sane defaults. Log everything, alert on unusual patterns rather than on absolute size, and make the limits configurable per account so the legitimate heavy user can be granted a higher threshold deliberately, by someone, with a record.
The implication
Your threat model probably covers an attacker breaking in. It may not cover a valid user, or a stolen session, using a feature exactly as designed to take everything at once.
Export is that feature. The question is not whether someone could, but whether anyone would know.