@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
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { run } from "./auth-boundary-missing.js";
|
|
5
|
+
/* ------------------------------------------------------------------ */
|
|
6
|
+
/* Helpers */
|
|
7
|
+
/* ------------------------------------------------------------------ */
|
|
8
|
+
const MUTATION_SIGNALS = {
|
|
9
|
+
hasMutationEvidence: true,
|
|
10
|
+
hasDbWriteEvidence: true,
|
|
11
|
+
hasStripeWriteEvidence: false,
|
|
12
|
+
mutationDetails: ["prisma.create"],
|
|
13
|
+
};
|
|
14
|
+
function protectionSummary(opts) {
|
|
15
|
+
return {
|
|
16
|
+
auth: {
|
|
17
|
+
satisfied: opts.authSatisfied ?? false,
|
|
18
|
+
enforced: false,
|
|
19
|
+
sources: opts.authSatisfied ? ["direct"] : [],
|
|
20
|
+
details: [],
|
|
21
|
+
unverifiedWrappers: opts.unverifiedWrappers ?? [],
|
|
22
|
+
},
|
|
23
|
+
rateLimit: {
|
|
24
|
+
satisfied: false,
|
|
25
|
+
enforced: false,
|
|
26
|
+
sources: [],
|
|
27
|
+
details: [],
|
|
28
|
+
unverifiedWrappers: [],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
let tmpDir;
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
tmpDir = path.join("/tmp", `prodcheck-auth-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
35
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
36
|
+
});
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
function createRoute(relPath, source, overrides = {}) {
|
|
41
|
+
const fullPath = path.join(tmpDir, relPath);
|
|
42
|
+
mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
43
|
+
writeFileSync(fullPath, source);
|
|
44
|
+
const pathname = "/" + relPath
|
|
45
|
+
.replace(/\/route\.(ts|tsx|js|jsx)$/, "")
|
|
46
|
+
.replace(/^app\//, "");
|
|
47
|
+
return {
|
|
48
|
+
kind: "route-handler",
|
|
49
|
+
file: relPath,
|
|
50
|
+
isApi: pathname.startsWith("/api/") || pathname === "/api",
|
|
51
|
+
isPublic: true,
|
|
52
|
+
pathname,
|
|
53
|
+
signals: MUTATION_SIGNALS,
|
|
54
|
+
protection: protectionSummary({}),
|
|
55
|
+
...overrides,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function makeIndex(routes) {
|
|
59
|
+
return {
|
|
60
|
+
version: 1,
|
|
61
|
+
framework: "next-app-router",
|
|
62
|
+
rootDir: tmpDir,
|
|
63
|
+
deps: {
|
|
64
|
+
hasNextAuth: false, hasClerk: false, hasSupabase: false,
|
|
65
|
+
hasKinde: false, hasWorkOS: false, hasBetterAuth: false,
|
|
66
|
+
hasLucia: false, hasAuth0: false, hasIronSession: false,
|
|
67
|
+
hasFirebaseAuth: false, hasUpstashRatelimit: false, hasArcjet: false,
|
|
68
|
+
hasUnkey: false, hasPrisma: false, hasDrizzle: false, hasTrpc: false,
|
|
69
|
+
},
|
|
70
|
+
hints: {
|
|
71
|
+
auth: { functions: ["auth", "getServerSession"], middlewareFiles: [], allowlistPaths: [] },
|
|
72
|
+
rateLimit: { wrappers: ["rateLimit"], allowlistPaths: [] },
|
|
73
|
+
tenancy: { orgFieldNames: [] },
|
|
74
|
+
},
|
|
75
|
+
middleware: { authLikely: false, rateLimitLikely: false, matcherPatterns: [] },
|
|
76
|
+
wrappers: { wrappers: new Map() },
|
|
77
|
+
routes: { all: routes, mutationRoutes: routes },
|
|
78
|
+
serverActions: { all: [], mutationActions: [] },
|
|
79
|
+
trpc: { detected: false, procedures: [], mutationProcedures: [] },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function makeConfig(overrides = {}) {
|
|
83
|
+
return {
|
|
84
|
+
framework: "next-app-router",
|
|
85
|
+
include: ["app/**"],
|
|
86
|
+
exclude: [],
|
|
87
|
+
ci: { failOn: "critical", minConfidence: "high", minScore: 70, maxNewCritical: 0 },
|
|
88
|
+
scoring: { start: 100, penalties: { critical: 25, high: 10, med: 3, low: 1 } },
|
|
89
|
+
hints: {
|
|
90
|
+
auth: { functions: ["auth", "getServerSession"], middlewareFiles: [], allowlistPaths: [] },
|
|
91
|
+
rateLimit: { wrappers: ["rateLimit"], allowlistPaths: [] },
|
|
92
|
+
tenancy: { orgFieldNames: [] },
|
|
93
|
+
},
|
|
94
|
+
rules: { "AUTH-BOUNDARY-MISSING": { severity: "critical" } },
|
|
95
|
+
waiversFile: "prodcheck.waivers.json",
|
|
96
|
+
...overrides,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const config = makeConfig();
|
|
100
|
+
/* ------------------------------------------------------------------ */
|
|
101
|
+
/* 1. Upstash Workflow serve() — should suppress finding */
|
|
102
|
+
/* ------------------------------------------------------------------ */
|
|
103
|
+
describe("Upstash Workflow serve() recognition", () => {
|
|
104
|
+
it("suppresses finding for @upstash/workflow serve()", () => {
|
|
105
|
+
const route = createRoute("app/api/workflows/process/route.ts", `
|
|
106
|
+
import { serve } from "@upstash/workflow/nextjs";
|
|
107
|
+
import { MemoryExtractionExecutor } from "@/server/services/memory";
|
|
108
|
+
|
|
109
|
+
export const { POST } = serve(async (context) => {
|
|
110
|
+
const executor = await MemoryExtractionExecutor.create();
|
|
111
|
+
await prisma.memory.create({ data: { userId: "test" } });
|
|
112
|
+
return { done: true };
|
|
113
|
+
});
|
|
114
|
+
`);
|
|
115
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
it("suppresses finding for serve with generic type param", () => {
|
|
118
|
+
const route = createRoute("app/api/workflows/extract/route.ts", `
|
|
119
|
+
import { serve } from "@upstash/workflow/nextjs";
|
|
120
|
+
|
|
121
|
+
export const { POST } = serve<PayloadInput>(async (context) => {
|
|
122
|
+
await prisma.topic.create({ data: context.requestPayload });
|
|
123
|
+
});
|
|
124
|
+
`);
|
|
125
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
126
|
+
});
|
|
127
|
+
it("does NOT suppress for serve() from unknown package", () => {
|
|
128
|
+
const route = createRoute("app/api/unknown/route.ts", `
|
|
129
|
+
import { serve } from "some-other-package";
|
|
130
|
+
|
|
131
|
+
export const { POST } = serve(async (context) => {
|
|
132
|
+
await prisma.user.create({ data: { name: "test" } });
|
|
133
|
+
});
|
|
134
|
+
`);
|
|
135
|
+
expect(run(makeIndex([route]), config)).toHaveLength(1);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
/* ------------------------------------------------------------------ */
|
|
139
|
+
/* 1b. Inngest serve() — should suppress finding */
|
|
140
|
+
/* ------------------------------------------------------------------ */
|
|
141
|
+
describe("Inngest serve() recognition", () => {
|
|
142
|
+
it("suppresses finding for inngest/next serve()", () => {
|
|
143
|
+
const route = createRoute("app/api/inngest/route.ts", `
|
|
144
|
+
import { serve } from "inngest/next";
|
|
145
|
+
import { inngest } from "@/inngest/client";
|
|
146
|
+
|
|
147
|
+
export const { GET, POST, PUT } = serve({ client: inngest, functions: [myFn] });
|
|
148
|
+
`);
|
|
149
|
+
// No mutation signals in this source, override to force mutation
|
|
150
|
+
const routeWithMutation = createRoute("app/api/inngest/route.ts", `
|
|
151
|
+
import { serve } from "inngest/next";
|
|
152
|
+
import { inngest } from "@/inngest/client";
|
|
153
|
+
|
|
154
|
+
export const { POST } = serve({ client: inngest, functions: [myFn] });
|
|
155
|
+
// hypothetical mutation
|
|
156
|
+
await prisma.job.create({ data: {} });
|
|
157
|
+
`);
|
|
158
|
+
expect(run(makeIndex([routeWithMutation]), config)).toHaveLength(0);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
/* ------------------------------------------------------------------ */
|
|
162
|
+
/* 2. Svix webhook verification — should suppress finding */
|
|
163
|
+
/* ------------------------------------------------------------------ */
|
|
164
|
+
describe("Svix webhook verification", () => {
|
|
165
|
+
it("suppresses finding for svix Webhook.verify()", () => {
|
|
166
|
+
const route = createRoute("app/api/webhooks/clerk/route.ts", `
|
|
167
|
+
import { Webhook } from "svix";
|
|
168
|
+
|
|
169
|
+
export async function POST(req: Request) {
|
|
170
|
+
const body = await req.text();
|
|
171
|
+
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
|
|
172
|
+
const payload = wh.verify(body, Object.fromEntries(req.headers));
|
|
173
|
+
await prisma.user.create({ data: payload });
|
|
174
|
+
return Response.json({ ok: true });
|
|
175
|
+
}
|
|
176
|
+
`);
|
|
177
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
/* ------------------------------------------------------------------ */
|
|
181
|
+
/* 3. timingSafeEqual — contextual webhook verification */
|
|
182
|
+
/* ------------------------------------------------------------------ */
|
|
183
|
+
describe("timingSafeEqual webhook verification", () => {
|
|
184
|
+
it("suppresses when timingSafeEqual + headers.get + 401", () => {
|
|
185
|
+
const route = createRoute("app/api/webhooks/video/route.ts", `
|
|
186
|
+
import { timingSafeEqual } from "node:crypto";
|
|
187
|
+
|
|
188
|
+
export const POST = async (req: Request) => {
|
|
189
|
+
const url = new URL(req.url);
|
|
190
|
+
const token = url.searchParams.get("token");
|
|
191
|
+
const expected = metadata?.webhookToken;
|
|
192
|
+
if (!expected || !token || !timingSafeEqual(Buffer.from(token), Buffer.from(expected))) {
|
|
193
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
194
|
+
}
|
|
195
|
+
await prisma.asyncTask.update({ where: { id }, data: { status: "success" } });
|
|
196
|
+
return NextResponse.json({ success: true });
|
|
197
|
+
};
|
|
198
|
+
`);
|
|
199
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
200
|
+
});
|
|
201
|
+
it("does NOT suppress timingSafeEqual without request-derived data", () => {
|
|
202
|
+
const route = createRoute("app/api/compare/route.ts", `
|
|
203
|
+
import { timingSafeEqual } from "node:crypto";
|
|
204
|
+
|
|
205
|
+
export const POST = async (req: Request) => {
|
|
206
|
+
// timingSafeEqual used for non-auth comparison
|
|
207
|
+
const match = timingSafeEqual(Buffer.from("a"), Buffer.from("b"));
|
|
208
|
+
await prisma.user.create({ data: { name: "test" } });
|
|
209
|
+
return Response.json({ match });
|
|
210
|
+
};
|
|
211
|
+
`);
|
|
212
|
+
expect(run(makeIndex([route]), config)).toHaveLength(1);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
/* ------------------------------------------------------------------ */
|
|
216
|
+
/* 4. Auth-guard return detection (safe version) */
|
|
217
|
+
/* ------------------------------------------------------------------ */
|
|
218
|
+
describe("auth-guard return detection", () => {
|
|
219
|
+
it("suppresses for x-api-key header check + env comparison + 401 before mutation", () => {
|
|
220
|
+
const route = createRoute("app/api/internal/route.ts", `
|
|
221
|
+
export async function POST(req: Request) {
|
|
222
|
+
const apiKey = req.headers.get("x-api-key");
|
|
223
|
+
if (apiKey !== process.env.API_KEY) {
|
|
224
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
225
|
+
}
|
|
226
|
+
await prisma.job.create({ data: { type: "sync" } });
|
|
227
|
+
return NextResponse.json({ ok: true });
|
|
228
|
+
}
|
|
229
|
+
`);
|
|
230
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
231
|
+
});
|
|
232
|
+
it("suppresses for authorization header + Bearer token check + 403", () => {
|
|
233
|
+
const route = createRoute("app/api/protected/route.ts", `
|
|
234
|
+
export async function POST(req: Request) {
|
|
235
|
+
const authorization = req.headers.get("authorization");
|
|
236
|
+
if (!authorization?.startsWith("Bearer ")) {
|
|
237
|
+
return new Response("Forbidden", { status: 403 });
|
|
238
|
+
}
|
|
239
|
+
await prisma.user.update({ where: { id }, data: body });
|
|
240
|
+
return Response.json({ ok: true });
|
|
241
|
+
}
|
|
242
|
+
`);
|
|
243
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
244
|
+
});
|
|
245
|
+
it("does NOT suppress for feature flag check + 403", () => {
|
|
246
|
+
const route = createRoute("app/api/gated/route.ts", `
|
|
247
|
+
export async function POST(req: Request) {
|
|
248
|
+
const enabled = await isFeatureEnabled("new-flow");
|
|
249
|
+
if (!enabled) {
|
|
250
|
+
return NextResponse.json({ error: "Not available" }, { status: 403 });
|
|
251
|
+
}
|
|
252
|
+
await prisma.user.create({ data: { name: "test" } });
|
|
253
|
+
return Response.json({ ok: true });
|
|
254
|
+
}
|
|
255
|
+
`);
|
|
256
|
+
expect(run(makeIndex([route]), config)).toHaveLength(1);
|
|
257
|
+
});
|
|
258
|
+
it("does NOT suppress for plan gating + 403", () => {
|
|
259
|
+
const route = createRoute("app/api/billing/route.ts", `
|
|
260
|
+
export async function POST(req: Request) {
|
|
261
|
+
const isPro = await checkPlan(userId);
|
|
262
|
+
if (!isPro) {
|
|
263
|
+
return NextResponse.json({ error: "Upgrade required" }, { status: 403 });
|
|
264
|
+
}
|
|
265
|
+
await prisma.subscription.create({ data: { userId } });
|
|
266
|
+
return Response.json({ ok: true });
|
|
267
|
+
}
|
|
268
|
+
`);
|
|
269
|
+
expect(run(makeIndex([route]), config)).toHaveLength(1);
|
|
270
|
+
});
|
|
271
|
+
it("does NOT suppress for 401/403 AFTER mutation (error handling, not guard)", () => {
|
|
272
|
+
const route = createRoute("app/api/late-check/route.ts", `
|
|
273
|
+
export async function POST(req: Request) {
|
|
274
|
+
await prisma.audit.create({ data: { action: "attempt" } });
|
|
275
|
+
const token = req.headers.get("authorization");
|
|
276
|
+
if (!token) {
|
|
277
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
278
|
+
}
|
|
279
|
+
return Response.json({ ok: true });
|
|
280
|
+
}
|
|
281
|
+
`);
|
|
282
|
+
expect(run(makeIndex([route]), config)).toHaveLength(1);
|
|
283
|
+
});
|
|
284
|
+
it("suppresses for webhook header secret verification + 403", () => {
|
|
285
|
+
const route = createRoute("app/api/webhooks/memory/route.ts", `
|
|
286
|
+
export const POST = async (req: Request) => {
|
|
287
|
+
const { webhook } = parseConfig();
|
|
288
|
+
if (webhook.headers && Object.keys(webhook.headers).length > 0) {
|
|
289
|
+
for (const [key, value] of Object.entries(webhook.headers)) {
|
|
290
|
+
const headerValue = req.headers.get(key);
|
|
291
|
+
if (headerValue !== value) {
|
|
292
|
+
return NextResponse.json(
|
|
293
|
+
{ error: "Unauthorized" },
|
|
294
|
+
{ status: 403 },
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const executor = await MemoryExtractionExecutor.create();
|
|
300
|
+
const result = await executor.runDirect(params);
|
|
301
|
+
return NextResponse.json({ result }, { status: 200 });
|
|
302
|
+
};
|
|
303
|
+
`);
|
|
304
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
/* ------------------------------------------------------------------ */
|
|
308
|
+
/* 5. JWT verification (jose / jsonwebtoken) */
|
|
309
|
+
/* ------------------------------------------------------------------ */
|
|
310
|
+
describe("JWT verification", () => {
|
|
311
|
+
it("suppresses for jose jwtVerify()", () => {
|
|
312
|
+
const route = createRoute("app/api/secure/route.ts", `
|
|
313
|
+
import { jwtVerify } from "jose";
|
|
314
|
+
|
|
315
|
+
export async function POST(req: Request) {
|
|
316
|
+
const token = req.headers.get("authorization")?.split(" ")[1];
|
|
317
|
+
const { payload } = await jwtVerify(token, secret);
|
|
318
|
+
await prisma.action.create({ data: { userId: payload.sub } });
|
|
319
|
+
return Response.json({ ok: true });
|
|
320
|
+
}
|
|
321
|
+
`);
|
|
322
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
323
|
+
});
|
|
324
|
+
it("suppresses for jsonwebtoken verify()", () => {
|
|
325
|
+
const route = createRoute("app/api/jwt/route.ts", `
|
|
326
|
+
import jwt from "jsonwebtoken";
|
|
327
|
+
|
|
328
|
+
export async function POST(req: Request) {
|
|
329
|
+
const token = req.headers.get("authorization")?.split(" ")[1];
|
|
330
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
|
|
331
|
+
await prisma.user.update({ where: { id: decoded.sub }, data: { lastSeen: new Date() } });
|
|
332
|
+
return Response.json({ ok: true });
|
|
333
|
+
}
|
|
334
|
+
`);
|
|
335
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
336
|
+
});
|
|
337
|
+
it("does NOT suppress for verify() without jsonwebtoken import", () => {
|
|
338
|
+
const route = createRoute("app/api/fake-verify/route.ts", `
|
|
339
|
+
import { verify } from "./my-utils";
|
|
340
|
+
|
|
341
|
+
export async function POST(req: Request) {
|
|
342
|
+
verify(someData);
|
|
343
|
+
await prisma.user.create({ data: { name: "test" } });
|
|
344
|
+
return Response.json({ ok: true });
|
|
345
|
+
}
|
|
346
|
+
`);
|
|
347
|
+
expect(run(makeIndex([route]), config)).toHaveLength(1);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
/* ------------------------------------------------------------------ */
|
|
351
|
+
/* 6. DB-backed API token lookup */
|
|
352
|
+
/* ------------------------------------------------------------------ */
|
|
353
|
+
describe("DB-backed API token lookup", () => {
|
|
354
|
+
it("suppresses for header + prisma.apiKey.findUnique + 401", () => {
|
|
355
|
+
const route = createRoute("app/api/external/route.ts", `
|
|
356
|
+
export async function POST(req: Request) {
|
|
357
|
+
const key = req.headers.get("x-api-key");
|
|
358
|
+
if (!key) return NextResponse.json({ error: "Missing key" }, { status: 401 });
|
|
359
|
+
const apiKey = await prisma.apiKey.findUnique({ where: { key } });
|
|
360
|
+
if (!apiKey) return NextResponse.json({ error: "Invalid key" }, { status: 403 });
|
|
361
|
+
await prisma.event.create({ data: { source: "api", keyId: apiKey.id } });
|
|
362
|
+
return Response.json({ ok: true });
|
|
363
|
+
}
|
|
364
|
+
`);
|
|
365
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
366
|
+
});
|
|
367
|
+
it("suppresses for header + prisma.apiToken.findFirst + 403", () => {
|
|
368
|
+
const route = createRoute("app/api/integration/route.ts", `
|
|
369
|
+
export async function POST(req: Request) {
|
|
370
|
+
const token = req.headers.get("authorization")?.replace("Bearer ", "");
|
|
371
|
+
const apiToken = await prisma.apiToken.findFirst({ where: { token, active: true } });
|
|
372
|
+
if (!apiToken) {
|
|
373
|
+
return new Response("Forbidden", { status: 403 });
|
|
374
|
+
}
|
|
375
|
+
await prisma.webhook.create({ data: { tokenId: apiToken.id } });
|
|
376
|
+
return Response.json({ ok: true });
|
|
377
|
+
}
|
|
378
|
+
`);
|
|
379
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
/* ------------------------------------------------------------------ */
|
|
383
|
+
/* 7. Callback path downgrade */
|
|
384
|
+
/* ------------------------------------------------------------------ */
|
|
385
|
+
describe("callback path exemption", () => {
|
|
386
|
+
it("exempts /oidc/callback — public by protocol design", () => {
|
|
387
|
+
const route = createRoute("app/oidc/callback/desktop/route.ts", `
|
|
388
|
+
export const GET = async (req: Request) => {
|
|
389
|
+
const code = new URL(req.url).searchParams.get("code");
|
|
390
|
+
const state = new URL(req.url).searchParams.get("state");
|
|
391
|
+
await prisma.oauthHandoff.create({ client: "desktop", id: state, payload: { code } });
|
|
392
|
+
return NextResponse.redirect(successUrl);
|
|
393
|
+
};
|
|
394
|
+
`, { pathname: "/oidc/callback/desktop" });
|
|
395
|
+
const findings = run(makeIndex([route]), config);
|
|
396
|
+
expect(findings).toHaveLength(0);
|
|
397
|
+
});
|
|
398
|
+
it("exempts /oauth/callback — public by protocol design", () => {
|
|
399
|
+
const route = createRoute("app/api/oauth/callback/route.ts", `
|
|
400
|
+
export async function GET(req: Request) {
|
|
401
|
+
const code = new URL(req.url).searchParams.get("code");
|
|
402
|
+
await prisma.session.create({ data: { code } });
|
|
403
|
+
return Response.redirect("/dashboard");
|
|
404
|
+
}
|
|
405
|
+
`, { pathname: "/api/oauth/callback" });
|
|
406
|
+
const findings = run(makeIndex([route]), config);
|
|
407
|
+
expect(findings).toHaveLength(0);
|
|
408
|
+
});
|
|
409
|
+
it("does NOT downgrade regular API routes", () => {
|
|
410
|
+
const route = createRoute("app/api/users/route.ts", `
|
|
411
|
+
export async function POST(req: Request) {
|
|
412
|
+
const body = await req.json();
|
|
413
|
+
await prisma.user.create({ data: body });
|
|
414
|
+
return Response.json({ ok: true });
|
|
415
|
+
}
|
|
416
|
+
`);
|
|
417
|
+
const findings = run(makeIndex([route]), config);
|
|
418
|
+
expect(findings).toHaveLength(1);
|
|
419
|
+
expect(findings[0].confidence).toBe("high");
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
/* ------------------------------------------------------------------ */
|
|
423
|
+
/* 8. Existing patterns still work */
|
|
424
|
+
/* ------------------------------------------------------------------ */
|
|
425
|
+
describe("existing patterns still work", () => {
|
|
426
|
+
it("suppresses for Stripe constructEvent", () => {
|
|
427
|
+
const route = createRoute("app/api/webhooks/stripe/route.ts", `
|
|
428
|
+
export async function POST(req: Request) {
|
|
429
|
+
const sig = req.headers.get("stripe-signature");
|
|
430
|
+
const event = stripe.webhooks.constructEvent(body, sig, secret);
|
|
431
|
+
await prisma.payment.create({ data: event.data });
|
|
432
|
+
return Response.json({ ok: true });
|
|
433
|
+
}
|
|
434
|
+
`);
|
|
435
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
436
|
+
});
|
|
437
|
+
it("suppresses for Supabase .auth.getUser()", () => {
|
|
438
|
+
const route = createRoute("app/api/posts/route.ts", `
|
|
439
|
+
export async function POST(req: Request) {
|
|
440
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
441
|
+
if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
442
|
+
await supabase.from("posts").insert({ title: "test", author: user.id });
|
|
443
|
+
return Response.json({ ok: true });
|
|
444
|
+
}
|
|
445
|
+
`);
|
|
446
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
447
|
+
});
|
|
448
|
+
it("flags unprotected mutation route", () => {
|
|
449
|
+
const route = createRoute("app/api/unprotected/route.ts", `
|
|
450
|
+
export async function POST(req: Request) {
|
|
451
|
+
const body = await req.json();
|
|
452
|
+
await prisma.user.create({ data: body });
|
|
453
|
+
return Response.json({ ok: true });
|
|
454
|
+
}
|
|
455
|
+
`);
|
|
456
|
+
const findings = run(makeIndex([route]), config);
|
|
457
|
+
expect(findings).toHaveLength(1);
|
|
458
|
+
expect(findings[0].ruleId).toBe("AUTH-BOUNDARY-MISSING");
|
|
459
|
+
expect(findings[0].severity).toBe("critical");
|
|
460
|
+
expect(findings[0].confidence).toBe("high");
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
/* ------------------------------------------------------------------ */
|
|
464
|
+
/* 9. public-intent suppression */
|
|
465
|
+
/* ------------------------------------------------------------------ */
|
|
466
|
+
describe("public-intent auth suppression", () => {
|
|
467
|
+
it("suppresses AUTH finding when public-intent has valid reason", () => {
|
|
468
|
+
const route = createRoute("app/api/checker/route.ts", `
|
|
469
|
+
// prodcheck:public-intent reason="Public URL health checker"
|
|
470
|
+
export async function POST(req: Request) {
|
|
471
|
+
const body = await req.json();
|
|
472
|
+
await prisma.check.create({ data: body });
|
|
473
|
+
return Response.json({ ok: true });
|
|
474
|
+
}
|
|
475
|
+
`, { publicIntent: { reason: "Public URL health checker", line: 1 } });
|
|
476
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
477
|
+
});
|
|
478
|
+
it("does NOT suppress AUTH finding when publicIntent is absent (malformed)", () => {
|
|
479
|
+
const route = createRoute("app/api/checker/route.ts", `
|
|
480
|
+
// prodcheck:public-intent
|
|
481
|
+
export async function POST(req: Request) {
|
|
482
|
+
const body = await req.json();
|
|
483
|
+
await prisma.check.create({ data: body });
|
|
484
|
+
return Response.json({ ok: true });
|
|
485
|
+
}
|
|
486
|
+
`, { malformedPublicIntent: { line: 1, raw: "// prodcheck:public-intent" } });
|
|
487
|
+
const findings = run(makeIndex([route]), config);
|
|
488
|
+
expect(findings).toHaveLength(1);
|
|
489
|
+
expect(findings[0].ruleId).toBe("AUTH-BOUNDARY-MISSING");
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
//# sourceMappingURL=auth-boundary-missing.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-boundary-missing.test.js","sourceRoot":"","sources":["../../src/rules/auth-boundary-missing.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,GAAG,EAAW,MAAM,4BAA4B,CAAC;AAI1D,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,MAAM,gBAAgB,GAAG;IACvB,mBAAmB,EAAE,IAAI;IACzB,kBAAkB,EAAE,IAAI;IACxB,sBAAsB,EAAE,KAAK;IAC7B,eAAe,EAAE,CAAC,eAAe,CAAC;CACnC,CAAC;AAEF,SAAS,iBAAiB,CAAC,IAG1B;IACC,OAAO;QACL,IAAI,EAAE;YACJ,SAAS,EAAE,IAAI,CAAC,aAAa,IAAI,KAAK;YACtC,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;YAC7C,OAAO,EAAE,EAAE;YACX,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,IAAI,EAAE;SAClD;QACD,SAAS,EAAE;YACT,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,EAAE;YACX,kBAAkB,EAAE,EAAE;SACvB;KACF,CAAC;AACJ,CAAC;AAED,IAAI,MAAc,CAAC;AAEnB,UAAU,CAAC,GAAG,EAAE;IACd,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,uBAAuB,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvG,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEH,SAAS,WAAW,CAClB,OAAe,EACf,MAAc,EACd,YAAgC,EAAE;IAElC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEhC,MAAM,QAAQ,GAAG,GAAG,GAAG,OAAO;SAC3B,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC;SACxC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEzB,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,QAAQ,KAAK,MAAM;QAC1D,QAAQ,EAAE,IAAI;QACd,QAAQ;QACR,OAAO,EAAE,gBAAgB;QACzB,UAAU,EAAE,iBAAiB,CAAC,EAAE,CAAC;QACjC,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,MAAmB;IACpC,OAAO;QACL,OAAO,EAAE,CAAC;QACV,SAAS,EAAE,iBAAiB;QAC5B,OAAO,EAAE,MAAM;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK;YACvD,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK;YACvD,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK;YACvD,eAAe,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK;YACpE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK;SACrE;QACD,KAAK,EAAE;YACL,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;YAC1F,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE;YAC1D,OAAO,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;SAC/B;QACD,UAAU,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE;QAC9E,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE;QACjC,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE;QAC/C,aAAa,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE;QAC/C,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE;KAClE,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,YAAsC,EAAE;IAC1D,OAAO;QACL,SAAS,EAAE,iBAAiB;QAC5B,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,OAAO,EAAE,EAAE;QACX,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE;QAClF,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE;QAC9E,KAAK,EAAE;YACL,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;YAC1F,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE;YAC1D,OAAO,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;SAC/B;QACD,KAAK,EAAE,EAAE,uBAAuB,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE;QAC5D,WAAW,EAAE,wBAAwB;QACrC,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAE5B,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACpD,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,WAAW,CAAC,oCAAoC,EAAE;;;;;;;;;CASnE,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,KAAK,GAAG,WAAW,CAAC,oCAAoC,EAAE;;;;;;CAMnE,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,KAAK,GAAG,WAAW,CAAC,0BAA0B,EAAE;;;;;;CAMzD,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,wEAAwE;AACxE,wEAAwE;AAExE,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,KAAK,GAAG,WAAW,CAAC,0BAA0B,EAAE;;;;;CAKzD,CAAC,CAAC;QACC,iEAAiE;QACjE,MAAM,iBAAiB,GAAG,WAAW,CAAC,0BAA0B,EAAE;;;;;;;CAOrE,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,iBAAiB,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,KAAK,GAAG,WAAW,CAAC,iCAAiC,EAAE;;;;;;;;;;CAUhE,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACpD,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,KAAK,GAAG,WAAW,CAAC,iCAAiC,EAAE;;;;;;;;;;;;;CAahE,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,KAAK,GAAG,WAAW,CAAC,0BAA0B,EAAE;;;;;;;;;CASzD,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,8EAA8E,EAAE,GAAG,EAAE;QACtF,MAAM,KAAK,GAAG,WAAW,CAAC,2BAA2B,EAAE;;;;;;;;;CAS1D,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,KAAK,GAAG,WAAW,CAAC,4BAA4B,EAAE;;;;;;;;;CAS3D,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,KAAK,GAAG,WAAW,CAAC,wBAAwB,EAAE;;;;;;;;;CASvD,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,KAAK,GAAG,WAAW,CAAC,0BAA0B,EAAE;;;;;;;;;CASzD,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,KAAK,GAAG,WAAW,CAAC,6BAA6B,EAAE;;;;;;;;;CAS5D,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,KAAK,GAAG,WAAW,CAAC,kCAAkC,EAAE;;;;;;;;;;;;;;;;;;CAkBjE,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,WAAW,CAAC,yBAAyB,EAAE;;;;;;;;;CASxD,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,sBAAsB,EAAE;;;;;;;;;CASrD,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,KAAK,GAAG,WAAW,CAAC,8BAA8B,EAAE;;;;;;;;CAQ7D,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,KAAK,GAAG,WAAW,CAAC,2BAA2B,EAAE;;;;;;;;;CAS1D,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,KAAK,GAAG,WAAW,CAAC,8BAA8B,EAAE;;;;;;;;;;CAU7D,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,KAAK,GAAG,WAAW,CAAC,oCAAoC,EAAE;;;;;;;CAOnE,EAAE,EAAE,QAAQ,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAEvC,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,KAAK,GAAG,WAAW,CAAC,iCAAiC,EAAE;;;;;;CAMhE,EAAE,EAAE,QAAQ,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAEpC,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,KAAK,GAAG,WAAW,CAAC,wBAAwB,EAAE;;;;;;CAMvD,CAAC,CAAC;QAEC,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,kCAAkC,EAAE;;;;;;;CAOjE,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,KAAK,GAAG,WAAW,CAAC,wBAAwB,EAAE;;;;;;;CAOvD,CAAC,CAAC;QACC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,KAAK,GAAG,WAAW,CAAC,8BAA8B,EAAE;;;;;;CAM7D,CAAC,CAAC;QACC,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACzD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9C,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,KAAK,GAAG,WAAW,CAAC,0BAA0B,EAAE;;;;;;;CAOzD,EAAE,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,2BAA2B,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACnE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAChF,MAAM,KAAK,GAAG,WAAW,CAAC,0BAA0B,EAAE;;;;;;;CAOzD,EAAE,EAAE,qBAAqB,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,4BAA4B,EAAE,EAAE,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NextIndex } from "../next/types.js";
|
|
2
|
+
import type { Finding, ProdcheckConfig } from "../engine/types.js";
|
|
3
|
+
export interface RuleMeta {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
defaultSeverity: string;
|
|
8
|
+
docs: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const RULE_REGISTRY: RuleMeta[];
|
|
11
|
+
export declare function runAllRules(index: NextIndex, config: ProdcheckConfig): Finding[];
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAOnE,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,aAAa,EAAE,QAAQ,EA2CnC,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,EAAE,CA+ChF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as authBoundary from "./auth-boundary-missing.js";
|
|
2
|
+
import * as rateLimit from "./rate-limit-missing.js";
|
|
3
|
+
import * as tenancyScope from "./tenancy-scope-missing.js";
|
|
4
|
+
import * as wrapperUnrecognized from "./wrapper-unrecognized.js";
|
|
5
|
+
import * as inputValidation from "./input-validation-missing.js";
|
|
6
|
+
export const RULE_REGISTRY = [
|
|
7
|
+
{
|
|
8
|
+
id: "AUTH-BOUNDARY-MISSING",
|
|
9
|
+
name: "Auth Boundary Missing",
|
|
10
|
+
description: "Flags server-side mutation endpoints that lack a recognized authentication boundary.",
|
|
11
|
+
defaultSeverity: "critical",
|
|
12
|
+
docs: "Prodcheck checks for calls to known auth functions (auth(), getServerSession(), currentUser(), etc.) in route handlers and server actions that perform database writes or Stripe operations. Configure additional auth function names in hints.auth.functions.",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "RATE-LIMIT-MISSING",
|
|
16
|
+
name: "Rate Limit Missing",
|
|
17
|
+
description: "Flags public API routes without recognized rate limiting.",
|
|
18
|
+
defaultSeverity: "critical",
|
|
19
|
+
docs: "Prodcheck checks for calls to known rate limiting wrappers (@upstash/ratelimit, rate-limiter-flexible, etc.) in API route handlers. If rate limiting is handled at the edge (Cloudflare WAF, Vercel), add a waiver. Configure custom wrappers in hints.rateLimit.wrappers.",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "TENANCY-SCOPE-MISSING",
|
|
23
|
+
name: "Tenancy Scope Missing",
|
|
24
|
+
description: "Flags Prisma queries on tenant-owned models that lack tenant field in the where clause.",
|
|
25
|
+
defaultSeverity: "critical",
|
|
26
|
+
docs: "Prodcheck checks that Prisma queries include a tenant scoping field (orgId, tenantId, workspaceId) in their where clause. Only runs when Prisma is detected and the schema contains tenant fields. Configure field names in hints.tenancy.orgFieldNames.",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "INPUT-VALIDATION-MISSING",
|
|
30
|
+
name: "Input Validation Missing",
|
|
31
|
+
description: "Flags endpoints that read user input and perform writes without schema validation.",
|
|
32
|
+
defaultSeverity: "high",
|
|
33
|
+
docs: "Prodcheck checks that endpoints reading request.json(), formData(), or req.body validate input through a schema library (zod, valibot, yup, joi) before passing data to database writes or payment operations. Only flags when both body reading and writes are detected without validation.",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "WRAPPER-UNRECOGNIZED",
|
|
37
|
+
name: "Wrapper Unrecognized",
|
|
38
|
+
description: "Flags HOF wrappers that could not be analyzed for auth or rate-limit enforcement.",
|
|
39
|
+
defaultSeverity: "high",
|
|
40
|
+
docs: "Prodcheck resolves and analyzes HOF wrapper implementations to detect auth and rate-limit enforcement. When a wrapper cannot be resolved or its enforcement cannot be verified, this rule emits a single grouped finding. Add the wrapper name to hints.auth.functions or hints.rateLimit.wrappers to suppress.",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "PUBLIC-INTENT-MISSING-REASON",
|
|
44
|
+
name: "Public Intent Missing Reason",
|
|
45
|
+
description: "Flags prodcheck:public-intent directives that lack a required reason string.",
|
|
46
|
+
defaultSeverity: "med",
|
|
47
|
+
docs: "The prodcheck:public-intent directive requires a reason for auditability. Without a reason, the directive is ignored and AUTH findings are NOT suppressed. Format: // prodcheck:public-intent reason=\"description\"",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
export function runAllRules(index, config) {
|
|
51
|
+
const findings = [];
|
|
52
|
+
// Only run rules that are configured (all 3 are on by default)
|
|
53
|
+
if (config.rules["AUTH-BOUNDARY-MISSING"]) {
|
|
54
|
+
findings.push(...authBoundary.run(index, config));
|
|
55
|
+
}
|
|
56
|
+
if (config.rules["RATE-LIMIT-MISSING"]) {
|
|
57
|
+
findings.push(...rateLimit.run(index, config));
|
|
58
|
+
}
|
|
59
|
+
if (config.rules["TENANCY-SCOPE-MISSING"]) {
|
|
60
|
+
findings.push(...tenancyScope.run(index, config));
|
|
61
|
+
}
|
|
62
|
+
if (config.rules["INPUT-VALIDATION-MISSING"]) {
|
|
63
|
+
findings.push(...inputValidation.run(index, config));
|
|
64
|
+
}
|
|
65
|
+
// WRAPPER-UNRECOGNIZED is always enabled unless explicitly configured out
|
|
66
|
+
if (config.rules["WRAPPER-UNRECOGNIZED"] !== undefined ? config.rules["WRAPPER-UNRECOGNIZED"] : true) {
|
|
67
|
+
findings.push(...wrapperUnrecognized.run(index, config));
|
|
68
|
+
}
|
|
69
|
+
// PUBLIC-INTENT-MISSING-REASON: flag malformed directives
|
|
70
|
+
for (const route of index.routes.all) {
|
|
71
|
+
if (route.malformedPublicIntent) {
|
|
72
|
+
findings.push({
|
|
73
|
+
ruleId: "PUBLIC-INTENT-MISSING-REASON",
|
|
74
|
+
severity: "med",
|
|
75
|
+
confidence: "high",
|
|
76
|
+
message: "prodcheck:public-intent requires a reason for auditability",
|
|
77
|
+
file: route.file,
|
|
78
|
+
line: route.malformedPublicIntent.line,
|
|
79
|
+
snippet: route.malformedPublicIntent.raw,
|
|
80
|
+
evidence: [
|
|
81
|
+
"Directive found without valid reason=\"...\" — treated as not public-intent",
|
|
82
|
+
"AUTH findings are NOT suppressed and RL severity is NOT floored",
|
|
83
|
+
],
|
|
84
|
+
confidenceRationale: "High: directive syntax is deterministic",
|
|
85
|
+
remediation: [
|
|
86
|
+
'Add a reason: // prodcheck:public-intent reason="Public URL health checker"',
|
|
87
|
+
"Without a reason, the directive is ignored for all rule behavior",
|
|
88
|
+
],
|
|
89
|
+
tags: ["misconfig", "public-intent"],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return findings;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NextIndex } from "../next/types.js";
|
|
2
|
+
import type { Finding, ProdcheckConfig } from "../engine/types.js";
|
|
3
|
+
export declare const RULE_ID = "INPUT-VALIDATION-MISSING";
|
|
4
|
+
export declare function run(index: NextIndex, config: ProdcheckConfig): Finding[];
|
|
5
|
+
//# sourceMappingURL=input-validation-missing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-validation-missing.d.ts","sourceRoot":"","sources":["../../src/rules/input-validation-missing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAA+B,MAAM,kBAAkB,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAInE,eAAO,MAAM,OAAO,6BAA6B,CAAC;AAqBlD,wBAAgB,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,EAAE,CA8ExE"}
|