Example security report

This is what you receive after a check. Sample data for a fictional app; every real report follows the same structure.

Security check completed for yourapp.com

Overall risk

Moderate

12 checks completed

  • 0Critical
  • 2High
  • 3Medium
  • 4Low
High

User data accessible without proper authorization

Affected
GET /api/users/{id}

What we found

The endpoint returns a full user record for any numeric id, as long as the request carries a valid session for any account.

Why it matters

Another user could read information belonging to a different account, including email address and billing details.

What could happen

A signed-in user increments the id in the URL and downloads the profile of every customer, one request at a time.

How to fix it

Check that the authenticated user is allowed to see the requested record before returning it. Do this on the server, on every request.

Fix with AI

Review the authorization logic protecting GET /api/users/:id.

Ensure that the authenticated user can only read their own record, or records they are explicitly allowed to access (for example as a workspace admin).

Add the ownership check on the server side before any database read. Return 404 (not 403) for records the caller is not allowed to see, so ids cannot be enumerated.

Write a test that signs in as user A, requests user B's id, and asserts the request fails.
CWE
CWE-639: Authorization Bypass Through User-Controlled Key
CVSS
7.5 (High)
Request
GET /api/users/1042 Cookie: session=<valid session for user 87>
Response
200 OK { "id": 1042, "email": "…", "plan": "pro", "stripeCustomerId": "cus_…" }
Evidence
Two different user ids returned 200 with full records from the same session.
Technical remediation
Load the record scoped to the caller (WHERE id = ? AND owner_id = ?), or enforce a policy layer such as row-level security. Reject with 404 when no row matches.
High

Administrative endpoint reachable without authentication

Affected
POST /api/admin/export

What we found

An export endpoint accepted requests without any session cookie or API key and started a background job.

Why it matters

Anyone who guesses or discovers the URL can trigger a data export, which may include customer records.

What could happen

An attacker requests exports repeatedly and collects the resulting files from the storage bucket if its links are predictable.

How to fix it

Require an authenticated admin session on every admin route, and verify the role on the server rather than in the frontend.

Fix with AI

Audit every route under /api/admin/*.

Add server-side middleware that rejects any request without a valid session whose role is "admin". Do not rely on the client hiding the admin UI.

Make sure POST /api/admin/export is covered, and log every export request with the acting user id.

Add a test that calls the endpoint with no session and expects a 401.
Medium

Login form has no rate limiting

Affected
POST /api/auth/login

What we found

Fifty failed login attempts in under a minute were accepted without any slowdown, lockout or challenge.

Why it matters

Weak or reused passwords can be guessed automatically when there is nothing limiting attempts.

What could happen

A script tries common passwords against known email addresses until one works.

How to fix it

Limit attempts per account and per IP address, and add a short delay or a challenge after repeated failures.

Fix with AI

Add rate limiting to POST /api/auth/login.

Limit failed attempts to roughly 5 per account per 15 minutes and 20 per IP per 15 minutes. After the limit, respond with 429 and a Retry-After header.

Keep successful logins unaffected. Store counters in the existing cache or database, not in process memory, so they survive restarts and multiple instances.

Add a test that sends 6 wrong passwords and expects the 6th to return 429.
Medium

Error pages reveal framework and stack traces

Affected
GET /api/projects/abc

What we found

An invalid id produced a stack trace with file paths, framework version and the database driver in use.

Why it matters

Details like these make it much easier to find a matching known vulnerability or to craft a targeted attack.

What could happen

An attacker learns the exact library versions and looks up published exploits for them.

How to fix it

Return a generic error to users and keep the full trace in your server logs only.

Fix with AI

Production error responses must not include stack traces, file paths or dependency versions.

Add a global error handler that returns a generic JSON body ({ "error": "Something went wrong" }) with the correct status code, and logs the full error server-side.

Confirm the framework is running with production settings in the deployed environment.
Low

Browser protections are not enabled

Affected
All pages

What we found

Responses are missing Content-Security-Policy, X-Content-Type-Options and Referrer-Policy headers.

Why it matters

These headers tell the browser to block a whole class of injection and data-leak attacks for free.

What could happen

If any input is ever rendered unsafely, a script injection would run with nothing in the browser stopping it.

How to fix it

Add a small set of security headers at the hosting or framework level.

Fix with AI

Add the following security headers to every response:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Start CSP in report-only mode if the app loads third-party scripts, then tighten it.

The sample above shows 5 of the 9 findings in the full report. Low findings with the same shape have been left out for readability.

Want this for your app?

One-time security checks from €59. One fix verification re-check included.

Check my app