@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.
Files changed (239) hide show
  1. package/README.md +252 -0
  2. package/bin/prodcheck.mjs +2 -0
  3. package/dist/cli/commands/baseline.d.ts +7 -0
  4. package/dist/cli/commands/baseline.d.ts.map +1 -0
  5. package/dist/cli/commands/baseline.js +22 -0
  6. package/dist/cli/commands/baseline.js.map +1 -0
  7. package/dist/cli/commands/ci.d.ts +14 -0
  8. package/dist/cli/commands/ci.d.ts.map +1 -0
  9. package/dist/cli/commands/ci.js +104 -0
  10. package/dist/cli/commands/ci.js.map +1 -0
  11. package/dist/cli/commands/explain.d.ts +2 -0
  12. package/dist/cli/commands/explain.d.ts.map +1 -0
  13. package/dist/cli/commands/explain.js +20 -0
  14. package/dist/cli/commands/explain.js.map +1 -0
  15. package/dist/cli/commands/init.d.ts +7 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -0
  17. package/dist/cli/commands/init.js +127 -0
  18. package/dist/cli/commands/init.js.map +1 -0
  19. package/dist/cli/commands/rules.d.ts +2 -0
  20. package/dist/cli/commands/rules.d.ts.map +1 -0
  21. package/dist/cli/commands/rules.js +13 -0
  22. package/dist/cli/commands/rules.js.map +1 -0
  23. package/dist/cli/commands/scan.d.ts +10 -0
  24. package/dist/cli/commands/scan.d.ts.map +1 -0
  25. package/dist/cli/commands/scan.js +65 -0
  26. package/dist/cli/commands/scan.js.map +1 -0
  27. package/dist/cli/commands/waive.d.ts +8 -0
  28. package/dist/cli/commands/waive.d.ts.map +1 -0
  29. package/dist/cli/commands/waive.js +34 -0
  30. package/dist/cli/commands/waive.js.map +1 -0
  31. package/dist/cli/index.d.ts +2 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +64 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/engine/baseline.d.ts +11 -0
  36. package/dist/engine/baseline.d.ts.map +1 -0
  37. package/dist/engine/baseline.js +39 -0
  38. package/dist/engine/baseline.js.map +1 -0
  39. package/dist/engine/baseline.test.d.ts +2 -0
  40. package/dist/engine/baseline.test.d.ts.map +1 -0
  41. package/dist/engine/baseline.test.js +135 -0
  42. package/dist/engine/baseline.test.js.map +1 -0
  43. package/dist/engine/config.d.ts +8 -0
  44. package/dist/engine/config.d.ts.map +1 -0
  45. package/dist/engine/config.js +134 -0
  46. package/dist/engine/config.js.map +1 -0
  47. package/dist/engine/config.test.d.ts +2 -0
  48. package/dist/engine/config.test.d.ts.map +1 -0
  49. package/dist/engine/config.test.js +107 -0
  50. package/dist/engine/config.test.js.map +1 -0
  51. package/dist/engine/extensions/load.d.ts +11 -0
  52. package/dist/engine/extensions/load.d.ts.map +1 -0
  53. package/dist/engine/extensions/load.js +26 -0
  54. package/dist/engine/extensions/load.js.map +1 -0
  55. package/dist/engine/extensions/registry.d.ts +5 -0
  56. package/dist/engine/extensions/registry.d.ts.map +1 -0
  57. package/dist/engine/extensions/registry.js +11 -0
  58. package/dist/engine/extensions/registry.js.map +1 -0
  59. package/dist/engine/extensions/types.d.ts +51 -0
  60. package/dist/engine/extensions/types.d.ts.map +1 -0
  61. package/dist/engine/extensions/types.js +2 -0
  62. package/dist/engine/extensions/types.js.map +1 -0
  63. package/dist/engine/license.d.ts +40 -0
  64. package/dist/engine/license.d.ts.map +1 -0
  65. package/dist/engine/license.js +104 -0
  66. package/dist/engine/license.js.map +1 -0
  67. package/dist/engine/report.d.ts +5 -0
  68. package/dist/engine/report.d.ts.map +1 -0
  69. package/dist/engine/report.js +115 -0
  70. package/dist/engine/report.js.map +1 -0
  71. package/dist/engine/run.d.ts +11 -0
  72. package/dist/engine/run.d.ts.map +1 -0
  73. package/dist/engine/run.js +105 -0
  74. package/dist/engine/run.js.map +1 -0
  75. package/dist/engine/sarif.d.ts +3 -0
  76. package/dist/engine/sarif.d.ts.map +1 -0
  77. package/dist/engine/sarif.js +58 -0
  78. package/dist/engine/sarif.js.map +1 -0
  79. package/dist/engine/sarif.test.d.ts +2 -0
  80. package/dist/engine/sarif.test.d.ts.map +1 -0
  81. package/dist/engine/sarif.test.js +152 -0
  82. package/dist/engine/sarif.test.js.map +1 -0
  83. package/dist/engine/score.d.ts +13 -0
  84. package/dist/engine/score.d.ts.map +1 -0
  85. package/dist/engine/score.js +116 -0
  86. package/dist/engine/score.js.map +1 -0
  87. package/dist/engine/score.test.d.ts +2 -0
  88. package/dist/engine/score.test.d.ts.map +1 -0
  89. package/dist/engine/score.test.js +227 -0
  90. package/dist/engine/score.test.js.map +1 -0
  91. package/dist/engine/types.d.ts +123 -0
  92. package/dist/engine/types.d.ts.map +1 -0
  93. package/dist/engine/types.js +2 -0
  94. package/dist/engine/types.js.map +1 -0
  95. package/dist/engine/version.d.ts +5 -0
  96. package/dist/engine/version.d.ts.map +1 -0
  97. package/dist/engine/version.js +15 -0
  98. package/dist/engine/version.js.map +1 -0
  99. package/dist/engine/waivers.d.ts +9 -0
  100. package/dist/engine/waivers.d.ts.map +1 -0
  101. package/dist/engine/waivers.js +55 -0
  102. package/dist/engine/waivers.js.map +1 -0
  103. package/dist/engine/waivers.test.d.ts +2 -0
  104. package/dist/engine/waivers.test.d.ts.map +1 -0
  105. package/dist/engine/waivers.test.js +147 -0
  106. package/dist/engine/waivers.test.js.map +1 -0
  107. package/dist/index.d.ts +14 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +12 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/next/deps.d.ts +4 -0
  112. package/dist/next/deps.d.ts.map +1 -0
  113. package/dist/next/deps.js +118 -0
  114. package/dist/next/deps.js.map +1 -0
  115. package/dist/next/deps.test.d.ts +2 -0
  116. package/dist/next/deps.test.d.ts.map +1 -0
  117. package/dist/next/deps.test.js +249 -0
  118. package/dist/next/deps.test.js.map +1 -0
  119. package/dist/next/detect.d.ts +10 -0
  120. package/dist/next/detect.d.ts.map +1 -0
  121. package/dist/next/detect.js +57 -0
  122. package/dist/next/detect.js.map +1 -0
  123. package/dist/next/detect.test.d.ts +2 -0
  124. package/dist/next/detect.test.d.ts.map +1 -0
  125. package/dist/next/detect.test.js +74 -0
  126. package/dist/next/detect.test.js.map +1 -0
  127. package/dist/next/index.d.ts +5 -0
  128. package/dist/next/index.d.ts.map +1 -0
  129. package/dist/next/index.js +59 -0
  130. package/dist/next/index.js.map +1 -0
  131. package/dist/next/middleware.d.ts +3 -0
  132. package/dist/next/middleware.d.ts.map +1 -0
  133. package/dist/next/middleware.js +48 -0
  134. package/dist/next/middleware.js.map +1 -0
  135. package/dist/next/middleware.test.d.ts +2 -0
  136. package/dist/next/middleware.test.d.ts.map +1 -0
  137. package/dist/next/middleware.test.js +203 -0
  138. package/dist/next/middleware.test.js.map +1 -0
  139. package/dist/next/routes.d.ts +10 -0
  140. package/dist/next/routes.d.ts.map +1 -0
  141. package/dist/next/routes.js +172 -0
  142. package/dist/next/routes.js.map +1 -0
  143. package/dist/next/routes.test.d.ts +2 -0
  144. package/dist/next/routes.test.d.ts.map +1 -0
  145. package/dist/next/routes.test.js +175 -0
  146. package/dist/next/routes.test.js.map +1 -0
  147. package/dist/next/server-actions.d.ts +4 -0
  148. package/dist/next/server-actions.d.ts.map +1 -0
  149. package/dist/next/server-actions.js +107 -0
  150. package/dist/next/server-actions.js.map +1 -0
  151. package/dist/next/server-actions.test.d.ts +2 -0
  152. package/dist/next/server-actions.test.d.ts.map +1 -0
  153. package/dist/next/server-actions.test.js +138 -0
  154. package/dist/next/server-actions.test.js.map +1 -0
  155. package/dist/next/trpc.d.ts +3 -0
  156. package/dist/next/trpc.d.ts.map +1 -0
  157. package/dist/next/trpc.js +312 -0
  158. package/dist/next/trpc.js.map +1 -0
  159. package/dist/next/types.d.ts +144 -0
  160. package/dist/next/types.d.ts.map +1 -0
  161. package/dist/next/types.js +2 -0
  162. package/dist/next/types.js.map +1 -0
  163. package/dist/next/wrappers.d.ts +10 -0
  164. package/dist/next/wrappers.d.ts.map +1 -0
  165. package/dist/next/wrappers.js +536 -0
  166. package/dist/next/wrappers.js.map +1 -0
  167. package/dist/next/wrappers.test.d.ts +2 -0
  168. package/dist/next/wrappers.test.d.ts.map +1 -0
  169. package/dist/next/wrappers.test.js +361 -0
  170. package/dist/next/wrappers.test.js.map +1 -0
  171. package/dist/rules/auth-boundary-missing.d.ts +5 -0
  172. package/dist/rules/auth-boundary-missing.d.ts.map +1 -0
  173. package/dist/rules/auth-boundary-missing.js +463 -0
  174. package/dist/rules/auth-boundary-missing.js.map +1 -0
  175. package/dist/rules/auth-boundary-missing.test.d.ts +2 -0
  176. package/dist/rules/auth-boundary-missing.test.d.ts.map +1 -0
  177. package/dist/rules/auth-boundary-missing.test.js +492 -0
  178. package/dist/rules/auth-boundary-missing.test.js.map +1 -0
  179. package/dist/rules/index.d.ts +12 -0
  180. package/dist/rules/index.d.ts.map +1 -0
  181. package/dist/rules/index.js +95 -0
  182. package/dist/rules/index.js.map +1 -0
  183. package/dist/rules/input-validation-missing.d.ts +5 -0
  184. package/dist/rules/input-validation-missing.d.ts.map +1 -0
  185. package/dist/rules/input-validation-missing.js +272 -0
  186. package/dist/rules/input-validation-missing.js.map +1 -0
  187. package/dist/rules/input-validation-missing.test.d.ts +2 -0
  188. package/dist/rules/input-validation-missing.test.d.ts.map +1 -0
  189. package/dist/rules/input-validation-missing.test.js +449 -0
  190. package/dist/rules/input-validation-missing.test.js.map +1 -0
  191. package/dist/rules/rate-limit-missing.d.ts +5 -0
  192. package/dist/rules/rate-limit-missing.d.ts.map +1 -0
  193. package/dist/rules/rate-limit-missing.js +316 -0
  194. package/dist/rules/rate-limit-missing.js.map +1 -0
  195. package/dist/rules/rate-limit-missing.test.d.ts +2 -0
  196. package/dist/rules/rate-limit-missing.test.d.ts.map +1 -0
  197. package/dist/rules/rate-limit-missing.test.js +381 -0
  198. package/dist/rules/rate-limit-missing.test.js.map +1 -0
  199. package/dist/rules/tenancy-scope-missing.d.ts +5 -0
  200. package/dist/rules/tenancy-scope-missing.d.ts.map +1 -0
  201. package/dist/rules/tenancy-scope-missing.js +149 -0
  202. package/dist/rules/tenancy-scope-missing.js.map +1 -0
  203. package/dist/rules/wrapper-unrecognized.d.ts +5 -0
  204. package/dist/rules/wrapper-unrecognized.d.ts.map +1 -0
  205. package/dist/rules/wrapper-unrecognized.js +81 -0
  206. package/dist/rules/wrapper-unrecognized.js.map +1 -0
  207. package/dist/util/hof.d.ts +22 -0
  208. package/dist/util/hof.d.ts.map +1 -0
  209. package/dist/util/hof.js +99 -0
  210. package/dist/util/hof.js.map +1 -0
  211. package/dist/util/hof.test.d.ts +2 -0
  212. package/dist/util/hof.test.d.ts.map +1 -0
  213. package/dist/util/hof.test.js +79 -0
  214. package/dist/util/hof.test.js.map +1 -0
  215. package/dist/util/monorepo.d.ts +6 -0
  216. package/dist/util/monorepo.d.ts.map +1 -0
  217. package/dist/util/monorepo.js +29 -0
  218. package/dist/util/monorepo.js.map +1 -0
  219. package/dist/util/outbound-fetch.d.ts +14 -0
  220. package/dist/util/outbound-fetch.d.ts.map +1 -0
  221. package/dist/util/outbound-fetch.js +59 -0
  222. package/dist/util/outbound-fetch.js.map +1 -0
  223. package/dist/util/outbound-fetch.test.d.ts +2 -0
  224. package/dist/util/outbound-fetch.test.d.ts.map +1 -0
  225. package/dist/util/outbound-fetch.test.js +83 -0
  226. package/dist/util/outbound-fetch.test.js.map +1 -0
  227. package/dist/util/paths.d.ts +6 -0
  228. package/dist/util/paths.d.ts.map +1 -0
  229. package/dist/util/paths.js +18 -0
  230. package/dist/util/paths.js.map +1 -0
  231. package/dist/util/resolve.d.ts +30 -0
  232. package/dist/util/resolve.d.ts.map +1 -0
  233. package/dist/util/resolve.js +306 -0
  234. package/dist/util/resolve.js.map +1 -0
  235. package/dist/util/resolve.test.d.ts +2 -0
  236. package/dist/util/resolve.test.d.ts.map +1 -0
  237. package/dist/util/resolve.test.js +186 -0
  238. package/dist/util/resolve.test.js.map +1 -0
  239. 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,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli/index.js";
@@ -0,0 +1,7 @@
1
+ interface BaselineOptions {
2
+ write?: boolean;
3
+ output?: string;
4
+ }
5
+ export declare function cmdBaseline(opts: BaselineOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=baseline.d.ts.map
@@ -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,2 @@
1
+ export declare function cmdExplain(ruleId: string): Promise<void>;
2
+ //# sourceMappingURL=explain.d.ts.map
@@ -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,7 @@
1
+ interface InitOptions {
2
+ force?: boolean;
3
+ dryRun?: boolean;
4
+ }
5
+ export declare function cmdInit(opts: InitOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=init.d.ts.map
@@ -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,2 @@
1
+ export declare function cmdRules(): Promise<void>;
2
+ //# sourceMappingURL=rules.d.ts.map
@@ -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"}