← Blog
·9 min read

I Fixed 23 Vulnerabilities in 10 Minutes with Data Hogo

Case study: scanning a real Next.js + Supabase repo with Data Hogo. 23 findings, 3 critical. Here's what they were, how I fixed them, and how long it took.

Rod

Founder & Developer

I'm going to walk you through a real scan. Not a demo with a toy repository — a real Next.js + Supabase project I built while shipping fast and not thinking enough about security.

The scan found 23 vulnerabilities. Three were critical. I fixed all of them in about 10 minutes.

Here's exactly what showed up, why it mattered, and what the fix was.


The Project

A SaaS project: Next.js App Router, Supabase for the database and auth, deployed on Vercel. The kind of stack that comes out of a Cursor session where you're focused on shipping features, not auditing every line the AI writes.

The project had been in development for about six weeks. It had real users — around 200 of them. No security scanning had been done since the initial setup.

The score before the scan: 42 out of 100.

That number felt bad. Let me show you why it was that number.


The Scan Results

Critical Findings (3)

Critical #1: Supabase Service Role Key in Client-Side Code

The most dangerous finding. The Supabase service role key was referenced in a utility function that had been moved from a server component to a client component during a refactor. The key never made it into the build output directly, but the import chain meant it was being evaluated in a context where it could leak.

// BAD: Service role key in a file imported by a client component
import { createClient } from "@supabase/supabase-js";
 
// This key has admin access to your entire database
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY! // NEVER use in client-side code
);
// GOOD: Use anon key in client context, service role only in server context
// In a server action or API route:
import { createClient } from "@/lib/supabase/server";
 
// In client components:
import { createClient } from "@/lib/supabase/client"; // uses NEXT_PUBLIC_SUPABASE_ANON_KEY

The fix took 3 minutes: identify the import chain, move the function back to a server-only file, verify with a build that the key wasn't included in the client bundle.

Critical #2: Missing RLS on the user_data Table

Data Hogo's database rule scanner checks your Supabase RLS policies. The user_data table had RLS enabled but no actual policies defined — which in Supabase means the table is completely inaccessible (good by default), but any SELECT from a server context with the service role key bypasses RLS entirely.

The actual problem: a data export endpoint was using the service role key to query this table and wasn't filtering by user ID. Every authenticated user could trigger the export and get data from all users.

-- BAD: No user filter on endpoint using service role
SELECT * FROM user_data; -- returns all users' data
 
-- GOOD: Always filter by authenticated user
SELECT * FROM user_data WHERE user_id = auth.uid();

See the Supabase RLS security checklist for a full walkthrough of RLS policy patterns.

Critical #3: API Key Committed in Git History

A Resend API key had been committed to the repo 11 commits ago in a .env.example file update. The key was removed in the next commit, but it lived in git history. Data Hogo scans git history, not just the current HEAD.

The fix: rotate the key immediately (takes 30 seconds in the Resend dashboard), then optionally clean the git history if the repo is public. Since this was a private repo, rotating the key was sufficient.

If you find a committed secret, rotate it first. Don't spend time cleaning git history while the key is still active.


High Severity Findings (7)

High #1-4: Missing Security Headers

Four high-severity findings, all security headers missing from the deployed URL:

  • Content-Security-Policy — not set
  • Strict-Transport-Security — not set
  • X-Frame-Options — not set
  • Permissions-Policy — not set

This is extremely common. In our scan of 50 repositories, 87% were missing at least one of these headers on the deployed URL.

The fix for Next.js:

// next.config.ts
const securityHeaders = [
  { key: "X-Frame-Options", value: "DENY" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "X-DNS-Prefetch-Control", value: "on" },
  {
    key: "Strict-Transport-Security",
    value: "max-age=63072000; includeSubDomains; preload",
  },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
];
 
const nextConfig = {
  async headers() {
    return [{ source: "/(.*)", headers: securityHeaders }];
  },
};
 
export default nextConfig;

See the complete Next.js security headers guide for a full CSP configuration.

High #5: Vulnerable Dependency — jsonwebtoken 8.5.1

jsonwebtoken 8.5.1 has a known vulnerability (CVE-2022-23529) related to header injection. The fix is upgrading to 9.0.0+.

npm install jsonwebtoken@latest

High #6: Unvalidated Redirect in Auth Callback

An OAuth callback route was accepting a redirect_to parameter without validating it against an allowlist. This allows open redirect attacks — an attacker sends a user to your legitimate OAuth flow but then bounces them to a malicious URL after authentication.

