Sample Report

What a SableOffensive Report Looks Like

Below is a redacted sample from a real engagement. Every report includes an executive summary, detailed findings with reproduction steps, and specific code-level remediation guidance.

Redacted for Privacy

All client-identifying information, URLs, and credentials have been removed. The vulnerability types, severity ratings, and remediation approaches shown are representative of real findings.

Every Report Includes

Executive Summary

High-level overview for founders and stakeholders

Scope & Methodology

What was tested and how

Risk Summary

Finding count by severity with risk score

Detailed Findings

Each vulnerability with reproduction & fix

Remediation Roadmap

Prioritized fix order based on impact

Penetration Test Report

SABLE-RPT-2026-XXXX

CONFIDENTIAL

Executive Summary

SableOffensive conducted a penetration test of [REDACTED]'s web application during February 2026. The assessment identified 2 critical, 1 high, 1 medium, and 1 low severity findings. The most critical issues involve unauthorized data access (IDOR) and exposed service credentials that bypass database security policies.

2
Critical
1
High
1
Medium
1
Low
3
Info

Detailed Findings

SABLE-001

Broken Object Level Authorization (IDOR) on User Profile API

CRITICALCVSS 9.1
Category: A01:2021 - Broken Access Control
Endpoint: GET /api/users/[REDACTED]
Description

The user profile API endpoint allows any authenticated user to access other users' profile data by modifying the user ID parameter. No server-side authorization check validates that the requesting user owns the requested resource.

Impact

An attacker can enumerate and access all user profiles including email addresses, billing information, and connected accounts. With approximately 2,400 users in the database, this exposes the complete user dataset.

Steps to Reproduce
  1. 1.Authenticate as User A (test account)
  2. 2.Send GET /api/users/{USER_A_ID} -- returns own profile (expected)
  3. 3.Change ID to User B: GET /api/users/{USER_B_ID}
  4. 4.Full profile data for User B is returned (unauthorized)
  5. 5.Increment IDs to enumerate all users
Remediation

Add server-side authorization middleware that validates the requesting user's session matches the requested resource. Example for Next.js API route:

// Before (vulnerable)
export async function GET(req, { params }) {
  const user = await db.users.findById(params.id);
  return Response.json(user);
}

// After (fixed)
export async function GET(req, { params }) {
  const session = await getSession(req);
  if (session.userId !== params.id) {
    return new Response('Forbidden', { status: 403 });
  }
  const user = await db.users.findById(params.id);
  return Response.json(user);
}
SABLE-002

Exposed Supabase Service Role Key in Client Bundle

CRITICALCVSS 9.8
Category: A02:2021 - Cryptographic Failures
Endpoint: JavaScript bundle: /_next/static/chunks/[REDACTED].js
Description

The Supabase service_role key is hardcoded in the client-side JavaScript bundle. This key bypasses all Row Level Security (RLS) policies and provides full database access.

Impact

An attacker with this key can read, modify, and delete any data in the Supabase database, bypassing all security policies. This is equivalent to direct database admin access.

Steps to Reproduce
  1. 1.View page source or open DevTools > Sources
  2. 2.Search for "supabase" in JavaScript bundles
  3. 3.Find service_role key: eyJhbGci...[REDACTED]
  4. 4.Use key with Supabase client to bypass RLS
  5. 5.Full CRUD access to all tables confirmed
Remediation

Move the service_role key to server-side only. Use the anon key for client-side operations and ensure RLS policies are properly configured.

// .env.local (never expose to client)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...

// next.config.js - ensure server-only
// DO NOT prefix with NEXT_PUBLIC_

// Server-side usage only:
import { createClient } from '@supabase/supabase-js'
const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY! // server only
)
SABLE-003

Cross-Site Scripting (XSS) via User Display Name

HIGHCVSS 7.5
Category: A03:2021 - Injection
Endpoint: POST /api/settings/profile
Description

The user display name field does not sanitize HTML/JavaScript input. When displayed in the team dashboard, the unsanitized name executes in other users' browsers.

Impact

An attacker can inject JavaScript that executes in other team members' browsers, enabling session hijacking, data exfiltration, and account takeover of admin users.

Steps to Reproduce
  1. 1.Navigate to Profile Settings
  2. 2.Set display name to: <img src=x onerror=alert(document.cookie)>
  3. 3.Save profile
  4. 4.Open team dashboard as another user
  5. 5.XSS payload executes in victim's browser
Remediation

Sanitize all user input before rendering. Use React's built-in XSS protection (avoid dangerouslySetInnerHTML) and add server-side input validation.

// Server-side validation
import { z } from 'zod';

const profileSchema = z.object({
  displayName: z.string()
    .min(1)
    .max(50)
    .regex(/^[a-zA-Z0-9 _-]+$/,
      'Only letters, numbers, spaces, hyphens')
});

// Validate before saving
const validated = profileSchema.parse(req.body);
SABLE-004

Missing Rate Limiting on Authentication Endpoints

MEDIUMCVSS 5.3
Category: A07:2021 - Identification and Authentication Failures
Endpoint: POST /api/auth/login
Description

The login endpoint has no rate limiting. An attacker can make unlimited authentication attempts, enabling brute force and credential stuffing attacks.

Impact

Attackers can perform credential stuffing using leaked password databases, or brute force weak passwords without any throttling or account lockout.

Steps to Reproduce
  1. 1.Send 100+ login requests in rapid succession
  2. 2.No rate limiting or CAPTCHA triggered
  3. 3.All requests processed normally
  4. 4.No account lockout after failed attempts
Remediation

Implement rate limiting with progressive delays and account lockout after failed attempts.

// Using upstash/ratelimit for Next.js
import { Ratelimit } from '@upstash/ratelimit';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, '60 s'),
});

// In auth handler:
const { success } = await ratelimit.limit(ip);
if (!success) {
  return new Response('Too many attempts',
    { status: 429 });
}
SABLE-005

Security Headers Not Configured

LOWCVSS 3.7
Category: A05:2021 - Security Misconfiguration
Endpoint: All responses
Description

The application does not set critical security headers including Content-Security-Policy, X-Frame-Options, and Strict-Transport-Security.

Impact

Missing headers increase the attack surface for clickjacking, MIME-type sniffing attacks, and reduce the effectiveness of browser security features.

Steps to Reproduce
  1. 1.curl -I https://[REDACTED].app
  2. 2.Missing: Content-Security-Policy
  3. 3.Missing: X-Frame-Options
  4. 4.Missing: Strict-Transport-Security
  5. 5.Missing: X-Content-Type-Options
Remediation

Add security headers in next.config.js or middleware.

// next.config.js
const securityHeaders = [
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains' },
  { key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self'" },
];

Full Reports Also Include

Remediation Timeline

Prioritized fix order -- which vulnerabilities to fix first based on exploitability and business impact.

Security Posture Score

Overall security rating from A to F, with specific areas of strength and weakness identified.

Code-Level Fixes

Copy-pasteable code snippets specific to your stack. Not generic advice -- real implementations.

Get Your Own Security Report

Same depth, same quality. Reports delivered within 24-48 hours. Starting at just $29 for MVPs.