Back to blog

Your App Scored 48/100. Here's What That Actually Means.

A single number can't capture your entire security posture — but it can tell you exactly where to focus. Here's how the VibeShield score works, with a real example of going from 48 to 78 in 10 minutes.

June 30, 20267 min read
security-score
devsecops
scoring

Header image

A founder scanned his production app last week. The number came back: 48 out of 100.

He panicked. "Is my app broken? Am I going to get hacked? Do I need to take it down?"

No. He needed to add five headers and fix two cookie flags. It took ten minutes. His next scan scored 78.

Here's what that number actually means — and how to make yours go up.

Why a Single Number?

Most security tools give you a list of findings and call it a day. If you're a security engineer, that's useful — you speak CVE, you dream in CVSS, you know what "missing Content-Security-Policy header" implies for your threat model.

If you're a solo founder who built your app with Bolt last weekend, a list of 14 findings with titles like "Missing X-Content-Type-Options" is just noise. You don't know which one matters. You don't know if you should fix everything or just the scary-sounding ones. You don't know if 14 findings is normal or catastrophic.

The score solves that. It collapses everything into one number — 0 to 100 — and every deduction maps to something concrete you can fix. Think of it like a credit score. One bad reading won't ruin you, but a downward trend means something changed, and you should figure out what.

How the Score Is Built

Every scan starts at 100. For each finding, we subtract points. The formula is simple:

deduction = severity_weight × impact_multiplier

Figure 3: The scoring formula — every finding deducts based on severity and attack impact.

Here are the severity weights:

SeverityWeightExample Finding
Critical25Exposed live API key (Stripe, OpenAI, AWS)
High15Missing CSP, missing HttpOnly on session cookie
Medium8Missing X-Frame-Options, missing X-Content-Type-Options
Low3Missing Referrer-Policy, server reveals version header
Info1Cookie missing explicit Path, optional hardening

Figure 1: Severity weights and example findings for each level.

Impact multipliers add weight when the finding enables a serious attack:

So two exposed API keys look like this: 25 × 1.5 × 2 = 75 points gone. Your score drops from 100 to 25. That feels harsh — and it should. An exposed Stripe live key means someone can charge cards on your account right now. That's not a configuration gap. That's an active breach waiting to happen.

A missing Referrer-Policy, by contrast: 3 × 1.0 = 3 points. Worth fixing, but not an emergency.

The floor is 0. You can't score below zero no matter how many findings you have.

A Real Example: 48 → 78

A production Next.js app scanned at 48 with these findings:

FindingSeverityDeduction
Missing Content-Security-PolicyHigh15
Missing X-Content-Type-OptionsMedium8
Missing X-Frame-OptionsMedium8
Cookie missing Secure flagHigh (1.5×)22.5 (rounded to 23)
Missing Referrer-PolicyLow3

Figure 2: Real-world scan results — deductions by finding and severity.

Total deductions: 57. But the score floor is 0 and the math rounds — actual score: 48.

Fix 1: Security headers via Next.js middleware (5 minutes). Added CSP, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy:

// middleware.ts import { NextResponse } from 'next/server'; export function middleware() { const response = NextResponse.next(); response.headers.set( 'Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" ); response.headers.set('X-Content-Type-Options', 'nosniff'); response.headers.set('X-Frame-Options', 'DENY'); response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); return response; }

Figure 4: Next.js middleware that sets CSP, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy in one file.

Recovered: 15 + 8 + 8 + 3 = 34 points.

Fix 2: Cookie flags in Supabase SSR (2 minutes). Enforced Secure, HttpOnly, and SameSite:

// utils/supabase/server.ts const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, { cookies: { getAll: () => /* ... */, setAll: (cookies) => cookies.forEach(({ name, value, options }) => cookieStore.set(name, value, { ...options, secure: true, httpOnly: true, sameSite: 'lax', }) ), }, });

Figure 5: Supabase SSR client configuration with Secure, HttpOnly, and SameSite cookie flags enforced.

Recovered: 23 points.

Re-scan: 78. Two changes, under 10 minutes.

The remaining 22 points came from Info-level findings — optional hardening like Permissions-Policy and server version disclosure. Worth addressing, but not urgent.

What Each Range Means

Critical: 0–39 — Actively Exploitable

Exposed API keys, missing all cookie flags, no CSP, no HTTPS. Someone can steal your Stripe key from a <script> tag and start charging cards. Or steal session cookies and impersonate users.

Fix today. Rotate exposed keys immediately — a compromised key stays compromised even after you remove it from code. Add HttpOnly, Secure, and SameSite to all cookies. Add CSP and HSTS headers. Then re-scan.

High Risk: 40–64 — Significant Gaps

No exposed credentials, but missing critical defensive layers. A determined attacker with an XSS vector can escalate because your CSP doesn't block inline scripts and your cookies aren't flagged.

Fix this week. Add all security headers via middleware or your CDN config. Fix cookie flags. Add CSRF protection if you're using cookie-based auth.

Medium Risk: 65–84 — Core Defenses in Place

Headers present, cookies flagged. What's left are secondary issues: SameSite set to None instead of Lax, missing Permissions-Policy, server reveals version in response headers, source maps exposed in production.

Fix when convenient. Tighten SameSite to Lax. Add a Permissions-Policy header. Disable source maps in production builds. Enable Subresource Integrity on third-party scripts.

Low Risk: 85–100 — Well-Configured

All security headers correct. No exposed secrets. No injection vectors. No misconfigured cookies. Remaining findings are Info-level hardening opportunities — nice to have, not pressing. Ship it.

How to Use Your Score

Before launch: Scan. Below 70? Fix critical and high findings first — those are the ones that represent real exploit paths.

After every deploy: Scan. A dropping score means the last deploy introduced a regression. Maybe someone added a new cookie without flags. Maybe a CSP header got dropped in a refactor. Catch it before it hits production long enough to matter.

Weekly monitoring: For production apps, scan weekly. Configuration drifts. Dependencies change. Headers get accidentally removed. A weekly scan catches the drift before it becomes an incident.

The number is not a judgment. It's a compass. It tells you where to look and how urgent it is. Everything it deducts for is something you can fix with a config change or a few lines of code. No black box. No mystery. Just a number that goes up when your app gets safer.


Summary

Found this useful? Scan your app at vibeshield.org — free, no account needed. You'll get your score, your findings, and the exact fixes in 30 seconds.

Read next: I Found 7% of AI-Built Supabase Apps Wide Open if you're dealing with Supabase RLS. Or see We Built a Security Scanner in a Weekend for how the scanner catches these findings.