@qodfy/core 0.2.1 → 0.2.3
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/dist/index.d.ts +4 -1
- package/dist/index.js +272 -15
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type IssueSeverity = "critical" | "warning" | "info";
|
|
2
|
+
type IssueConfidence = "high" | "medium" | "low";
|
|
2
3
|
type IssueCategory = "security" | "environment" | "api" | "webhook" | "ai" | "maintainability" | "project";
|
|
3
4
|
declare const validScanChecks: readonly ["project", "api", "environment", "ai", "webhook", "maintainability", "security"];
|
|
4
5
|
type ScanCheck = typeof validScanChecks[number];
|
|
@@ -8,6 +9,7 @@ type Issue = {
|
|
|
8
9
|
ruleId: string;
|
|
9
10
|
category: IssueCategory;
|
|
10
11
|
severity: IssueSeverity;
|
|
12
|
+
confidence: IssueConfidence;
|
|
11
13
|
title: string;
|
|
12
14
|
message: string;
|
|
13
15
|
file?: string;
|
|
@@ -30,7 +32,8 @@ type ScanReport = {
|
|
|
30
32
|
type ScanOptions = {
|
|
31
33
|
projectPath: string;
|
|
32
34
|
checks?: ScanCheck[];
|
|
35
|
+
includeLowConfidence?: boolean;
|
|
33
36
|
};
|
|
34
37
|
declare function scanProject(input: string | ScanOptions): Promise<ScanReport>;
|
|
35
38
|
|
|
36
|
-
export { type Issue, type IssueCategory, type IssueSeverity, type ScanCheck, type ScanOptions, type ScanReport, recommendedScanChecks, scanProject, validScanChecks };
|
|
39
|
+
export { type Issue, type IssueCategory, type IssueConfidence, type IssueSeverity, type ScanCheck, type ScanOptions, type ScanReport, recommendedScanChecks, scanProject, validScanChecks };
|
package/dist/index.js
CHANGED
|
@@ -115,6 +115,9 @@ var issueIdPrefixes = {
|
|
|
115
115
|
"security-client-side-secret": "security-client-side-secret",
|
|
116
116
|
"security-hardcoded-secret": "security-hardcoded-secret",
|
|
117
117
|
"api-route-missing-auth": "security-api-auth",
|
|
118
|
+
"api-public-read-route": "api-public-read-route",
|
|
119
|
+
"api-public-form-abuse-protection": "api-public-form-protection",
|
|
120
|
+
"api-internal-route-protection": "api-internal-route-protection",
|
|
118
121
|
"ai-route-missing-rate-limit": "ai-route-rate-limit",
|
|
119
122
|
"maintainability-large-file": "maintainability-large-file",
|
|
120
123
|
"maintainability-large-file-skipped": "maintainability-large-file-skipped",
|
|
@@ -125,6 +128,7 @@ var issueIdPrefixes = {
|
|
|
125
128
|
async function scanProject(input) {
|
|
126
129
|
const startTime = Date.now();
|
|
127
130
|
const projectPath = typeof input === "string" ? input : input.projectPath;
|
|
131
|
+
const includeLowConfidence = typeof input === "string" ? false : Boolean(input.includeLowConfidence);
|
|
128
132
|
const enabledChecks = getEnabledChecks(
|
|
129
133
|
typeof input === "string" ? void 0 : input.checks
|
|
130
134
|
);
|
|
@@ -148,6 +152,7 @@ async function scanProject(input) {
|
|
|
148
152
|
ruleId: "project-missing-package-json",
|
|
149
153
|
category: "project",
|
|
150
154
|
severity: "critical",
|
|
155
|
+
confidence: "high",
|
|
151
156
|
title: "Missing package.json",
|
|
152
157
|
message: "Qodfy could not find a package.json file in this project.",
|
|
153
158
|
suggestion: "Run Qodfy from the project root or pass --path to the app folder.",
|
|
@@ -160,6 +165,7 @@ async function scanProject(input) {
|
|
|
160
165
|
ruleId: "project-invalid-package-json",
|
|
161
166
|
category: "project",
|
|
162
167
|
severity: "critical",
|
|
168
|
+
confidence: "high",
|
|
163
169
|
title: "Could not read package.json",
|
|
164
170
|
message: packageJsonResult.reason,
|
|
165
171
|
file: "package.json",
|
|
@@ -171,6 +177,7 @@ async function scanProject(input) {
|
|
|
171
177
|
ruleId: "project-invalid-package-json",
|
|
172
178
|
category: "project",
|
|
173
179
|
severity: "critical",
|
|
180
|
+
confidence: "high",
|
|
174
181
|
title: "Invalid package.json",
|
|
175
182
|
message: "package.json must contain a JSON object at the top level.",
|
|
176
183
|
file: "package.json",
|
|
@@ -188,6 +195,7 @@ async function scanProject(input) {
|
|
|
188
195
|
ruleId: "project-next-not-detected",
|
|
189
196
|
category: "project",
|
|
190
197
|
severity: "warning",
|
|
198
|
+
confidence: "low",
|
|
191
199
|
title: "Next.js not detected",
|
|
192
200
|
message: "This first version of Qodfy is optimized for Next.js projects.",
|
|
193
201
|
suggestion: "If this is a monorepo, scan the Next.js app folder directly.",
|
|
@@ -204,6 +212,7 @@ async function scanProject(input) {
|
|
|
204
212
|
ruleId: "environment-missing-env-example",
|
|
205
213
|
category: "environment",
|
|
206
214
|
severity: "warning",
|
|
215
|
+
confidence: "medium",
|
|
207
216
|
title: "Missing .env.example",
|
|
208
217
|
message: "Add a .env.example file so future developers know which environment variables are required.",
|
|
209
218
|
suggestion: "Document required variable names only, never real secret values.",
|
|
@@ -216,6 +225,7 @@ async function scanProject(input) {
|
|
|
216
225
|
ruleId: "environment-missing-env-example",
|
|
217
226
|
category: "environment",
|
|
218
227
|
severity: "warning",
|
|
228
|
+
confidence: "medium",
|
|
219
229
|
title: "Could not read .env.example",
|
|
220
230
|
message: envExampleResult.reason,
|
|
221
231
|
file: ".env.example",
|
|
@@ -232,6 +242,7 @@ async function scanProject(input) {
|
|
|
232
242
|
ruleId: "project-missing-readme",
|
|
233
243
|
category: "project",
|
|
234
244
|
severity: "info",
|
|
245
|
+
confidence: "low",
|
|
235
246
|
title: "Missing README.md",
|
|
236
247
|
message: "A README helps other developers understand how to run and maintain the project.",
|
|
237
248
|
fixPrompt: createReadmeFixPrompt()
|
|
@@ -257,6 +268,7 @@ async function scanProject(input) {
|
|
|
257
268
|
ruleId: "maintainability-file-unreadable",
|
|
258
269
|
category: "maintainability",
|
|
259
270
|
severity: "info",
|
|
271
|
+
confidence: "low",
|
|
260
272
|
title: "File could not be checked",
|
|
261
273
|
message: statResult.reason,
|
|
262
274
|
file: relativeFile,
|
|
@@ -272,6 +284,7 @@ async function scanProject(input) {
|
|
|
272
284
|
ruleId: "maintainability-large-file-skipped",
|
|
273
285
|
category: "maintainability",
|
|
274
286
|
severity: "info",
|
|
287
|
+
confidence: "low",
|
|
275
288
|
title: "Large file skipped from deep scan",
|
|
276
289
|
message: "This file is larger than 500KB and was skipped from deep content checks.",
|
|
277
290
|
file: relativeFile,
|
|
@@ -298,6 +311,7 @@ async function scanProject(input) {
|
|
|
298
311
|
ruleId: "maintainability-file-unreadable",
|
|
299
312
|
category: "maintainability",
|
|
300
313
|
severity: "info",
|
|
314
|
+
confidence: "low",
|
|
301
315
|
title: "File could not be read",
|
|
302
316
|
message: fileResult.reason,
|
|
303
317
|
file: relativeFile,
|
|
@@ -318,6 +332,7 @@ async function scanProject(input) {
|
|
|
318
332
|
ruleId: "ai-route-missing-rate-limit",
|
|
319
333
|
category: "ai",
|
|
320
334
|
severity: "critical",
|
|
335
|
+
confidence: "high",
|
|
321
336
|
title: "AI route may be missing rate limiting",
|
|
322
337
|
message: "AI routes can create real API costs. Add rate limiting or usage limits before launch.",
|
|
323
338
|
file: relativeFile,
|
|
@@ -326,12 +341,13 @@ async function scanProject(input) {
|
|
|
326
341
|
});
|
|
327
342
|
}
|
|
328
343
|
}
|
|
329
|
-
const webhookRouteInfo = runWebhookChecks && apiRouteSet.has(file) ? getWebhookRouteInfo(relativeFile, content) : null;
|
|
344
|
+
const webhookRouteInfo = (runWebhookChecks || runApiChecks) && apiRouteSet.has(file) ? getWebhookRouteInfo(relativeFile, content) : null;
|
|
330
345
|
if (webhookRouteInfo && !hasWebhookSignatureVerification(content, webhookRouteInfo.provider)) {
|
|
331
346
|
addIssue({
|
|
332
347
|
ruleId: "webhook-missing-signature-verification",
|
|
333
348
|
category: "webhook",
|
|
334
349
|
severity: webhookRouteInfo.confidence === "high" ? "critical" : "warning",
|
|
350
|
+
confidence: webhookRouteInfo.confidence === "high" ? "high" : "medium",
|
|
335
351
|
title: "Webhook route may be missing signature verification",
|
|
336
352
|
message: "This webhook route appears to handle external events, but Qodfy could not find signature verification before the event is handled.",
|
|
337
353
|
file: relativeFile,
|
|
@@ -350,6 +366,7 @@ async function scanProject(input) {
|
|
|
350
366
|
ruleId: "security-hardcoded-secret",
|
|
351
367
|
category: "security",
|
|
352
368
|
severity: "critical",
|
|
369
|
+
confidence: "high",
|
|
353
370
|
title: "Possible hardcoded secret",
|
|
354
371
|
message: `A string literal in ${relativeFile} matches the pattern for ${secretMatch.label}. Qodfy does not print possible secret values.`,
|
|
355
372
|
file: relativeFile,
|
|
@@ -359,19 +376,13 @@ async function scanProject(input) {
|
|
|
359
376
|
}
|
|
360
377
|
}
|
|
361
378
|
if (runApiChecks && apiRouteSet.has(file)) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
message: "This API route does not appear to contain an auth/session check.",
|
|
370
|
-
file: relativeFile,
|
|
371
|
-
suggestion: "Confirm the route is public, or add an auth/session check before handling user data.",
|
|
372
|
-
fixPrompt: createApiAuthFixPrompt(relativeFile)
|
|
373
|
-
});
|
|
374
|
-
}
|
|
379
|
+
addApiRouteProtectionIssues({
|
|
380
|
+
addIssue,
|
|
381
|
+
content,
|
|
382
|
+
includeLowConfidence,
|
|
383
|
+
relativeFile,
|
|
384
|
+
webhookRouteInfo
|
|
385
|
+
});
|
|
375
386
|
}
|
|
376
387
|
const usedEnvVariables = runEnvironmentChecks || runSecurityChecks ? getUsedEnvVariables(content) : /* @__PURE__ */ new Set();
|
|
377
388
|
if (runEnvironmentChecks && envExampleVariables) {
|
|
@@ -396,6 +407,7 @@ async function scanProject(input) {
|
|
|
396
407
|
ruleId: "security-client-side-secret",
|
|
397
408
|
category: "security",
|
|
398
409
|
severity: "warning",
|
|
410
|
+
confidence: "medium",
|
|
399
411
|
title: "Possible server secret used in client-side code",
|
|
400
412
|
message: `${variableName} appears in a client-side file. Server secrets should not be exposed to the browser.`,
|
|
401
413
|
file: relativeFile,
|
|
@@ -411,6 +423,7 @@ async function scanProject(input) {
|
|
|
411
423
|
ruleId: "maintainability-large-file",
|
|
412
424
|
category: "maintainability",
|
|
413
425
|
severity: "info",
|
|
426
|
+
confidence: "low",
|
|
414
427
|
title: "Large file detected",
|
|
415
428
|
message: "This file is larger than the recommended maintainability threshold. Large files can be harder to review, test, and safely modify.",
|
|
416
429
|
file: largeFile.relativeFile,
|
|
@@ -424,6 +437,7 @@ async function scanProject(input) {
|
|
|
424
437
|
ruleId: "environment-variable-missing-from-example",
|
|
425
438
|
category: "environment",
|
|
426
439
|
severity: "warning",
|
|
440
|
+
confidence: "medium",
|
|
427
441
|
title: "Environment variable missing from .env.example",
|
|
428
442
|
message: getMissingEnvMessage(variableName, files2),
|
|
429
443
|
file: files2.length === 1 ? files2[0] : void 0,
|
|
@@ -455,8 +469,9 @@ function createIssueFactory(issues) {
|
|
|
455
469
|
const currentCount = (issueCounts.get(issue.ruleId) ?? 0) + 1;
|
|
456
470
|
issueCounts.set(issue.ruleId, currentCount);
|
|
457
471
|
issues.push({
|
|
472
|
+
...issue,
|
|
458
473
|
id: `${getIssueIdPrefix(issue.ruleId, issue.category)}-${currentCount}`,
|
|
459
|
-
|
|
474
|
+
confidence: issue.confidence ?? "medium"
|
|
460
475
|
});
|
|
461
476
|
};
|
|
462
477
|
}
|
|
@@ -574,6 +589,7 @@ async function getSourceFiles(projectPath, addIssue) {
|
|
|
574
589
|
ruleId: "project-source-files-unreadable",
|
|
575
590
|
category: "project",
|
|
576
591
|
severity: "critical",
|
|
592
|
+
confidence: "high",
|
|
577
593
|
title: "Could not scan source files",
|
|
578
594
|
message: "Qodfy could not list source files in this project.",
|
|
579
595
|
suggestion: "Check that the project path exists and is readable.",
|
|
@@ -668,6 +684,191 @@ function isApiRoute(filePath) {
|
|
|
668
684
|
const sourceFileExtension = "(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)";
|
|
669
685
|
return new RegExp(`/app/api(?:/.+)?/route\\.${sourceFileExtension}$`).test(normalizedFile) || new RegExp(`/pages/api/.+\\.${sourceFileExtension}$`).test(normalizedFile);
|
|
670
686
|
}
|
|
687
|
+
function addApiRouteProtectionIssues({
|
|
688
|
+
addIssue,
|
|
689
|
+
content,
|
|
690
|
+
includeLowConfidence,
|
|
691
|
+
relativeFile,
|
|
692
|
+
webhookRouteInfo
|
|
693
|
+
}) {
|
|
694
|
+
const intent = classifyApiRouteIntent(relativeFile, content, webhookRouteInfo);
|
|
695
|
+
const hasAuth = hasAuthOrSessionCheck(content);
|
|
696
|
+
const methods = getHttpMethods(content);
|
|
697
|
+
if (intent === "webhook") {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (intent === "public-read") {
|
|
701
|
+
if (includeLowConfidence) {
|
|
702
|
+
addIssue({
|
|
703
|
+
ruleId: "api-public-read-route",
|
|
704
|
+
category: "api",
|
|
705
|
+
severity: "info",
|
|
706
|
+
confidence: "low",
|
|
707
|
+
title: "Public read API route detected",
|
|
708
|
+
message: "This route appears intentionally public. Authentication may not be required.",
|
|
709
|
+
file: relativeFile,
|
|
710
|
+
suggestion: "Verify that it only exposes public or published data and has appropriate validation, caching, and abuse protection.",
|
|
711
|
+
fixPrompt: createPublicReadRouteFixPrompt(relativeFile)
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (intent === "public-form") {
|
|
717
|
+
if (!hasAbuseProtection(content)) {
|
|
718
|
+
addIssue({
|
|
719
|
+
ruleId: "api-public-form-abuse-protection",
|
|
720
|
+
category: "api",
|
|
721
|
+
severity: "warning",
|
|
722
|
+
confidence: "medium",
|
|
723
|
+
title: "Public form route may be missing abuse protection",
|
|
724
|
+
message: "This route appears to accept public submissions. Consider adding rate limiting, validation, or spam protection.",
|
|
725
|
+
file: relativeFile,
|
|
726
|
+
suggestion: "Check for rate limiting, validation, captcha, Turnstile, reCAPTCHA, hCaptcha, or another spam protection pattern.",
|
|
727
|
+
fixPrompt: createPublicFormProtectionFixPrompt(relativeFile)
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (intent === "internal") {
|
|
733
|
+
if (!hasInternalRouteProtection(content)) {
|
|
734
|
+
addIssue({
|
|
735
|
+
ruleId: "api-internal-route-protection",
|
|
736
|
+
category: "api",
|
|
737
|
+
severity: "warning",
|
|
738
|
+
confidence: "high",
|
|
739
|
+
title: "Internal API route may be missing protection",
|
|
740
|
+
message: "This route appears internal or operational. Confirm it is protected by auth, a secret token, or server-only access.",
|
|
741
|
+
file: relativeFile,
|
|
742
|
+
suggestion: "Use the project's existing auth pattern or a secret token check for operational routes such as cron, cleanup, or revalidation.",
|
|
743
|
+
fixPrompt: createInternalRouteProtectionFixPrompt(relativeFile)
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (intent === "sensitive-mutation") {
|
|
749
|
+
if (!hasAuth) {
|
|
750
|
+
addIssue({
|
|
751
|
+
ruleId: "api-route-missing-auth",
|
|
752
|
+
category: "api",
|
|
753
|
+
severity: "warning",
|
|
754
|
+
confidence: "high",
|
|
755
|
+
title: "Sensitive API route may be missing authentication",
|
|
756
|
+
message: "This route appears to handle user-specific or sensitive operations. Confirm it is protected before launch.",
|
|
757
|
+
file: relativeFile,
|
|
758
|
+
suggestion: "Review the existing project auth/session pattern and apply it if this route handles private data, uploads, payments, or account changes.",
|
|
759
|
+
fixPrompt: createApiAuthFixPrompt(relativeFile)
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (hasMutationMethod(methods) && !hasAuth) {
|
|
765
|
+
addIssue({
|
|
766
|
+
ruleId: "api-route-missing-auth",
|
|
767
|
+
category: "api",
|
|
768
|
+
severity: "warning",
|
|
769
|
+
confidence: "medium",
|
|
770
|
+
title: "API mutation route should be reviewed for authentication",
|
|
771
|
+
message: "This route appears to handle a mutation, but Qodfy could not find an auth/session check.",
|
|
772
|
+
file: relativeFile,
|
|
773
|
+
suggestion: "Confirm the route is intentionally public, or add the existing project auth/session check before handling private data.",
|
|
774
|
+
fixPrompt: createApiAuthFixPrompt(relativeFile)
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function classifyApiRouteIntent(relativeFile, content, webhookRouteInfo) {
|
|
779
|
+
const normalizedFile = relativeFile.toLowerCase();
|
|
780
|
+
const methods = getHttpMethods(content);
|
|
781
|
+
if (webhookRouteInfo || routePathHasAny(normalizedFile, ["webhook", "webhooks", "callback"])) {
|
|
782
|
+
return "webhook";
|
|
783
|
+
}
|
|
784
|
+
if (routePathHasAny(normalizedFile, ["internal", "admin", "cron", "cleanup", "revalidate", "private"])) {
|
|
785
|
+
return "internal";
|
|
786
|
+
}
|
|
787
|
+
if (routePathHasAny(normalizedFile, ["contact", "subscribe", "newsletter", "lead", "inquiry"])) {
|
|
788
|
+
return "public-form";
|
|
789
|
+
}
|
|
790
|
+
if (routePathHasAny(normalizedFile, [
|
|
791
|
+
"upload",
|
|
792
|
+
"checkout",
|
|
793
|
+
"order",
|
|
794
|
+
"orders",
|
|
795
|
+
"invoice",
|
|
796
|
+
"invoices",
|
|
797
|
+
"account",
|
|
798
|
+
"user",
|
|
799
|
+
"users",
|
|
800
|
+
"payment",
|
|
801
|
+
"billing",
|
|
802
|
+
"cart",
|
|
803
|
+
"profile",
|
|
804
|
+
"settings"
|
|
805
|
+
])) {
|
|
806
|
+
return "sensitive-mutation";
|
|
807
|
+
}
|
|
808
|
+
if (routePathHasAny(normalizedFile, [
|
|
809
|
+
"blog",
|
|
810
|
+
"blogs",
|
|
811
|
+
"post",
|
|
812
|
+
"posts",
|
|
813
|
+
"product",
|
|
814
|
+
"products",
|
|
815
|
+
"search",
|
|
816
|
+
"i18n",
|
|
817
|
+
"category",
|
|
818
|
+
"categories",
|
|
819
|
+
"sitemap",
|
|
820
|
+
"rss"
|
|
821
|
+
])) {
|
|
822
|
+
return "public-read";
|
|
823
|
+
}
|
|
824
|
+
if (hasMutationMethod(methods)) {
|
|
825
|
+
return "unknown";
|
|
826
|
+
}
|
|
827
|
+
if (methods.size === 0 || isReadOnlyRoute(methods)) {
|
|
828
|
+
return "unknown";
|
|
829
|
+
}
|
|
830
|
+
return "unknown";
|
|
831
|
+
}
|
|
832
|
+
function routePathHasAny(normalizedFile, terms) {
|
|
833
|
+
return terms.some((term) => {
|
|
834
|
+
const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
835
|
+
return new RegExp(`(^|[\\/._\\[\\]-])${escapedTerm}([\\/._\\[\\]-]|$)`).test(normalizedFile);
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
function getHttpMethods(content) {
|
|
839
|
+
const methods = /* @__PURE__ */ new Set();
|
|
840
|
+
const exportedMethodPattern = /\bexport\s+(?:async\s+)?(?:function|const)\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;
|
|
841
|
+
const requestMethodPattern = /\b(?:request|req)\.method\s*(?:={2,3}|!={1,2})\s*["'](GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)["']/g;
|
|
842
|
+
const methodCasePattern = /\bcase\s+["'](GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)["']/g;
|
|
843
|
+
for (const match of content.matchAll(exportedMethodPattern)) {
|
|
844
|
+
methods.add(match[1]);
|
|
845
|
+
}
|
|
846
|
+
for (const match of content.matchAll(requestMethodPattern)) {
|
|
847
|
+
methods.add(match[1]);
|
|
848
|
+
}
|
|
849
|
+
for (const match of content.matchAll(methodCasePattern)) {
|
|
850
|
+
methods.add(match[1]);
|
|
851
|
+
}
|
|
852
|
+
return methods;
|
|
853
|
+
}
|
|
854
|
+
function hasMutationMethod(methods) {
|
|
855
|
+
return ["POST", "PUT", "PATCH", "DELETE"].some((method) => methods.has(method));
|
|
856
|
+
}
|
|
857
|
+
function isReadOnlyRoute(methods) {
|
|
858
|
+
return [...methods].every((method) => method === "GET" || method === "HEAD" || method === "OPTIONS");
|
|
859
|
+
}
|
|
860
|
+
function hasAuthOrSessionCheck(content) {
|
|
861
|
+
const normalizedContent = content.toLowerCase();
|
|
862
|
+
return normalizedContent.includes("auth(") || normalizedContent.includes("getserversession") || normalizedContent.includes("currentuser") || normalizedContent.includes("clerkclient") || normalizedContent.includes("session") || normalizedContent.includes("requireauth") || normalizedContent.includes("requireuser") || normalizedContent.includes("requireadmin") || normalizedContent.includes("verifysession") || normalizedContent.includes("getuser") || normalizedContent.includes("jwt") || normalizedContent.includes("authorization") || normalizedContent.includes("bearer") || normalizedContent.includes("cookies()") || normalizedContent.includes("request.cookies") || normalizedContent.includes("middleware");
|
|
863
|
+
}
|
|
864
|
+
function hasInternalRouteProtection(content) {
|
|
865
|
+
const normalizedContent = content.toLowerCase();
|
|
866
|
+
return hasAuthOrSessionCheck(content) || /\bprocess\.env\.[A-Za-z0-9_]*SECRET\b/.test(content) || /\bprocess\.env\[['"`][A-Za-z0-9_]*SECRET['"`]\]/.test(content) || normalizedContent.includes("cron_secret") || normalizedContent.includes("revalidate_secret") || normalizedContent.includes("authorization") || normalizedContent.includes("bearer") || normalizedContent.includes("token");
|
|
867
|
+
}
|
|
868
|
+
function hasAbuseProtection(content) {
|
|
869
|
+
const normalizedContent = content.toLowerCase();
|
|
870
|
+
return normalizedContent.includes("ratelimit") || normalizedContent.includes("rate limit") || normalizedContent.includes("limiter") || normalizedContent.includes("captcha") || normalizedContent.includes("turnstile") || normalizedContent.includes("recaptcha") || normalizedContent.includes("hcaptcha") || normalizedContent.includes("validation") || normalizedContent.includes("validate") || normalizedContent.includes("zod") || normalizedContent.includes("safeparse");
|
|
871
|
+
}
|
|
671
872
|
function getWebhookRouteInfo(relativeFile, content) {
|
|
672
873
|
const normalizedFile = relativeFile.toLowerCase();
|
|
673
874
|
const normalizedContent = content.toLowerCase();
|
|
@@ -761,6 +962,62 @@ Return:
|
|
|
761
962
|
- The updated code.
|
|
762
963
|
- Any edge cases I should test.`;
|
|
763
964
|
}
|
|
965
|
+
function createPublicReadRouteFixPrompt(file) {
|
|
966
|
+
return `Review the public read API route at ${file}.
|
|
967
|
+
|
|
968
|
+
Goal:
|
|
969
|
+
Verify that this route is safe to remain public.
|
|
970
|
+
|
|
971
|
+
Instructions:
|
|
972
|
+
- Confirm it only exposes published, public, or non-sensitive data.
|
|
973
|
+
- Check that route params and query values are validated or sanitized.
|
|
974
|
+
- Check for appropriate cache headers where useful.
|
|
975
|
+
- Check for abuse protection if the route can be called heavily.
|
|
976
|
+
- Do not add user authentication unless the route should be private.
|
|
977
|
+
- Do not refactor unrelated code.
|
|
978
|
+
|
|
979
|
+
Return:
|
|
980
|
+
- Whether this route appears intentionally public.
|
|
981
|
+
- Any low-risk safety improvements.
|
|
982
|
+
- Any edge cases I should test.`;
|
|
983
|
+
}
|
|
984
|
+
function createPublicFormProtectionFixPrompt(file) {
|
|
985
|
+
return `Review the public form API route at ${file}.
|
|
986
|
+
|
|
987
|
+
Goal:
|
|
988
|
+
Verify validation, rate limiting, and spam protection.
|
|
989
|
+
|
|
990
|
+
Instructions:
|
|
991
|
+
- Confirm submitted input is validated before it is used.
|
|
992
|
+
- Check for rate limiting or another abuse protection pattern.
|
|
993
|
+
- Check whether captcha, Turnstile, reCAPTCHA, or hCaptcha is appropriate.
|
|
994
|
+
- Do not add user authentication unless the form should be private.
|
|
995
|
+
- Do not introduce a new service unless necessary.
|
|
996
|
+
- Keep existing behavior unchanged.
|
|
997
|
+
|
|
998
|
+
Return:
|
|
999
|
+
- Whether abuse protection already exists.
|
|
1000
|
+
- The safest minimal change if protection is missing.
|
|
1001
|
+
- Any edge cases I should test.`;
|
|
1002
|
+
}
|
|
1003
|
+
function createInternalRouteProtectionFixPrompt(file) {
|
|
1004
|
+
return `Review the internal API route at ${file}.
|
|
1005
|
+
|
|
1006
|
+
Goal:
|
|
1007
|
+
Confirm this operational route is protected before launch.
|
|
1008
|
+
|
|
1009
|
+
Instructions:
|
|
1010
|
+
- Check whether the route is protected by existing auth, a secret token, or server-only access.
|
|
1011
|
+
- For cron, cleanup, revalidation, or admin routes, prefer the existing project protection pattern.
|
|
1012
|
+
- Do not introduce a new auth provider.
|
|
1013
|
+
- Do not change route behavior unless protection is missing.
|
|
1014
|
+
- If the route is intentionally reachable, add a short comment explaining the protection boundary.
|
|
1015
|
+
|
|
1016
|
+
Return:
|
|
1017
|
+
- Whether protection already exists.
|
|
1018
|
+
- The safest minimal change if protection is missing.
|
|
1019
|
+
- Any edge cases I should test.`;
|
|
1020
|
+
}
|
|
764
1021
|
function createMissingEnvVariableFixPrompt(variableName, files) {
|
|
765
1022
|
return `Update the environment documentation for this project.
|
|
766
1023
|
|