@qodfy/core 0.2.5 → 0.2.7
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 +1 -0
- package/dist/index.js +684 -176
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -287,6 +287,7 @@ async function scanProject(input) {
|
|
|
287
287
|
if (statResult.size > MAX_FILE_SIZE_BYTES) {
|
|
288
288
|
if (runMaintainabilityChecks) {
|
|
289
289
|
largeFiles++;
|
|
290
|
+
const largeFileKind = getMaintainabilityFileKind(relativeFile);
|
|
290
291
|
addIssue({
|
|
291
292
|
ruleId: "maintainability-large-file-skipped",
|
|
292
293
|
category: "maintainability",
|
|
@@ -296,7 +297,7 @@ async function scanProject(input) {
|
|
|
296
297
|
message: "This file is larger than 500KB and was skipped from deep content checks.",
|
|
297
298
|
file: relativeFile,
|
|
298
299
|
suggestion: "Review large generated or bundled files manually.",
|
|
299
|
-
fixPrompt: createLargeFileFixPrompt(relativeFile)
|
|
300
|
+
fixPrompt: createLargeFileFixPrompt(relativeFile, largeFileKind)
|
|
300
301
|
});
|
|
301
302
|
}
|
|
302
303
|
continue;
|
|
@@ -353,18 +354,10 @@ async function scanProject(input) {
|
|
|
353
354
|
relativeFile,
|
|
354
355
|
content
|
|
355
356
|
}) : null;
|
|
356
|
-
if (runWebhookChecks && apiRouteAnalysis
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
severity: apiRouteAnalysis.confidence === "high" ? "critical" : "warning",
|
|
361
|
-
confidence: apiRouteAnalysis.confidence,
|
|
362
|
-
title: "Webhook route may be missing signature verification",
|
|
363
|
-
message: "This webhook route appears to handle external events, but Qodfy could not find signature verification before the event is handled.",
|
|
364
|
-
file: relativeFile,
|
|
365
|
-
suggestion: getWebhookSignatureSuggestion(apiRouteAnalysis.webhookProvider),
|
|
366
|
-
fixPrompt: createWebhookSignatureFixPrompt(relativeFile),
|
|
367
|
-
evidence: apiRouteAnalysis.evidence
|
|
357
|
+
if (runWebhookChecks && apiRouteAnalysis) {
|
|
358
|
+
addWebhookSignatureIssues({
|
|
359
|
+
addIssue,
|
|
360
|
+
analysis: apiRouteAnalysis
|
|
368
361
|
});
|
|
369
362
|
}
|
|
370
363
|
if (runSecurityChecks) {
|
|
@@ -429,16 +422,18 @@ async function scanProject(input) {
|
|
|
429
422
|
}
|
|
430
423
|
}
|
|
431
424
|
for (const largeFile of getReportedLargeFiles(largeFileCandidates)) {
|
|
425
|
+
const largeFileKind = getMaintainabilityFileKind(largeFile.relativeFile);
|
|
426
|
+
const largeFileCopy = getLargeFileIssueCopy(largeFileKind);
|
|
432
427
|
addIssue({
|
|
433
428
|
ruleId: "maintainability-large-file",
|
|
434
429
|
category: "maintainability",
|
|
435
430
|
severity: "info",
|
|
436
431
|
confidence: "low",
|
|
437
|
-
title:
|
|
438
|
-
message:
|
|
432
|
+
title: largeFileCopy.title,
|
|
433
|
+
message: largeFileCopy.message,
|
|
439
434
|
file: largeFile.relativeFile,
|
|
440
|
-
suggestion:
|
|
441
|
-
fixPrompt: createLargeFileFixPrompt(largeFile.relativeFile)
|
|
435
|
+
suggestion: largeFileCopy.suggestion,
|
|
436
|
+
fixPrompt: createLargeFileFixPrompt(largeFile.relativeFile, largeFileKind)
|
|
442
437
|
});
|
|
443
438
|
}
|
|
444
439
|
for (const [variableName, filesUsingVariable] of getSortedMissingEnvUsages(missingEnvUsages)) {
|
|
@@ -723,95 +718,125 @@ function isApiRoute(filePath) {
|
|
|
723
718
|
const sourceFileExtension = "(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)";
|
|
724
719
|
return new RegExp(`/app/api(?:/.+)?/route\\.${sourceFileExtension}$`).test(normalizedFile) || new RegExp(`/pages/api/.+\\.${sourceFileExtension}$`).test(normalizedFile);
|
|
725
720
|
}
|
|
721
|
+
function addWebhookSignatureIssues({
|
|
722
|
+
addIssue,
|
|
723
|
+
analysis
|
|
724
|
+
}) {
|
|
725
|
+
for (const handler of analysis.handlers) {
|
|
726
|
+
if (handler.intent !== "webhook" || handler.hasWebhookVerification) {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
addIssue({
|
|
730
|
+
ruleId: "webhook-missing-signature-verification",
|
|
731
|
+
category: "webhook",
|
|
732
|
+
severity: handler.confidence === "high" ? "critical" : "warning",
|
|
733
|
+
confidence: handler.confidence,
|
|
734
|
+
title: `Webhook ${handler.method} handler may be missing signature verification`,
|
|
735
|
+
message: `The ${handler.method} handler in this route appears to handle external events, but Qodfy could not find signature verification before the event is handled.`,
|
|
736
|
+
file: analysis.relativeFile,
|
|
737
|
+
suggestion: getWebhookSignatureSuggestion(analysis.webhookProvider),
|
|
738
|
+
fixPrompt: createWebhookSignatureFixPrompt(analysis.relativeFile, handler.method),
|
|
739
|
+
evidence: handler.evidence,
|
|
740
|
+
context: handler.context
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
726
744
|
function addApiRouteProtectionIssues({
|
|
727
745
|
addIssue,
|
|
728
746
|
includeLowConfidence,
|
|
729
747
|
analysis
|
|
730
748
|
}) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if (analysis.intent === "public-read") {
|
|
735
|
-
if (includeLowConfidence) {
|
|
736
|
-
addIssue({
|
|
737
|
-
ruleId: "api-public-read-route",
|
|
738
|
-
category: "api",
|
|
739
|
-
severity: "info",
|
|
740
|
-
confidence: analysis.confidence,
|
|
741
|
-
title: "Public read API route detected",
|
|
742
|
-
message: "This route appears intentionally public. Authentication may not be required.",
|
|
743
|
-
file: analysis.relativeFile,
|
|
744
|
-
suggestion: "Verify that it only exposes public or published data and has appropriate validation, caching, and abuse protection.",
|
|
745
|
-
fixPrompt: createPublicReadRouteFixPrompt(analysis.relativeFile),
|
|
746
|
-
evidence: analysis.evidence
|
|
747
|
-
});
|
|
749
|
+
for (const handler of analysis.handlers) {
|
|
750
|
+
if (handler.intent === "webhook") {
|
|
751
|
+
continue;
|
|
748
752
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
753
|
+
if (handler.intent === "public-read") {
|
|
754
|
+
if (includeLowConfidence) {
|
|
755
|
+
addIssue({
|
|
756
|
+
ruleId: "api-public-read-route",
|
|
757
|
+
category: "api",
|
|
758
|
+
severity: "info",
|
|
759
|
+
confidence: handler.confidence,
|
|
760
|
+
title: `Public ${handler.method} handler detected`,
|
|
761
|
+
message: `The ${handler.method} handler in this route appears intentionally public. Authentication may not be required.`,
|
|
762
|
+
file: analysis.relativeFile,
|
|
763
|
+
suggestion: "Verify that it only exposes public or published data and has appropriate validation, caching, and abuse protection.",
|
|
764
|
+
fixPrompt: createPublicReadRouteFixPrompt(analysis.relativeFile, handler.method),
|
|
765
|
+
evidence: handler.evidence,
|
|
766
|
+
context: handler.context
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
continue;
|
|
765
770
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
771
|
+
if (handler.intent === "public-form") {
|
|
772
|
+
if (!handler.hasRateLimit && !handler.hasValidation) {
|
|
773
|
+
addIssue({
|
|
774
|
+
ruleId: "public-form-missing-abuse-protection",
|
|
775
|
+
category: "api",
|
|
776
|
+
severity: "warning",
|
|
777
|
+
confidence: handler.confidence,
|
|
778
|
+
title: `Public form ${handler.method} handler may be missing abuse protection`,
|
|
779
|
+
message: `The ${handler.method} handler in this route appears to accept public submissions. Consider adding rate limiting, validation, or spam protection.`,
|
|
780
|
+
file: analysis.relativeFile,
|
|
781
|
+
suggestion: "Check for rate limiting, validation, captcha, Turnstile, reCAPTCHA, hCaptcha, or another spam protection pattern.",
|
|
782
|
+
fixPrompt: createPublicFormProtectionFixPrompt(analysis.relativeFile, handler.method),
|
|
783
|
+
evidence: handler.evidence,
|
|
784
|
+
context: handler.context
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
continue;
|
|
782
788
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
789
|
+
if (handler.intent === "internal") {
|
|
790
|
+
if (!handler.hasAuth && !handler.hasSecretProtection) {
|
|
791
|
+
addIssue({
|
|
792
|
+
ruleId: "internal-route-missing-protection",
|
|
793
|
+
category: "security",
|
|
794
|
+
severity: "warning",
|
|
795
|
+
confidence: handler.confidence,
|
|
796
|
+
title: `Internal ${handler.method} handler may be missing protection`,
|
|
797
|
+
message: `The ${handler.method} handler in this route appears internal or operational. Confirm it is protected by auth, a secret token, or server-only access.`,
|
|
798
|
+
file: analysis.relativeFile,
|
|
799
|
+
suggestion: "Use the project's existing auth pattern or a secret token check for operational handlers such as cron, cleanup, or revalidation.",
|
|
800
|
+
fixPrompt: createInternalRouteProtectionFixPrompt(analysis.relativeFile, handler.method),
|
|
801
|
+
evidence: handler.evidence,
|
|
802
|
+
context: handler.context
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
if (handler.intent === "sensitive-mutation") {
|
|
808
|
+
if (!handler.hasAuth) {
|
|
809
|
+
addIssue({
|
|
810
|
+
ruleId: "sensitive-api-route-missing-auth",
|
|
811
|
+
category: "security",
|
|
812
|
+
severity: "warning",
|
|
813
|
+
confidence: handler.confidence,
|
|
814
|
+
title: getSensitiveHandlerTitle(handler),
|
|
815
|
+
message: `The ${handler.method} handler in this route appears to handle uploads or sensitive operations. Confirm it is protected before launch.`,
|
|
816
|
+
file: analysis.relativeFile,
|
|
817
|
+
suggestion: "Review the existing project auth/session pattern and apply it if this handler processes private data, uploads, payments, or account changes.",
|
|
818
|
+
fixPrompt: createApiAuthFixPrompt(analysis.relativeFile, handler.method, handler.intent),
|
|
819
|
+
evidence: handler.evidence,
|
|
820
|
+
context: handler.context
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (handler.authExpected === "review" && !handler.hasAuth) {
|
|
787
826
|
addIssue({
|
|
788
|
-
ruleId: "
|
|
789
|
-
category: "
|
|
827
|
+
ruleId: "api-mutation-route-review-auth",
|
|
828
|
+
category: "api",
|
|
790
829
|
severity: "warning",
|
|
791
|
-
confidence:
|
|
792
|
-
title: "
|
|
793
|
-
message:
|
|
830
|
+
confidence: handler.confidence,
|
|
831
|
+
title: "API mutation handler should be reviewed for authentication",
|
|
832
|
+
message: `The ${handler.method} handler mutates data or handles requests, but Qodfy could not determine whether authentication is required.`,
|
|
794
833
|
file: analysis.relativeFile,
|
|
795
|
-
suggestion: "
|
|
796
|
-
fixPrompt: createApiAuthFixPrompt(analysis.relativeFile),
|
|
797
|
-
evidence:
|
|
834
|
+
suggestion: "Confirm the handler is intentionally public, or add the existing project auth/session check before handling private data.",
|
|
835
|
+
fixPrompt: createApiAuthFixPrompt(analysis.relativeFile, handler.method, handler.intent),
|
|
836
|
+
evidence: handler.evidence,
|
|
837
|
+
context: handler.context
|
|
798
838
|
});
|
|
799
839
|
}
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
if (analysis.authExpected === "review" && !analysis.hasAuth) {
|
|
803
|
-
addIssue({
|
|
804
|
-
ruleId: "api-mutation-route-review-auth",
|
|
805
|
-
category: "api",
|
|
806
|
-
severity: "warning",
|
|
807
|
-
confidence: analysis.confidence,
|
|
808
|
-
title: "API mutation route should be reviewed for authentication",
|
|
809
|
-
message: "This route mutates data or handles requests, but Qodfy could not determine whether authentication is required.",
|
|
810
|
-
file: analysis.relativeFile,
|
|
811
|
-
suggestion: "Confirm the route is intentionally public, or add the existing project auth/session check before handling private data.",
|
|
812
|
-
fixPrompt: createApiAuthFixPrompt(analysis.relativeFile),
|
|
813
|
-
evidence: analysis.evidence
|
|
814
|
-
});
|
|
815
840
|
}
|
|
816
841
|
}
|
|
817
842
|
function analyzeApiRoute({
|
|
@@ -819,25 +844,44 @@ function analyzeApiRoute({
|
|
|
819
844
|
relativeFile,
|
|
820
845
|
content
|
|
821
846
|
}) {
|
|
822
|
-
const
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
evidence.push({ label: "no exported HTTP method detected" });
|
|
847
|
+
const exportedHandlers = getExportedRouteHandlers(content);
|
|
848
|
+
const handlersToAnalyze = exportedHandlers.length > 0 ? exportedHandlers : getRouteHttpMethods(content).map((method) => ({
|
|
849
|
+
method,
|
|
850
|
+
body: content,
|
|
851
|
+
usedFallbackBody: true
|
|
852
|
+
}));
|
|
853
|
+
const handlers = handlersToAnalyze.map(
|
|
854
|
+
(handler) => analyzeApiHandler({
|
|
855
|
+
relativeFile,
|
|
856
|
+
content,
|
|
857
|
+
method: handler.method,
|
|
858
|
+
body: handler.body,
|
|
859
|
+
usedFallbackBody: handler.usedFallbackBody
|
|
860
|
+
})
|
|
861
|
+
);
|
|
862
|
+
for (const handler of handlers) {
|
|
863
|
+
handler.context = getHandlerContext(handler, handlers);
|
|
840
864
|
}
|
|
865
|
+
const methods = handlers.map((handler) => handler.method);
|
|
866
|
+
const webhookProvider = getRouteWebhookProvider(handlers, relativeFile, content);
|
|
867
|
+
return {
|
|
868
|
+
file,
|
|
869
|
+
relativeFile,
|
|
870
|
+
methods,
|
|
871
|
+
handlers,
|
|
872
|
+
routeIntent: getRouteIntent(handlers),
|
|
873
|
+
evidence: methods.map((method) => ({ label: "exports", detail: method })),
|
|
874
|
+
webhookProvider
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function analyzeApiHandler({
|
|
878
|
+
relativeFile,
|
|
879
|
+
content,
|
|
880
|
+
method,
|
|
881
|
+
body,
|
|
882
|
+
usedFallbackBody
|
|
883
|
+
}) {
|
|
884
|
+
const normalizedFile = relativeFile.toLowerCase();
|
|
841
885
|
const webhookPathMatch = getRoutePathMatch(normalizedFile, ["webhook", "webhooks", "callback"]);
|
|
842
886
|
const internalPathMatch = getRoutePathMatch(normalizedFile, ["internal", "admin", "cron", "cleanup", "revalidate", "private"]);
|
|
843
887
|
const formPathMatch = getRoutePathMatch(normalizedFile, ["contact", "subscribe", "newsletter", "lead", "inquiry"]);
|
|
@@ -871,7 +915,22 @@ function analyzeApiRoute({
|
|
|
871
915
|
"sitemap",
|
|
872
916
|
"rss"
|
|
873
917
|
]);
|
|
918
|
+
const handlerContent = body || content;
|
|
919
|
+
const webhookRouteInfo = getWebhookRouteInfo(relativeFile, handlerContent);
|
|
920
|
+
const webhookProvider = webhookRouteInfo?.provider ?? getWebhookProvider(`${normalizedFile}
|
|
921
|
+
${handlerContent.toLowerCase()}`);
|
|
922
|
+
const hasAuth = hasAuthOrSessionCheck(handlerContent);
|
|
923
|
+
const hasSecretProtection = hasSecretProtectionSignal(handlerContent);
|
|
924
|
+
const hasRateLimit = hasRateLimitSignal(handlerContent);
|
|
925
|
+
const hasValidation = hasValidationSignal(handlerContent);
|
|
926
|
+
const hasCacheHeaders = hasCacheHeaderSignal(handlerContent);
|
|
927
|
+
const hasMethodBlocking = hasMethodBlockingSignal(handlerContent);
|
|
928
|
+
const hasWebhookVerification = hasWebhookSignatureVerification(handlerContent, webhookProvider);
|
|
929
|
+
const evidence = [{ label: "exports", detail: method }];
|
|
874
930
|
let intent = "unknown";
|
|
931
|
+
if (usedFallbackBody) {
|
|
932
|
+
evidence.push({ label: "handler body extraction fallback", detail: "using full file" });
|
|
933
|
+
}
|
|
875
934
|
if (webhookRouteInfo || webhookPathMatch) {
|
|
876
935
|
intent = "webhook";
|
|
877
936
|
evidence.push({
|
|
@@ -881,54 +940,59 @@ function analyzeApiRoute({
|
|
|
881
940
|
} else if (internalPathMatch) {
|
|
882
941
|
intent = "internal";
|
|
883
942
|
evidence.push({ label: "path contains", detail: internalPathMatch });
|
|
884
|
-
} else if (formPathMatch) {
|
|
943
|
+
} else if (method === "POST" && formPathMatch) {
|
|
885
944
|
intent = "public-form";
|
|
886
945
|
evidence.push({ label: "path contains", detail: formPathMatch });
|
|
887
|
-
} else if (
|
|
946
|
+
} else if (method === "GET" && publicContentPathMatch && !sensitivePathMatch && !internalPathMatch) {
|
|
947
|
+
intent = "public-read";
|
|
948
|
+
evidence.push({ label: "public read path detected", detail: publicContentPathMatch });
|
|
949
|
+
} else if (isMutationMethod(method) && sensitivePathMatch && !hasMethodBlocking) {
|
|
888
950
|
intent = "sensitive-mutation";
|
|
889
951
|
evidence.push({ label: "path contains", detail: sensitivePathMatch });
|
|
890
|
-
} else if (methods.includes("GET") && publicContentPathMatch && !sensitivePathMatch && !internalPathMatch) {
|
|
891
|
-
intent = "public-read";
|
|
892
|
-
evidence.push({ label: "public content route detected", detail: publicContentPathMatch });
|
|
893
952
|
}
|
|
894
953
|
if (hasAuth) {
|
|
895
|
-
evidence.push({ label:
|
|
954
|
+
evidence.push({ label: `auth/session check detected in ${method} handler` });
|
|
896
955
|
} else {
|
|
897
|
-
evidence.push({ label:
|
|
956
|
+
evidence.push({ label: `no auth/session check detected in ${method} handler` });
|
|
898
957
|
}
|
|
899
958
|
if (hasSecretProtection) {
|
|
900
|
-
evidence.push({ label:
|
|
901
|
-
}
|
|
902
|
-
if (hasRateLimit) {
|
|
903
|
-
evidence.push({ label: "rate limit detected" });
|
|
904
|
-
} else if (intent === "public-form") {
|
|
905
|
-
evidence.push({ label: "no rate limit detected" });
|
|
959
|
+
evidence.push({ label: `secret token check detected in ${method} handler` });
|
|
906
960
|
}
|
|
907
|
-
if (
|
|
908
|
-
evidence.push({
|
|
909
|
-
|
|
910
|
-
|
|
961
|
+
if (intent === "public-form") {
|
|
962
|
+
evidence.push({
|
|
963
|
+
label: hasRateLimit ? "rate limit detected" : "no rate limit detected",
|
|
964
|
+
detail: `${method} handler`
|
|
965
|
+
});
|
|
966
|
+
evidence.push({
|
|
967
|
+
label: hasValidation ? "validation detected" : "no validation detected",
|
|
968
|
+
detail: `${method} handler`
|
|
969
|
+
});
|
|
911
970
|
}
|
|
912
|
-
if (
|
|
913
|
-
|
|
971
|
+
if (intent === "public-read") {
|
|
972
|
+
if (hasRateLimit) {
|
|
973
|
+
evidence.push({ label: "rate limit detected", detail: `${method} handler` });
|
|
974
|
+
}
|
|
975
|
+
if (hasValidation) {
|
|
976
|
+
evidence.push({ label: "validation detected", detail: `${method} handler` });
|
|
977
|
+
}
|
|
978
|
+
if (hasCacheHeaders) {
|
|
979
|
+
evidence.push({ label: "cache/public-read safety signal detected", detail: `${method} handler` });
|
|
980
|
+
}
|
|
914
981
|
}
|
|
915
982
|
if (hasMethodBlocking) {
|
|
916
|
-
evidence.push({ label: "method blocking detected" });
|
|
983
|
+
evidence.push({ label: "method blocking detected", detail: `${method} handler` });
|
|
917
984
|
}
|
|
918
985
|
if (intent === "webhook") {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
986
|
+
evidence.push({
|
|
987
|
+
label: hasWebhookVerification ? "webhook signature verification detected" : "no webhook signature verification detected",
|
|
988
|
+
detail: `${method} handler`
|
|
989
|
+
});
|
|
924
990
|
}
|
|
925
991
|
return {
|
|
926
|
-
|
|
927
|
-
relativeFile,
|
|
928
|
-
methods,
|
|
992
|
+
method,
|
|
929
993
|
intent,
|
|
930
|
-
authExpected:
|
|
931
|
-
confidence:
|
|
994
|
+
authExpected: getHandlerAuthExpectation(intent, method, hasMethodBlocking),
|
|
995
|
+
confidence: getApiHandlerConfidence(intent, method, webhookRouteInfo, hasMethodBlocking),
|
|
932
996
|
evidence,
|
|
933
997
|
hasAuth,
|
|
934
998
|
hasSecretProtection,
|
|
@@ -936,31 +1000,89 @@ function analyzeApiRoute({
|
|
|
936
1000
|
hasValidation,
|
|
937
1001
|
hasCacheHeaders,
|
|
938
1002
|
hasMethodBlocking,
|
|
939
|
-
hasWebhookVerification
|
|
940
|
-
webhookProvider
|
|
1003
|
+
hasWebhookVerification
|
|
941
1004
|
};
|
|
942
1005
|
}
|
|
943
|
-
function
|
|
1006
|
+
function getHandlerContext(handler, handlers) {
|
|
1007
|
+
const context = [];
|
|
1008
|
+
for (const otherHandler of handlers) {
|
|
1009
|
+
if (otherHandler.method === handler.method) {
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
context.push({ label: "route also exports", detail: otherHandler.method });
|
|
1013
|
+
if (otherHandler.intent === "public-read") {
|
|
1014
|
+
context.push({ label: "public read handler detected", detail: otherHandler.method });
|
|
1015
|
+
}
|
|
1016
|
+
if (otherHandler.hasCacheHeaders) {
|
|
1017
|
+
context.push({ label: "cache/public-read safety signal detected", detail: `${otherHandler.method} handler` });
|
|
1018
|
+
}
|
|
1019
|
+
if (otherHandler.hasMethodBlocking) {
|
|
1020
|
+
context.push({ label: "method blocking detected", detail: `${otherHandler.method} handler` });
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return context.length > 0 ? context : void 0;
|
|
1024
|
+
}
|
|
1025
|
+
function getRouteIntent(handlers) {
|
|
1026
|
+
const intentPriority = [
|
|
1027
|
+
"webhook",
|
|
1028
|
+
"internal",
|
|
1029
|
+
"sensitive-mutation",
|
|
1030
|
+
"public-form",
|
|
1031
|
+
"public-read",
|
|
1032
|
+
"unknown"
|
|
1033
|
+
];
|
|
1034
|
+
return intentPriority.find(
|
|
1035
|
+
(intent) => handlers.some((handler) => handler.intent === intent)
|
|
1036
|
+
) ?? "unknown";
|
|
1037
|
+
}
|
|
1038
|
+
function getRouteWebhookProvider(handlers, relativeFile, content) {
|
|
1039
|
+
const routeWebhookInfo = getWebhookRouteInfo(relativeFile, content);
|
|
1040
|
+
if (routeWebhookInfo?.provider && routeWebhookInfo.provider !== "unknown") {
|
|
1041
|
+
return routeWebhookInfo.provider;
|
|
1042
|
+
}
|
|
1043
|
+
const webhookHandler = handlers.find((handler) => handler.intent === "webhook");
|
|
1044
|
+
if (!webhookHandler) {
|
|
1045
|
+
return "unknown";
|
|
1046
|
+
}
|
|
1047
|
+
const providerFromEvidence = webhookHandler.evidence.find(
|
|
1048
|
+
(item) => item.label === "webhook content detected" && item.detail && item.detail !== "unknown"
|
|
1049
|
+
);
|
|
1050
|
+
return providerFromEvidence?.detail ?? "unknown";
|
|
1051
|
+
}
|
|
1052
|
+
function getHandlerAuthExpectation(intent, method, hasMethodBlocking) {
|
|
1053
|
+
if (hasMethodBlocking || intent === "public-read" || intent === "public-form" || intent === "webhook") {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
944
1056
|
if (intent === "internal" || intent === "sensitive-mutation") {
|
|
945
1057
|
return true;
|
|
946
1058
|
}
|
|
947
|
-
if (
|
|
1059
|
+
if (isMutationMethod(method)) {
|
|
948
1060
|
return "review";
|
|
949
1061
|
}
|
|
950
1062
|
return false;
|
|
951
1063
|
}
|
|
952
|
-
function
|
|
1064
|
+
function getApiHandlerConfidence(intent, method, webhookRouteInfo, hasMethodBlocking) {
|
|
1065
|
+
if (hasMethodBlocking) {
|
|
1066
|
+
return "low";
|
|
1067
|
+
}
|
|
953
1068
|
if (intent === "sensitive-mutation" || intent === "internal") {
|
|
954
1069
|
return "high";
|
|
955
1070
|
}
|
|
956
1071
|
if (intent === "webhook") {
|
|
957
1072
|
return webhookRouteInfo?.confidence === "high" ? "high" : "medium";
|
|
958
1073
|
}
|
|
959
|
-
if (intent === "public-form" || intent === "unknown" &&
|
|
1074
|
+
if (intent === "public-form" || intent === "unknown" && isMutationMethod(method)) {
|
|
960
1075
|
return "medium";
|
|
961
1076
|
}
|
|
962
1077
|
return "low";
|
|
963
1078
|
}
|
|
1079
|
+
function getSensitiveHandlerTitle(handler) {
|
|
1080
|
+
const pathSignal = handler.evidence.find((item) => item.label === "path contains")?.detail;
|
|
1081
|
+
if (handler.method === "POST" && pathSignal === "upload") {
|
|
1082
|
+
return "Upload POST handler may be missing authentication";
|
|
1083
|
+
}
|
|
1084
|
+
return `Sensitive ${handler.method} handler may be missing authentication`;
|
|
1085
|
+
}
|
|
964
1086
|
function getRoutePathMatch(normalizedFile, terms) {
|
|
965
1087
|
return terms.find((term) => {
|
|
966
1088
|
const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -968,16 +1090,96 @@ function getRoutePathMatch(normalizedFile, terms) {
|
|
|
968
1090
|
});
|
|
969
1091
|
}
|
|
970
1092
|
function getExportedHttpMethods(content) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1093
|
+
return getExportedRouteHandlers(content).map((handler) => handler.method);
|
|
1094
|
+
}
|
|
1095
|
+
function getExportedRouteHandlers(content) {
|
|
1096
|
+
const handlers = [];
|
|
1097
|
+
const functionExportPattern = /\bexport\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)\s*\(/g;
|
|
1098
|
+
const constExportPattern = /\bexport\s+const\s+(GET|POST|PUT|PATCH|DELETE)\s*=/g;
|
|
974
1099
|
for (const match of content.matchAll(functionExportPattern)) {
|
|
975
|
-
|
|
1100
|
+
handlers.push(extractRouteHandlerBody(content, match.index ?? 0, match[1], "function"));
|
|
976
1101
|
}
|
|
977
1102
|
for (const match of content.matchAll(constExportPattern)) {
|
|
978
|
-
|
|
1103
|
+
handlers.push(extractRouteHandlerBody(content, match.index ?? 0, match[1], "const"));
|
|
979
1104
|
}
|
|
980
|
-
return
|
|
1105
|
+
return handlers.sort(
|
|
1106
|
+
(leftHandler, rightHandler) => getMethodRank(leftHandler.method) - getMethodRank(rightHandler.method)
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
function extractRouteHandlerBody(content, exportIndex, method, exportKind) {
|
|
1110
|
+
const nextExportIndex = findNextRouteHandlerExport(content, exportIndex + 1);
|
|
1111
|
+
const handlerEnd = nextExportIndex === -1 ? content.length : nextExportIndex;
|
|
1112
|
+
const openBraceIndex = getRouteHandlerBodyOpenBrace(content, exportIndex, handlerEnd, exportKind);
|
|
1113
|
+
if (openBraceIndex !== -1 && openBraceIndex < handlerEnd) {
|
|
1114
|
+
const closeBraceIndex = findMatchingBrace(content, openBraceIndex);
|
|
1115
|
+
if (closeBraceIndex !== -1) {
|
|
1116
|
+
return {
|
|
1117
|
+
method,
|
|
1118
|
+
body: content.slice(openBraceIndex, closeBraceIndex + 1),
|
|
1119
|
+
usedFallbackBody: false
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return {
|
|
1124
|
+
method,
|
|
1125
|
+
body: content.slice(exportIndex, handlerEnd),
|
|
1126
|
+
usedFallbackBody: true
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function getRouteHandlerBodyOpenBrace(content, exportIndex, handlerEnd, exportKind) {
|
|
1130
|
+
if (exportKind === "function") {
|
|
1131
|
+
const openParenIndex = content.indexOf("(", exportIndex);
|
|
1132
|
+
if (openParenIndex !== -1 && openParenIndex < handlerEnd) {
|
|
1133
|
+
const closeParenIndex = findMatchingParen(content, openParenIndex);
|
|
1134
|
+
if (closeParenIndex !== -1 && closeParenIndex < handlerEnd) {
|
|
1135
|
+
return content.indexOf("{", closeParenIndex);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const equalsIndex = content.indexOf("=", exportIndex);
|
|
1140
|
+
if (equalsIndex !== -1 && equalsIndex < handlerEnd) {
|
|
1141
|
+
const arrowIndex = content.indexOf("=>", equalsIndex);
|
|
1142
|
+
if (arrowIndex !== -1 && arrowIndex < handlerEnd) {
|
|
1143
|
+
return content.indexOf("{", arrowIndex);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return content.indexOf("{", exportIndex);
|
|
1147
|
+
}
|
|
1148
|
+
function findNextRouteHandlerExport(content, startIndex) {
|
|
1149
|
+
const nextExportPattern = /\bexport\s+(?:async\s+)?(?:function|const)\s+(GET|POST|PUT|PATCH|DELETE)\b/g;
|
|
1150
|
+
nextExportPattern.lastIndex = startIndex;
|
|
1151
|
+
const match = nextExportPattern.exec(content);
|
|
1152
|
+
return match?.index ?? -1;
|
|
1153
|
+
}
|
|
1154
|
+
function findMatchingParen(content, openParenIndex) {
|
|
1155
|
+
let depth = 0;
|
|
1156
|
+
for (let index = openParenIndex; index < content.length; index++) {
|
|
1157
|
+
const character = content[index];
|
|
1158
|
+
if (character === "(") {
|
|
1159
|
+
depth++;
|
|
1160
|
+
} else if (character === ")") {
|
|
1161
|
+
depth--;
|
|
1162
|
+
if (depth === 0) {
|
|
1163
|
+
return index;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return -1;
|
|
1168
|
+
}
|
|
1169
|
+
function findMatchingBrace(content, openBraceIndex) {
|
|
1170
|
+
let depth = 0;
|
|
1171
|
+
for (let index = openBraceIndex; index < content.length; index++) {
|
|
1172
|
+
const character = content[index];
|
|
1173
|
+
if (character === "{") {
|
|
1174
|
+
depth++;
|
|
1175
|
+
} else if (character === "}") {
|
|
1176
|
+
depth--;
|
|
1177
|
+
if (depth === 0) {
|
|
1178
|
+
return index;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return -1;
|
|
981
1183
|
}
|
|
982
1184
|
function getRouteHttpMethods(content) {
|
|
983
1185
|
const methods = new Set(getExportedHttpMethods(content));
|
|
@@ -991,10 +1193,12 @@ function getRouteHttpMethods(content) {
|
|
|
991
1193
|
}
|
|
992
1194
|
return [...methods];
|
|
993
1195
|
}
|
|
994
|
-
function
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1196
|
+
function getMethodRank(method) {
|
|
1197
|
+
const methodOrder = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
1198
|
+
return methodOrder.indexOf(method);
|
|
1199
|
+
}
|
|
1200
|
+
function isMutationMethod(method) {
|
|
1201
|
+
return method !== "GET";
|
|
998
1202
|
}
|
|
999
1203
|
function hasAuthOrSessionCheck(content) {
|
|
1000
1204
|
const normalizedContent = content.toLowerCase();
|
|
@@ -1014,7 +1218,7 @@ function hasValidationSignal(content) {
|
|
|
1014
1218
|
}
|
|
1015
1219
|
function hasCacheHeaderSignal(content) {
|
|
1016
1220
|
const normalizedContent = content.toLowerCase();
|
|
1017
|
-
return normalizedContent.includes("cache-control") || normalizedContent.includes("s-maxage") || normalizedContent.includes("stale-while-revalidate") || normalizedContent.includes("public") || normalizedContent.includes("published")
|
|
1221
|
+
return normalizedContent.includes("cache-control") || normalizedContent.includes("s-maxage") || normalizedContent.includes("stale-while-revalidate") || normalizedContent.includes("public") || normalizedContent.includes("published");
|
|
1018
1222
|
}
|
|
1019
1223
|
function hasMethodBlockingSignal(content) {
|
|
1020
1224
|
const normalizedContent = content.toLowerCase();
|
|
@@ -1094,7 +1298,135 @@ function getWebhookSignatureSuggestion(provider) {
|
|
|
1094
1298
|
}
|
|
1095
1299
|
return "Verify the provider signature using the raw request body and signature header before trusting the event.";
|
|
1096
1300
|
}
|
|
1097
|
-
function
|
|
1301
|
+
function getMaintainabilityFileKind(relativeFile) {
|
|
1302
|
+
const normalizedFile = normalizePath(relativeFile).toLowerCase();
|
|
1303
|
+
const fileName = path.basename(normalizedFile);
|
|
1304
|
+
if (/(^|\/)app\/api\/.+\/route\.(?:ts|js)$/.test(normalizedFile) || /(^|\/)pages\/api\/.+\.(?:ts|js)$/.test(normalizedFile)) {
|
|
1305
|
+
return "api-route";
|
|
1306
|
+
}
|
|
1307
|
+
if (fileName === "actions.ts" || fileName === "actions.tsx") {
|
|
1308
|
+
return "server-action";
|
|
1309
|
+
}
|
|
1310
|
+
if (fileName === "schema.ts" || fileName === "schemas.ts" || fileName === "validation.ts" || fileName === "validator.ts") {
|
|
1311
|
+
return "schema-or-validation";
|
|
1312
|
+
}
|
|
1313
|
+
if (fileName === "types.ts" || fileName === "type.ts" || fileName === "interfaces.ts") {
|
|
1314
|
+
return "types";
|
|
1315
|
+
}
|
|
1316
|
+
if (fileName === "config.ts" || fileName === "config.js" || fileName === "next.config.ts" || fileName === "tailwind.config.ts") {
|
|
1317
|
+
return "config";
|
|
1318
|
+
}
|
|
1319
|
+
if (normalizedFile.endsWith(".tsx") || normalizedFile.endsWith(".jsx")) {
|
|
1320
|
+
return "react-component";
|
|
1321
|
+
}
|
|
1322
|
+
if (/\.(?:ts|js|mts|cts|mjs|cjs)$/.test(normalizedFile)) {
|
|
1323
|
+
return "typescript-module";
|
|
1324
|
+
}
|
|
1325
|
+
return "unknown";
|
|
1326
|
+
}
|
|
1327
|
+
function getLargeFileIssueCopy(kind) {
|
|
1328
|
+
if (kind === "react-component") {
|
|
1329
|
+
return {
|
|
1330
|
+
title: "Large React component detected",
|
|
1331
|
+
message: "This component is larger than the recommended maintainability threshold. Large components can be harder to review, test, and safely modify.",
|
|
1332
|
+
suggestion: "Review whether this component mixes UI, state, data fetching, validation, or business logic. If so, split it into smaller components, hooks, or utilities."
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
if (kind === "typescript-module") {
|
|
1336
|
+
return {
|
|
1337
|
+
title: "Large TypeScript module detected",
|
|
1338
|
+
message: "This module is larger than the recommended maintainability threshold. Large modules can mix business logic, data access, transformations, constants, or helper functions in one place.",
|
|
1339
|
+
suggestion: "Review whether this module mixes unrelated responsibilities such as data fetching, filtering, mapping, sorting, constants, types, or business rules. If so, split it into smaller modules while preserving public exports."
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
if (kind === "api-route") {
|
|
1343
|
+
return {
|
|
1344
|
+
title: "Large API route detected",
|
|
1345
|
+
message: "This API route is larger than the recommended maintainability threshold. Large route handlers can mix validation, auth, business logic, and response formatting.",
|
|
1346
|
+
suggestion: "Review whether this route mixes validation, authentication, business logic, and response formatting. If so, extract reusable helpers without changing route behavior."
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
if (kind === "server-action") {
|
|
1350
|
+
return {
|
|
1351
|
+
title: "Large server action file detected",
|
|
1352
|
+
message: "This server action file is larger than the recommended maintainability threshold and may mix validation, data mutations, and business rules.",
|
|
1353
|
+
suggestion: "Review whether this file mixes validation, permission checks, mutations, and formatting. If so, extract helpers while preserving action behavior."
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
if (kind === "schema-or-validation") {
|
|
1357
|
+
return {
|
|
1358
|
+
title: "Large schema or validation file detected",
|
|
1359
|
+
message: "This schema or validation file is larger than the recommended maintainability threshold and may mix unrelated validation concerns.",
|
|
1360
|
+
suggestion: "Review whether related schemas or validators can be grouped into smaller files while preserving exported names and validation behavior."
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
if (kind === "types") {
|
|
1364
|
+
return {
|
|
1365
|
+
title: "Large type definition file detected",
|
|
1366
|
+
message: "This type file is larger than the recommended maintainability threshold and may be harder to navigate safely.",
|
|
1367
|
+
suggestion: "Review whether types can be split by domain or feature while preserving public exports and imports."
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
if (kind === "config") {
|
|
1371
|
+
return {
|
|
1372
|
+
title: "Large config file detected",
|
|
1373
|
+
message: "This config file is larger than the recommended maintainability threshold and may mix unrelated configuration concerns.",
|
|
1374
|
+
suggestion: "Review whether configuration values or helper functions can be moved to smaller supporting modules without changing runtime behavior."
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
return {
|
|
1378
|
+
title: "Large file detected",
|
|
1379
|
+
message: "This file is larger than the recommended maintainability threshold. Large files can be harder to review, test, and safely modify.",
|
|
1380
|
+
suggestion: "Review whether this file mixes unrelated responsibilities. If so, split it into smaller modules while preserving behavior."
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function createApiAuthFixPrompt(file, method, intent = "unknown") {
|
|
1384
|
+
if (method && intent === "sensitive-mutation") {
|
|
1385
|
+
return `Review the API route at ${file}.
|
|
1386
|
+
|
|
1387
|
+
Qodfy detected a possible issue in the ${method} handler.
|
|
1388
|
+
|
|
1389
|
+
Goal:
|
|
1390
|
+
Determine whether the ${method} handler should be protected.
|
|
1391
|
+
|
|
1392
|
+
Instructions:
|
|
1393
|
+
- Inspect each exported HTTP handler separately.
|
|
1394
|
+
- Do not add authentication to a GET handler if it is intentionally public.
|
|
1395
|
+
- If the ${method} handler handles file uploads, private data, storage writes, payments, account changes, or user-specific actions, add the existing project auth/session check to the ${method} handler.
|
|
1396
|
+
- Also verify protections such as input validation, file size limits for uploads, storage path safety, and rate limiting where relevant.
|
|
1397
|
+
- Do not introduce a new auth provider.
|
|
1398
|
+
- Do not refactor unrelated code.
|
|
1399
|
+
- Keep existing response formats unchanged.
|
|
1400
|
+
- If the ${method} handler is intentionally public, add a short comment explaining why and confirm abuse protection exists.
|
|
1401
|
+
|
|
1402
|
+
Return:
|
|
1403
|
+
- Whether each handler is public or protected.
|
|
1404
|
+
- Whether the ${method} handler should be protected.
|
|
1405
|
+
- The safest code change.
|
|
1406
|
+
- Edge cases to test.`;
|
|
1407
|
+
}
|
|
1408
|
+
if (method && intent === "unknown") {
|
|
1409
|
+
return `Review the API route at ${file}.
|
|
1410
|
+
|
|
1411
|
+
Qodfy detected a mutation handler that should be reviewed: ${method}.
|
|
1412
|
+
|
|
1413
|
+
Goal:
|
|
1414
|
+
Determine whether the ${method} handler should be public or protected.
|
|
1415
|
+
|
|
1416
|
+
Instructions:
|
|
1417
|
+
- Inspect each exported HTTP handler separately.
|
|
1418
|
+
- Check what data the ${method} handler reads, writes, or returns.
|
|
1419
|
+
- If it handles private data, user-specific data, writes, uploads, or admin actions, add the existing project auth/session check.
|
|
1420
|
+
- If it is intentionally public, document why and confirm validation and abuse protection exist.
|
|
1421
|
+
- Do not introduce a new auth provider.
|
|
1422
|
+
- Do not refactor unrelated code.
|
|
1423
|
+
- Keep existing response formats unchanged.
|
|
1424
|
+
|
|
1425
|
+
Return:
|
|
1426
|
+
- Whether the ${method} handler should be protected.
|
|
1427
|
+
- The safest code change, if any.
|
|
1428
|
+
- Edge cases to test.`;
|
|
1429
|
+
}
|
|
1098
1430
|
return `Review the API route at ${file}.
|
|
1099
1431
|
|
|
1100
1432
|
Goal:
|
|
@@ -1113,34 +1445,40 @@ Return:
|
|
|
1113
1445
|
- The updated code.
|
|
1114
1446
|
- Any edge cases I should test.`;
|
|
1115
1447
|
}
|
|
1116
|
-
function createPublicReadRouteFixPrompt(file) {
|
|
1448
|
+
function createPublicReadRouteFixPrompt(file, method = "GET") {
|
|
1117
1449
|
return `Review the public read API route at ${file}.
|
|
1118
1450
|
|
|
1451
|
+
Qodfy detected this as a likely public ${method} handler.
|
|
1452
|
+
|
|
1119
1453
|
Goal:
|
|
1120
|
-
Verify that
|
|
1454
|
+
Verify that the ${method} handler is safe to remain public.
|
|
1121
1455
|
|
|
1122
1456
|
Instructions:
|
|
1123
|
-
-
|
|
1457
|
+
- Inspect each exported HTTP handler separately.
|
|
1458
|
+
- Confirm the ${method} handler only exposes published, public, or non-sensitive data.
|
|
1124
1459
|
- Check that route params and query values are validated or sanitized.
|
|
1125
1460
|
- Check for appropriate cache headers where useful.
|
|
1126
1461
|
- Check for abuse protection if the route can be called heavily.
|
|
1127
|
-
- Do not add user authentication
|
|
1462
|
+
- Do not add user authentication to the ${method} handler unless it should be private.
|
|
1128
1463
|
- Do not refactor unrelated code.
|
|
1129
1464
|
|
|
1130
1465
|
Return:
|
|
1131
|
-
- Whether
|
|
1466
|
+
- Whether the ${method} handler appears intentionally public.
|
|
1132
1467
|
- Any low-risk safety improvements.
|
|
1133
1468
|
- Any edge cases I should test.`;
|
|
1134
1469
|
}
|
|
1135
|
-
function createPublicFormProtectionFixPrompt(file) {
|
|
1470
|
+
function createPublicFormProtectionFixPrompt(file, method = "POST") {
|
|
1136
1471
|
return `Review the public form API route at ${file}.
|
|
1137
1472
|
|
|
1473
|
+
Qodfy detected this as a likely public ${method} handler.
|
|
1474
|
+
|
|
1138
1475
|
Goal:
|
|
1139
1476
|
Verify validation, rate limiting, and spam protection.
|
|
1140
1477
|
|
|
1141
1478
|
Instructions:
|
|
1142
|
-
-
|
|
1143
|
-
-
|
|
1479
|
+
- Inspect each exported HTTP handler separately.
|
|
1480
|
+
- Confirm submitted input in the ${method} handler is validated before it is used.
|
|
1481
|
+
- Check for rate limiting or another abuse protection pattern on the ${method} handler.
|
|
1144
1482
|
- Check whether captcha, Turnstile, reCAPTCHA, or hCaptcha is appropriate.
|
|
1145
1483
|
- Do not add user authentication unless the form should be private.
|
|
1146
1484
|
- Do not introduce a new service unless necessary.
|
|
@@ -1151,18 +1489,24 @@ Return:
|
|
|
1151
1489
|
- The safest minimal change if protection is missing.
|
|
1152
1490
|
- Any edge cases I should test.`;
|
|
1153
1491
|
}
|
|
1154
|
-
function createInternalRouteProtectionFixPrompt(file) {
|
|
1492
|
+
function createInternalRouteProtectionFixPrompt(file, method) {
|
|
1493
|
+
const handlerLine = method ? `
|
|
1494
|
+
Qodfy detected this as a likely internal ${method} handler.
|
|
1495
|
+
` : "";
|
|
1496
|
+
const handlerReference = method ? `the ${method} handler` : "the route";
|
|
1155
1497
|
return `Review the internal API route at ${file}.
|
|
1498
|
+
${handlerLine}
|
|
1156
1499
|
|
|
1157
1500
|
Goal:
|
|
1158
1501
|
Confirm this operational route is protected before launch.
|
|
1159
1502
|
|
|
1160
1503
|
Instructions:
|
|
1161
|
-
-
|
|
1504
|
+
- Inspect each exported HTTP handler separately.
|
|
1505
|
+
- Check whether ${handlerReference} is protected by existing auth, a secret token, or server-only access.
|
|
1162
1506
|
- For cron, cleanup, revalidation, or admin routes, prefer the existing project protection pattern.
|
|
1163
1507
|
- Do not introduce a new auth provider.
|
|
1164
1508
|
- Do not change route behavior unless protection is missing.
|
|
1165
|
-
- If
|
|
1509
|
+
- If ${handlerReference} is intentionally reachable, add a short comment explaining the protection boundary.
|
|
1166
1510
|
|
|
1167
1511
|
Return:
|
|
1168
1512
|
- Whether protection already exists.
|
|
@@ -1184,16 +1528,17 @@ Return:
|
|
|
1184
1528
|
- The updated .env.example lines.
|
|
1185
1529
|
- A short explanation.`;
|
|
1186
1530
|
}
|
|
1187
|
-
function createLargeFileFixPrompt(file) {
|
|
1188
|
-
|
|
1531
|
+
function createLargeFileFixPrompt(file, kind = getMaintainabilityFileKind(file)) {
|
|
1532
|
+
if (kind === "react-component") {
|
|
1533
|
+
return `Review ${file}.
|
|
1189
1534
|
|
|
1190
|
-
Qodfy detected this as a large
|
|
1535
|
+
Qodfy detected this as a large React component.
|
|
1191
1536
|
|
|
1192
1537
|
Goal:
|
|
1193
1538
|
Suggest a safe refactor plan without changing behavior.
|
|
1194
1539
|
|
|
1195
1540
|
Instructions:
|
|
1196
|
-
- Identify the main responsibilities inside the
|
|
1541
|
+
- Identify the main responsibilities inside the component.
|
|
1197
1542
|
- Suggest smaller components, hooks, or utility files that can be extracted.
|
|
1198
1543
|
- Do not rewrite the whole file at once.
|
|
1199
1544
|
- Do not change UI behavior.
|
|
@@ -1204,6 +1549,163 @@ Return:
|
|
|
1204
1549
|
- A short responsibility breakdown.
|
|
1205
1550
|
- A step-by-step refactor plan.
|
|
1206
1551
|
- The safest first extraction.`;
|
|
1552
|
+
}
|
|
1553
|
+
if (kind === "typescript-module") {
|
|
1554
|
+
return `Review ${file}.
|
|
1555
|
+
|
|
1556
|
+
Qodfy detected this as a large TypeScript module.
|
|
1557
|
+
|
|
1558
|
+
Goal:
|
|
1559
|
+
Create a safe refactor plan without changing behavior.
|
|
1560
|
+
|
|
1561
|
+
Instructions:
|
|
1562
|
+
- Identify the main responsibilities inside this module.
|
|
1563
|
+
- Check whether the module mixes unrelated concerns such as data access, filtering, mapping, sorting, constants, types, validation, or business rules.
|
|
1564
|
+
- Suggest smaller TypeScript modules that could be extracted safely.
|
|
1565
|
+
- Do not rewrite the whole file at once.
|
|
1566
|
+
- Do not change business logic.
|
|
1567
|
+
- Do not change public exports unless you also update all imports safely.
|
|
1568
|
+
- Preserve existing function behavior, return types, and error handling.
|
|
1569
|
+
- Prioritize the lowest-risk extraction first.
|
|
1570
|
+
|
|
1571
|
+
Return:
|
|
1572
|
+
- Responsibility breakdown.
|
|
1573
|
+
- Suggested new file/module structure.
|
|
1574
|
+
- Step-by-step refactor plan.
|
|
1575
|
+
- The safest first extraction.
|
|
1576
|
+
- Tests or manual checks to run.`;
|
|
1577
|
+
}
|
|
1578
|
+
if (kind === "api-route") {
|
|
1579
|
+
return `Review the API route at ${file}.
|
|
1580
|
+
|
|
1581
|
+
Qodfy detected this as a large API route file.
|
|
1582
|
+
|
|
1583
|
+
Goal:
|
|
1584
|
+
Create a safe refactor plan without changing HTTP behavior.
|
|
1585
|
+
|
|
1586
|
+
Instructions:
|
|
1587
|
+
- Identify where validation, authentication, business logic, and response formatting happen.
|
|
1588
|
+
- Suggest helper modules that can be extracted without changing the route contract.
|
|
1589
|
+
- Preserve HTTP methods, status codes, headers, auth checks, validation behavior, and response shape.
|
|
1590
|
+
- Do not rewrite the whole route at once.
|
|
1591
|
+
- Do not introduce a new auth provider or validation library.
|
|
1592
|
+
- Prioritize the lowest-risk extraction first.
|
|
1593
|
+
|
|
1594
|
+
Return:
|
|
1595
|
+
- Responsibility breakdown.
|
|
1596
|
+
- Suggested helper/module structure.
|
|
1597
|
+
- Step-by-step refactor plan.
|
|
1598
|
+
- The safest first extraction.
|
|
1599
|
+
- Tests or manual checks to run.`;
|
|
1600
|
+
}
|
|
1601
|
+
if (kind === "server-action") {
|
|
1602
|
+
return `Review the server action file at ${file}.
|
|
1603
|
+
|
|
1604
|
+
Qodfy detected this as a large server action file.
|
|
1605
|
+
|
|
1606
|
+
Goal:
|
|
1607
|
+
Create a safe refactor plan without changing action behavior.
|
|
1608
|
+
|
|
1609
|
+
Instructions:
|
|
1610
|
+
- Identify validation, permission checks, mutations, cache invalidation, formatting, and error handling.
|
|
1611
|
+
- Suggest helper modules that can be extracted safely.
|
|
1612
|
+
- Preserve permissions, validation behavior, data mutations, cache invalidation, return shape, and error handling.
|
|
1613
|
+
- Do not rewrite the whole file at once.
|
|
1614
|
+
- Do not change public action names unless you also update every import safely.
|
|
1615
|
+
- Prioritize the lowest-risk extraction first.
|
|
1616
|
+
|
|
1617
|
+
Return:
|
|
1618
|
+
- Responsibility breakdown.
|
|
1619
|
+
- Suggested helper/module structure.
|
|
1620
|
+
- Step-by-step refactor plan.
|
|
1621
|
+
- The safest first extraction.
|
|
1622
|
+
- Tests or manual checks to run.`;
|
|
1623
|
+
}
|
|
1624
|
+
if (kind === "schema-or-validation") {
|
|
1625
|
+
return `Review the schema or validation file at ${file}.
|
|
1626
|
+
|
|
1627
|
+
Qodfy detected this as a large validation-focused file.
|
|
1628
|
+
|
|
1629
|
+
Goal:
|
|
1630
|
+
Create a safe refactor plan without changing validation behavior.
|
|
1631
|
+
|
|
1632
|
+
Instructions:
|
|
1633
|
+
- Identify related schemas, validators, shared constants, and inferred types.
|
|
1634
|
+
- Suggest smaller validation modules grouped by feature or domain.
|
|
1635
|
+
- Do not change validation rules, error messages, inferred types, or public exports unless you also update all imports safely.
|
|
1636
|
+
- Prioritize the lowest-risk extraction first.
|
|
1637
|
+
|
|
1638
|
+
Return:
|
|
1639
|
+
- Responsibility breakdown.
|
|
1640
|
+
- Suggested file/module structure.
|
|
1641
|
+
- Step-by-step refactor plan.
|
|
1642
|
+
- The safest first extraction.
|
|
1643
|
+
- Tests or manual checks to run.`;
|
|
1644
|
+
}
|
|
1645
|
+
if (kind === "types") {
|
|
1646
|
+
return `Review the type definition file at ${file}.
|
|
1647
|
+
|
|
1648
|
+
Qodfy detected this as a large type file.
|
|
1649
|
+
|
|
1650
|
+
Goal:
|
|
1651
|
+
Create a safe organization plan without changing runtime behavior.
|
|
1652
|
+
|
|
1653
|
+
Instructions:
|
|
1654
|
+
- Identify groups of related types, interfaces, enums, and exported utility types.
|
|
1655
|
+
- Suggest smaller type modules grouped by domain or feature.
|
|
1656
|
+
- Do not change type names, public exports, or imports unless you also update all references safely.
|
|
1657
|
+
- Prioritize the lowest-risk extraction first.
|
|
1658
|
+
|
|
1659
|
+
Return:
|
|
1660
|
+
- Type responsibility breakdown.
|
|
1661
|
+
- Suggested file/module structure.
|
|
1662
|
+
- Step-by-step refactor plan.
|
|
1663
|
+
- The safest first extraction.
|
|
1664
|
+
- Type-check command to run.`;
|
|
1665
|
+
}
|
|
1666
|
+
if (kind === "config") {
|
|
1667
|
+
return `Review the config file at ${file}.
|
|
1668
|
+
|
|
1669
|
+
Qodfy detected this as a large config file.
|
|
1670
|
+
|
|
1671
|
+
Goal:
|
|
1672
|
+
Create a safe simplification plan without changing runtime configuration.
|
|
1673
|
+
|
|
1674
|
+
Instructions:
|
|
1675
|
+
- Identify config sections, constants, plugin setup, and helper functions.
|
|
1676
|
+
- Suggest supporting modules only if extraction reduces complexity.
|
|
1677
|
+
- Preserve all existing config values, plugin order, environment behavior, and exports.
|
|
1678
|
+
- Do not upgrade dependencies or change framework behavior.
|
|
1679
|
+
- Prioritize the lowest-risk extraction first.
|
|
1680
|
+
|
|
1681
|
+
Return:
|
|
1682
|
+
- Config responsibility breakdown.
|
|
1683
|
+
- Suggested supporting module structure.
|
|
1684
|
+
- Step-by-step refactor plan.
|
|
1685
|
+
- The safest first extraction.
|
|
1686
|
+
- Build or validation command to run.`;
|
|
1687
|
+
}
|
|
1688
|
+
return `Review ${file}.
|
|
1689
|
+
|
|
1690
|
+
Qodfy detected this as a large file.
|
|
1691
|
+
|
|
1692
|
+
Goal:
|
|
1693
|
+
Create a safe refactor plan without changing behavior.
|
|
1694
|
+
|
|
1695
|
+
Instructions:
|
|
1696
|
+
- Identify the main responsibilities inside the file.
|
|
1697
|
+
- Suggest smaller files or modules that can be extracted safely.
|
|
1698
|
+
- Do not rewrite the whole file at once.
|
|
1699
|
+
- Do not change public exports unless you also update all imports safely.
|
|
1700
|
+
- Preserve existing behavior, return values, and error handling.
|
|
1701
|
+
- Prioritize the lowest-risk extraction first.
|
|
1702
|
+
|
|
1703
|
+
Return:
|
|
1704
|
+
- Responsibility breakdown.
|
|
1705
|
+
- Suggested file/module structure.
|
|
1706
|
+
- Step-by-step refactor plan.
|
|
1707
|
+
- The safest first extraction.
|
|
1708
|
+
- Tests or manual checks to run.`;
|
|
1207
1709
|
}
|
|
1208
1710
|
function createAiRateLimitFixPrompt(file) {
|
|
1209
1711
|
return `Review the AI-related API route at ${file}.
|
|
@@ -1224,8 +1726,13 @@ Return:
|
|
|
1224
1726
|
- The updated code.
|
|
1225
1727
|
- Any environment variables required.`;
|
|
1226
1728
|
}
|
|
1227
|
-
function createWebhookSignatureFixPrompt(file) {
|
|
1729
|
+
function createWebhookSignatureFixPrompt(file, method) {
|
|
1730
|
+
const handlerLine = method ? `
|
|
1731
|
+
Qodfy detected a possible issue in the ${method} webhook handler.
|
|
1732
|
+
` : "";
|
|
1733
|
+
const handlerReference = method ? `the ${method} handler` : "the webhook route";
|
|
1228
1734
|
return `Review the webhook API route at ${file}.
|
|
1735
|
+
${handlerLine}
|
|
1229
1736
|
|
|
1230
1737
|
Goal:
|
|
1231
1738
|
Verify that webhook signature validation happens before the event is handled.
|
|
@@ -1233,7 +1740,8 @@ Verify that webhook signature validation happens before the event is handled.
|
|
|
1233
1740
|
Instructions:
|
|
1234
1741
|
- Detect which provider this webhook belongs to based on imports, headers, and environment variables.
|
|
1235
1742
|
- Use the provider's existing verification pattern if already present.
|
|
1236
|
-
-
|
|
1743
|
+
- Verify that ${handlerReference} validates the provider signature before processing the event unless the provider requires a different order.
|
|
1744
|
+
- Do not add user authentication unless the webhook provider requires it.
|
|
1237
1745
|
- Do not introduce unrelated changes.
|
|
1238
1746
|
- If verification already exists, explain where it happens.
|
|
1239
1747
|
|