
Testing CSV, PDF, and Excel Exports for Permission Bypasses
Export functions—CSV downloads, PDF reports, Excel spreadsheets—almost always get lighter authorization checks than the rest of the application. That makes them one of the most reliable places to find permission bypasses on a web-security assessment. In this post I'll walk through the exact techniques I use to test CSV, PDF, and Excel exports for access‑control gaps, from discovering hidden endpoints to automating the whole sweep with JavaScript.
The Overlooked Export Surface
A “Download CSV” button usually fires a request to /api/reports/export?type=csv. These endpoints rarely get the same scrutiny as the CRUD routes that power the UI. There's often an assumption that if the user is authenticated and can reach the button, they're authorized—but that ignores direct links, saved cURL commands, and parameter tampering.
I always treat export endpoints as separate APIs that need their own auth checks, because that's where most permission-bypass bugs slip through.
How Permission Bypasses Surface in Export Endpoints
Directly Accessing Endpoints Without Role Checks
An admin panel might restrict the “Export Users” button to admins, but the underlying endpoint /admin/export/users.csv might only verify the session, not the role. A low‑privilege user who guesses that URL can still download the same data.
GET /admin/export/users.csv
Cookie: session=lowpriv
If the response is 200 and the CSV contains sensitive fields, you have a clean bypass.
Parameter Tampering Across User Contexts
Many export endpoints accept identifiers to scope the data: ?projectId=42. If the backend trusts that value without confirming the requester owns project 42, you can walk through IDs to pull reports from other tenants—a classic IDOR on the export function.
for (let i = 1; i <= 100; i++) {
fetch(`/api/projects/${i}/export/pdf`)
.then(r => r.blob())
.then(blob => { /* check size / content */ });
}
Sensitive Data Leaks Hidden by Format (CSV, PDF, Excel)
The UI might only show a user's partial email, but the CSV export can include the full email, phone number, or internal IDs. Excel exports are especially prone to exposing hidden sheets or metadata that the UI never shows. I once ran into a report where the salary column was hidden in the UI table but still present in the exported XLSX.
Excel and PDF generators often use libraries that quietly include data from internal objects. Always inspect the raw output, not just the visible columns when you open the file.
A Practical Testing Methodology
Step 1: Discover and Map All Export Endpoints
I start by crawling the app as a low‑privilege user and collecting every export link. Then I pull the actual URLs from the page source or network tab—never rely on UI visibility alone. Plenty of apps have undocumented routes like /report/export/pdf that work if you know they exist.
Step 2: Test CSV Exports for Unauthorized Data
Request the CSV while logged in as a user who shouldn't have access. If you get a 200, comb through the content: are there extra rows, columns, or identifiers belonging to other users? Check whether the CSV includes deleted or inactive records that the UI filters out.
Step 3: Probe PDF Exports Through ID Enumeration
PDF export paths often follow predictable patterns like .../order/1234/pdf. Fire Intruder or a custom script at the range. Even if the PDF returns “Not Found” for unauthorized IDs, timing differences or error messages can leak resource existence, confirming a permission-bypass surface.
Step 4: Inspect Excel Exports for Hidden Data and Metadata
Download the XLSX and parse it with a library like xlsx in Node to surface hidden sheets or rows that are filtered in the UI. Also look for metadata—author names, internal file paths—that shouldn't leave the server.
Automating Tests with JavaScript
Wrapping these checks into a quick Node script using fetch and xlsx lets you iterate candidate endpoints and flag any export function that returns data it shouldn't:
const fs = require('fs');
const xlsx = require('xlsx');
async function checkExports(baseUrl, cookie) {
const endpoints = [
'/admin/export/users',
'/api/reports/transactions?format=csv',
'/api/orders/export/pdf?orderId='
];
for (const ep of endpoints) {
const res = await fetch(`${baseUrl}${ep}`, {
headers: { Cookie: cookie }
});
if (res.ok) {
const buffer = await res.arrayBuffer();
// Try parsing as Excel; if succeeds, inspect sheets
try {
const wb = xlsx.read(buffer, { type: 'buffer' });
console.log(`Parsed ${ep}: sheets=${wb.SheetNames.length}`);
} catch { /* not Excel */ }
}
}
}
This catches direct‑access bypasses and confirms whether the response is real data or just an HTML error page.
Fixing Export Permission Bypasses
The fix is straightforward: apply the same authorization check on CSV, PDF, and Excel export endpoints that the UI does before showing the button. If the view layer verifies that the user owns project X, the backend must repeat that check when the export request hits. In multi‑tenant apps, always resolve ownership from the session, not from the user‑supplied ID.
Add integration tests that log in as a low‑privilege user and attempt every export endpoint. The test should assert 403 or an empty response, not just rely on the UI hiding the button.
Further Reading
- OWASP File Upload Cheat Sheet – principles around file handling that apply to exports as generated files
- PortSwigger: Testing for Insecure Direct Object References (IDOR) – parameter tampering is often an IDOR in export endpoints

