I released next-secure-check v0.3.0.
It is an open-source CLI tool for Next.js projects.
The idea is simple:
npx --yes next-secure-check@latest scan . --preset app
It scans a project and reports risky patterns before PR/deploy.
But I want to be very clear about the positioning:
- It is not a pentest.
- It is not a full security audit.
- It does not use AI at runtime.
- It is deterministic and rule-based.
- It gives review signals.
That last part matters a lot.
I think many developer security tools have a positioning problem. They either market themselves as if they can “secure your app” automatically, or they become so noisy that developers stop trusting them.
With next-secure-check, I am trying to stay somewhere more honest:
“This looks risky enough that a developer should review it.”
Not:
“This is definitely exploitable.”
Why I built it
I work on Next.js projects and I wanted a quick local check for common mistakes before shipping.
Things like:
- leaked
.envfiles - hardcoded secret/token patterns
- risky API routes
- admin routes without auth signals
- login/register routes without rate-limit signals
dangerouslySetInnerHTML- raw SQL interpolation
- upload endpoints without size/type validation signals
- missing security headers
- risky Next.js config patterns
A lot of these are not always vulnerabilities by themselves.
For example, dangerouslySetInnerHTML is not automatically a bug. It depends on the source of the HTML and whether it is sanitized.
A route themselves.
For example, dangerouslySetInnerHTML is not automatically a bug. It named admin is not automatically exposed. It may be protected by middleware.
A NEXT_PUBLIC_* variable is not automatically leaked secret material. But if it looks secret-like, it is worth reviewing.
That is why I keep calling the tool a security sanity check, not a security audit.
What changed in v0.3.0
The biggest focus of v0.3.0 was not “add more rules”.
It was:
- reduce false positives
- improve signal quality
- make the CLI easier to understand
- improve GitHub Code Scanning output
Regression fixtures
I added a regression fixture suite so that future rule changes can be tested against small, controlled examples.
This is important because static analysis tools can easily regress.
You fix one false positive, then accidentally silence a real finding somewhere else.
The goal is to make those tradeoffs visible.
Current validation baseline:
package tests: 429
web tests: 143
self scan: 100/100, 0 findings
vulnerable fixture: 26 findings, critical
secure fixture: 99/100, 1 LOW
These numbers are not a guarantee of correctness.
They are just a baseline to keep the project from drifting randomly.
Better context handling
One problem I found in larger repos is that not every file should be treated the same way.
A production API route, a demo component, a registry template, and a test fixture can contain similar-looking code but mean very different things.
So v0.3.0 improves classification for paths like:
registrydemoplaygroundstoriesfixtures- package UI paths
This helps reduce noisy findings in monorepos and component-heavy repositories.
XSS sanitizer/source refinement
The dangerouslySetInnerHTML rule became more careful.
It now has better AST-assisted handling for cases like:
- static HTML constants
- sanitizer imports
- sanitizer wrappers
- user-controlled-looking sources
It still does not do full data-flow analysis. That would be a much bigger step.
But it is better than simply saying:
“I saw dangerouslySetInnerHTML, therefore this is bad.”
Middleware auth/rate-limit signals
Next.js middleware can protect routes outside the route file itself.
So if a tool only looks inside app/api/admin/route.ts, it may produce noisy results.
v0.3.0 added a small deterministic middleware signal model.
It looks for simple auth/rate-limit signals in:
middleware.ts
src/middleware.ts
And it checks basic matcher coverage.
This helps with cases like:
export const config = {
matcher: ["/api/admin/:path*"]
}
Again, this is not a full auth graph.
It is a review signal improvement.
Rate-limit refinement
The login/register rate-limit rule was also improved.
It now recognizes more route-level and middleware-level signals such as:
rateLimitcheckRateLimitapplyRateLimitlimiter429too many requests- Upstash/Redis limiter usage
One important fix: an unrelated file containing the word rateLimit should not automatically clean a login route.
That was the kind of noise reduction v0.3.0 focused on.
SARIF / GitHub Code Scanning polish
The SARIF output is now more useful for GitHub Code Scanning.
v0.3.0 added/improved:
helpUri- CWE/security tags
- additional fingerprints
- context metadata
- secret/evidence safety checks
I also checked that raw demo secrets were not leaked into SARIF output.
New CLI helper commands
v0.3.0 added a few commands to make the tool easier to understand:
npx --yes next-secure-check@latest rules
Lists built-in rules.
npx --yes next-secure-check@latest explain xss/dangerously-set-inner-html
Explains what a rule checks, why it matters, and where false positives can happen.
npx --yes next-secure-check@latest init
Generates a minimal config and GitHub Actions workflow.
This matters because a CLI tool should not require users to read the whole README before they understand what it does.
The uncomfortable part: static analysis is always a tradeoff
This is the part I am most interested in getting feedback on.
Security scanners have a hard balance:
If the tool is too strict, people ignore it.
If the tool is too soft, it misses useful signals.
If it says “critical” too often, developers stop caring.
If it hides too much, the tool becomes pointless.
So where should a small open-source security sanity checker sit?
My current answer is:
- Be deterministic.
- Be honest about uncertainty.
- Prefer explainable findings.
- Avoid pretending to be a pentest.
- Make false positives easy to reason about.
- Keep rule behavior tested with fixtures.
- Give users presets for different noise levels.
That is why next-secure-check has presets like:
--preset app
--preset strict
--preset ci
--preset audit
--preset library
--preset monorepo
I do not think every scan mode should behave the same way.
A production app scan and an aggressive audit-style scan have different goals.
What I am thinking about for v0.4
The most interesting next step is source/sink tracking.
Right now, many rules still answer:
“Does this risky pattern exist?”
The better question is:
“Can risky input actually reach a risky sink?”
Full interprocedural data-flow analysis is a big project.
So I am considering two smaller steps first:
1. Taint source tagging
Mark known sources like:
req.bodyrequest.json()request.formData()searchParams.get()paramscookies()headers()
Then check whether those values are used in risky sinks within the same scope.
Not perfect, but practical.
2. Optional type-aware analysis
TypeScript’s TypeChecker API can help answer questions like:
“Where did this identifier come from?”
Moving from ts.createSourceFile to ts.createProgram has performance and config-discovery costs, especially in monorepos.
So if I try it, it will probably start as an opt-in spike, not default behavior.
Open questions
I would like feedback from people who work on real Next.js projects or static analysis tools:
- Should a tool like this prefer fewer findings, even if that risks missing some weak signals?
- Are presets a good way to handle different noise levels?
- Would you use a CLI like this locally, in CI, or only through GitHub Code Scanning?
- For Next.js apps, what security checks are useful but still practical to detect statically?
- Is source/sink tracking worth the complexity for a small deterministic scanner?
- Should rules be conservative by default and aggressive only in
--preset strict/--preset audit?
Links
GitHub:
https://github.com/SetraTheXX/next-secure-check
npm:
https://www.npmjs.com/package/next-secure-check
Try:
npx --yes next-secure-check@latest scan . --preset app
I am especially interested in false positives, missing rule ideas, and whether the “review signal, not audit replacement” positioning makes sense.
Top comments (0)