@fourteensystems/prodcheck 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +252 -0
- package/bin/prodcheck.mjs +2 -0
- package/dist/cli/commands/baseline.d.ts +7 -0
- package/dist/cli/commands/baseline.d.ts.map +1 -0
- package/dist/cli/commands/baseline.js +22 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +14 -0
- package/dist/cli/commands/ci.d.ts.map +1 -0
- package/dist/cli/commands/ci.js +104 -0
- package/dist/cli/commands/ci.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +2 -0
- package/dist/cli/commands/explain.d.ts.map +1 -0
- package/dist/cli/commands/explain.js +20 -0
- package/dist/cli/commands/explain.js.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +127 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +2 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +13 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +10 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +65 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/waive.d.ts +8 -0
- package/dist/cli/commands/waive.d.ts.map +1 -0
- package/dist/cli/commands/waive.js +34 -0
- package/dist/cli/commands/waive.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/engine/baseline.d.ts +11 -0
- package/dist/engine/baseline.d.ts.map +1 -0
- package/dist/engine/baseline.js +39 -0
- package/dist/engine/baseline.js.map +1 -0
- package/dist/engine/baseline.test.d.ts +2 -0
- package/dist/engine/baseline.test.d.ts.map +1 -0
- package/dist/engine/baseline.test.js +135 -0
- package/dist/engine/baseline.test.js.map +1 -0
- package/dist/engine/config.d.ts +8 -0
- package/dist/engine/config.d.ts.map +1 -0
- package/dist/engine/config.js +134 -0
- package/dist/engine/config.js.map +1 -0
- package/dist/engine/config.test.d.ts +2 -0
- package/dist/engine/config.test.d.ts.map +1 -0
- package/dist/engine/config.test.js +107 -0
- package/dist/engine/config.test.js.map +1 -0
- package/dist/engine/extensions/load.d.ts +11 -0
- package/dist/engine/extensions/load.d.ts.map +1 -0
- package/dist/engine/extensions/load.js +26 -0
- package/dist/engine/extensions/load.js.map +1 -0
- package/dist/engine/extensions/registry.d.ts +5 -0
- package/dist/engine/extensions/registry.d.ts.map +1 -0
- package/dist/engine/extensions/registry.js +11 -0
- package/dist/engine/extensions/registry.js.map +1 -0
- package/dist/engine/extensions/types.d.ts +51 -0
- package/dist/engine/extensions/types.d.ts.map +1 -0
- package/dist/engine/extensions/types.js +2 -0
- package/dist/engine/extensions/types.js.map +1 -0
- package/dist/engine/license.d.ts +40 -0
- package/dist/engine/license.d.ts.map +1 -0
- package/dist/engine/license.js +104 -0
- package/dist/engine/license.js.map +1 -0
- package/dist/engine/report.d.ts +5 -0
- package/dist/engine/report.d.ts.map +1 -0
- package/dist/engine/report.js +115 -0
- package/dist/engine/report.js.map +1 -0
- package/dist/engine/run.d.ts +11 -0
- package/dist/engine/run.d.ts.map +1 -0
- package/dist/engine/run.js +105 -0
- package/dist/engine/run.js.map +1 -0
- package/dist/engine/sarif.d.ts +3 -0
- package/dist/engine/sarif.d.ts.map +1 -0
- package/dist/engine/sarif.js +58 -0
- package/dist/engine/sarif.js.map +1 -0
- package/dist/engine/sarif.test.d.ts +2 -0
- package/dist/engine/sarif.test.d.ts.map +1 -0
- package/dist/engine/sarif.test.js +152 -0
- package/dist/engine/sarif.test.js.map +1 -0
- package/dist/engine/score.d.ts +13 -0
- package/dist/engine/score.d.ts.map +1 -0
- package/dist/engine/score.js +116 -0
- package/dist/engine/score.js.map +1 -0
- package/dist/engine/score.test.d.ts +2 -0
- package/dist/engine/score.test.d.ts.map +1 -0
- package/dist/engine/score.test.js +227 -0
- package/dist/engine/score.test.js.map +1 -0
- package/dist/engine/types.d.ts +123 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +2 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/version.d.ts +5 -0
- package/dist/engine/version.d.ts.map +1 -0
- package/dist/engine/version.js +15 -0
- package/dist/engine/version.js.map +1 -0
- package/dist/engine/waivers.d.ts +9 -0
- package/dist/engine/waivers.d.ts.map +1 -0
- package/dist/engine/waivers.js +55 -0
- package/dist/engine/waivers.js.map +1 -0
- package/dist/engine/waivers.test.d.ts +2 -0
- package/dist/engine/waivers.test.d.ts.map +1 -0
- package/dist/engine/waivers.test.js +147 -0
- package/dist/engine/waivers.test.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/next/deps.d.ts +4 -0
- package/dist/next/deps.d.ts.map +1 -0
- package/dist/next/deps.js +118 -0
- package/dist/next/deps.js.map +1 -0
- package/dist/next/deps.test.d.ts +2 -0
- package/dist/next/deps.test.d.ts.map +1 -0
- package/dist/next/deps.test.js +249 -0
- package/dist/next/deps.test.js.map +1 -0
- package/dist/next/detect.d.ts +10 -0
- package/dist/next/detect.d.ts.map +1 -0
- package/dist/next/detect.js +57 -0
- package/dist/next/detect.js.map +1 -0
- package/dist/next/detect.test.d.ts +2 -0
- package/dist/next/detect.test.d.ts.map +1 -0
- package/dist/next/detect.test.js +74 -0
- package/dist/next/detect.test.js.map +1 -0
- package/dist/next/index.d.ts +5 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +59 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/middleware.d.ts +3 -0
- package/dist/next/middleware.d.ts.map +1 -0
- package/dist/next/middleware.js +48 -0
- package/dist/next/middleware.js.map +1 -0
- package/dist/next/middleware.test.d.ts +2 -0
- package/dist/next/middleware.test.d.ts.map +1 -0
- package/dist/next/middleware.test.js +203 -0
- package/dist/next/middleware.test.js.map +1 -0
- package/dist/next/routes.d.ts +10 -0
- package/dist/next/routes.d.ts.map +1 -0
- package/dist/next/routes.js +172 -0
- package/dist/next/routes.js.map +1 -0
- package/dist/next/routes.test.d.ts +2 -0
- package/dist/next/routes.test.d.ts.map +1 -0
- package/dist/next/routes.test.js +175 -0
- package/dist/next/routes.test.js.map +1 -0
- package/dist/next/server-actions.d.ts +4 -0
- package/dist/next/server-actions.d.ts.map +1 -0
- package/dist/next/server-actions.js +107 -0
- package/dist/next/server-actions.js.map +1 -0
- package/dist/next/server-actions.test.d.ts +2 -0
- package/dist/next/server-actions.test.d.ts.map +1 -0
- package/dist/next/server-actions.test.js +138 -0
- package/dist/next/server-actions.test.js.map +1 -0
- package/dist/next/trpc.d.ts +3 -0
- package/dist/next/trpc.d.ts.map +1 -0
- package/dist/next/trpc.js +312 -0
- package/dist/next/trpc.js.map +1 -0
- package/dist/next/types.d.ts +144 -0
- package/dist/next/types.d.ts.map +1 -0
- package/dist/next/types.js +2 -0
- package/dist/next/types.js.map +1 -0
- package/dist/next/wrappers.d.ts +10 -0
- package/dist/next/wrappers.d.ts.map +1 -0
- package/dist/next/wrappers.js +536 -0
- package/dist/next/wrappers.js.map +1 -0
- package/dist/next/wrappers.test.d.ts +2 -0
- package/dist/next/wrappers.test.d.ts.map +1 -0
- package/dist/next/wrappers.test.js +361 -0
- package/dist/next/wrappers.test.js.map +1 -0
- package/dist/rules/auth-boundary-missing.d.ts +5 -0
- package/dist/rules/auth-boundary-missing.d.ts.map +1 -0
- package/dist/rules/auth-boundary-missing.js +463 -0
- package/dist/rules/auth-boundary-missing.js.map +1 -0
- package/dist/rules/auth-boundary-missing.test.d.ts +2 -0
- package/dist/rules/auth-boundary-missing.test.d.ts.map +1 -0
- package/dist/rules/auth-boundary-missing.test.js +492 -0
- package/dist/rules/auth-boundary-missing.test.js.map +1 -0
- package/dist/rules/index.d.ts +12 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +95 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/input-validation-missing.d.ts +5 -0
- package/dist/rules/input-validation-missing.d.ts.map +1 -0
- package/dist/rules/input-validation-missing.js +272 -0
- package/dist/rules/input-validation-missing.js.map +1 -0
- package/dist/rules/input-validation-missing.test.d.ts +2 -0
- package/dist/rules/input-validation-missing.test.d.ts.map +1 -0
- package/dist/rules/input-validation-missing.test.js +449 -0
- package/dist/rules/input-validation-missing.test.js.map +1 -0
- package/dist/rules/rate-limit-missing.d.ts +5 -0
- package/dist/rules/rate-limit-missing.d.ts.map +1 -0
- package/dist/rules/rate-limit-missing.js +316 -0
- package/dist/rules/rate-limit-missing.js.map +1 -0
- package/dist/rules/rate-limit-missing.test.d.ts +2 -0
- package/dist/rules/rate-limit-missing.test.d.ts.map +1 -0
- package/dist/rules/rate-limit-missing.test.js +381 -0
- package/dist/rules/rate-limit-missing.test.js.map +1 -0
- package/dist/rules/tenancy-scope-missing.d.ts +5 -0
- package/dist/rules/tenancy-scope-missing.d.ts.map +1 -0
- package/dist/rules/tenancy-scope-missing.js +149 -0
- package/dist/rules/tenancy-scope-missing.js.map +1 -0
- package/dist/rules/wrapper-unrecognized.d.ts +5 -0
- package/dist/rules/wrapper-unrecognized.d.ts.map +1 -0
- package/dist/rules/wrapper-unrecognized.js +81 -0
- package/dist/rules/wrapper-unrecognized.js.map +1 -0
- package/dist/util/hof.d.ts +22 -0
- package/dist/util/hof.d.ts.map +1 -0
- package/dist/util/hof.js +99 -0
- package/dist/util/hof.js.map +1 -0
- package/dist/util/hof.test.d.ts +2 -0
- package/dist/util/hof.test.d.ts.map +1 -0
- package/dist/util/hof.test.js +79 -0
- package/dist/util/hof.test.js.map +1 -0
- package/dist/util/monorepo.d.ts +6 -0
- package/dist/util/monorepo.d.ts.map +1 -0
- package/dist/util/monorepo.js +29 -0
- package/dist/util/monorepo.js.map +1 -0
- package/dist/util/outbound-fetch.d.ts +14 -0
- package/dist/util/outbound-fetch.d.ts.map +1 -0
- package/dist/util/outbound-fetch.js +59 -0
- package/dist/util/outbound-fetch.js.map +1 -0
- package/dist/util/outbound-fetch.test.d.ts +2 -0
- package/dist/util/outbound-fetch.test.d.ts.map +1 -0
- package/dist/util/outbound-fetch.test.js +83 -0
- package/dist/util/outbound-fetch.test.js.map +1 -0
- package/dist/util/paths.d.ts +6 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +18 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/resolve.d.ts +30 -0
- package/dist/util/resolve.d.ts.map +1 -0
- package/dist/util/resolve.js +306 -0
- package/dist/util/resolve.js.map +1 -0
- package/dist/util/resolve.test.d.ts +2 -0
- package/dist/util/resolve.test.d.ts.map +1 -0
- package/dist/util/resolve.test.js +186 -0
- package/dist/util/resolve.test.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Prodcheck
|
|
2
|
+
|
|
3
|
+
Static analysis guardrail for Next.js SaaS — flags unprotected routes, missing rate limiting, and SSRF surfaces.
|
|
4
|
+
|
|
5
|
+
Prodcheck statically analyzes your Next.js App Router codebase and flags mutation endpoints missing auth boundaries, rate limiting, or tenant scoping. It understands your stack — Auth.js, Clerk, Supabase, tRPC, Prisma — resolves your wrapper implementations, and stays quiet when protections are in place.
|
|
6
|
+
|
|
7
|
+
Zero config for most projects. Prodcheck auto-detects your auth library, rate limiter, ORM, middleware, tsconfig path aliases, and HOF wrappers. No manual hints needed unless you're doing something exotic.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @fourteensystems/prodcheck init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Detects your framework and dependencies, generates a config, and runs your first scan.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Prodcheck 0.3.0
|
|
19
|
+
Detected: next-app-router · next-auth · prisma · upstash-ratelimit · middleware.ts
|
|
20
|
+
Score: 85 PASS
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Scan and print report
|
|
27
|
+
prodcheck
|
|
28
|
+
|
|
29
|
+
# Only run specific rules
|
|
30
|
+
prodcheck scan --only AUTH-BOUNDARY-MISSING,RATE-LIMIT-MISSING
|
|
31
|
+
|
|
32
|
+
# Exclude paths
|
|
33
|
+
prodcheck scan --exclude "app/api/internal/**"
|
|
34
|
+
|
|
35
|
+
# JSON or SARIF output
|
|
36
|
+
prodcheck scan --format json
|
|
37
|
+
prodcheck scan --format sarif --output report.sarif
|
|
38
|
+
|
|
39
|
+
# CI mode (fail on critical findings)
|
|
40
|
+
prodcheck ci --fail-on critical --min-confidence high
|
|
41
|
+
|
|
42
|
+
# Save baseline for regression detection
|
|
43
|
+
prodcheck baseline --write
|
|
44
|
+
|
|
45
|
+
# Waive a finding
|
|
46
|
+
prodcheck waive RATE-LIMIT-MISSING --file app/api/foo/route.ts --reason "Handled by Cloudflare WAF"
|
|
47
|
+
|
|
48
|
+
# List rules
|
|
49
|
+
prodcheck rules
|
|
50
|
+
|
|
51
|
+
# Explain a rule
|
|
52
|
+
prodcheck explain AUTH-BOUNDARY-MISSING
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What It Detects
|
|
56
|
+
|
|
57
|
+
### Rules
|
|
58
|
+
|
|
59
|
+
| Rule | Severity | What it catches |
|
|
60
|
+
|------|----------|----------------|
|
|
61
|
+
| AUTH-BOUNDARY-MISSING | critical | Mutation endpoints without auth checks |
|
|
62
|
+
| RATE-LIMIT-MISSING | critical | Public API routes without rate limiting |
|
|
63
|
+
| TENANCY-SCOPE-MISSING | critical | Prisma queries without tenant scoping |
|
|
64
|
+
| INPUT-VALIDATION-MISSING | med | Mutation endpoints accepting input without schema validation |
|
|
65
|
+
| WRAPPER-UNRECOGNIZED | high | HOF wrappers that couldn't be verified for auth/rate-limit enforcement |
|
|
66
|
+
| PUBLIC-INTENT-MISSING-REASON | med | `prodcheck:public-intent` directives missing a required reason |
|
|
67
|
+
|
|
68
|
+
### Auth-Aware Rate Limiting
|
|
69
|
+
|
|
70
|
+
Prodcheck suppresses RATE-LIMIT-MISSING findings on routes with **strongly enforced auth** — where the auth call is proven to throw/return on failure (e.g., `if (!session) throw new Error(401)`). Routes with weak or optional auth (call present but no enforcement) are still flagged.
|
|
71
|
+
|
|
72
|
+
This means authenticated routes behind `withWorkspace()`, `protectedProcedure`, or `requireAuth()` with verified enforcement won't produce rate-limit noise. Public routes and weakly-authed routes still get full RL findings.
|
|
73
|
+
|
|
74
|
+
### `prodcheck:public-intent` Annotation
|
|
75
|
+
|
|
76
|
+
For routes that are **intentionally public** (no auth by design), add a directive:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// prodcheck:public-intent reason="Public URL health checker"
|
|
80
|
+
export async function GET(request: Request) { ... }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This tells Prodcheck:
|
|
84
|
+
- **AUTH-BOUNDARY-MISSING** is suppressed (auth absence is intentional)
|
|
85
|
+
- **RATE-LIMIT-MISSING** severity is floored at HIGH (public by design = rate limiting mandatory)
|
|
86
|
+
- If the route performs outbound fetch with user-influenced URLs, severity escalates to CRITICAL with `ssrf-surface` tag
|
|
87
|
+
- **INPUT-VALIDATION-MISSING** severity is bumped (public + unvalidated = higher exposure)
|
|
88
|
+
|
|
89
|
+
Missing or empty `reason` produces a `PUBLIC-INTENT-MISSING-REASON` finding and the directive is ignored.
|
|
90
|
+
|
|
91
|
+
### Wrapper Introspection
|
|
92
|
+
|
|
93
|
+
The dominant pattern in real-world Next.js codebases is HOF wrappers:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
export const POST = withWorkspace(async (req) => {
|
|
97
|
+
await prisma.user.create({ data: { name: "test" } });
|
|
98
|
+
return Response.json({});
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Prodcheck doesn't just detect the wrapper name — it **follows the import, reads the implementation, and verifies enforcement**:
|
|
103
|
+
|
|
104
|
+
1. **Resolve**: follows `import { withWorkspace } from "@/lib/auth"` through tsconfig path aliases (`@/lib/*` → `lib/*`), barrel re-exports (`index.ts` → `export * from "./workspace"`), up to 5 hops with cycle detection
|
|
105
|
+
2. **Analyze**: parses the wrapper body with TypeScript AST to find auth/rate-limit calls
|
|
106
|
+
3. **Verify enforcement**: checks that the call result is used in a conditional (`if (!session) throw`) — calling `getSession()` without checking the result is NOT an auth boundary
|
|
107
|
+
4. **Built-in patterns**: recognizes webhook signature verification (`stripe.webhooks.constructEvent`, `verifyVercelSignature`, `verifyQstashSignature`, HMAC + `timingSafeEqual`) as auth enforcement
|
|
108
|
+
5. **Apply**: routes using a verified wrapper are automatically cleared, no hints needed
|
|
109
|
+
|
|
110
|
+
When a wrapper can't be resolved (npm package) or enforcement can't be proven, Prodcheck emits a single grouped `WRAPPER-UNRECOGNIZED` finding instead of N identical per-route alerts.
|
|
111
|
+
|
|
112
|
+
### Stack Support
|
|
113
|
+
|
|
114
|
+
Prodcheck auto-detects your stack and adjusts detection accordingly:
|
|
115
|
+
|
|
116
|
+
| Stack | What Prodcheck understands |
|
|
117
|
+
|-------|---------------------------|
|
|
118
|
+
| **Auth.js / NextAuth** | `auth()`, `getServerSession()`, `withAuth()`, middleware auth |
|
|
119
|
+
| **Clerk** | `auth()`, `currentUser()`, `clerkMiddleware()` |
|
|
120
|
+
| **Supabase** | `.auth.getUser()`, `.auth.getSession()` (call-based, not import-based) |
|
|
121
|
+
| **Kinde** | `getKindeServerSession()` |
|
|
122
|
+
| **WorkOS / AuthKit** | `withAuth()`, `getUser()`, `authkitMiddleware()` |
|
|
123
|
+
| **Better Auth** | `auth()` |
|
|
124
|
+
| **Lucia** | `validateRequest()`, `validateSession()` |
|
|
125
|
+
| **Auth0** | `getSession()`, `withApiAuthRequired()` |
|
|
126
|
+
| **iron-session** | `getIronSession()` |
|
|
127
|
+
| **Firebase Auth** | `verifyIdToken()`, `getTokens()`, `verifySessionCookie()` |
|
|
128
|
+
| **tRPC** | `protectedProcedure` vs `publicProcedure`, `.mutation()` surfaces |
|
|
129
|
+
| **Prisma** | `.create()`, `.update()`, `.delete()` as mutation evidence, tenant scoping |
|
|
130
|
+
| **Drizzle** | Detected but gracefully degraded (tenancy rule skips) |
|
|
131
|
+
| **Upstash** | `Ratelimit`, `ratelimit.limit()` as rate-limit evidence |
|
|
132
|
+
| **Arcjet** | `fixedWindow()`, `slidingWindow()`, `tokenBucket()` |
|
|
133
|
+
| **Unkey** | `withUnkey()`, `verifyKey()` |
|
|
134
|
+
| **Zod / Valibot / Yup** | Schema validation in mutation handlers (INPUT-VALIDATION-MISSING) |
|
|
135
|
+
| **Webhook signatures** | Stripe, WorkOS, Vercel cron, QStash signature verification as auth |
|
|
136
|
+
|
|
137
|
+
### What It Skips
|
|
138
|
+
|
|
139
|
+
- Webhook routes (any path containing `webhook`) — exempt from rate-limit
|
|
140
|
+
- Cron routes (`/api/cron/*`) — exempt from rate-limit
|
|
141
|
+
- Framework-managed routes (NextAuth catch-all, OAuth/SAML endpoints, callbacks, OG images) — exempt from rate-limit
|
|
142
|
+
- OAuth/OIDC/SSO/SCIM callback paths — exempt from auth (public by protocol design)
|
|
143
|
+
- `GET`-only route handlers — not mutation surfaces
|
|
144
|
+
- Routes covered by `middleware.ts` auth — no double-flagging
|
|
145
|
+
- Routes wrapped by verified HOF wrappers (`withWorkspace(handler)` where auth+RL enforcement is proven)
|
|
146
|
+
- DB-backed token lookups with deny on failure (password reset tokens, API keys)
|
|
147
|
+
- Inline auth guards (`getCurrentUser()` + null check + throw/return)
|
|
148
|
+
- **Strongly authenticated routes** — RL findings suppressed when auth is enforced (proven throw/return on failure)
|
|
149
|
+
- Login/signin endpoints get critical severity for missing rate limiting (brute-force risk)
|
|
150
|
+
- Public file upload endpoints get critical severity for missing rate limiting (storage abuse risk)
|
|
151
|
+
|
|
152
|
+
### Rate Limit Detection
|
|
153
|
+
|
|
154
|
+
Prodcheck recognizes rate limiting through multiple patterns:
|
|
155
|
+
|
|
156
|
+
- **Package imports**: `@upstash/ratelimit`, `rate-limiter-flexible`, `@arcjet/next`, `@unkey/ratelimit`
|
|
157
|
+
- **Method calls**: `ratelimit.limit()`, `rl.limit()`, `limiter.limit()`, `rateLimiter.limit()`
|
|
158
|
+
- **General function pattern**: any function with `rateLimit`, `ratelimit`, or `rate_limit` in the name — catches `ratelimitOrThrow()`, `checkRateLimit()`, `rateLimitMiddleware()`, etc.
|
|
159
|
+
- **Wrapper introspection**: follows imports and verifies RL calls in wrapper bodies
|
|
160
|
+
|
|
161
|
+
See [PATTERNS.md](../../PATTERNS.md) for full detection logic.
|
|
162
|
+
|
|
163
|
+
## Scoring
|
|
164
|
+
|
|
165
|
+
Prodcheck computes a 0-100 security score. Each finding deducts points based on severity **and** confidence:
|
|
166
|
+
|
|
167
|
+
| | high confidence | med confidence | low confidence |
|
|
168
|
+
|---|---|---|---|
|
|
169
|
+
| **critical** | -15 | -3.75 | -1.5 |
|
|
170
|
+
| **high** | -6 | -1.5 | -0.6 |
|
|
171
|
+
| **med** | -3 | -0.75 | -0.3 |
|
|
172
|
+
| **low** | -1 | -0.25 | -0.1 |
|
|
173
|
+
|
|
174
|
+
A single rule can deduct at most 35 points (preventing one noisy rule from tanking the score).
|
|
175
|
+
|
|
176
|
+
| Score | Status | Meaning |
|
|
177
|
+
|-------|--------|---------|
|
|
178
|
+
| 80-100 | PASS | Healthy — no critical gaps |
|
|
179
|
+
| 50-79 | WARN | Issues to address |
|
|
180
|
+
| 0-49 | FAIL | Critical gaps in protection |
|
|
181
|
+
|
|
182
|
+
## Confidence Levels
|
|
183
|
+
|
|
184
|
+
Every finding has a confidence level:
|
|
185
|
+
|
|
186
|
+
- **high** — strong evidence (e.g., `publicProcedure.mutation()` with `prisma.create`)
|
|
187
|
+
- **med** — likely but uncertain (e.g., unrecognized procedure type)
|
|
188
|
+
- **low** — possible issue, may be false positive
|
|
189
|
+
|
|
190
|
+
Use `--min-confidence` in CI to control noise:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
prodcheck ci --min-confidence high
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Monorepos
|
|
197
|
+
|
|
198
|
+
Prodcheck must be run from the Next.js app directory (the one with `package.json` and `app/`). In a monorepo like Turborepo or pnpm workspaces:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
cd apps/web && npx @fourteensystems/prodcheck scan
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Prodcheck automatically reads dependencies from both the app's `package.json` and the workspace root, and checks for `middleware.ts` at both levels. tsconfig `extends` chains (e.g., `"extends": "tsconfig/nextjs.json"`) and monorepo path aliases are resolved automatically.
|
|
205
|
+
|
|
206
|
+
## Configuration
|
|
207
|
+
|
|
208
|
+
Most teams do not need to configure Prodcheck. Run `prodcheck init` and commit the generated config.
|
|
209
|
+
|
|
210
|
+
With wrapper introspection, Prodcheck resolves and analyzes your custom wrappers automatically. Hints are only needed for edge cases where the wrapper can't be resolved (e.g., auth handled by an API gateway, rate limiting at the CDN edge).
|
|
211
|
+
|
|
212
|
+
For advanced use cases, create `prodcheck.config.json`:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"framework": "next-app-router",
|
|
217
|
+
"include": ["app/**", "src/**"],
|
|
218
|
+
"exclude": ["**/*.test.*", "**/*.spec.*"],
|
|
219
|
+
"ci": {
|
|
220
|
+
"failOn": "critical",
|
|
221
|
+
"minConfidence": "high",
|
|
222
|
+
"minScore": 70,
|
|
223
|
+
"maxNewCritical": 0
|
|
224
|
+
},
|
|
225
|
+
"hints": {
|
|
226
|
+
"auth": {
|
|
227
|
+
"functions": ["auth", "getServerSession", "currentUser"],
|
|
228
|
+
"middlewareFiles": ["middleware.ts"],
|
|
229
|
+
"allowlistPaths": ["app/api/public/**"]
|
|
230
|
+
},
|
|
231
|
+
"rateLimit": {
|
|
232
|
+
"wrappers": ["rateLimit", "withRateLimit"],
|
|
233
|
+
"allowlistPaths": ["app/api/webhooks/**"]
|
|
234
|
+
},
|
|
235
|
+
"tenancy": {
|
|
236
|
+
"orgFieldNames": ["orgId", "tenantId", "workspaceId"]
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Hints
|
|
243
|
+
|
|
244
|
+
Hints are the "hard allow" escape hatch. Add function names when Prodcheck can't verify protection automatically:
|
|
245
|
+
|
|
246
|
+
- **Wrapper introspection handles most cases** — if your wrapper calls `getSession()` and throws on failure, Prodcheck detects this without hints
|
|
247
|
+
- **Unresolvable wrappers** (npm packages, API gateway auth) need hints: add to `hints.auth.functions` or `hints.rateLimit.wrappers`
|
|
248
|
+
- **CDN/edge rate limiting** (Cloudflare, Vercel) is invisible to static analysis — use waivers or allowlist paths
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/baseline.ts"],"names":[],"mappings":"AAIA,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBtE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { runScan } from "../../engine/run.js";
|
|
3
|
+
import { writeBaseline } from "../../engine/baseline.js";
|
|
4
|
+
export async function cmdBaseline(opts) {
|
|
5
|
+
if (!opts.write) {
|
|
6
|
+
console.log(pc.dim(" Use --write to save a baseline snapshot."));
|
|
7
|
+
console.log(pc.dim(" Example: prodcheck baseline --write"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const rootDir = process.cwd();
|
|
12
|
+
const result = await runScan({ rootDir });
|
|
13
|
+
const dest = writeBaseline(rootDir, result, opts.output);
|
|
14
|
+
console.log(pc.green(` Baseline written to ${dest}`));
|
|
15
|
+
console.log(pc.dim(` Score: ${result.score} | Findings: ${result.findings.length}`));
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
console.error(pc.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../../src/cli/commands/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAOzD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAqB;IACrD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAEzD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,KAAK,gBAAgB,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,YAAY,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface CiOptions {
|
|
2
|
+
failOn: string;
|
|
3
|
+
minConfidence: string;
|
|
4
|
+
minScore: string;
|
|
5
|
+
baseline?: string;
|
|
6
|
+
maxNewCritical: string;
|
|
7
|
+
maxNewHigh?: string;
|
|
8
|
+
format: string;
|
|
9
|
+
output?: string;
|
|
10
|
+
preview?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function cmdCi(opts: CiOptions): Promise<void>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=ci.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/ci.ts"],"names":[],"mappings":"AAUA,UAAU,SAAS;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAsB,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA+G1D"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { runScan } from "../../engine/run.js";
|
|
4
|
+
import { formatPretty, formatJson } from "../../engine/report.js";
|
|
5
|
+
import { formatSarif } from "../../engine/sarif.js";
|
|
6
|
+
import { loadBaseline, diffBaseline } from "../../engine/baseline.js";
|
|
7
|
+
import { confidenceLevel, severityLevel, parseConfidence, parseSeverity, parseIntOrThrow } from "../../engine/score.js";
|
|
8
|
+
import { loadConfigIfExists } from "../../engine/config.js";
|
|
9
|
+
import { requireProLicense } from "../../engine/license.js";
|
|
10
|
+
export async function cmdCi(opts) {
|
|
11
|
+
try {
|
|
12
|
+
const rootDir = process.cwd();
|
|
13
|
+
// --preview runs a free, non-blocking scan (no enforcement, no exit code)
|
|
14
|
+
if (!opts.preview) {
|
|
15
|
+
const config = loadConfigIfExists(rootDir);
|
|
16
|
+
requireProLicense(config?.license?.key);
|
|
17
|
+
}
|
|
18
|
+
const result = await runScan({ rootDir });
|
|
19
|
+
const minConf = parseConfidence(opts.minConfidence ?? "high");
|
|
20
|
+
const failOnSeverity = parseSeverity(opts.failOn ?? "critical");
|
|
21
|
+
const minScore = parseIntOrThrow(opts.minScore ?? "70", "min-score");
|
|
22
|
+
const maxNewCritical = parseIntOrThrow(opts.maxNewCritical ?? "0", "max-new-critical");
|
|
23
|
+
const maxNewHigh = opts.maxNewHigh !== undefined ? parseIntOrThrow(opts.maxNewHigh, "max-new-high") : undefined;
|
|
24
|
+
// Filter findings by confidence for failure evaluation
|
|
25
|
+
const gatedFindings = result.findings.filter((f) => confidenceLevel(f.confidence) >= confidenceLevel(minConf));
|
|
26
|
+
// Check baseline
|
|
27
|
+
let diff;
|
|
28
|
+
if (opts.baseline) {
|
|
29
|
+
const baseline = loadBaseline(opts.baseline);
|
|
30
|
+
if (baseline) {
|
|
31
|
+
diff = diffBaseline(baseline, result);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Output report
|
|
35
|
+
let output;
|
|
36
|
+
switch (opts.format) {
|
|
37
|
+
case "json":
|
|
38
|
+
output = formatJson(result);
|
|
39
|
+
break;
|
|
40
|
+
case "sarif":
|
|
41
|
+
output = formatSarif(result);
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
output = formatPretty(result, diff);
|
|
45
|
+
}
|
|
46
|
+
if (opts.output) {
|
|
47
|
+
writeFileSync(opts.output, output);
|
|
48
|
+
}
|
|
49
|
+
console.log(output);
|
|
50
|
+
// --preview: print summary and exit without enforcement
|
|
51
|
+
if (opts.preview) {
|
|
52
|
+
console.log(pc.dim("\n Preview mode — no enforcement applied."));
|
|
53
|
+
console.log(pc.dim(" Upgrade to Pro for CI enforcement: https://fourteensystems.com/prodcheck#pricing\n"));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Evaluate gates
|
|
57
|
+
const failures = [];
|
|
58
|
+
// Score gate
|
|
59
|
+
if (result.score < minScore) {
|
|
60
|
+
failures.push(`Score ${result.score} is below minimum ${minScore}`);
|
|
61
|
+
}
|
|
62
|
+
// Severity gate: any findings at or above fail-on severity with sufficient confidence
|
|
63
|
+
const failingSeverities = gatedFindings.filter((f) => severityLevel(f.severity) >= severityLevel(failOnSeverity));
|
|
64
|
+
if (failingSeverities.length > 0) {
|
|
65
|
+
failures.push(`${failingSeverities.length} finding(s) at ${failOnSeverity} or above (${minConf}+ confidence)`);
|
|
66
|
+
}
|
|
67
|
+
// New findings gate (baseline)
|
|
68
|
+
if (diff) {
|
|
69
|
+
const newCritical = diff.newFindings.filter((f) => f.severity === "critical").length;
|
|
70
|
+
const newHigh = diff.newFindings.filter((f) => f.severity === "high").length;
|
|
71
|
+
if (newCritical > maxNewCritical) {
|
|
72
|
+
failures.push(`${newCritical} new critical finding(s) exceeds max ${maxNewCritical}`);
|
|
73
|
+
}
|
|
74
|
+
if (maxNewHigh !== undefined && newHigh > maxNewHigh) {
|
|
75
|
+
failures.push(`${newHigh} new high finding(s) exceeds max ${maxNewHigh}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (failures.length > 0) {
|
|
79
|
+
console.log(pc.red("\n CI FAILED:"));
|
|
80
|
+
for (const f of failures) {
|
|
81
|
+
console.log(pc.red(` - ${f}`));
|
|
82
|
+
}
|
|
83
|
+
// Show specific rule IDs + files that triggered the failure
|
|
84
|
+
if (failingSeverities.length > 0) {
|
|
85
|
+
console.log("");
|
|
86
|
+
console.log(pc.dim(" Failing findings:"));
|
|
87
|
+
for (const f of failingSeverities) {
|
|
88
|
+
const loc = f.line ? `:${f.line}` : "";
|
|
89
|
+
console.log(` ${pc.red(f.ruleId)} ${pc.dim(`(${f.severity})`)} ${pc.dim(f.file + loc)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
console.log("");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(pc.green("\n CI PASSED\n"));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error(pc.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=ci.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.js","sourceRoot":"","sources":["../../../src/cli/commands/ci.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AAGA,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB9D"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { RULE_REGISTRY } from "../../rules/index.js";
|
|
3
|
+
export async function cmdExplain(ruleId) {
|
|
4
|
+
const rule = RULE_REGISTRY.find((r) => r.id === ruleId || r.id === ruleId.toUpperCase());
|
|
5
|
+
if (!rule) {
|
|
6
|
+
console.error(pc.red(` Unknown rule: ${ruleId}`));
|
|
7
|
+
console.error(pc.dim(` Run \`prodcheck rules\` to see available rules.`));
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
console.log(`\n ${pc.bold(rule.id)}`);
|
|
11
|
+
console.log(` ${rule.name}`);
|
|
12
|
+
console.log(` Default severity: ${rule.defaultSeverity}`);
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log(` ${rule.description}`);
|
|
15
|
+
console.log("");
|
|
16
|
+
console.log(` ${pc.dim("How it works:")}`);
|
|
17
|
+
console.log(` ${rule.docs}`);
|
|
18
|
+
console.log("");
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc;IAC7C,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAC7B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,WAAW,EAAE,CACxD,CAAC;IAEF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC,CAAC;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AASA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiH9D"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { findConfigFile, writeDefaultConfig } from "../../engine/config.js";
|
|
3
|
+
import { runScan } from "../../engine/run.js";
|
|
4
|
+
import { scoreStatus } from "../../engine/score.js";
|
|
5
|
+
import { readDeps } from "../../next/deps.js";
|
|
6
|
+
import { detectNextAppRouter } from "../../next/detect.js";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
export async function cmdInit(opts) {
|
|
10
|
+
const rootDir = process.cwd();
|
|
11
|
+
// 1. Detect framework
|
|
12
|
+
const det = detectNextAppRouter(rootDir);
|
|
13
|
+
if (!det.ok) {
|
|
14
|
+
console.error(pc.red(`\n Prodcheck v1 requires a Next.js App Router project.`));
|
|
15
|
+
console.error(pc.dim(` Reason: ${det.reason}`));
|
|
16
|
+
console.error(pc.dim(` Make sure you're in the project root with package.json and app/ directory.\n`));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
console.log(pc.green(" Detected Next.js App Router"));
|
|
20
|
+
// 2. Detect dependencies and print what we found
|
|
21
|
+
const deps = readDeps(rootDir);
|
|
22
|
+
const detected = ["next-app-router"];
|
|
23
|
+
if (deps.hasNextAuth)
|
|
24
|
+
detected.push("next-auth");
|
|
25
|
+
if (deps.hasClerk)
|
|
26
|
+
detected.push("clerk");
|
|
27
|
+
if (deps.hasSupabase)
|
|
28
|
+
detected.push("supabase");
|
|
29
|
+
if (deps.hasPrisma)
|
|
30
|
+
detected.push("prisma");
|
|
31
|
+
if (deps.hasDrizzle)
|
|
32
|
+
detected.push("drizzle");
|
|
33
|
+
if (deps.hasTrpc)
|
|
34
|
+
detected.push("trpc");
|
|
35
|
+
if (deps.hasUpstashRatelimit)
|
|
36
|
+
detected.push("upstash-ratelimit");
|
|
37
|
+
// Check for middleware
|
|
38
|
+
const hasMiddleware = existsSync(path.join(rootDir, "middleware.ts"))
|
|
39
|
+
|| existsSync(path.join(rootDir, "middleware.js"))
|
|
40
|
+
|| existsSync(path.join(rootDir, "src/middleware.ts"))
|
|
41
|
+
|| existsSync(path.join(rootDir, "src/middleware.js"));
|
|
42
|
+
if (hasMiddleware)
|
|
43
|
+
detected.push("middleware.ts");
|
|
44
|
+
console.log(pc.green(` Detected: ${detected.join(" · ")}`));
|
|
45
|
+
// 3. Write config (idempotent)
|
|
46
|
+
const existingConfig = findConfigFile(rootDir);
|
|
47
|
+
if (existingConfig && !opts.force) {
|
|
48
|
+
console.log(pc.dim(` Found existing config → skipping generation (${path.basename(existingConfig)})`));
|
|
49
|
+
}
|
|
50
|
+
else if (opts.dryRun) {
|
|
51
|
+
console.log(pc.dim(" Would create prodcheck.config.json (--dry-run)"));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
writeDefaultConfig(rootDir, { force: Boolean(opts.force) });
|
|
55
|
+
console.log(pc.green(" Created prodcheck.config.json"));
|
|
56
|
+
}
|
|
57
|
+
// 4. Run scan
|
|
58
|
+
console.log(pc.dim("\n Running scan..."));
|
|
59
|
+
try {
|
|
60
|
+
const result = await runScan({ rootDir });
|
|
61
|
+
const status = scoreStatus(result.score);
|
|
62
|
+
const scoreColor = status === "PASS" ? pc.green : status === "WARN" ? pc.yellow : pc.red;
|
|
63
|
+
console.log(`\n Prodcheck Score: ${scoreColor(String(result.score))} ${scoreColor(status)}`);
|
|
64
|
+
if (result.findings.length === 0) {
|
|
65
|
+
console.log(pc.green(" No findings — looking good!"));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Show top 5 findings
|
|
69
|
+
const top = result.findings.slice(0, 5);
|
|
70
|
+
for (const f of top) {
|
|
71
|
+
const loc = f.line ? `:${f.line}` : "";
|
|
72
|
+
const conf = pc.dim(`(${f.confidence})`);
|
|
73
|
+
console.log(` ${pc.red(f.ruleId)} ${conf} ${pc.dim(f.file + loc)}`);
|
|
74
|
+
}
|
|
75
|
+
if (result.findings.length > 5) {
|
|
76
|
+
console.log(pc.dim(` ... and ${result.findings.length - 5} more`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Wrapper suggestions
|
|
80
|
+
const wrapperFindings = result.findings.filter((f) => f.ruleId === "WRAPPER-UNRECOGNIZED");
|
|
81
|
+
if (wrapperFindings.length > 0) {
|
|
82
|
+
console.log(pc.yellow("\n Wrapper hints needed:"));
|
|
83
|
+
for (const f of wrapperFindings) {
|
|
84
|
+
const nameMatch = f.message.match(/Wrapper "(\w+)"/);
|
|
85
|
+
if (!nameMatch)
|
|
86
|
+
continue;
|
|
87
|
+
const name = nameMatch[1];
|
|
88
|
+
// Determine suggestion based on evidence
|
|
89
|
+
const hasAuth = f.evidence.some((e) => e.startsWith("Auth call detected:"));
|
|
90
|
+
const hasRL = f.evidence.some((e) => e.startsWith("Rate-limit call detected:"));
|
|
91
|
+
const isUnresolved = f.message.includes("could not be resolved");
|
|
92
|
+
const isUnverified = f.message.includes("enforcement not proven");
|
|
93
|
+
if (isUnresolved) {
|
|
94
|
+
console.log(pc.dim(` ${name} — wraps routes but could not be resolved`));
|
|
95
|
+
console.log(pc.dim(` If auth: add "${name}" to hints.auth.functions`));
|
|
96
|
+
console.log(pc.dim(` If rate limit: add "${name}" to hints.rateLimit.wrappers`));
|
|
97
|
+
}
|
|
98
|
+
else if (isUnverified) {
|
|
99
|
+
if (hasAuth && !hasRL) {
|
|
100
|
+
console.log(pc.dim(` ${name} — calls auth but enforcement not proven`));
|
|
101
|
+
console.log(pc.dim(` Verify wrapper or add "${name}" to hints.auth.functions`));
|
|
102
|
+
}
|
|
103
|
+
else if (hasRL && !hasAuth) {
|
|
104
|
+
console.log(pc.dim(` ${name} — calls rate limiter but enforcement not proven`));
|
|
105
|
+
console.log(pc.dim(` Verify wrapper or add "${name}" to hints.rateLimit.wrappers`));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.log(pc.dim(` ${name} — missing protections`));
|
|
109
|
+
console.log(pc.dim(` If auth: add "${name}" to hints.auth.functions`));
|
|
110
|
+
console.log(pc.dim(` If rate limit: add "${name}" to hints.rateLimit.wrappers`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Next steps
|
|
116
|
+
console.log(pc.dim("\n Next:"));
|
|
117
|
+
console.log(pc.dim(" prodcheck baseline --write Save current state as baseline"));
|
|
118
|
+
console.log(pc.dim(" prodcheck explain <RULE> Learn about a specific rule"));
|
|
119
|
+
console.log(pc.dim(" prodcheck ci Run in CI mode"));
|
|
120
|
+
console.log("");
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
console.error(pc.red(` Scan failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/rules.ts"],"names":[],"mappings":"AAGA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAW9C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { RULE_REGISTRY } from "../../rules/index.js";
|
|
3
|
+
export async function cmdRules() {
|
|
4
|
+
console.log("\n Prodcheck Rules (v1)\n");
|
|
5
|
+
for (const rule of RULE_REGISTRY) {
|
|
6
|
+
const severityColor = rule.defaultSeverity === "critical" ? pc.red : pc.yellow;
|
|
7
|
+
console.log(` ${pc.bold(rule.id)} ${severityColor(`[${rule.defaultSeverity}]`)}`);
|
|
8
|
+
console.log(` ${pc.dim(rule.description)}`);
|
|
9
|
+
console.log("");
|
|
10
|
+
}
|
|
11
|
+
console.log(pc.dim(" Run `prodcheck explain <RULE>` for full details.\n"));
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/cli/commands/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;QAC/E,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,aAAa,CAAC,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,EAAE,CAAC,CAAC;QACnF,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC,CAAC;AAC9E,CAAC"}
|