See exactly what you’ll get.
This is an anonymised, illustrative example — a fictional SaaS, realistic findings. Your real report follows the same structure: a summary you can read, a technical section your developer (or your AI coding tool) can act on directly, and honest limitations.
Executive summary
Elitor performed a grey-box external security assessment of “Acme” (a fictional B2B SaaS) over a five-day window. Testing covered authentication, access control across three roles, the public API, the billing flow and common web vulnerability classes, aligned to the OWASP Testing Guide.
We identified two High, one Medium and one Low issue. The two High findings are broken access control between tenants and a replayable password-reset token — both allow access to data or accounts that shouldn’t be reachable, and both should be fixed before further growth. Access control elsewhere, session handling and the payment integration were otherwise sound. This report is a point-in-time snapshot of the tested scope; it is not a guarantee that the application is free of all vulnerabilities.
Scope & approach
Findings
Broken access control — any user can read another tenant’s invoices
What it means for you. A logged-in customer could read any other customer’s invoices — names, amounts, line items — by changing the numeric id in the URL. A data-protection incident and a breach of tenant isolation.
Steps to reproduce
- Sign in as a low-privilege member of Tenant A; open an invoice — note the id (e.g. 4021).
- Request GET /api/v2/invoices/4022 (an id belonging to Tenant B).
- Server returns 200 with Tenant B’s full invoice. Control case: the sibling /api/v2/orders/{id} correctly returns 403, confirming the check is missing here specifically.
Fix. Enforce ownership on the server: scope the query by the authenticated tenant (WHERE tenant_id = :caller_tenant), and return 404/403 for out-of-tenant ids. Don’t rely on the client only ever requesting its own ids.
Password-reset token does not expire and can be replayed
What it means for you. Reset links stayed valid indefinitely and could be used more than once. A single leaked link (browser history, an old email, a shared inbox) allows account takeover long after it was issued.
Steps to reproduce
- Request a reset link for a test account; complete the reset.
- Re-submit the same token an hour later — it is accepted again and a second reset succeeds.
- Confirmed the token carried no server-side expiry or single-use flag.
Fix. Bind reset tokens to a short TTL (e.g. 30–60 min), mark them single-use, and invalidate all outstanding tokens plus existing sessions once a reset completes.
Subscription tier enforced only in the client
What it means for you. Paid-only features (bulk export) were hidden in the UI but not enforced on the server, so a free-tier user could call the endpoint directly and use them — lost revenue and a broken paywall.
Steps to reproduce
- As a free-tier account, observe the export button is hidden.
- Call POST /api/export directly with a valid session — the export runs and returns paid-tier data.
Fix. Check the caller’s entitlement on the server for every gated action, not just in the UI. Treat the client as untrusted for anything tied to billing.
Missing security headers (CSP, HSTS, X-Content-Type-Options)
What it means for you. Defence-in-depth headers were absent, making the app more exposed to content-injection and downgrade attacks if another flaw is introduced later.
Steps to reproduce
- Inspect response headers on the app’s main routes.
- No Content-Security-Policy, Strict-Transport-Security, or X-Content-Type-Options present.
Fix. Add a baseline CSP, HSTS (with a sensible max-age), X-Content-Type-Options: nosniff, and Referrer-Policy. Most frameworks and CDNs can set these globally.
Areas tested and found sound
Session management (rotation, logout, cookie flags), horizontal access control on orders and projects, the Stripe payment integration, file-upload handling, and SSRF via the link-preview feature were all tested and behaved correctly. A clean area is still a result — it tells you where you’re already strong.
Limitations
This assessment reflects the application as it existed during the testing window and the scope agreed above; areas and test types outside that scope were not examined. It is not a formally accredited (CREST/CHECK) penetration test, and it does not constitute a guarantee that the application is secure. Decisions on which findings to remediate remain yours.
Want one for your app?
Every finding delivered prompt-ready, so your developer or AI coding tool can start fixing straight away.