// BAD: Trusting the redirect_to parameter directly
const redirectTo = searchParams.get("redirect_to") ?? "/dashboard";
return NextResponse.redirect(new URL(redirectTo, request.url));
 
// GOOD: Validate against an allowlist of safe paths
const ALLOWED_REDIRECTS = ["/dashboard", "/settings", "/repos"];
const rawRedirect = searchParams.get("redirect_to") ?? "/dashboard";
const redirectTo = ALLOWED_REDIRECTS.includes(rawRedirect)
  ? rawRedirect
  : "/dashboard";
return NextResponse.redirect(new URL(redirectTo, request.url));

High #7: No Rate Limiting on Auth Endpoints

The login and signup endpoints had no rate limiting. A brute force attack on user passwords would be unrestricted. The fix: add rate limiting middleware.


Medium Severity Findings (9)

The medium findings included:

  • 3 instances of console.log with objects that could contain user data (not PII directly, but user IDs and email hashes)
  • 2 API routes missing explicit Content-Type: application/json response headers
  • 1 Supabase query using select('*') on a table with columns that didn't need to be returned to the client
  • 1 commented-out debug endpoint that was still in the codebase
  • 2 dependency updates with minor vulnerabilities (not critical, but worth patching)

These took about 15 minutes collectively. The console.log instances were the ones that needed most thought — verifying that the logged objects couldn't surface actual user data in production logs.

Informational Findings (4)

Informational findings flag patterns worth knowing about but not actively dangerous. These included using Math.random() for generating IDs (not cryptographically secure, but not a security flaw in the context it was used) and a couple of missing autocomplete="off" attributes on sensitive form fields.


The Score After

After fixing the 3 critical and 7 high severity findings, I ran a second scan.

Score: 87 out of 100.

That 45-point improvement came from:

  • Fixing the critical findings: +18 points
  • Adding security headers: +15 points
  • Patching the vulnerable dependency: +7 points
  • Fixing the open redirect: +5 points

The medium and informational findings that remained dropped the score slightly from a theoretical maximum. They're on the backlog.


What the 10 Minutes Actually Looked Like

Let me be specific about the time:

  • Minutes 1-2: Read the scan results, prioritize by severity
  • Minutes 3-5: Fix the service role key import (the actual refactor)
  • Minutes 5-6: Fix the data export endpoint to filter by user ID
  • Minutes 6-7: Rotate the committed Resend API key
  • Minutes 7-9: Add security headers to next.config.ts, deploy
  • Minutes 9-10: Upgrade jsonwebtoken, push

The open redirect and rate limiting fixes happened in a separate 15-minute session — they were a bit more involved and I wanted to test them properly.

The point: the critical findings were fast. The reason security feels overwhelming is the list. When you have 23 findings staring at you, it looks like a week of work. When you sort by severity and address the top three, it's a morning.


Run the Same Scan on Your Repo

If you're reading this and thinking "I should check if my project has any of these," you're right. You should.

The scan takes under 5 minutes. The findings are specific — not "we found potential issues" but "line 47 of lib/supabase.ts uses the service role key in a client-accessible context."

Scan your repo free →


Frequently Asked Questions

What does a typical Data Hogo scan find?

In our scan of 50 repositories, the most common findings were: missing security headers (87% of repos), vulnerable npm dependencies (73%), and at least one committed secret or credential (41%). The average security score before a first scan was 52 out of 100. After addressing the top findings, the average score rose to 81.

How long does a Data Hogo scan take?

Most scans complete in 2-4 minutes for a typical Next.js or Python repo. Larger repositories (100k+ lines of code) may take 5-8 minutes. The scan runs all six engines in parallel — secrets, dependencies, code patterns, config, headers, and DB rules — so you're not waiting for them to run sequentially.

What is a critical vulnerability versus a high vulnerability?

Critical findings represent immediate risk: exposed credentials that can be used right now, or code patterns that allow unauthenticated access to protected data. High findings are serious vulnerabilities that require specific conditions to exploit. Both should be addressed before a production launch. Medium and low findings are worth fixing but don't represent immediate risk.

Can Data Hogo fix vulnerabilities automatically?

Yes, on the Pro plan ($39/month). Data Hogo uses Claude to generate fix code and opens a pull request in your GitHub repo. The fix is explained in plain English so you can review it before merging. The free and Basic plans show you every finding with instructions — you apply the fix manually.

What should I fix first after a scan?

Critical findings first, always. If you have an exposed API key or credential, rotate it immediately — the exposure may have already happened. Then address high severity findings before the next deployment. Medium and low findings can be addressed in normal development cycles.

case studysecurity scan resultsdata hogosupabasenextjsvibe-codingfix vulnerabilities