Blog / Continuous Scanning

A Free Scanner Found 11 Holes in a Ready App

· Fortus Team · 6 min read

Picture this: your app is ready to ship. You run a free scanner against it on a whim, and minutes later it reports eleven issues — missing headers, permissive policies, small misconfigurations you never considered. Here's a scenario I keep seeing: the builder's first reaction is embarrassment, and the second is the right one — if a free tool found this much in minutes, imagine what automated bots find every day. Shipping felt like the finish line. It was actually the starting line for exposure.

This story lands because it lowers the bar to the first scan to zero. No budget, no procurement, no security team required — just a free tool and a staging target. And the lesson generalizes beyond whatever the eleven findings were: the gap between "works" and "hardened" is invisible from inside the build process, because the app behaves identically either way. Scanning is how you see the gap. Continuous scanning is how you keep it closed as the app changes.

Start with the header that contains the blast radius

Content Security Policy is the highest-leverage first fix for most browser-facing apps, because it does not remove vulnerabilities so much as contain their impact.

Without a policy, a single injected script runs with full page authority: inline scripts execute, eval works, resources load from any origin, and stolen data flows to attacker-controlled domains. With a tight policy, the same injection hits walls — scripts load only from trusted origins, inline execution requires nonces or hashes instead of blanket permission, and exfiltration endpoints outside the allowlist are blocked. The vulnerability may still exist, but its consequences shrink dramatically.

Builders skip this because generators never emit it and the app works without it. When policies are added carelessly they break legitimate third-party scripts, so they get removed and never revisited. The way back is incremental: start from a report-only policy that logs violations without breaking, tighten origins for scripts, styles, images, and connections to trusted sources, replace blanket inline permissions with nonces or hashes for the inline code you genuinely need, and re-grep for wildcard and unsafe-eval leftovers before enforcing. Every HTML response should eventually carry an enforcing policy, and intentionally public resources deserve a comment saying so rather than a silent exception.

What the other ten findings usually are

A first scan rarely finds one problem. The usual companions are worth knowing so the follow-up has a shape.

Missing companion headers appear together: clickjacking protection, transport-security enforcement, content-type controls. Permissive cross-origin configuration that trusts more origins than the app uses. Verbose error messages and exposed development artifacts — the source-map and staging exposures covered elsewhere in this blog. Each is small alone; together they turn isolated bugs into exploitable chains by giving attackers framing, downgrade, and reconnaissance advantages.

The right response is triage, not panic. Fix the containing controls first — the policy headers that shrink every blast radius — then work the list by severity with file-level fixes: header config in framework middleware or hosting config, dependency updates where the scanner flags known-vulnerable packages, error-message tightening where stack traces leak. What matters is not clearing eleven findings in a day. It is building the loop where the twelfth finding gets caught next week instead of next year.

A manual self-test: run the free scan, then wire the loop

Here is a concrete self-test you can run manually on your own app this week. It has two phases: the one-off scan and the continuous loop.

Phase one: point a free scanner at a staging copy of your app — never production for active scans — and read the report by category rather than by count. For each finding, trace it to the file that decides it: header config in framework settings or hosting config, dependency versions in manifests and lockfiles, error handling in API routes. Fix the headers first, starting with the content policy rollout above, then the rest by severity. Re-scan staging and confirm the count drops; keep the before-and-after reports as your baseline.

Phase two: make it continuous. Add header checks to your definition of done for every frontend change, pin the policy config in files so drift is reviewable, and schedule recurring scans whose reports route to whoever ships. The goal is not a clean report — it is a short interval between introduction and detection. Bots scan daily. Your loop should run at least as often as you deploy.

Where a file scanner fits

A source-file scanner complements active scanning rather than replacing it. Active tools probe running behavior; file checks catch the config before it ships — the missing policy string in middleware or hosting config, the hollow policy with wildcards and blanket inline permissions, the deliberately disabled header block. Each file finding carries the path and excerpt that an active report cannot give you.

Some enforcement lives outside the repo — edge-layer header injection, dashboard-managed rules — so a missing code-side header with possible edge coverage deserves a review note rather than a failure. Pair the two loops: file checks on every commit for the config you control, active scans on a schedule for the behavior users face. Eleven findings on day one is a gift. Finding number twelve yourself, before the bots do, is the habit.

Automated Code Review

Turn eleven findings into a habit

Fortus reads your repo and flags missing CSP headers, hollow policies, and the usual companions — with the file and line for each one.

or learn more about Fortus →