@critiq/cli 0.2.0 → 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/main.d.ts +26 -1
- package/main.js +2786 -188
- package/package.json +1 -1
- package/schema/rule-document-v0alpha1.schema.json +1 -0
package/main.js
CHANGED
|
@@ -56,22 +56,22 @@ function createJsonPointer(segments) {
|
|
|
56
56
|
}
|
|
57
57
|
return `/${segments.map(escapeJsonPointerSegment).join("/")}`;
|
|
58
58
|
}
|
|
59
|
-
function createDiagnostic(
|
|
60
|
-
assertNonEmptyString(String(
|
|
61
|
-
assertNonEmptyString(
|
|
62
|
-
const severity =
|
|
59
|
+
function createDiagnostic(input2) {
|
|
60
|
+
assertNonEmptyString(String(input2.code), "code");
|
|
61
|
+
assertNonEmptyString(input2.message, "message");
|
|
62
|
+
const severity = input2.severity ?? DIAGNOSTIC_SEVERITY_ERROR;
|
|
63
63
|
assertSeverity(severity);
|
|
64
|
-
if (
|
|
65
|
-
assertNonEmptyString(
|
|
64
|
+
if (input2.summary !== void 0) {
|
|
65
|
+
assertNonEmptyString(input2.summary, "summary");
|
|
66
66
|
}
|
|
67
67
|
return {
|
|
68
|
-
code:
|
|
68
|
+
code: input2.code,
|
|
69
69
|
severity,
|
|
70
|
-
message:
|
|
71
|
-
summary:
|
|
72
|
-
sourceSpan:
|
|
73
|
-
jsonPointer:
|
|
74
|
-
details:
|
|
70
|
+
message: input2.message,
|
|
71
|
+
summary: input2.summary,
|
|
72
|
+
sourceSpan: input2.sourceSpan,
|
|
73
|
+
jsonPointer: input2.jsonPointer,
|
|
74
|
+
details: input2.details
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
function compareNullableStrings(left, right) {
|
|
@@ -142,7 +142,7 @@ function formatDiagnosticsForTerminal(diagnostics) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// apps/cli/src/main.ts
|
|
145
|
-
var
|
|
145
|
+
var import_node_path22 = require("node:path");
|
|
146
146
|
|
|
147
147
|
// apps/cli/src/cli-runtime.ts
|
|
148
148
|
var defaultRuntime = {
|
|
@@ -167,7 +167,10 @@ function resolveRuntime(runtime = {}) {
|
|
|
167
167
|
writeStdout: runtime.writeStdout ?? defaultRuntime.writeStdout,
|
|
168
168
|
writeStderr: runtime.writeStderr ?? defaultRuntime.writeStderr,
|
|
169
169
|
writeRaw: runtime.writeRaw ?? (runtime.writeStdout || runtime.writeStderr ? discardOutput : defaultRuntime.writeRaw),
|
|
170
|
-
isInteractive: runtime.isInteractive ?? (runtime.writeRaw ? true : runtime.writeStdout || runtime.writeStderr ? false : defaultRuntime.isInteractive)
|
|
170
|
+
isInteractive: runtime.isInteractive ?? (runtime.writeRaw ? true : runtime.writeStdout || runtime.writeStderr ? false : defaultRuntime.isInteractive),
|
|
171
|
+
cliInstallScope: runtime.cliInstallScope,
|
|
172
|
+
promptChoice: runtime.promptChoice,
|
|
173
|
+
runPackageInstall: runtime.runPackageInstall
|
|
171
174
|
};
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -366,8 +369,8 @@ var ruleCatalogSchema = import_zod.z.object({
|
|
|
366
369
|
}).strict()
|
|
367
370
|
).min(1)
|
|
368
371
|
}).strict();
|
|
369
|
-
function validateRuleCatalog(
|
|
370
|
-
const result = ruleCatalogSchema.safeParse(
|
|
372
|
+
function validateRuleCatalog(input2) {
|
|
373
|
+
const result = ruleCatalogSchema.safeParse(input2);
|
|
371
374
|
if (result.success) {
|
|
372
375
|
return {
|
|
373
376
|
success: true,
|
|
@@ -415,20 +418,20 @@ function loadRuleCatalogText(text, path) {
|
|
|
415
418
|
const uri = (0, import_node_url.pathToFileURL)(path).href;
|
|
416
419
|
const loaded = loadYamlText(text, uri);
|
|
417
420
|
if (!loaded.success) {
|
|
418
|
-
const
|
|
421
|
+
const failure2 = loaded;
|
|
419
422
|
return {
|
|
420
423
|
success: false,
|
|
421
|
-
diagnostics:
|
|
424
|
+
diagnostics: failure2.issues.map(issueToDiagnostic),
|
|
422
425
|
path,
|
|
423
426
|
uri
|
|
424
427
|
};
|
|
425
428
|
}
|
|
426
429
|
const validated = validateRuleCatalog(loaded.data);
|
|
427
430
|
if (!validated.success) {
|
|
428
|
-
const
|
|
431
|
+
const failure2 = validated;
|
|
429
432
|
return {
|
|
430
433
|
success: false,
|
|
431
|
-
diagnostics:
|
|
434
|
+
diagnostics: failure2.diagnostics,
|
|
432
435
|
path,
|
|
433
436
|
uri
|
|
434
437
|
};
|
|
@@ -506,7 +509,7 @@ function resolveCatalogPackage(cwd, packageName, additionalResolverBasePaths = [
|
|
|
506
509
|
diagnostics: [
|
|
507
510
|
createDiagnostic({
|
|
508
511
|
code: "catalog.package.not-found",
|
|
509
|
-
message: `Unable to resolve catalog package \`${packageName}
|
|
512
|
+
message: `Unable to resolve catalog package \`${packageName}\`. Install it in this repository or globally, then run \`critiq check\` again.`,
|
|
510
513
|
details: {
|
|
511
514
|
packageName
|
|
512
515
|
}
|
|
@@ -531,6 +534,18 @@ function resolveCatalogRulePaths(catalog, packageRoot, preset) {
|
|
|
531
534
|
rulePath: (0, import_node_path.resolve)(packageRoot, entry.rulePath)
|
|
532
535
|
}));
|
|
533
536
|
}
|
|
537
|
+
function looksLikeCloudFormationPath(filePath) {
|
|
538
|
+
const normalized = filePath.replace(/\\/gu, "/").toLowerCase();
|
|
539
|
+
const fileName = normalized.split("/").pop() ?? normalized;
|
|
540
|
+
if (/(?:^|\/)(?:templates?|cloudformation|cfn|sam|infra|iac)(?:\/|$)/u.test(
|
|
541
|
+
normalized
|
|
542
|
+
)) {
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
return /(?:template|cloudformation|stack|cfn|sam)[^/]*\.(?:ya?ml|json)$/u.test(
|
|
546
|
+
fileName
|
|
547
|
+
);
|
|
548
|
+
}
|
|
534
549
|
function detectRepositoryLanguages(filePaths) {
|
|
535
550
|
const detected = /* @__PURE__ */ new Set();
|
|
536
551
|
for (const filePath of filePaths) {
|
|
@@ -559,6 +574,9 @@ function detectRepositoryLanguages(filePaths) {
|
|
|
559
574
|
if (extension === ".rs") {
|
|
560
575
|
detected.add("rust");
|
|
561
576
|
}
|
|
577
|
+
if ((extension === ".yaml" || extension === ".yml" || extension === ".json") && looksLikeCloudFormationPath(filePath)) {
|
|
578
|
+
detected.add("cloudformation");
|
|
579
|
+
}
|
|
562
580
|
}
|
|
563
581
|
return [...detected].sort();
|
|
564
582
|
}
|
|
@@ -713,8 +731,8 @@ function normalizeZodIssue(issue) {
|
|
|
713
731
|
}
|
|
714
732
|
return normalized;
|
|
715
733
|
}
|
|
716
|
-
function validateFinding(
|
|
717
|
-
const result = findingV0Schema.safeParse(
|
|
734
|
+
function validateFinding(input2) {
|
|
735
|
+
const result = findingV0Schema.safeParse(input2);
|
|
718
736
|
if (result.success) {
|
|
719
737
|
return {
|
|
720
738
|
success: true,
|
|
@@ -744,7 +762,8 @@ var configLanguageSchema = import_zod4.z.enum([
|
|
|
744
762
|
"java",
|
|
745
763
|
"php",
|
|
746
764
|
"ruby",
|
|
747
|
-
"rust"
|
|
765
|
+
"rust",
|
|
766
|
+
"cloudformation"
|
|
748
767
|
]);
|
|
749
768
|
var CRITIQ_CONFIG_DEFAULT_PATH = ".critiq/config.yaml";
|
|
750
769
|
var critiqPresetSchema = import_zod4.z.enum([
|
|
@@ -866,8 +885,8 @@ function normalizeCritiqConfig(config) {
|
|
|
866
885
|
secretsScan: normalizeSecretsScanConfig(config.secretsScan)
|
|
867
886
|
};
|
|
868
887
|
}
|
|
869
|
-
function validateCritiqConfig(
|
|
870
|
-
const result = critiqConfigSchema.safeParse(
|
|
888
|
+
function validateCritiqConfig(input2) {
|
|
889
|
+
const result = critiqConfigSchema.safeParse(input2);
|
|
871
890
|
if (result.success) {
|
|
872
891
|
return {
|
|
873
892
|
success: true,
|
|
@@ -889,20 +908,20 @@ function loadCritiqConfigText(text, path) {
|
|
|
889
908
|
const uri = (0, import_node_url2.pathToFileURL)(path).href;
|
|
890
909
|
const loaded = loadYamlText(text, uri);
|
|
891
910
|
if (!loaded.success) {
|
|
892
|
-
const
|
|
911
|
+
const failure2 = loaded;
|
|
893
912
|
return {
|
|
894
913
|
success: false,
|
|
895
|
-
diagnostics:
|
|
914
|
+
diagnostics: failure2.issues.map(issueToDiagnostic2),
|
|
896
915
|
path,
|
|
897
916
|
uri
|
|
898
917
|
};
|
|
899
918
|
}
|
|
900
919
|
const validated = validateCritiqConfig(loaded.data);
|
|
901
920
|
if (!validated.success) {
|
|
902
|
-
const
|
|
921
|
+
const failure2 = validated;
|
|
903
922
|
return {
|
|
904
923
|
success: false,
|
|
905
|
-
diagnostics:
|
|
924
|
+
diagnostics: failure2.diagnostics,
|
|
906
925
|
path,
|
|
907
926
|
uri
|
|
908
927
|
};
|
|
@@ -1721,10 +1740,10 @@ function buildFinding(rule, analyzedFile, match, options = {}) {
|
|
|
1721
1740
|
};
|
|
1722
1741
|
const validation = validateFinding(finding);
|
|
1723
1742
|
if (!validation.success) {
|
|
1724
|
-
const
|
|
1743
|
+
const failure2 = validation;
|
|
1725
1744
|
return {
|
|
1726
1745
|
success: false,
|
|
1727
|
-
issues: toFindingIssues(
|
|
1746
|
+
issues: toFindingIssues(failure2.issues)
|
|
1728
1747
|
};
|
|
1729
1748
|
}
|
|
1730
1749
|
return {
|
|
@@ -1734,8 +1753,8 @@ function buildFinding(rule, analyzedFile, match, options = {}) {
|
|
|
1734
1753
|
}
|
|
1735
1754
|
|
|
1736
1755
|
// libs/runtime/check-runner/src/lib/check-runner/runtime.ts
|
|
1737
|
-
var
|
|
1738
|
-
var
|
|
1756
|
+
var import_node_fs9 = require("node:fs");
|
|
1757
|
+
var import_node_path12 = require("node:path");
|
|
1739
1758
|
|
|
1740
1759
|
// libs/runtime/check-runner/src/lib/check-runner/findings.ts
|
|
1741
1760
|
function applySeverityOverride(finding, severityOverride) {
|
|
@@ -3220,6 +3239,9 @@ var RUBY_RAILS_SECURITY_FACT_KINDS = {
|
|
|
3220
3239
|
csrfDisabled: "ruby.security.rails-csrf-disabled",
|
|
3221
3240
|
openRedirect: "ruby.security.rails-open-redirect",
|
|
3222
3241
|
unsafeHtmlOutput: "ruby.security.rails-unsafe-html-output",
|
|
3242
|
+
plaintextPasswordInCallback: "ruby.security.plaintext-password-in-callback",
|
|
3243
|
+
linkToBlankWithoutNoopener: "ruby.security.rails-link-to-blank-without-noopener",
|
|
3244
|
+
railsOutputUnsafe: "ruby.security.rails-output-unsafe",
|
|
3223
3245
|
sidekiqWebUnauthenticatedMount: "ruby.security.sidekiq-web-unauthenticated-mount",
|
|
3224
3246
|
unsafeRender: "ruby.security.rails-unsafe-render",
|
|
3225
3247
|
detailedExceptionsEnabled: "ruby.security.rails-detailed-exceptions-enabled",
|
|
@@ -3274,7 +3296,10 @@ function collectRubyRailsSecurityFacts(options) {
|
|
|
3274
3296
|
...collectSidekiqMountFacts(text, detector),
|
|
3275
3297
|
...collectUnsafeRenderFacts(text, detector, state, matchesTainted),
|
|
3276
3298
|
...collectDetailedExceptionsFacts(text, detector, path),
|
|
3277
|
-
...collectUnsafeSessionCookieFacts(text, detector)
|
|
3299
|
+
...collectUnsafeSessionCookieFacts(text, detector),
|
|
3300
|
+
...collectPlaintextPasswordInCallbackFacts(text, detector),
|
|
3301
|
+
...collectLinkToBlankWithoutNoopenerFacts(text, detector),
|
|
3302
|
+
...collectRailsOutputUnsafeFacts(text, path, detector)
|
|
3278
3303
|
];
|
|
3279
3304
|
}
|
|
3280
3305
|
function collectUnsafeStrongParametersFacts(text, detector) {
|
|
@@ -3551,6 +3576,89 @@ function collectDetailedExceptionsFacts(text, detector, path) {
|
|
|
3551
3576
|
})
|
|
3552
3577
|
);
|
|
3553
3578
|
}
|
|
3579
|
+
var RAILS_HASH_CALL_WINDOW = 420;
|
|
3580
|
+
function sliceRailsHashCallWindow(text, startOffset, endOffset) {
|
|
3581
|
+
return text.slice(
|
|
3582
|
+
startOffset,
|
|
3583
|
+
Math.min(text.length, endOffset + RAILS_HASH_CALL_WINDOW)
|
|
3584
|
+
);
|
|
3585
|
+
}
|
|
3586
|
+
function collectPlaintextPasswordInCallbackFacts(text, detector) {
|
|
3587
|
+
const kind = RUBY_RAILS_SECURITY_FACT_KINDS.plaintextPasswordInCallback;
|
|
3588
|
+
return collectMatchedFacts({
|
|
3589
|
+
text,
|
|
3590
|
+
detector,
|
|
3591
|
+
kind,
|
|
3592
|
+
appliesTo: "block",
|
|
3593
|
+
pattern: /\bhttp_basic_authenticate_with\b/g,
|
|
3594
|
+
predicate: (match) => {
|
|
3595
|
+
const window = sliceRailsHashCallWindow(
|
|
3596
|
+
text,
|
|
3597
|
+
match.startOffset,
|
|
3598
|
+
match.endOffset
|
|
3599
|
+
);
|
|
3600
|
+
if (!/password:\s*["']/u.test(window)) {
|
|
3601
|
+
return false;
|
|
3602
|
+
}
|
|
3603
|
+
if (/password:\s*ENV(?:\.fetch)?\b/u.test(window)) {
|
|
3604
|
+
return false;
|
|
3605
|
+
}
|
|
3606
|
+
if (/password:\s*Rails\.application\.credentials/u.test(window)) {
|
|
3607
|
+
return false;
|
|
3608
|
+
}
|
|
3609
|
+
return true;
|
|
3610
|
+
}
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
function collectLinkToBlankWithoutNoopenerFacts(text, detector) {
|
|
3614
|
+
const kind = RUBY_RAILS_SECURITY_FACT_KINDS.linkToBlankWithoutNoopener;
|
|
3615
|
+
return collectMatchedFacts({
|
|
3616
|
+
text,
|
|
3617
|
+
detector,
|
|
3618
|
+
kind,
|
|
3619
|
+
appliesTo: "block",
|
|
3620
|
+
pattern: /\blink_to\b/g,
|
|
3621
|
+
predicate: (match) => {
|
|
3622
|
+
const window = sliceRailsHashCallWindow(
|
|
3623
|
+
text,
|
|
3624
|
+
match.startOffset,
|
|
3625
|
+
match.endOffset
|
|
3626
|
+
);
|
|
3627
|
+
if (!/target:\s*['"]_blank['"]/u.test(window)) {
|
|
3628
|
+
return false;
|
|
3629
|
+
}
|
|
3630
|
+
return !/rel:\s*['"][^'"]*(?:noopener|noreferrer)/u.test(window);
|
|
3631
|
+
}
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
function collectRailsOutputUnsafeFacts(text, path, detector) {
|
|
3635
|
+
if (isTestLikeSourcePath(path)) {
|
|
3636
|
+
return [];
|
|
3637
|
+
}
|
|
3638
|
+
const kind = RUBY_RAILS_SECURITY_FACT_KINDS.railsOutputUnsafe;
|
|
3639
|
+
return collectMatchedFacts({
|
|
3640
|
+
text,
|
|
3641
|
+
detector,
|
|
3642
|
+
kind,
|
|
3643
|
+
appliesTo: "block",
|
|
3644
|
+
pattern: /\.html_safe\b/g
|
|
3645
|
+
}).concat(
|
|
3646
|
+
collectMatchedFacts({
|
|
3647
|
+
text,
|
|
3648
|
+
detector,
|
|
3649
|
+
kind,
|
|
3650
|
+
appliesTo: "block",
|
|
3651
|
+
pattern: /\braw\s*\(/g
|
|
3652
|
+
}),
|
|
3653
|
+
collectMatchedFacts({
|
|
3654
|
+
text,
|
|
3655
|
+
detector,
|
|
3656
|
+
kind,
|
|
3657
|
+
appliesTo: "block",
|
|
3658
|
+
pattern: /\.safe_concat\s*\(/g
|
|
3659
|
+
})
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3554
3662
|
function collectUnsafeSessionCookieFacts(text, detector) {
|
|
3555
3663
|
const kind = RUBY_RAILS_SECURITY_FACT_KINDS.unsafeSessionOrCookieStore;
|
|
3556
3664
|
return collectMatchedFacts({
|
|
@@ -3584,6 +3692,234 @@ function collectRubySensitiveDataEgressFacts(options) {
|
|
|
3584
3692
|
});
|
|
3585
3693
|
}
|
|
3586
3694
|
|
|
3695
|
+
// libs/adapters/shared/src/lib/polyglot/domains/ruby-general-security.ts
|
|
3696
|
+
var RUBY_GENERAL_SECURITY_FACT_KINDS = {
|
|
3697
|
+
dynamicCodeExecution: "ruby.security.dynamic-code-execution",
|
|
3698
|
+
kernelOpen: "ruby.security.kernel-open",
|
|
3699
|
+
insecureJsonLoad: "ruby.security.insecure-json-load",
|
|
3700
|
+
debuggerCall: "ruby.security.debugger-call"
|
|
3701
|
+
};
|
|
3702
|
+
function collectRubyGeneralSecurityFacts(options) {
|
|
3703
|
+
const { text, path, detector } = options;
|
|
3704
|
+
return [
|
|
3705
|
+
...collectDynamicCodeExecutionFacts2(text, detector),
|
|
3706
|
+
...collectKernelOpenFacts(text, detector),
|
|
3707
|
+
...collectInsecureJsonLoadFacts(text, detector),
|
|
3708
|
+
...collectDebuggerCallFacts(text, path, detector)
|
|
3709
|
+
];
|
|
3710
|
+
}
|
|
3711
|
+
function collectDynamicCodeExecutionFacts2(text, detector) {
|
|
3712
|
+
return collectMatchedFacts({
|
|
3713
|
+
text,
|
|
3714
|
+
detector,
|
|
3715
|
+
kind: RUBY_GENERAL_SECURITY_FACT_KINDS.dynamicCodeExecution,
|
|
3716
|
+
appliesTo: "block",
|
|
3717
|
+
pattern: /\b(?:eval|exec|binding\.eval|class_eval|module_eval|instance_eval)\s*[({]/g
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
function collectKernelOpenFacts(text, detector) {
|
|
3721
|
+
return collectMatchedFacts({
|
|
3722
|
+
text,
|
|
3723
|
+
detector,
|
|
3724
|
+
kind: RUBY_GENERAL_SECURITY_FACT_KINDS.kernelOpen,
|
|
3725
|
+
appliesTo: "block",
|
|
3726
|
+
pattern: /\b(?:Kernel\.)?open\s*\(\s*["'][|]/g
|
|
3727
|
+
});
|
|
3728
|
+
}
|
|
3729
|
+
function collectInsecureJsonLoadFacts(text, detector) {
|
|
3730
|
+
return collectMatchedFacts({
|
|
3731
|
+
text,
|
|
3732
|
+
detector,
|
|
3733
|
+
kind: RUBY_GENERAL_SECURITY_FACT_KINDS.insecureJsonLoad,
|
|
3734
|
+
appliesTo: "block",
|
|
3735
|
+
pattern: /\b(?:JSON\.(?:load|restore)|Oj\.load|MultiJson\.load)\s*\(/g
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3738
|
+
function collectDebuggerCallFacts(text, path, detector) {
|
|
3739
|
+
if (isTestLikeSourcePath(path)) {
|
|
3740
|
+
return [];
|
|
3741
|
+
}
|
|
3742
|
+
return collectMatchedFacts({
|
|
3743
|
+
text,
|
|
3744
|
+
detector,
|
|
3745
|
+
kind: RUBY_GENERAL_SECURITY_FACT_KINDS.debuggerCall,
|
|
3746
|
+
appliesTo: "block",
|
|
3747
|
+
pattern: /\b(?:debugger|byebug|binding\.break)\b/g
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
// libs/adapters/shared/src/lib/polyglot/domains/ruby-bug-risk.ts
|
|
3752
|
+
var RUBY_BUG_RISK_FACT_KINDS = {
|
|
3753
|
+
exceptionClassOverwritten: "ruby.bug-risk.exception-class-overwritten",
|
|
3754
|
+
rawSqlWithoutSquish: "ruby.bug-risk.raw-sql-without-squish",
|
|
3755
|
+
divisionByZero: "ruby.bug-risk.division-by-zero",
|
|
3756
|
+
assignmentInCondition: "ruby.bug-risk.assignment-in-condition",
|
|
3757
|
+
duplicateHashKeys: "ruby.bug-risk.duplicate-hash-keys",
|
|
3758
|
+
deprecatedUriEscape: "ruby.bug-risk.deprecated-uri-escape"
|
|
3759
|
+
};
|
|
3760
|
+
var RESCUE_EXCEPTION_CLASS_NAMES = "StandardError|Exception|RuntimeError|ArgumentError|NameError|TypeError|NoMethodError|IOError|IndexError|RangeError|RegexpError|SyntaxError|LoadError|ZeroDivisionError|NotImplementedError|ScriptError|SecurityError|SystemCallError|SystemStackError|ThreadError|FrozenError|LocalJumpError";
|
|
3761
|
+
function collectRubyBugRiskFacts(options) {
|
|
3762
|
+
const { text, detector } = options;
|
|
3763
|
+
return dedupeFacts([
|
|
3764
|
+
...collectExceptionClassOverwrittenFacts(text, detector),
|
|
3765
|
+
...collectRawSqlWithoutSquishFacts(text, detector),
|
|
3766
|
+
...collectDivisionByZeroFacts(text, detector),
|
|
3767
|
+
...collectAssignmentInConditionFacts(text, detector),
|
|
3768
|
+
...collectDuplicateHashKeyFacts(text, detector),
|
|
3769
|
+
...collectDeprecatedUriEscapeFacts(text, detector)
|
|
3770
|
+
]);
|
|
3771
|
+
}
|
|
3772
|
+
function collectExceptionClassOverwrittenFacts(text, detector) {
|
|
3773
|
+
return collectMatchedFacts({
|
|
3774
|
+
text,
|
|
3775
|
+
detector,
|
|
3776
|
+
kind: RUBY_BUG_RISK_FACT_KINDS.exceptionClassOverwritten,
|
|
3777
|
+
appliesTo: "block",
|
|
3778
|
+
pattern: new RegExp(
|
|
3779
|
+
`\\brescue\\s*=>\\s*(?:${RESCUE_EXCEPTION_CLASS_NAMES})\\b`,
|
|
3780
|
+
"g"
|
|
3781
|
+
)
|
|
3782
|
+
});
|
|
3783
|
+
}
|
|
3784
|
+
function collectRawSqlWithoutSquishFacts(text, detector) {
|
|
3785
|
+
const kind = RUBY_BUG_RISK_FACT_KINDS.rawSqlWithoutSquish;
|
|
3786
|
+
const callPatterns = [/\bwhere\s*\(/g, /\bfind_by_sql\s*\(/g];
|
|
3787
|
+
return callPatterns.flatMap(
|
|
3788
|
+
(pattern) => collectSnippetFacts({
|
|
3789
|
+
text,
|
|
3790
|
+
detector,
|
|
3791
|
+
kind,
|
|
3792
|
+
appliesTo: "block",
|
|
3793
|
+
pattern,
|
|
3794
|
+
state: void 0,
|
|
3795
|
+
predicate: (snippet) => {
|
|
3796
|
+
if (!/<<-?~?\w*/u.test(snippet.text)) {
|
|
3797
|
+
return false;
|
|
3798
|
+
}
|
|
3799
|
+
return !/\.squish\b/u.test(snippet.text);
|
|
3800
|
+
}
|
|
3801
|
+
})
|
|
3802
|
+
);
|
|
3803
|
+
}
|
|
3804
|
+
function collectDivisionByZeroFacts(text, detector) {
|
|
3805
|
+
const kind = RUBY_BUG_RISK_FACT_KINDS.divisionByZero;
|
|
3806
|
+
const facts = [];
|
|
3807
|
+
const lines = text.split("\n");
|
|
3808
|
+
let offset = 0;
|
|
3809
|
+
for (const line of lines) {
|
|
3810
|
+
const stripped = stripHashLineComment(line);
|
|
3811
|
+
const pattern = /\/\s*0(?:\.0+)?\b/g;
|
|
3812
|
+
for (const match of findAllMatches(stripped, pattern)) {
|
|
3813
|
+
facts.push(
|
|
3814
|
+
createOffsetFact(text, {
|
|
3815
|
+
detector,
|
|
3816
|
+
appliesTo: "block",
|
|
3817
|
+
kind,
|
|
3818
|
+
startOffset: offset + match.startOffset,
|
|
3819
|
+
endOffset: offset + match.endOffset,
|
|
3820
|
+
text: match.matchedText
|
|
3821
|
+
})
|
|
3822
|
+
);
|
|
3823
|
+
}
|
|
3824
|
+
offset += line.length + 1;
|
|
3825
|
+
}
|
|
3826
|
+
return facts;
|
|
3827
|
+
}
|
|
3828
|
+
function collectAssignmentInConditionFacts(text, detector) {
|
|
3829
|
+
const kind = RUBY_BUG_RISK_FACT_KINDS.assignmentInCondition;
|
|
3830
|
+
return collectMatchedFacts({
|
|
3831
|
+
text,
|
|
3832
|
+
detector,
|
|
3833
|
+
kind,
|
|
3834
|
+
appliesTo: "block",
|
|
3835
|
+
pattern: /\b(?:if|unless|while|until)\s+(?:\([^)]*\s*)?(\w+)\s*=(?!=)(?![=>])/g
|
|
3836
|
+
});
|
|
3837
|
+
}
|
|
3838
|
+
function collectDeprecatedUriEscapeFacts(text, detector) {
|
|
3839
|
+
return collectMatchedFacts({
|
|
3840
|
+
text,
|
|
3841
|
+
detector,
|
|
3842
|
+
kind: RUBY_BUG_RISK_FACT_KINDS.deprecatedUriEscape,
|
|
3843
|
+
appliesTo: "block",
|
|
3844
|
+
pattern: /\bURI\.(?:escape|unescape|encode|decode)\s*\(/g
|
|
3845
|
+
});
|
|
3846
|
+
}
|
|
3847
|
+
function collectDuplicateHashKeyFacts(text, detector) {
|
|
3848
|
+
const kind = RUBY_BUG_RISK_FACT_KINDS.duplicateHashKeys;
|
|
3849
|
+
const findings = [];
|
|
3850
|
+
for (const literal of collectRubyHashLiteralRanges(text)) {
|
|
3851
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3852
|
+
for (const key of extractRubyHashKeys(literal.content)) {
|
|
3853
|
+
if (seen.has(key.normalized)) {
|
|
3854
|
+
findings.push(
|
|
3855
|
+
createOffsetFact(text, {
|
|
3856
|
+
detector,
|
|
3857
|
+
appliesTo: "block",
|
|
3858
|
+
kind,
|
|
3859
|
+
startOffset: literal.startOffset + key.startOffset,
|
|
3860
|
+
endOffset: literal.startOffset + key.endOffset,
|
|
3861
|
+
text: key.raw
|
|
3862
|
+
})
|
|
3863
|
+
);
|
|
3864
|
+
continue;
|
|
3865
|
+
}
|
|
3866
|
+
seen.add(key.normalized);
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
return findings;
|
|
3870
|
+
}
|
|
3871
|
+
function collectRubyHashLiteralRanges(text) {
|
|
3872
|
+
const ranges = [];
|
|
3873
|
+
const stack = [];
|
|
3874
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
3875
|
+
const char = text[index];
|
|
3876
|
+
if (char === "{") {
|
|
3877
|
+
stack.push(index);
|
|
3878
|
+
continue;
|
|
3879
|
+
}
|
|
3880
|
+
if (char !== "}" || stack.length === 0) {
|
|
3881
|
+
continue;
|
|
3882
|
+
}
|
|
3883
|
+
const start = stack.pop();
|
|
3884
|
+
if (start === void 0) {
|
|
3885
|
+
continue;
|
|
3886
|
+
}
|
|
3887
|
+
const content = text.slice(start + 1, index);
|
|
3888
|
+
if (!looksLikeRubyHashLiteral(content)) {
|
|
3889
|
+
continue;
|
|
3890
|
+
}
|
|
3891
|
+
ranges.push({
|
|
3892
|
+
startOffset: start,
|
|
3893
|
+
endOffset: index + 1,
|
|
3894
|
+
content
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
return ranges;
|
|
3898
|
+
}
|
|
3899
|
+
function looksLikeRubyHashLiteral(content) {
|
|
3900
|
+
return /:\w+\s*=>|["'][\w-]+["']\s*=>|\b\w+\s*:/u.test(content);
|
|
3901
|
+
}
|
|
3902
|
+
function extractRubyHashKeys(hashContent) {
|
|
3903
|
+
const keys = [];
|
|
3904
|
+
for (const match of hashContent.matchAll(
|
|
3905
|
+
/(?:(["'])([^"']+)\1\s*=>)|(?::(\w+)\s*=>)|(?:\b(\w+)\s*:)/gu
|
|
3906
|
+
)) {
|
|
3907
|
+
const symbol = match[2] ?? match[3] ?? match[4];
|
|
3908
|
+
if (!symbol) {
|
|
3909
|
+
continue;
|
|
3910
|
+
}
|
|
3911
|
+
const raw = match[0];
|
|
3912
|
+
const startOffset = match.index ?? 0;
|
|
3913
|
+
keys.push({
|
|
3914
|
+
raw,
|
|
3915
|
+
normalized: symbol.toLowerCase(),
|
|
3916
|
+
startOffset,
|
|
3917
|
+
endOffset: startOffset + raw.length
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
return keys;
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3587
3923
|
// libs/adapters/shared/src/lib/polyglot/domains/php-framework-security.ts
|
|
3588
3924
|
var PHP_FRAMEWORK_SECURITY_FACT_KINDS = {
|
|
3589
3925
|
laravelUnsafeMassAssignment: "php.security.laravel-unsafe-mass-assignment",
|
|
@@ -3870,8 +4206,60 @@ var PHP_CORRECTNESS_FACT_KINDS = {
|
|
|
3870
4206
|
switchMultipleDefault: "php.correctness.switch-multiple-default",
|
|
3871
4207
|
errorSuppressionOperator: "php.correctness.error-suppression-operator",
|
|
3872
4208
|
unreachableAfterReturn: "php.correctness.unreachable-after-return",
|
|
3873
|
-
nullsafeReturnedByReference: "php.correctness.nullsafe-returned-by-reference"
|
|
4209
|
+
nullsafeReturnedByReference: "php.correctness.nullsafe-returned-by-reference",
|
|
4210
|
+
emptyArrayLiteralSlot: "php.correctness.empty-array-literal-slot",
|
|
4211
|
+
emptyBracketArrayAccess: "php.correctness.empty-bracket-array-access",
|
|
4212
|
+
deprecatedUnsetCast: "php.correctness.deprecated-unset-cast",
|
|
4213
|
+
duplicateDeclaration: "php.correctness.duplicate-declaration",
|
|
4214
|
+
nestedFunctionDeclaration: "php.correctness.nested-function-declaration",
|
|
4215
|
+
breakContinueOutsideLoop: "php.correctness.break-continue-outside-loop",
|
|
4216
|
+
abstractMethodOutsideAbstractClass: "php.correctness.abstract-method-outside-abstract-class",
|
|
4217
|
+
uselessUnset: "php.correctness.useless-unset",
|
|
4218
|
+
invalidRegexLiteral: "php.correctness.invalid-regex-literal",
|
|
4219
|
+
todoFixmeMarker: "php.correctness.todo-fixme-marker",
|
|
4220
|
+
selfAssignment: "php.correctness.self-assignment",
|
|
4221
|
+
defaultParameterNotLast: "php.correctness.default-parameter-not-last",
|
|
4222
|
+
emptyFunctionBody: "php.correctness.empty-function-body",
|
|
4223
|
+
unknownMagicMethod: "php.correctness.unknown-magic-method",
|
|
4224
|
+
caseInsensitiveDefine: "php.correctness.case-insensitive-define",
|
|
4225
|
+
deprecatedFilterConstant: "php.correctness.deprecated-filter-constant",
|
|
4226
|
+
emptyCodeBlock: "php.correctness.empty-code-block",
|
|
4227
|
+
deprecatedLibxmlEntityLoader: "php.correctness.deprecated-libxml-entity-loader",
|
|
4228
|
+
redundantStringCastConcat: "php.correctness.redundant-string-cast-concat",
|
|
4229
|
+
missingMemberVisibility: "php.correctness.missing-member-visibility",
|
|
4230
|
+
functionComparison: "php.correctness.function-comparison",
|
|
4231
|
+
uselessPostIncrement: "php.correctness.useless-post-increment",
|
|
4232
|
+
nestedSwitch: "php.correctness.nested-switch",
|
|
4233
|
+
invalidCookieOptions: "php.correctness.invalid-cookie-options"
|
|
3874
4234
|
};
|
|
4235
|
+
var PHP_VALID_MAGIC_METHODS = /* @__PURE__ */ new Set([
|
|
4236
|
+
"__construct",
|
|
4237
|
+
"__destruct",
|
|
4238
|
+
"__call",
|
|
4239
|
+
"__callStatic",
|
|
4240
|
+
"__get",
|
|
4241
|
+
"__set",
|
|
4242
|
+
"__isset",
|
|
4243
|
+
"__unset",
|
|
4244
|
+
"__sleep",
|
|
4245
|
+
"__wakeup",
|
|
4246
|
+
"__serialize",
|
|
4247
|
+
"__unserialize",
|
|
4248
|
+
"__toString",
|
|
4249
|
+
"__invoke",
|
|
4250
|
+
"__set_state",
|
|
4251
|
+
"__clone",
|
|
4252
|
+
"__debugInfo"
|
|
4253
|
+
]);
|
|
4254
|
+
var PHP_VALID_COOKIE_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
4255
|
+
"expires",
|
|
4256
|
+
"path",
|
|
4257
|
+
"domain",
|
|
4258
|
+
"secure",
|
|
4259
|
+
"httponly",
|
|
4260
|
+
"samesite"
|
|
4261
|
+
]);
|
|
4262
|
+
var PHP_DEPRECATED_FILTER_CONSTANTS = /\bFILTER_(?:SANITIZE_STRING|SANITIZE_MAGIC_QUOTES|FLAG_ALLOW_THOUSAND)\b/gu;
|
|
3875
4263
|
function collectPhpCorrectnessFacts(options) {
|
|
3876
4264
|
const { text, detector } = options;
|
|
3877
4265
|
return dedupeFacts([
|
|
@@ -3879,7 +4267,31 @@ function collectPhpCorrectnessFacts(options) {
|
|
|
3879
4267
|
...collectSwitchMultipleDefaultFacts(text, detector),
|
|
3880
4268
|
...collectErrorSuppressionOperatorFacts(text, detector),
|
|
3881
4269
|
...collectUnreachableAfterReturnFacts(text, detector),
|
|
3882
|
-
...collectNullsafeReturnedByReferenceFacts(text, detector)
|
|
4270
|
+
...collectNullsafeReturnedByReferenceFacts(text, detector),
|
|
4271
|
+
...collectEmptyArrayLiteralSlotFacts(text, detector),
|
|
4272
|
+
...collectEmptyBracketArrayAccessFacts(text, detector),
|
|
4273
|
+
...collectDeprecatedUnsetCastFacts(text, detector),
|
|
4274
|
+
...collectDuplicateDeclarationFacts(text, detector),
|
|
4275
|
+
...collectNestedFunctionDeclarationFacts(text, detector),
|
|
4276
|
+
...collectBreakContinueOutsideLoopFacts(text, detector),
|
|
4277
|
+
...collectAbstractMethodOutsideAbstractClassFacts(text, detector),
|
|
4278
|
+
...collectUselessUnsetFacts(text, detector),
|
|
4279
|
+
...collectInvalidRegexLiteralFacts(text, detector),
|
|
4280
|
+
...collectTodoFixmeMarkerFacts(text, detector),
|
|
4281
|
+
...collectSelfAssignmentFacts(text, detector),
|
|
4282
|
+
...collectDefaultParameterNotLastFacts(text, detector),
|
|
4283
|
+
...collectEmptyFunctionBodyFacts(text, detector),
|
|
4284
|
+
...collectUnknownMagicMethodFacts(text, detector),
|
|
4285
|
+
...collectCaseInsensitiveDefineFacts(text, detector),
|
|
4286
|
+
...collectDeprecatedFilterConstantFacts(text, detector),
|
|
4287
|
+
...collectEmptyCodeBlockFacts(text, detector),
|
|
4288
|
+
...collectDeprecatedLibxmlEntityLoaderFacts(text, detector),
|
|
4289
|
+
...collectRedundantStringCastConcatFacts(text, detector),
|
|
4290
|
+
...collectMissingMemberVisibilityFacts(text, detector),
|
|
4291
|
+
...collectFunctionComparisonFacts(text, detector),
|
|
4292
|
+
...collectUselessPostIncrementFacts(text, detector),
|
|
4293
|
+
...collectNestedSwitchFacts(text, detector),
|
|
4294
|
+
...collectInvalidCookieOptionsFacts(text, detector)
|
|
3883
4295
|
]);
|
|
3884
4296
|
}
|
|
3885
4297
|
function collectDuplicateArrayKeyFacts(text, detector) {
|
|
@@ -4259,6 +4671,771 @@ function collectNullsafeReturnedByReferenceFacts(text, detector) {
|
|
|
4259
4671
|
pattern: /\b(?:static\s+)?fn\s*&\s*\([^)]*\)\s*=>\s*[^;{}\n]*\?->/gu
|
|
4260
4672
|
});
|
|
4261
4673
|
}
|
|
4674
|
+
function collectEmptyArrayLiteralSlotFacts(text, detector) {
|
|
4675
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.emptyArrayLiteralSlot;
|
|
4676
|
+
const findings = [];
|
|
4677
|
+
for (const literal of collectAllPhpArrayLiteralRanges(text)) {
|
|
4678
|
+
for (const match of findAllMatches(literal.content, /,\s*,|\[\s*,/gu)) {
|
|
4679
|
+
findings.push(
|
|
4680
|
+
createOffsetFact(text, {
|
|
4681
|
+
detector,
|
|
4682
|
+
appliesTo: "block",
|
|
4683
|
+
kind,
|
|
4684
|
+
startOffset: literal.startOffset + 1 + match.startOffset,
|
|
4685
|
+
endOffset: literal.startOffset + 1 + match.endOffset,
|
|
4686
|
+
text: match.matchedText.trim() || ","
|
|
4687
|
+
})
|
|
4688
|
+
);
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
return dedupeFacts(findings);
|
|
4692
|
+
}
|
|
4693
|
+
function collectAllPhpArrayLiteralRanges(text) {
|
|
4694
|
+
const ranges = [];
|
|
4695
|
+
const arrayCallPattern = /\barray\s*\(/gu;
|
|
4696
|
+
for (const match of findAllMatches(text, arrayCallPattern)) {
|
|
4697
|
+
const openParen = match.endOffset - 1;
|
|
4698
|
+
const closeParen = findMatchingDelimiter(text, openParen, "(", ")");
|
|
4699
|
+
if (closeParen < 0) {
|
|
4700
|
+
continue;
|
|
4701
|
+
}
|
|
4702
|
+
ranges.push({
|
|
4703
|
+
startOffset: match.startOffset,
|
|
4704
|
+
endOffset: closeParen + 1,
|
|
4705
|
+
content: text.slice(openParen + 1, closeParen)
|
|
4706
|
+
});
|
|
4707
|
+
}
|
|
4708
|
+
const stack = [];
|
|
4709
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
4710
|
+
const char = text[index];
|
|
4711
|
+
if (char === "[") {
|
|
4712
|
+
stack.push(index);
|
|
4713
|
+
continue;
|
|
4714
|
+
}
|
|
4715
|
+
if (char !== "]" || stack.length === 0) {
|
|
4716
|
+
continue;
|
|
4717
|
+
}
|
|
4718
|
+
const start = stack.pop();
|
|
4719
|
+
if (start === void 0) {
|
|
4720
|
+
continue;
|
|
4721
|
+
}
|
|
4722
|
+
ranges.push({
|
|
4723
|
+
startOffset: start,
|
|
4724
|
+
endOffset: index + 1,
|
|
4725
|
+
content: text.slice(start + 1, index)
|
|
4726
|
+
});
|
|
4727
|
+
}
|
|
4728
|
+
return ranges;
|
|
4729
|
+
}
|
|
4730
|
+
function collectEmptyBracketArrayAccessFacts(text, detector) {
|
|
4731
|
+
return collectMatchedFacts({
|
|
4732
|
+
text,
|
|
4733
|
+
detector,
|
|
4734
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.emptyBracketArrayAccess,
|
|
4735
|
+
appliesTo: "block",
|
|
4736
|
+
pattern: /\$\w+\[\s*\](?!\s*=)/gu
|
|
4737
|
+
});
|
|
4738
|
+
}
|
|
4739
|
+
function collectDeprecatedUnsetCastFacts(text, detector) {
|
|
4740
|
+
return collectMatchedFacts({
|
|
4741
|
+
text,
|
|
4742
|
+
detector,
|
|
4743
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.deprecatedUnsetCast,
|
|
4744
|
+
appliesTo: "block",
|
|
4745
|
+
pattern: /\(unset\)/gu
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
function collectDuplicateDeclarationFacts(text, detector) {
|
|
4749
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.duplicateDeclaration;
|
|
4750
|
+
const findings = [];
|
|
4751
|
+
const seenFunctions = /* @__PURE__ */ new Map();
|
|
4752
|
+
const seenClasses = /* @__PURE__ */ new Map();
|
|
4753
|
+
for (const match of findAllMatches(
|
|
4754
|
+
text,
|
|
4755
|
+
/\bfunction\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/gu
|
|
4756
|
+
)) {
|
|
4757
|
+
const name = /function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/u.exec(
|
|
4758
|
+
match.matchedText
|
|
4759
|
+
)?.[1];
|
|
4760
|
+
if (!name || !isTopLevelDeclaration(text, match.startOffset)) {
|
|
4761
|
+
continue;
|
|
4762
|
+
}
|
|
4763
|
+
if (seenFunctions.has(name)) {
|
|
4764
|
+
findings.push(
|
|
4765
|
+
createOffsetFact(text, {
|
|
4766
|
+
detector,
|
|
4767
|
+
appliesTo: "block",
|
|
4768
|
+
kind,
|
|
4769
|
+
startOffset: match.startOffset,
|
|
4770
|
+
endOffset: match.endOffset,
|
|
4771
|
+
text: match.matchedText
|
|
4772
|
+
})
|
|
4773
|
+
);
|
|
4774
|
+
continue;
|
|
4775
|
+
}
|
|
4776
|
+
seenFunctions.set(name, match.startOffset);
|
|
4777
|
+
}
|
|
4778
|
+
for (const match of findAllMatches(
|
|
4779
|
+
text,
|
|
4780
|
+
/\b(?:class|interface|trait|enum)\s+([A-Za-z_][A-Za-z0-9_]*)\b/gu
|
|
4781
|
+
)) {
|
|
4782
|
+
const name = /(?:class|interface|trait|enum)\s+([A-Za-z_][A-Za-z0-9_]*)\b/u.exec(
|
|
4783
|
+
match.matchedText
|
|
4784
|
+
)?.[1];
|
|
4785
|
+
if (!name || !isTopLevelDeclaration(text, match.startOffset)) {
|
|
4786
|
+
continue;
|
|
4787
|
+
}
|
|
4788
|
+
if (seenClasses.has(name)) {
|
|
4789
|
+
findings.push(
|
|
4790
|
+
createOffsetFact(text, {
|
|
4791
|
+
detector,
|
|
4792
|
+
appliesTo: "block",
|
|
4793
|
+
kind,
|
|
4794
|
+
startOffset: match.startOffset,
|
|
4795
|
+
endOffset: match.endOffset,
|
|
4796
|
+
text: match.matchedText
|
|
4797
|
+
})
|
|
4798
|
+
);
|
|
4799
|
+
continue;
|
|
4800
|
+
}
|
|
4801
|
+
seenClasses.set(name, match.startOffset);
|
|
4802
|
+
}
|
|
4803
|
+
return findings;
|
|
4804
|
+
}
|
|
4805
|
+
function isTopLevelDeclaration(text, offset) {
|
|
4806
|
+
let braceDepth = 0;
|
|
4807
|
+
for (let index = 0; index < offset; index += 1) {
|
|
4808
|
+
const char = text[index];
|
|
4809
|
+
if (char === "{") {
|
|
4810
|
+
braceDepth += 1;
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
if (char === "}") {
|
|
4814
|
+
braceDepth = Math.max(0, braceDepth - 1);
|
|
4815
|
+
}
|
|
4816
|
+
}
|
|
4817
|
+
return braceDepth === 0;
|
|
4818
|
+
}
|
|
4819
|
+
function collectNestedFunctionDeclarationFacts(text, detector) {
|
|
4820
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.nestedFunctionDeclaration;
|
|
4821
|
+
const findings = [];
|
|
4822
|
+
for (const match of findAllMatches(
|
|
4823
|
+
text,
|
|
4824
|
+
/\bfunction\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/gu
|
|
4825
|
+
)) {
|
|
4826
|
+
if (!isInsideFunctionBody(text, match.startOffset)) {
|
|
4827
|
+
continue;
|
|
4828
|
+
}
|
|
4829
|
+
findings.push(
|
|
4830
|
+
createOffsetFact(text, {
|
|
4831
|
+
detector,
|
|
4832
|
+
appliesTo: "block",
|
|
4833
|
+
kind,
|
|
4834
|
+
startOffset: match.startOffset,
|
|
4835
|
+
endOffset: match.endOffset,
|
|
4836
|
+
text: match.matchedText
|
|
4837
|
+
})
|
|
4838
|
+
);
|
|
4839
|
+
}
|
|
4840
|
+
return findings;
|
|
4841
|
+
}
|
|
4842
|
+
function isInsideFunctionBody(text, offset) {
|
|
4843
|
+
const stack = [];
|
|
4844
|
+
let index = 0;
|
|
4845
|
+
while (index < offset) {
|
|
4846
|
+
index = skipPhpTrivia(text, index);
|
|
4847
|
+
if (index >= offset) {
|
|
4848
|
+
break;
|
|
4849
|
+
}
|
|
4850
|
+
const remaining = text.slice(index);
|
|
4851
|
+
if (/^\bfunction\s+[A-Za-z_][\w]*\s*\(/u.test(remaining)) {
|
|
4852
|
+
const openBrace = findNextControlOpenBrace(text, index);
|
|
4853
|
+
if (openBrace >= 0 && openBrace < offset) {
|
|
4854
|
+
stack.push("function");
|
|
4855
|
+
index = openBrace + 1;
|
|
4856
|
+
continue;
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
if (/^\b(?:class|trait|enum)\s+[A-Za-z_]/u.test(remaining)) {
|
|
4860
|
+
const openBrace = findNextControlOpenBrace(text, index);
|
|
4861
|
+
if (openBrace >= 0 && openBrace < offset) {
|
|
4862
|
+
stack.push("class");
|
|
4863
|
+
index = openBrace + 1;
|
|
4864
|
+
continue;
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
const char = text[index];
|
|
4868
|
+
if (char === "{") {
|
|
4869
|
+
index += 1;
|
|
4870
|
+
continue;
|
|
4871
|
+
}
|
|
4872
|
+
if (char === "}") {
|
|
4873
|
+
stack.pop();
|
|
4874
|
+
index += 1;
|
|
4875
|
+
continue;
|
|
4876
|
+
}
|
|
4877
|
+
index += 1;
|
|
4878
|
+
}
|
|
4879
|
+
return stack[stack.length - 1] === "function";
|
|
4880
|
+
}
|
|
4881
|
+
function collectBreakContinueOutsideLoopFacts(text, detector) {
|
|
4882
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.breakContinueOutsideLoop;
|
|
4883
|
+
const findings = [];
|
|
4884
|
+
const controlPattern = /\b(?:break|continue)\b(?:\s+\d+\s*)?;/gu;
|
|
4885
|
+
for (const match of findAllMatches(text, controlPattern)) {
|
|
4886
|
+
const keyword = /\b(break|continue)\b/u.exec(match.matchedText)?.[1];
|
|
4887
|
+
if (!keyword) {
|
|
4888
|
+
continue;
|
|
4889
|
+
}
|
|
4890
|
+
const context = getControlFlowContext(text, match.startOffset);
|
|
4891
|
+
if (keyword === "continue" && context.loopDepth === 0) {
|
|
4892
|
+
findings.push(
|
|
4893
|
+
createOffsetFact(text, {
|
|
4894
|
+
detector,
|
|
4895
|
+
appliesTo: "block",
|
|
4896
|
+
kind,
|
|
4897
|
+
startOffset: match.startOffset,
|
|
4898
|
+
endOffset: match.endOffset,
|
|
4899
|
+
text: match.matchedText
|
|
4900
|
+
})
|
|
4901
|
+
);
|
|
4902
|
+
continue;
|
|
4903
|
+
}
|
|
4904
|
+
if (keyword === "break" && context.loopDepth === 0 && context.switchDepth === 0) {
|
|
4905
|
+
findings.push(
|
|
4906
|
+
createOffsetFact(text, {
|
|
4907
|
+
detector,
|
|
4908
|
+
appliesTo: "block",
|
|
4909
|
+
kind,
|
|
4910
|
+
startOffset: match.startOffset,
|
|
4911
|
+
endOffset: match.endOffset,
|
|
4912
|
+
text: match.matchedText
|
|
4913
|
+
})
|
|
4914
|
+
);
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4917
|
+
return findings;
|
|
4918
|
+
}
|
|
4919
|
+
function getControlFlowContext(text, offset) {
|
|
4920
|
+
const stack = [];
|
|
4921
|
+
let index = 0;
|
|
4922
|
+
while (index < offset) {
|
|
4923
|
+
index = skipPhpTrivia(text, index);
|
|
4924
|
+
if (index >= offset) {
|
|
4925
|
+
break;
|
|
4926
|
+
}
|
|
4927
|
+
const remaining = text.slice(index);
|
|
4928
|
+
if (/^\b(?:for|while|foreach|do)\b/u.test(remaining)) {
|
|
4929
|
+
const openBrace = findNextControlOpenBrace(text, index);
|
|
4930
|
+
if (openBrace >= 0 && openBrace < offset) {
|
|
4931
|
+
stack.push("loop");
|
|
4932
|
+
index = openBrace + 1;
|
|
4933
|
+
continue;
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
if (/^\bswitch\b/u.test(remaining)) {
|
|
4937
|
+
const openBrace = findNextControlOpenBrace(text, index);
|
|
4938
|
+
if (openBrace >= 0 && openBrace < offset) {
|
|
4939
|
+
stack.push("switch");
|
|
4940
|
+
index = openBrace + 1;
|
|
4941
|
+
continue;
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
const char = text[index];
|
|
4945
|
+
if (char === "{") {
|
|
4946
|
+
index += 1;
|
|
4947
|
+
continue;
|
|
4948
|
+
}
|
|
4949
|
+
if (char === "}") {
|
|
4950
|
+
stack.pop();
|
|
4951
|
+
index += 1;
|
|
4952
|
+
continue;
|
|
4953
|
+
}
|
|
4954
|
+
index += 1;
|
|
4955
|
+
}
|
|
4956
|
+
let loopDepth = 0;
|
|
4957
|
+
let switchDepth = 0;
|
|
4958
|
+
for (const frame of stack) {
|
|
4959
|
+
if (frame === "loop") {
|
|
4960
|
+
loopDepth += 1;
|
|
4961
|
+
} else {
|
|
4962
|
+
switchDepth += 1;
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
return { loopDepth, switchDepth };
|
|
4966
|
+
}
|
|
4967
|
+
function skipPhpTrivia(text, index) {
|
|
4968
|
+
const remaining = text.slice(index);
|
|
4969
|
+
if (remaining.startsWith("//") || remaining.startsWith("#")) {
|
|
4970
|
+
const nextLine = remaining.search(/\r?\n/u);
|
|
4971
|
+
return nextLine < 0 ? text.length : index + nextLine + 1;
|
|
4972
|
+
}
|
|
4973
|
+
if (remaining.startsWith("/*")) {
|
|
4974
|
+
const end = remaining.indexOf("*/");
|
|
4975
|
+
return end < 0 ? text.length : index + end + 2;
|
|
4976
|
+
}
|
|
4977
|
+
const quote = remaining[0];
|
|
4978
|
+
if (quote === '"' || quote === "'") {
|
|
4979
|
+
return skipQuotedPhpString(text, index, quote);
|
|
4980
|
+
}
|
|
4981
|
+
return index;
|
|
4982
|
+
}
|
|
4983
|
+
function skipQuotedPhpString(text, index, quote) {
|
|
4984
|
+
let cursor = index + 1;
|
|
4985
|
+
while (cursor < text.length) {
|
|
4986
|
+
const char = text[cursor];
|
|
4987
|
+
if (char === "\\") {
|
|
4988
|
+
cursor += 2;
|
|
4989
|
+
continue;
|
|
4990
|
+
}
|
|
4991
|
+
if (char === quote) {
|
|
4992
|
+
return cursor + 1;
|
|
4993
|
+
}
|
|
4994
|
+
cursor += 1;
|
|
4995
|
+
}
|
|
4996
|
+
return text.length;
|
|
4997
|
+
}
|
|
4998
|
+
function findNextControlOpenBrace(text, fromOffset) {
|
|
4999
|
+
let depth = 0;
|
|
5000
|
+
let index = fromOffset;
|
|
5001
|
+
while (index < text.length) {
|
|
5002
|
+
index = skipPhpTrivia(text, index);
|
|
5003
|
+
if (index >= text.length) {
|
|
5004
|
+
return -1;
|
|
5005
|
+
}
|
|
5006
|
+
const char = text[index];
|
|
5007
|
+
if (char === "(") {
|
|
5008
|
+
depth += 1;
|
|
5009
|
+
index += 1;
|
|
5010
|
+
continue;
|
|
5011
|
+
}
|
|
5012
|
+
if (char === ")") {
|
|
5013
|
+
depth = Math.max(0, depth - 1);
|
|
5014
|
+
index += 1;
|
|
5015
|
+
continue;
|
|
5016
|
+
}
|
|
5017
|
+
if (char === "{" && depth === 0) {
|
|
5018
|
+
return index;
|
|
5019
|
+
}
|
|
5020
|
+
if (char === ";" && depth === 0) {
|
|
5021
|
+
return -1;
|
|
5022
|
+
}
|
|
5023
|
+
index += 1;
|
|
5024
|
+
}
|
|
5025
|
+
return -1;
|
|
5026
|
+
}
|
|
5027
|
+
function collectAbstractMethodOutsideAbstractClassFacts(text, detector) {
|
|
5028
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.abstractMethodOutsideAbstractClass;
|
|
5029
|
+
const findings = [];
|
|
5030
|
+
const classPattern = /\b(?:(abstract)\s+)?class\s+([A-Za-z_][A-Za-z0-9_]*)\b[^{]*\{/gu;
|
|
5031
|
+
for (const match of findAllMatches(text, classPattern)) {
|
|
5032
|
+
const isAbstract = Boolean(
|
|
5033
|
+
/^\s*abstract\s+class\b/u.test(match.matchedText)
|
|
5034
|
+
);
|
|
5035
|
+
if (isAbstract) {
|
|
5036
|
+
continue;
|
|
5037
|
+
}
|
|
5038
|
+
const openBrace = match.endOffset - 1;
|
|
5039
|
+
const closeBrace = findMatchingDelimiter(text, openBrace, "{", "}");
|
|
5040
|
+
if (closeBrace < 0) {
|
|
5041
|
+
continue;
|
|
5042
|
+
}
|
|
5043
|
+
const body = text.slice(openBrace + 1, closeBrace);
|
|
5044
|
+
const abstractMethodPattern = /\babstract\s+(?:(?:public|protected|private|static)\s+)*function\s+[A-Za-z_][\w]*\s*\(/gu;
|
|
5045
|
+
for (const methodMatch of findAllMatches(body, abstractMethodPattern)) {
|
|
5046
|
+
const absoluteStart = openBrace + 1 + methodMatch.startOffset;
|
|
5047
|
+
const absoluteEnd = openBrace + 1 + methodMatch.endOffset;
|
|
5048
|
+
findings.push(
|
|
5049
|
+
createOffsetFact(text, {
|
|
5050
|
+
detector,
|
|
5051
|
+
appliesTo: "block",
|
|
5052
|
+
kind,
|
|
5053
|
+
startOffset: absoluteStart,
|
|
5054
|
+
endOffset: absoluteEnd,
|
|
5055
|
+
text: methodMatch.matchedText
|
|
5056
|
+
})
|
|
5057
|
+
);
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
5060
|
+
return findings;
|
|
5061
|
+
}
|
|
5062
|
+
function collectUselessUnsetFacts(text, detector) {
|
|
5063
|
+
return collectMatchedFacts({
|
|
5064
|
+
text,
|
|
5065
|
+
detector,
|
|
5066
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.uselessUnset,
|
|
5067
|
+
appliesTo: "block",
|
|
5068
|
+
pattern: /\bunset\s*\(\s*(?:\$this->[\w]+|true|false|null|\d+|['"][^'"]*['"])\s*\)/gu
|
|
5069
|
+
});
|
|
5070
|
+
}
|
|
5071
|
+
function collectInvalidRegexLiteralFacts(text, detector) {
|
|
5072
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.invalidRegexLiteral;
|
|
5073
|
+
const findings = [];
|
|
5074
|
+
const pregPattern = /\bpreg_(?:match|match_all|replace|replace_callback|filter|grep|split)\s*\(\s*(['"])([\s\S]*?)\1/gu;
|
|
5075
|
+
for (const match of findAllMatches(text, pregPattern)) {
|
|
5076
|
+
const literalMatch = /(['"])([\s\S]*?)\1/u.exec(match.matchedText);
|
|
5077
|
+
if (!literalMatch) {
|
|
5078
|
+
continue;
|
|
5079
|
+
}
|
|
5080
|
+
const patternLiteral = literalMatch[2];
|
|
5081
|
+
if (!isInvalidPhpRegexPattern(patternLiteral)) {
|
|
5082
|
+
continue;
|
|
5083
|
+
}
|
|
5084
|
+
findings.push(
|
|
5085
|
+
createOffsetFact(text, {
|
|
5086
|
+
detector,
|
|
5087
|
+
appliesTo: "block",
|
|
5088
|
+
kind,
|
|
5089
|
+
startOffset: match.startOffset,
|
|
5090
|
+
endOffset: match.endOffset,
|
|
5091
|
+
text: match.matchedText
|
|
5092
|
+
})
|
|
5093
|
+
);
|
|
5094
|
+
}
|
|
5095
|
+
return findings;
|
|
5096
|
+
}
|
|
5097
|
+
function isInvalidPhpRegexPattern(literal) {
|
|
5098
|
+
const delimiter = literal[0];
|
|
5099
|
+
if (!delimiter || !/[/#~@%]/u.test(delimiter)) {
|
|
5100
|
+
return false;
|
|
5101
|
+
}
|
|
5102
|
+
let index = 1;
|
|
5103
|
+
let escaped = false;
|
|
5104
|
+
while (index < literal.length) {
|
|
5105
|
+
const char = literal[index];
|
|
5106
|
+
if (escaped) {
|
|
5107
|
+
escaped = false;
|
|
5108
|
+
index += 1;
|
|
5109
|
+
continue;
|
|
5110
|
+
}
|
|
5111
|
+
if (char === "\\") {
|
|
5112
|
+
escaped = true;
|
|
5113
|
+
index += 1;
|
|
5114
|
+
continue;
|
|
5115
|
+
}
|
|
5116
|
+
if (char === delimiter) {
|
|
5117
|
+
const body = literal.slice(1, index);
|
|
5118
|
+
try {
|
|
5119
|
+
new RegExp(body, "u");
|
|
5120
|
+
return false;
|
|
5121
|
+
} catch {
|
|
5122
|
+
return true;
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
index += 1;
|
|
5126
|
+
}
|
|
5127
|
+
return true;
|
|
5128
|
+
}
|
|
5129
|
+
function collectTodoFixmeMarkerFacts(text, detector) {
|
|
5130
|
+
return collectMatchedFacts({
|
|
5131
|
+
text,
|
|
5132
|
+
detector,
|
|
5133
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.todoFixmeMarker,
|
|
5134
|
+
appliesTo: "block",
|
|
5135
|
+
pattern: /(?:\/\/|#|\/\*|\*)\s*.*\b(?:TODO|FIXME|XXX)\b/giu
|
|
5136
|
+
});
|
|
5137
|
+
}
|
|
5138
|
+
function collectSelfAssignmentFacts(text, detector) {
|
|
5139
|
+
return collectMatchedFacts({
|
|
5140
|
+
text,
|
|
5141
|
+
detector,
|
|
5142
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.selfAssignment,
|
|
5143
|
+
appliesTo: "block",
|
|
5144
|
+
pattern: /(\$this->[\w]+|\$\w+(?:->[\w]+|\[[^\]]+\])*)\s*=\s*\1\s*;/gu
|
|
5145
|
+
});
|
|
5146
|
+
}
|
|
5147
|
+
function collectDefaultParameterNotLastFacts(text, detector) {
|
|
5148
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.defaultParameterNotLast;
|
|
5149
|
+
const findings = [];
|
|
5150
|
+
const signaturePattern = /\b(?:function\s+[A-Za-z_][\w]*|fn\s*\()\s*\(([^)]*)\)/gu;
|
|
5151
|
+
for (const match of findAllMatches(text, signaturePattern)) {
|
|
5152
|
+
const paramsMatch = /\(([^)]*)\)/u.exec(match.matchedText);
|
|
5153
|
+
if (!paramsMatch) {
|
|
5154
|
+
continue;
|
|
5155
|
+
}
|
|
5156
|
+
const paramsStartInMatch = match.matchedText.indexOf("(") + 1;
|
|
5157
|
+
const params = splitParameterList(paramsMatch[1]);
|
|
5158
|
+
let sawDefault = false;
|
|
5159
|
+
for (const param of params) {
|
|
5160
|
+
const trimmed = param.text.trim();
|
|
5161
|
+
if (!trimmed) {
|
|
5162
|
+
continue;
|
|
5163
|
+
}
|
|
5164
|
+
const hasDefault = /(?:^|[^=<>!])=(?!=)/u.test(trimmed);
|
|
5165
|
+
if (hasDefault) {
|
|
5166
|
+
sawDefault = true;
|
|
5167
|
+
continue;
|
|
5168
|
+
}
|
|
5169
|
+
if (sawDefault) {
|
|
5170
|
+
const absoluteStart = match.startOffset + paramsStartInMatch + param.startOffset;
|
|
5171
|
+
const absoluteEnd = match.startOffset + paramsStartInMatch + param.endOffset;
|
|
5172
|
+
findings.push(
|
|
5173
|
+
createOffsetFact(text, {
|
|
5174
|
+
detector,
|
|
5175
|
+
appliesTo: "block",
|
|
5176
|
+
kind,
|
|
5177
|
+
startOffset: absoluteStart,
|
|
5178
|
+
endOffset: absoluteEnd,
|
|
5179
|
+
text: trimmed
|
|
5180
|
+
})
|
|
5181
|
+
);
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
return findings;
|
|
5186
|
+
}
|
|
5187
|
+
function splitParameterList(source) {
|
|
5188
|
+
const params = [];
|
|
5189
|
+
let start = 0;
|
|
5190
|
+
let depth = 0;
|
|
5191
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
5192
|
+
const char = source[index];
|
|
5193
|
+
if (char === "(" || char === "[") {
|
|
5194
|
+
depth += 1;
|
|
5195
|
+
continue;
|
|
5196
|
+
}
|
|
5197
|
+
if (char === ")" || char === "]") {
|
|
5198
|
+
depth = Math.max(0, depth - 1);
|
|
5199
|
+
continue;
|
|
5200
|
+
}
|
|
5201
|
+
if (char === "," && depth === 0) {
|
|
5202
|
+
params.push({
|
|
5203
|
+
text: source.slice(start, index),
|
|
5204
|
+
startOffset: start,
|
|
5205
|
+
endOffset: index
|
|
5206
|
+
});
|
|
5207
|
+
start = index + 1;
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
params.push({
|
|
5211
|
+
text: source.slice(start),
|
|
5212
|
+
startOffset: start,
|
|
5213
|
+
endOffset: source.length
|
|
5214
|
+
});
|
|
5215
|
+
return params;
|
|
5216
|
+
}
|
|
5217
|
+
function collectEmptyFunctionBodyFacts(text, detector) {
|
|
5218
|
+
return collectMatchedFacts({
|
|
5219
|
+
text,
|
|
5220
|
+
detector,
|
|
5221
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.emptyFunctionBody,
|
|
5222
|
+
appliesTo: "block",
|
|
5223
|
+
pattern: /\bfunction\s+[A-Za-z_][\w]*\s*\([^)]*\)\s*(?::\s*[^{]+)?\{\s*(?:\/\/[^\n]*\s*|\/\*[\s\S]*?\*\/\s*)*\}/gu
|
|
5224
|
+
});
|
|
5225
|
+
}
|
|
5226
|
+
function collectUnknownMagicMethodFacts(text, detector) {
|
|
5227
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.unknownMagicMethod;
|
|
5228
|
+
const findings = [];
|
|
5229
|
+
const magicPattern = /\bfunction\s+(__[A-Za-z_][\w]*)\s*\(/gu;
|
|
5230
|
+
for (const match of findAllMatches(text, magicPattern)) {
|
|
5231
|
+
const methodName = /function\s+(__[A-Za-z_][\w]*)\s*\(/u.exec(
|
|
5232
|
+
match.matchedText
|
|
5233
|
+
)?.[1];
|
|
5234
|
+
if (!methodName || PHP_VALID_MAGIC_METHODS.has(methodName)) {
|
|
5235
|
+
continue;
|
|
5236
|
+
}
|
|
5237
|
+
findings.push(
|
|
5238
|
+
createOffsetFact(text, {
|
|
5239
|
+
detector,
|
|
5240
|
+
appliesTo: "block",
|
|
5241
|
+
kind,
|
|
5242
|
+
startOffset: match.startOffset,
|
|
5243
|
+
endOffset: match.endOffset,
|
|
5244
|
+
text: match.matchedText
|
|
5245
|
+
})
|
|
5246
|
+
);
|
|
5247
|
+
}
|
|
5248
|
+
return findings;
|
|
5249
|
+
}
|
|
5250
|
+
function collectCaseInsensitiveDefineFacts(text, detector) {
|
|
5251
|
+
return collectMatchedFacts({
|
|
5252
|
+
text,
|
|
5253
|
+
detector,
|
|
5254
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.caseInsensitiveDefine,
|
|
5255
|
+
appliesTo: "block",
|
|
5256
|
+
pattern: /\bdefine\s*\(\s*['"][^'"]+['"]\s*,[\s\S]*?,\s*(?:true|1)\s*\)/gu
|
|
5257
|
+
});
|
|
5258
|
+
}
|
|
5259
|
+
function collectDeprecatedFilterConstantFacts(text, detector) {
|
|
5260
|
+
return collectMatchedFacts({
|
|
5261
|
+
text,
|
|
5262
|
+
detector,
|
|
5263
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.deprecatedFilterConstant,
|
|
5264
|
+
appliesTo: "block",
|
|
5265
|
+
pattern: PHP_DEPRECATED_FILTER_CONSTANTS
|
|
5266
|
+
});
|
|
5267
|
+
}
|
|
5268
|
+
function collectEmptyCodeBlockFacts(text, detector) {
|
|
5269
|
+
return collectMatchedFacts({
|
|
5270
|
+
text,
|
|
5271
|
+
detector,
|
|
5272
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.emptyCodeBlock,
|
|
5273
|
+
appliesTo: "block",
|
|
5274
|
+
pattern: /\b(?:if|else|elseif|try|catch|finally|for|while|foreach)\b[^{;]*\{\s*(?:\/\/[^\n]*\s*|\/\*[\s\S]*?\*\/\s*)*\}/gu
|
|
5275
|
+
});
|
|
5276
|
+
}
|
|
5277
|
+
function collectDeprecatedLibxmlEntityLoaderFacts(text, detector) {
|
|
5278
|
+
return collectMatchedFacts({
|
|
5279
|
+
text,
|
|
5280
|
+
detector,
|
|
5281
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.deprecatedLibxmlEntityLoader,
|
|
5282
|
+
appliesTo: "block",
|
|
5283
|
+
pattern: /\blibxml_disable_entity_loader\s*\(/gu
|
|
5284
|
+
});
|
|
5285
|
+
}
|
|
5286
|
+
function collectRedundantStringCastConcatFacts(text, detector) {
|
|
5287
|
+
return collectMatchedFacts({
|
|
5288
|
+
text,
|
|
5289
|
+
detector,
|
|
5290
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.redundantStringCastConcat,
|
|
5291
|
+
appliesTo: "block",
|
|
5292
|
+
pattern: /\(\s*string\s*\)\s*\$\w+\s*\./gu
|
|
5293
|
+
});
|
|
5294
|
+
}
|
|
5295
|
+
function collectMissingMemberVisibilityFacts(text, detector) {
|
|
5296
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.missingMemberVisibility;
|
|
5297
|
+
const findings = [];
|
|
5298
|
+
const classPattern = /\bclass\s+[A-Za-z_][\w]*\b[^{]*\{/gu;
|
|
5299
|
+
for (const match of findAllMatches(text, classPattern)) {
|
|
5300
|
+
const openBrace = match.endOffset - 1;
|
|
5301
|
+
const closeBrace = findMatchingDelimiter(text, openBrace, "{", "}");
|
|
5302
|
+
if (closeBrace < 0) {
|
|
5303
|
+
continue;
|
|
5304
|
+
}
|
|
5305
|
+
const body = text.slice(openBrace + 1, closeBrace);
|
|
5306
|
+
const memberPattern = /^\s*(?:\/\/[^\n]*|\/\*[\s\S]*?\*\/\s*)*(?:function\s+[A-Za-z_]|(?:var\s+)?\$[A-Za-z_])/gmu;
|
|
5307
|
+
for (const memberMatch of body.matchAll(memberPattern)) {
|
|
5308
|
+
const line = memberMatch[0];
|
|
5309
|
+
const trimmed = line.trimStart();
|
|
5310
|
+
if (/^(?:public|private|protected)\b/u.test(trimmed)) {
|
|
5311
|
+
continue;
|
|
5312
|
+
}
|
|
5313
|
+
if (/^function\s+__/u.test(trimmed)) {
|
|
5314
|
+
continue;
|
|
5315
|
+
}
|
|
5316
|
+
const matchIndex = memberMatch.index ?? 0;
|
|
5317
|
+
const absoluteStart = openBrace + 1 + matchIndex;
|
|
5318
|
+
const absoluteEnd = absoluteStart + line.trimEnd().length;
|
|
5319
|
+
findings.push(
|
|
5320
|
+
createOffsetFact(text, {
|
|
5321
|
+
detector,
|
|
5322
|
+
appliesTo: "block",
|
|
5323
|
+
kind,
|
|
5324
|
+
startOffset: absoluteStart,
|
|
5325
|
+
endOffset: absoluteEnd,
|
|
5326
|
+
text: line.trimEnd()
|
|
5327
|
+
})
|
|
5328
|
+
);
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
return findings;
|
|
5332
|
+
}
|
|
5333
|
+
function collectFunctionComparisonFacts(text, detector) {
|
|
5334
|
+
return collectMatchedFacts({
|
|
5335
|
+
text,
|
|
5336
|
+
detector,
|
|
5337
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.functionComparison,
|
|
5338
|
+
appliesTo: "block",
|
|
5339
|
+
pattern: /\$\w+\s*(?:==|===|!=|!==)\s*['"][^'"]+['"]|\[\s*\$\w+\s*,\s*['"][^'"]+['"]\s*\]\s*(?:<|>|<=|>=)\s*\[\s*\$\w+\s*,\s*['"][^'"]+['"]\s*\]/gu
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5342
|
+
function collectUselessPostIncrementFacts(text, detector) {
|
|
5343
|
+
return collectMatchedFacts({
|
|
5344
|
+
text,
|
|
5345
|
+
detector,
|
|
5346
|
+
kind: PHP_CORRECTNESS_FACT_KINDS.uselessPostIncrement,
|
|
5347
|
+
appliesTo: "block",
|
|
5348
|
+
pattern: /^\s*(?:\$\w+(?:\[[^\]]+\])?(?:->\w+)*|\$this->\w+)\s*\+\+\s*;/gmu
|
|
5349
|
+
});
|
|
5350
|
+
}
|
|
5351
|
+
function collectNestedSwitchFacts(text, detector) {
|
|
5352
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.nestedSwitch;
|
|
5353
|
+
const findings = [];
|
|
5354
|
+
const switchPattern = /\bswitch\s*\(/gu;
|
|
5355
|
+
for (const switchMatch of findAllMatches(text, switchPattern)) {
|
|
5356
|
+
const openBrace = findSwitchOpenBrace(text, switchMatch.endOffset);
|
|
5357
|
+
if (openBrace < 0) {
|
|
5358
|
+
continue;
|
|
5359
|
+
}
|
|
5360
|
+
const parentDepth = countEnclosingSwitchDepth(text, switchMatch.startOffset);
|
|
5361
|
+
if (parentDepth === 0) {
|
|
5362
|
+
continue;
|
|
5363
|
+
}
|
|
5364
|
+
findings.push(
|
|
5365
|
+
createOffsetFact(text, {
|
|
5366
|
+
detector,
|
|
5367
|
+
appliesTo: "block",
|
|
5368
|
+
kind,
|
|
5369
|
+
startOffset: switchMatch.startOffset,
|
|
5370
|
+
endOffset: switchMatch.endOffset,
|
|
5371
|
+
text: switchMatch.matchedText
|
|
5372
|
+
})
|
|
5373
|
+
);
|
|
5374
|
+
}
|
|
5375
|
+
return findings;
|
|
5376
|
+
}
|
|
5377
|
+
function countEnclosingSwitchDepth(text, offset) {
|
|
5378
|
+
let depth = 0;
|
|
5379
|
+
let index = 0;
|
|
5380
|
+
while (index < offset) {
|
|
5381
|
+
index = skipPhpTrivia(text, index);
|
|
5382
|
+
if (index >= offset) {
|
|
5383
|
+
break;
|
|
5384
|
+
}
|
|
5385
|
+
const remaining = text.slice(index);
|
|
5386
|
+
if (/^\bswitch\b/u.test(remaining)) {
|
|
5387
|
+
const openBrace = findNextControlOpenBrace(text, index);
|
|
5388
|
+
if (openBrace >= 0 && openBrace < offset) {
|
|
5389
|
+
depth += 1;
|
|
5390
|
+
index = openBrace + 1;
|
|
5391
|
+
continue;
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
const char = text[index];
|
|
5395
|
+
if (char === "{") {
|
|
5396
|
+
index += 1;
|
|
5397
|
+
continue;
|
|
5398
|
+
}
|
|
5399
|
+
if (char === "}") {
|
|
5400
|
+
depth = Math.max(0, depth - 1);
|
|
5401
|
+
index += 1;
|
|
5402
|
+
continue;
|
|
5403
|
+
}
|
|
5404
|
+
index += 1;
|
|
5405
|
+
}
|
|
5406
|
+
return depth;
|
|
5407
|
+
}
|
|
5408
|
+
function collectInvalidCookieOptionsFacts(text, detector) {
|
|
5409
|
+
const kind = PHP_CORRECTNESS_FACT_KINDS.invalidCookieOptions;
|
|
5410
|
+
const findings = [];
|
|
5411
|
+
const cookiePattern = /\bset(?:raw)?cookie\s*\(/gu;
|
|
5412
|
+
for (const match of findAllMatches(text, cookiePattern)) {
|
|
5413
|
+
const openParen = match.endOffset - 1;
|
|
5414
|
+
const closeParen = findMatchingDelimiter(text, openParen, "(", ")");
|
|
5415
|
+
if (closeParen < 0) {
|
|
5416
|
+
continue;
|
|
5417
|
+
}
|
|
5418
|
+
const callText = text.slice(match.startOffset, closeParen + 1);
|
|
5419
|
+
const optionsPattern = /['"]([\w-]+)['"]\s*=>/gu;
|
|
5420
|
+
for (const optionMatch of findAllMatches(callText, optionsPattern)) {
|
|
5421
|
+
const key = /['"]([\w-]+)['"]\s*=>/u.exec(optionMatch.matchedText)?.[1];
|
|
5422
|
+
if (!key || PHP_VALID_COOKIE_OPTION_KEYS.has(key.toLowerCase())) {
|
|
5423
|
+
continue;
|
|
5424
|
+
}
|
|
5425
|
+
findings.push(
|
|
5426
|
+
createOffsetFact(text, {
|
|
5427
|
+
detector,
|
|
5428
|
+
appliesTo: "block",
|
|
5429
|
+
kind,
|
|
5430
|
+
startOffset: match.startOffset + optionMatch.startOffset,
|
|
5431
|
+
endOffset: match.startOffset + optionMatch.endOffset,
|
|
5432
|
+
text: optionMatch.matchedText
|
|
5433
|
+
})
|
|
5434
|
+
);
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
return findings;
|
|
5438
|
+
}
|
|
4262
5439
|
|
|
4263
5440
|
// libs/adapters/shared/src/lib/polyglot/domains/php-baseline-security.ts
|
|
4264
5441
|
var PHP_BASELINE_SECURITY_FACT_KINDS = {
|
|
@@ -4267,7 +5444,9 @@ var PHP_BASELINE_SECURITY_FACT_KINDS = {
|
|
|
4267
5444
|
weakCipher: "php.security.weak-cipher",
|
|
4268
5445
|
insecureSessionIdGeneration: "php.security.insecure-session-id-generation",
|
|
4269
5446
|
xmlExternalEntity: "php.security.xml-external-entity",
|
|
4270
|
-
debugFunctionExposure: "php.security.debug-function-exposure"
|
|
5447
|
+
debugFunctionExposure: "php.security.debug-function-exposure",
|
|
5448
|
+
unsafeNewStatic: "php.security.unsafe-new-static",
|
|
5449
|
+
deprecatedLibxmlEntityLoader: "php.security.deprecated-libxml-entity-loader"
|
|
4271
5450
|
};
|
|
4272
5451
|
var WEAK_OPENSSL_CIPHER_PATTERN = /\b(?:DES|RC4|BF|ECB)\b|(?:^|[^A-Za-z])DES(?:[^A-Za-z]|$)/iu;
|
|
4273
5452
|
var XML_LOAD_PATTERN = /\b(?:simplexml_load_(?:file|string|xml)|domxml_(?:open_mem|xml)|xml_parse)\s*\(|\b(?:DOMDocument|SimpleXMLElement)\b[\s\S]{0,120}?->\s*load(?:XML|HTML)?\s*\(/g;
|
|
@@ -4292,7 +5471,9 @@ function collectPhpBaselineSecurityFacts(options) {
|
|
|
4292
5471
|
matchesTainted
|
|
4293
5472
|
}),
|
|
4294
5473
|
...collectXmlExternalEntityFacts(text, detector),
|
|
4295
|
-
...collectDebugFunctionExposureFacts(text, path, detector)
|
|
5474
|
+
...collectDebugFunctionExposureFacts(text, path, detector),
|
|
5475
|
+
...collectUnsafeNewStaticFacts(text, detector),
|
|
5476
|
+
...collectDeprecatedLibxmlEntityLoaderFacts2(text, detector)
|
|
4296
5477
|
]);
|
|
4297
5478
|
}
|
|
4298
5479
|
function collectNoDynamicEvalFacts(text, detector) {
|
|
@@ -4441,6 +5622,51 @@ function collectDebugFunctionExposureFacts(text, path, detector) {
|
|
|
4441
5622
|
pattern: /\b(?:var_dump|print_r|debug_zval_dump)\s*\(|\bxdebug_[a-z_]+\s*\(/gi
|
|
4442
5623
|
});
|
|
4443
5624
|
}
|
|
5625
|
+
function collectUnsafeNewStaticFacts(text, detector) {
|
|
5626
|
+
const kind = PHP_BASELINE_SECURITY_FACT_KINDS.unsafeNewStatic;
|
|
5627
|
+
const findings = [];
|
|
5628
|
+
const classPattern = /\b(?:(?:abstract|readonly|final)\s+)*class\s+([A-Za-z_][\w]*)\b[^{]*\{/gu;
|
|
5629
|
+
for (const match of findAllMatches(text, classPattern)) {
|
|
5630
|
+
const declarationWindow = text.slice(
|
|
5631
|
+
Math.max(0, match.startOffset - 12),
|
|
5632
|
+
match.endOffset
|
|
5633
|
+
);
|
|
5634
|
+
if (/\bfinal\b/u.test(declarationWindow)) {
|
|
5635
|
+
continue;
|
|
5636
|
+
}
|
|
5637
|
+
const openBrace = match.endOffset - 1;
|
|
5638
|
+
const closeBrace = findMatchingDelimiter(text, openBrace, "{", "}");
|
|
5639
|
+
if (closeBrace < 0) {
|
|
5640
|
+
continue;
|
|
5641
|
+
}
|
|
5642
|
+
const body = text.slice(openBrace + 1, closeBrace);
|
|
5643
|
+
const staticPattern = /\bnew\s+static\s*\(/gu;
|
|
5644
|
+
for (const staticMatch of findAllMatches(body, staticPattern)) {
|
|
5645
|
+
const absoluteStart = openBrace + 1 + staticMatch.startOffset;
|
|
5646
|
+
const absoluteEnd = openBrace + 1 + staticMatch.endOffset;
|
|
5647
|
+
findings.push(
|
|
5648
|
+
createOffsetFact(text, {
|
|
5649
|
+
detector,
|
|
5650
|
+
appliesTo: "block",
|
|
5651
|
+
kind,
|
|
5652
|
+
startOffset: absoluteStart,
|
|
5653
|
+
endOffset: absoluteEnd,
|
|
5654
|
+
text: staticMatch.matchedText
|
|
5655
|
+
})
|
|
5656
|
+
);
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
5659
|
+
return findings;
|
|
5660
|
+
}
|
|
5661
|
+
function collectDeprecatedLibxmlEntityLoaderFacts2(text, detector) {
|
|
5662
|
+
return collectMatchedFacts({
|
|
5663
|
+
text,
|
|
5664
|
+
detector,
|
|
5665
|
+
kind: PHP_BASELINE_SECURITY_FACT_KINDS.deprecatedLibxmlEntityLoader,
|
|
5666
|
+
appliesTo: "block",
|
|
5667
|
+
pattern: /\blibxml_disable_entity_loader\s*\(/gu
|
|
5668
|
+
});
|
|
5669
|
+
}
|
|
4444
5670
|
|
|
4445
5671
|
// libs/adapters/shared/src/lib/polyglot/domains/java-open-redirect.ts
|
|
4446
5672
|
var javaRedirectSinkPattern = /\bsendRedirect\s*\(|\bnew\s+RedirectView\s*\(/g;
|
|
@@ -4551,6 +5777,49 @@ function collectSpringConfigDebugExposureFacts(options) {
|
|
|
4551
5777
|
return [...debugPairs, ...verboseLogging];
|
|
4552
5778
|
}
|
|
4553
5779
|
|
|
5780
|
+
// libs/adapters/shared/src/lib/polyglot/domains/android-screenshot-exposure.ts
|
|
5781
|
+
var defaultAndroidActivityPattern = /\bclass\s+[A-Za-z_][A-Za-z0-9_]*\s+extends\s+(?:[A-Za-z_][A-Za-z0-9_]*Activity|Activity|AppCompatActivity|ComponentActivity|FragmentActivity)\b/g;
|
|
5782
|
+
var secureFlagEnabledPattern = /\b(?:getWindow\(\)\.)?(?:addFlags|setFlags)\s*\([^;\n]*FLAG_SECURE\b/g;
|
|
5783
|
+
var secureFlagClearedPattern = /\b(?:getWindow\(\)\.)?clearFlags\s*\([^;\n]*FLAG_SECURE\b/g;
|
|
5784
|
+
var sensitiveAndroidScreenPattern = /\b(?:account|auth|balance|billing|card|credential|login|otp|password|payment|pin|secret|session|token|transfer|wallet)\b|[A-Za-z0-9_]*(?:Otp|Passcode|Password|Pin|Secret|Session|Token)\b/i;
|
|
5785
|
+
function collectAndroidScreenshotExposureFacts(options) {
|
|
5786
|
+
const hasSecureFlag = secureFlagEnabledPattern.test(options.text);
|
|
5787
|
+
const hasClearedSecureFlag = secureFlagClearedPattern.test(options.text);
|
|
5788
|
+
if (hasSecureFlag && !hasClearedSecureFlag) {
|
|
5789
|
+
return [];
|
|
5790
|
+
}
|
|
5791
|
+
if (!hasClearedSecureFlag && !sensitiveAndroidScreenPattern.test(options.text)) {
|
|
5792
|
+
return [];
|
|
5793
|
+
}
|
|
5794
|
+
return collectMatchedFacts({
|
|
5795
|
+
text: options.text,
|
|
5796
|
+
detector: options.detector,
|
|
5797
|
+
kind: "security.android-screenshot-exposure",
|
|
5798
|
+
pattern: options.activityPattern ?? defaultAndroidActivityPattern,
|
|
5799
|
+
appliesTo: options.appliesTo ?? "file",
|
|
5800
|
+
props: () => ({
|
|
5801
|
+
reason: hasClearedSecureFlag ? "flag-secure-cleared" : "flag-secure-missing"
|
|
5802
|
+
}),
|
|
5803
|
+
textValue: ({ matchedText }) => matchedText.trim()
|
|
5804
|
+
});
|
|
5805
|
+
}
|
|
5806
|
+
|
|
5807
|
+
// libs/adapters/shared/src/lib/polyglot/domains/android-world-readable-mode.ts
|
|
5808
|
+
var defaultAndroidWorldReadablePattern = /\b(?:Context\.)?MODE_WORLD_(?:READABLE|WRITABLE|WRITEABLE)\b/g;
|
|
5809
|
+
function collectAndroidWorldReadableModeFacts(options) {
|
|
5810
|
+
return collectMatchedFacts({
|
|
5811
|
+
text: options.text,
|
|
5812
|
+
detector: options.detector,
|
|
5813
|
+
kind: "security.android-world-readable-mode",
|
|
5814
|
+
pattern: options.pattern ?? defaultAndroidWorldReadablePattern,
|
|
5815
|
+
appliesTo: options.appliesTo ?? "block",
|
|
5816
|
+
props: ({ matchedText }) => ({
|
|
5817
|
+
mode: matchedText.trim()
|
|
5818
|
+
}),
|
|
5819
|
+
textValue: ({ matchedText }) => matchedText.trim()
|
|
5820
|
+
});
|
|
5821
|
+
}
|
|
5822
|
+
|
|
4554
5823
|
// libs/adapters/shared/src/lib/polyglot/domains/java-framework-security.ts
|
|
4555
5824
|
var JAVA_FRAMEWORK_SECURITY_FACT_KINDS = {
|
|
4556
5825
|
springPermitAllDefault: "java.security.spring-permit-all-default",
|
|
@@ -8269,6 +9538,12 @@ function collectRustQualityMaintainabilityFacts(options) {
|
|
|
8269
9538
|
}
|
|
8270
9539
|
|
|
8271
9540
|
// libs/adapters/shared/src/lib/polyglot/domains/performance.ts
|
|
9541
|
+
var PHP_PERFORMANCE_FACT_KINDS = {
|
|
9542
|
+
noRegexConstructionInLoop: "php.performance.no-regex-construction-in-loop",
|
|
9543
|
+
noSyncFsInRequestPath: "php.performance.no-sync-fs-in-request-path",
|
|
9544
|
+
expensiveLoopCondition: "php.performance.expensive-loop-condition",
|
|
9545
|
+
noUnboundedConcurrency: "php.performance.no-unbounded-concurrency"
|
|
9546
|
+
};
|
|
8272
9547
|
function collectSharedPerformanceFacts(options, languagePrefix) {
|
|
8273
9548
|
const { text, detector } = options;
|
|
8274
9549
|
return [
|
|
@@ -8302,7 +9577,57 @@ function collectJavaPerformanceFacts(options) {
|
|
|
8302
9577
|
return collectSharedPerformanceFacts(options, "java");
|
|
8303
9578
|
}
|
|
8304
9579
|
function collectPhpPerformanceFacts(options) {
|
|
8305
|
-
|
|
9580
|
+
const { text, detector } = options;
|
|
9581
|
+
return [
|
|
9582
|
+
...collectMatchedFacts({
|
|
9583
|
+
text,
|
|
9584
|
+
detector,
|
|
9585
|
+
kind: PHP_PERFORMANCE_FACT_KINDS.noRegexConstructionInLoop,
|
|
9586
|
+
pattern: /\b(?:for|while)\b[\s\S]{0,300}\bpreg_(?:match|match_all|replace|replace_callback|filter|grep|split)\s*\(/gu,
|
|
9587
|
+
appliesTo: "block"
|
|
9588
|
+
}),
|
|
9589
|
+
...collectPhpSyncFsInRequestPathFacts(options),
|
|
9590
|
+
...collectMatchedFacts({
|
|
9591
|
+
text,
|
|
9592
|
+
detector,
|
|
9593
|
+
kind: PHP_PERFORMANCE_FACT_KINDS.expensiveLoopCondition,
|
|
9594
|
+
pattern: /\b(?:for|while)\s*\([\s\S]{0,240}?\b(?:count|sizeof|strlen|preg_match|preg_match_all|array_sum|in_array|file_get_contents|file_exists|glob)\s*\(/gu,
|
|
9595
|
+
appliesTo: "block"
|
|
9596
|
+
}),
|
|
9597
|
+
...collectMatchedFacts({
|
|
9598
|
+
text,
|
|
9599
|
+
detector,
|
|
9600
|
+
kind: PHP_PERFORMANCE_FACT_KINDS.noUnboundedConcurrency,
|
|
9601
|
+
appliesTo: "block",
|
|
9602
|
+
pattern: /\b(?:GuzzleHttp\\Promise\\(?:all|unwrap)|Amp\\Promise\\all)\s*\(\s*\$\w+/gu
|
|
9603
|
+
})
|
|
9604
|
+
];
|
|
9605
|
+
}
|
|
9606
|
+
var phpSyncFsCallPattern = /\b(?:file_get_contents|fopen|readfile|file|scandir|glob)\s*\(/gu;
|
|
9607
|
+
function collectPhpSyncFsInRequestPathFacts(options) {
|
|
9608
|
+
const { text, detector, state, matchesTainted } = options;
|
|
9609
|
+
if (!state || !matchesTainted) {
|
|
9610
|
+
return [];
|
|
9611
|
+
}
|
|
9612
|
+
return collectSnippetFacts({
|
|
9613
|
+
text,
|
|
9614
|
+
detector,
|
|
9615
|
+
kind: PHP_PERFORMANCE_FACT_KINDS.noSyncFsInRequestPath,
|
|
9616
|
+
pattern: phpSyncFsCallPattern,
|
|
9617
|
+
state,
|
|
9618
|
+
appliesTo: "block",
|
|
9619
|
+
predicate: (snippet, scanState) => isPhpSyncFsInRequestHandler(text, snippet.startOffset) && matchesTainted(snippet.text, scanState)
|
|
9620
|
+
});
|
|
9621
|
+
}
|
|
9622
|
+
function isPhpSyncFsInRequestHandler(text, callStartOffset) {
|
|
9623
|
+
const prefix = text.slice(0, callStartOffset);
|
|
9624
|
+
const functionStart = prefix.lastIndexOf("function");
|
|
9625
|
+
if (functionStart < 0) {
|
|
9626
|
+
return false;
|
|
9627
|
+
}
|
|
9628
|
+
return /\$_(?:GET|POST|REQUEST|COOKIE|FILES|SERVER)\b/u.test(
|
|
9629
|
+
text.slice(functionStart, callStartOffset)
|
|
9630
|
+
);
|
|
8306
9631
|
}
|
|
8307
9632
|
function collectPythonPerformanceFacts(options) {
|
|
8308
9633
|
return collectSharedPerformanceFacts(options, "py");
|
|
@@ -8314,6 +9639,323 @@ function collectRustPerformanceFacts(options) {
|
|
|
8314
9639
|
return collectSharedPerformanceFacts(options, "rust");
|
|
8315
9640
|
}
|
|
8316
9641
|
|
|
9642
|
+
// libs/adapters/cloudformation/src/lib/cloudformation.ts
|
|
9643
|
+
var import_node_fs4 = require("node:fs");
|
|
9644
|
+
var import_node_os = require("node:os");
|
|
9645
|
+
var import_node_path5 = require("node:path");
|
|
9646
|
+
|
|
9647
|
+
// libs/adapters/cloudformation/src/lib/collect-cfn-lint-facts.util.ts
|
|
9648
|
+
var CFN_LINT_FACT_KIND = "cfn.lint.finding";
|
|
9649
|
+
var CFN_LINT_DETECTOR = "cfn-lint";
|
|
9650
|
+
function lineColumnToOffset(text, line, column) {
|
|
9651
|
+
const lines = text.split("\n");
|
|
9652
|
+
const safeLine = Math.max(line, 1);
|
|
9653
|
+
let offset = 0;
|
|
9654
|
+
for (let index = 0; index < safeLine - 1 && index < lines.length; index += 1) {
|
|
9655
|
+
offset += lines[index].length + 1;
|
|
9656
|
+
}
|
|
9657
|
+
const lineText = lines[safeLine - 1] ?? "";
|
|
9658
|
+
offset += Math.min(Math.max(column - 1, 0), lineText.length);
|
|
9659
|
+
return offset;
|
|
9660
|
+
}
|
|
9661
|
+
function createRangeFromLineColumns(text, startLine, startColumn, endLine, endColumn) {
|
|
9662
|
+
const startOffset = lineColumnToOffset(text, startLine, startColumn);
|
|
9663
|
+
const endOffset = Math.max(
|
|
9664
|
+
startOffset + 1,
|
|
9665
|
+
lineColumnToOffset(text, endLine, endColumn)
|
|
9666
|
+
);
|
|
9667
|
+
const lines = text.split("\n");
|
|
9668
|
+
const excerptLines = lines.slice(
|
|
9669
|
+
Math.max(startLine - 1, 0),
|
|
9670
|
+
Math.min(endLine, lines.length)
|
|
9671
|
+
);
|
|
9672
|
+
return {
|
|
9673
|
+
startOffset,
|
|
9674
|
+
endOffset,
|
|
9675
|
+
excerpt: excerptLines.join("\n"),
|
|
9676
|
+
range: {
|
|
9677
|
+
startLine,
|
|
9678
|
+
startColumn,
|
|
9679
|
+
endLine,
|
|
9680
|
+
endColumn
|
|
9681
|
+
}
|
|
9682
|
+
};
|
|
9683
|
+
}
|
|
9684
|
+
function collectCfnLintFacts(text, findings) {
|
|
9685
|
+
return findings.map((finding) => {
|
|
9686
|
+
const positioned = createRangeFromLineColumns(
|
|
9687
|
+
text,
|
|
9688
|
+
finding.line,
|
|
9689
|
+
finding.column,
|
|
9690
|
+
finding.endLine,
|
|
9691
|
+
finding.endColumn
|
|
9692
|
+
);
|
|
9693
|
+
return {
|
|
9694
|
+
id: [
|
|
9695
|
+
CFN_LINT_DETECTOR,
|
|
9696
|
+
CFN_LINT_FACT_KIND,
|
|
9697
|
+
finding.ruleId,
|
|
9698
|
+
positioned.range.startLine,
|
|
9699
|
+
positioned.range.startColumn,
|
|
9700
|
+
positioned.range.endLine,
|
|
9701
|
+
positioned.range.endColumn
|
|
9702
|
+
].join(":"),
|
|
9703
|
+
kind: CFN_LINT_FACT_KIND,
|
|
9704
|
+
appliesTo: "file",
|
|
9705
|
+
range: positioned.range,
|
|
9706
|
+
text: positioned.excerpt,
|
|
9707
|
+
props: {
|
|
9708
|
+
ruleId: finding.ruleId,
|
|
9709
|
+
level: finding.level,
|
|
9710
|
+
message: finding.message,
|
|
9711
|
+
line: finding.line,
|
|
9712
|
+
column: finding.column
|
|
9713
|
+
}
|
|
9714
|
+
};
|
|
9715
|
+
});
|
|
9716
|
+
}
|
|
9717
|
+
|
|
9718
|
+
// libs/adapters/cloudformation/src/lib/is-cloudformation-template.util.ts
|
|
9719
|
+
var import_node_path4 = require("node:path");
|
|
9720
|
+
function looksLikeCloudFormationPath2(filePath) {
|
|
9721
|
+
const normalized = filePath.replace(/\\/gu, "/").toLowerCase();
|
|
9722
|
+
const fileName = normalized.split("/").pop() ?? normalized;
|
|
9723
|
+
if (/(?:^|\/)(?:templates?|cloudformation|cfn|sam|infra|iac)(?:\/|$)/u.test(
|
|
9724
|
+
normalized
|
|
9725
|
+
)) {
|
|
9726
|
+
return true;
|
|
9727
|
+
}
|
|
9728
|
+
return /(?:template|cloudformation|stack|cfn|sam)[^/]*\.(?:ya?ml|json)$/u.test(
|
|
9729
|
+
fileName
|
|
9730
|
+
);
|
|
9731
|
+
}
|
|
9732
|
+
function hasServerlessTransform(transform) {
|
|
9733
|
+
if (transform === "AWS::Serverless") {
|
|
9734
|
+
return true;
|
|
9735
|
+
}
|
|
9736
|
+
if (Array.isArray(transform)) {
|
|
9737
|
+
return transform.includes("AWS::Serverless");
|
|
9738
|
+
}
|
|
9739
|
+
return false;
|
|
9740
|
+
}
|
|
9741
|
+
function hasCloudFormationObjectMarkers(value) {
|
|
9742
|
+
if (typeof value["AWSTemplateFormatVersion"] === "string") {
|
|
9743
|
+
return true;
|
|
9744
|
+
}
|
|
9745
|
+
if (value["Resources"] !== void 0 && typeof value["Resources"] === "object" && value["Resources"] !== null) {
|
|
9746
|
+
return true;
|
|
9747
|
+
}
|
|
9748
|
+
return hasServerlessTransform(value["Transform"]);
|
|
9749
|
+
}
|
|
9750
|
+
function isCloudFormationTemplate(path, text) {
|
|
9751
|
+
const trimmed = text.trim();
|
|
9752
|
+
if (!trimmed) {
|
|
9753
|
+
return false;
|
|
9754
|
+
}
|
|
9755
|
+
const extension = (0, import_node_path4.extname)(path).toLowerCase();
|
|
9756
|
+
if (extension === ".json") {
|
|
9757
|
+
try {
|
|
9758
|
+
const parsed = JSON.parse(trimmed);
|
|
9759
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9760
|
+
return false;
|
|
9761
|
+
}
|
|
9762
|
+
return hasCloudFormationObjectMarkers(parsed);
|
|
9763
|
+
} catch {
|
|
9764
|
+
return false;
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9767
|
+
if (/AWSTemplateFormatVersion\s*:/m.test(trimmed)) {
|
|
9768
|
+
return true;
|
|
9769
|
+
}
|
|
9770
|
+
if (/^Resources\s*:/m.test(trimmed)) {
|
|
9771
|
+
return true;
|
|
9772
|
+
}
|
|
9773
|
+
if (/Transform\s*:\s*(?:AWS::Serverless|['"]AWS::Serverless['"])/m.test(trimmed)) {
|
|
9774
|
+
return true;
|
|
9775
|
+
}
|
|
9776
|
+
return false;
|
|
9777
|
+
}
|
|
9778
|
+
|
|
9779
|
+
// libs/adapters/cloudformation/src/lib/parse-cfn-lint-json.util.ts
|
|
9780
|
+
function isCfnLintJsonMatch(value) {
|
|
9781
|
+
return typeof value === "object" && value !== null;
|
|
9782
|
+
}
|
|
9783
|
+
function parseCfnLintJson(stdout) {
|
|
9784
|
+
const trimmed = stdout.trim();
|
|
9785
|
+
if (!trimmed) {
|
|
9786
|
+
return [];
|
|
9787
|
+
}
|
|
9788
|
+
let parsed;
|
|
9789
|
+
try {
|
|
9790
|
+
parsed = JSON.parse(trimmed);
|
|
9791
|
+
} catch {
|
|
9792
|
+
return [];
|
|
9793
|
+
}
|
|
9794
|
+
if (!Array.isArray(parsed)) {
|
|
9795
|
+
return [];
|
|
9796
|
+
}
|
|
9797
|
+
const findings = [];
|
|
9798
|
+
for (const entry of parsed) {
|
|
9799
|
+
if (!isCfnLintJsonMatch(entry)) {
|
|
9800
|
+
continue;
|
|
9801
|
+
}
|
|
9802
|
+
const ruleId = entry.Rule?.Id?.trim();
|
|
9803
|
+
if (!ruleId) {
|
|
9804
|
+
continue;
|
|
9805
|
+
}
|
|
9806
|
+
const line = entry.Location?.Start?.LineNumber ?? 1;
|
|
9807
|
+
const column = entry.Location?.Start?.ColumnNumber ?? 1;
|
|
9808
|
+
const endLine = entry.Location?.End?.LineNumber ?? line;
|
|
9809
|
+
const endColumn = entry.Location?.End?.ColumnNumber ?? column;
|
|
9810
|
+
const message = entry.Message?.trim() ?? "";
|
|
9811
|
+
const level = entry.Level?.trim() ?? "Unknown";
|
|
9812
|
+
findings.push({
|
|
9813
|
+
ruleId,
|
|
9814
|
+
level,
|
|
9815
|
+
message,
|
|
9816
|
+
line,
|
|
9817
|
+
column,
|
|
9818
|
+
endLine,
|
|
9819
|
+
endColumn
|
|
9820
|
+
});
|
|
9821
|
+
}
|
|
9822
|
+
return findings;
|
|
9823
|
+
}
|
|
9824
|
+
|
|
9825
|
+
// libs/adapters/cloudformation/src/lib/run-cfn-lint.util.ts
|
|
9826
|
+
var import_node_child_process = require("node:child_process");
|
|
9827
|
+
function readExecOutput(value) {
|
|
9828
|
+
if (typeof value === "string") {
|
|
9829
|
+
return value;
|
|
9830
|
+
}
|
|
9831
|
+
if (Buffer.isBuffer(value)) {
|
|
9832
|
+
return value.toString("utf8");
|
|
9833
|
+
}
|
|
9834
|
+
return "";
|
|
9835
|
+
}
|
|
9836
|
+
function runCfnLint(filePath) {
|
|
9837
|
+
try {
|
|
9838
|
+
const stdout = (0, import_node_child_process.execFileSync)("cfn-lint", ["-f", "json", filePath], {
|
|
9839
|
+
encoding: "utf8",
|
|
9840
|
+
maxBuffer: 16 * 1024 * 1024
|
|
9841
|
+
});
|
|
9842
|
+
return {
|
|
9843
|
+
ok: true,
|
|
9844
|
+
stdout,
|
|
9845
|
+
stderr: "",
|
|
9846
|
+
exitCode: 0
|
|
9847
|
+
};
|
|
9848
|
+
} catch (error) {
|
|
9849
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
9850
|
+
return {
|
|
9851
|
+
ok: false,
|
|
9852
|
+
stdout: "",
|
|
9853
|
+
stderr: "",
|
|
9854
|
+
exitCode: -1,
|
|
9855
|
+
errorCode: "ENOENT"
|
|
9856
|
+
};
|
|
9857
|
+
}
|
|
9858
|
+
if (typeof error === "object" && error !== null && "stdout" in error) {
|
|
9859
|
+
return {
|
|
9860
|
+
ok: true,
|
|
9861
|
+
stdout: readExecOutput(error.stdout),
|
|
9862
|
+
stderr: readExecOutput(
|
|
9863
|
+
"stderr" in error ? error.stderr : void 0
|
|
9864
|
+
),
|
|
9865
|
+
exitCode: "status" in error && typeof error.status === "number" ? error.status : 1
|
|
9866
|
+
};
|
|
9867
|
+
}
|
|
9868
|
+
const message = error instanceof Error ? error.message : "Unexpected cfn-lint failure.";
|
|
9869
|
+
return {
|
|
9870
|
+
ok: false,
|
|
9871
|
+
stdout: "",
|
|
9872
|
+
stderr: message,
|
|
9873
|
+
exitCode: 1
|
|
9874
|
+
};
|
|
9875
|
+
}
|
|
9876
|
+
}
|
|
9877
|
+
|
|
9878
|
+
// libs/adapters/cloudformation/src/lib/cloudformation.ts
|
|
9879
|
+
var SUPPORTED_EXTENSIONS = [".yaml", ".yml", ".json"];
|
|
9880
|
+
function templateExtensionForPath(path) {
|
|
9881
|
+
const extension = (0, import_node_path5.extname)(path).toLowerCase();
|
|
9882
|
+
if (extension === ".yaml" || extension === ".yml" || extension === ".json") {
|
|
9883
|
+
return extension;
|
|
9884
|
+
}
|
|
9885
|
+
return ".yaml";
|
|
9886
|
+
}
|
|
9887
|
+
function createMissingCfnLintDiagnostic(path) {
|
|
9888
|
+
return createDiagnostic({
|
|
9889
|
+
code: "adapter.cloudformation.cfn-lint-missing",
|
|
9890
|
+
message: "The `cfn-lint` executable was not found on PATH. Install cfn-lint to analyze CloudFormation templates.",
|
|
9891
|
+
severity: "warning",
|
|
9892
|
+
details: {
|
|
9893
|
+
path
|
|
9894
|
+
}
|
|
9895
|
+
});
|
|
9896
|
+
}
|
|
9897
|
+
function createCfnLintFailureDiagnostic(path, stderr) {
|
|
9898
|
+
return createDiagnostic({
|
|
9899
|
+
code: "adapter.cloudformation.cfn-lint-failed",
|
|
9900
|
+
message: `cfn-lint failed while analyzing \`${path}\`.`,
|
|
9901
|
+
severity: "warning",
|
|
9902
|
+
details: {
|
|
9903
|
+
path,
|
|
9904
|
+
stderr
|
|
9905
|
+
}
|
|
9906
|
+
});
|
|
9907
|
+
}
|
|
9908
|
+
function analyzeCloudFormationFile(path, text, options = {}) {
|
|
9909
|
+
if (!isCloudFormationTemplate(path, text)) {
|
|
9910
|
+
return {
|
|
9911
|
+
success: true,
|
|
9912
|
+
data: buildAnalyzedFileWithFacts(path, "cloudformation", text, [])
|
|
9913
|
+
};
|
|
9914
|
+
}
|
|
9915
|
+
const runner = options.runCfnLint ?? runCfnLint;
|
|
9916
|
+
const tempDirectory = (0, import_node_fs4.mkdtempSync)((0, import_node_path5.join)((0, import_node_os.tmpdir)(), "critiq-cfn-"));
|
|
9917
|
+
const tempPath = (0, import_node_path5.join)(
|
|
9918
|
+
tempDirectory,
|
|
9919
|
+
`${(0, import_node_path5.basename)(path, (0, import_node_path5.extname)(path)) || "template"}${templateExtensionForPath(path)}`
|
|
9920
|
+
);
|
|
9921
|
+
try {
|
|
9922
|
+
(0, import_node_fs4.writeFileSync)(tempPath, text, "utf8");
|
|
9923
|
+
const lintResult = runner(tempPath);
|
|
9924
|
+
if (lintResult.errorCode === "ENOENT") {
|
|
9925
|
+
return {
|
|
9926
|
+
success: true,
|
|
9927
|
+
data: buildAnalyzedFileWithFacts(path, "cloudformation", text, []),
|
|
9928
|
+
diagnostics: [createMissingCfnLintDiagnostic(path)]
|
|
9929
|
+
};
|
|
9930
|
+
}
|
|
9931
|
+
if (!lintResult.ok && !lintResult.stdout.trim()) {
|
|
9932
|
+
return {
|
|
9933
|
+
success: true,
|
|
9934
|
+
data: buildAnalyzedFileWithFacts(path, "cloudformation", text, []),
|
|
9935
|
+
diagnostics: [
|
|
9936
|
+
createCfnLintFailureDiagnostic(path, lintResult.stderr)
|
|
9937
|
+
]
|
|
9938
|
+
};
|
|
9939
|
+
}
|
|
9940
|
+
const findings = parseCfnLintJson(lintResult.stdout);
|
|
9941
|
+
const facts = collectCfnLintFacts(text, findings);
|
|
9942
|
+
return {
|
|
9943
|
+
success: true,
|
|
9944
|
+
data: buildAnalyzedFileWithFacts(path, "cloudformation", text, facts)
|
|
9945
|
+
};
|
|
9946
|
+
} finally {
|
|
9947
|
+
(0, import_node_fs4.rmSync)(tempDirectory, { recursive: true, force: true });
|
|
9948
|
+
}
|
|
9949
|
+
}
|
|
9950
|
+
var cloudformationSourceAdapter = {
|
|
9951
|
+
packageName: "@critiq/adapter-cloudformation",
|
|
9952
|
+
supportedExtensions: SUPPORTED_EXTENSIONS,
|
|
9953
|
+
supportedLanguages: ["cloudformation"],
|
|
9954
|
+
canHandlePath: looksLikeCloudFormationPath2,
|
|
9955
|
+
canHandle: isCloudFormationTemplate,
|
|
9956
|
+
analyze: analyzeCloudFormationFile
|
|
9957
|
+
};
|
|
9958
|
+
|
|
8317
9959
|
// libs/adapters/go/src/lib/go.ts
|
|
8318
9960
|
var hardcodedCredentialPattern = /(?:^|\n)\s*(?:const|var)?\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?::=|=)\s*["'`][^"'`\n]{8,}["'`]/g;
|
|
8319
9961
|
var fileReadCallPattern = /\b(?:os|ioutil)\.ReadFile\s*\(/g;
|
|
@@ -8625,6 +10267,14 @@ var javaAdapterDefinition = {
|
|
|
8625
10267
|
text,
|
|
8626
10268
|
detector
|
|
8627
10269
|
}),
|
|
10270
|
+
...collectAndroidScreenshotExposureFacts({
|
|
10271
|
+
text,
|
|
10272
|
+
detector
|
|
10273
|
+
}),
|
|
10274
|
+
...collectAndroidWorldReadableModeFacts({
|
|
10275
|
+
text,
|
|
10276
|
+
detector
|
|
10277
|
+
}),
|
|
8628
10278
|
...collectSpringConfigDebugExposureFacts({
|
|
8629
10279
|
text,
|
|
8630
10280
|
detector,
|
|
@@ -8860,7 +10510,13 @@ var phpAdapterDefinition = {
|
|
|
8860
10510
|
}),
|
|
8861
10511
|
...collectPhpTestingHygieneFacts({ text, path, detector }),
|
|
8862
10512
|
...collectPhpQualityMaintainabilityFacts({ text, path, detector }),
|
|
8863
|
-
...collectPhpPerformanceFacts({
|
|
10513
|
+
...collectPhpPerformanceFacts({
|
|
10514
|
+
text,
|
|
10515
|
+
path,
|
|
10516
|
+
detector,
|
|
10517
|
+
state,
|
|
10518
|
+
matchesTainted: matchesPhpTainted
|
|
10519
|
+
}),
|
|
8864
10520
|
...collectPhpCorrectnessFacts({ text, detector })
|
|
8865
10521
|
]
|
|
8866
10522
|
};
|
|
@@ -9236,6 +10892,8 @@ var rubyAdapterDefinition = {
|
|
|
9236
10892
|
detector,
|
|
9237
10893
|
pattern: weakHashCallPattern5
|
|
9238
10894
|
}),
|
|
10895
|
+
...collectRubyGeneralSecurityFacts({ text, path, detector }),
|
|
10896
|
+
...collectRubyBugRiskFacts({ text, detector }),
|
|
9239
10897
|
...collectRubyRailsSecurityFacts({
|
|
9240
10898
|
text,
|
|
9241
10899
|
detector,
|
|
@@ -9503,7 +11161,7 @@ function looksLikeRustSqlInterpolation(expression) {
|
|
|
9503
11161
|
|
|
9504
11162
|
// libs/adapters/typescript/src/lib/typescript.ts
|
|
9505
11163
|
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
9506
|
-
var
|
|
11164
|
+
var import_node_path6 = require("node:path");
|
|
9507
11165
|
|
|
9508
11166
|
// libs/adapters/typescript/src/lib/ast/source-text.ts
|
|
9509
11167
|
function isNodeLike(value) {
|
|
@@ -17927,6 +19585,323 @@ var collectAdditionalPublicSecurityFacts = (context) => {
|
|
|
17927
19585
|
];
|
|
17928
19586
|
};
|
|
17929
19587
|
|
|
19588
|
+
// libs/adapters/typescript/src/lib/custom-facts/additional-public-security/electron-shell-open-external-unvalidated.ts
|
|
19589
|
+
var openExternalCalleePattern = /(^|\.)(openExternal)$/u;
|
|
19590
|
+
var requestDrivenUrlPattern = /\b(?:req|request|body|query|params)\b/u;
|
|
19591
|
+
function collectElectronShellOpenExternalUnvalidatedFacts(context) {
|
|
19592
|
+
const facts = [];
|
|
19593
|
+
walkAst(context.program, (node) => {
|
|
19594
|
+
if (node.type !== "CallExpression") {
|
|
19595
|
+
return;
|
|
19596
|
+
}
|
|
19597
|
+
const calleeText2 = getCalleeText(node.callee, context.sourceText);
|
|
19598
|
+
if (!calleeText2 || !openExternalCalleePattern.test(calleeText2)) {
|
|
19599
|
+
return;
|
|
19600
|
+
}
|
|
19601
|
+
const urlText = getNodeText(
|
|
19602
|
+
node.arguments[0],
|
|
19603
|
+
context.sourceText
|
|
19604
|
+
);
|
|
19605
|
+
if (!urlText || !requestDrivenUrlPattern.test(urlText)) {
|
|
19606
|
+
return;
|
|
19607
|
+
}
|
|
19608
|
+
facts.push(
|
|
19609
|
+
createObservedFact({
|
|
19610
|
+
appliesTo: "block",
|
|
19611
|
+
kind: FACT_KINDS.electronShellOpenExternalUnvalidated,
|
|
19612
|
+
node,
|
|
19613
|
+
nodeIds: context.nodeIds,
|
|
19614
|
+
text: calleeText2,
|
|
19615
|
+
props: {
|
|
19616
|
+
url: urlText
|
|
19617
|
+
}
|
|
19618
|
+
})
|
|
19619
|
+
);
|
|
19620
|
+
});
|
|
19621
|
+
return facts;
|
|
19622
|
+
}
|
|
19623
|
+
|
|
19624
|
+
// libs/adapters/typescript/src/lib/custom-facts/substrate/client-security.ts
|
|
19625
|
+
var electronDangerousWebPreferences = [
|
|
19626
|
+
{
|
|
19627
|
+
name: "allowRunningInsecureContent",
|
|
19628
|
+
insecureBooleanValue: true
|
|
19629
|
+
},
|
|
19630
|
+
{
|
|
19631
|
+
name: "contextIsolation",
|
|
19632
|
+
insecureBooleanValue: false
|
|
19633
|
+
},
|
|
19634
|
+
{
|
|
19635
|
+
name: "enableRemoteModule",
|
|
19636
|
+
insecureBooleanValue: true
|
|
19637
|
+
},
|
|
19638
|
+
{
|
|
19639
|
+
name: "nodeIntegration",
|
|
19640
|
+
insecureBooleanValue: true
|
|
19641
|
+
},
|
|
19642
|
+
{
|
|
19643
|
+
name: "sandbox",
|
|
19644
|
+
insecureBooleanValue: false
|
|
19645
|
+
},
|
|
19646
|
+
{
|
|
19647
|
+
name: "webSecurity",
|
|
19648
|
+
insecureBooleanValue: false
|
|
19649
|
+
}
|
|
19650
|
+
];
|
|
19651
|
+
var electronTrustedOriginValidatorNames = /* @__PURE__ */ new Set([
|
|
19652
|
+
"allowlistedOrigin",
|
|
19653
|
+
"assertAllowedOrigin",
|
|
19654
|
+
"assertTrustedSenderFrame",
|
|
19655
|
+
"assertTrustedWebContents",
|
|
19656
|
+
"ensureAllowedOrigin",
|
|
19657
|
+
"ensureTrustedSender",
|
|
19658
|
+
"validateAllowedOrigin",
|
|
19659
|
+
"validateTrustedOrigin",
|
|
19660
|
+
"validateTrustedSender"
|
|
19661
|
+
]);
|
|
19662
|
+
var electronPrivilegedIpcSinkPattern = /\b(?:BrowserWindow|dialog\.(?:showOpenDialog|showSaveDialog)|exec|fs\.(?:appendFile|appendFileSync|readFile|readFileSync|rm|rmSync|unlink|unlinkSync|writeFile|writeFileSync)|openExternal|process\.env|shell\.(?:openExternal|openPath|showItemInFolder)|spawn|systemPreferences|webContents)\b/u;
|
|
19663
|
+
function isElectronTrustedOriginValidatorName(calleeText2) {
|
|
19664
|
+
return Boolean(
|
|
19665
|
+
calleeText2 && electronTrustedOriginValidatorNames.has(calleeText2)
|
|
19666
|
+
);
|
|
19667
|
+
}
|
|
19668
|
+
function isElectronSensitiveStorageKey(text) {
|
|
19669
|
+
return isAuthStorageKey(text) || isAuthLikeText(text);
|
|
19670
|
+
}
|
|
19671
|
+
function isLikelyPrivilegedElectronIpcBody(bodyText2) {
|
|
19672
|
+
return Boolean(
|
|
19673
|
+
bodyText2 && electronPrivilegedIpcSinkPattern.test(bodyText2)
|
|
19674
|
+
);
|
|
19675
|
+
}
|
|
19676
|
+
|
|
19677
|
+
// libs/adapters/typescript/src/lib/custom-facts/client-application-security.ts
|
|
19678
|
+
var ELECTRON_DANGEROUS_WEBPREFERENCES_FACT_KIND = "security.electron-dangerous-webpreferences";
|
|
19679
|
+
var ELECTRON_MISSING_IPC_ORIGIN_CHECK_FACT_KIND = "security.electron-missing-ipc-origin-check";
|
|
19680
|
+
var ELECTRON_INSECURE_LOCAL_STATE_FACT_KIND = "security.electron-insecure-local-state";
|
|
19681
|
+
var browserWindowCalleePattern = /(^|\.)(BrowserWindow)$/u;
|
|
19682
|
+
var ipcMainHandlerPattern = /(^|\.)(ipcMain)\.(handle|on)$/u;
|
|
19683
|
+
function collectElectronStoreConstructorNames(context) {
|
|
19684
|
+
const constructorNames = /* @__PURE__ */ new Set();
|
|
19685
|
+
walkAst(context.program, (node) => {
|
|
19686
|
+
if (node.type === "ImportDeclaration" && node.source.value === "electron-store") {
|
|
19687
|
+
for (const specifier of node.specifiers) {
|
|
19688
|
+
constructorNames.add(specifier.local.name);
|
|
19689
|
+
}
|
|
19690
|
+
return;
|
|
19691
|
+
}
|
|
19692
|
+
if (node.type !== "VariableDeclarator" || node.id.type !== "Identifier" || !node.init || node.init.type !== "CallExpression" || node.init.callee.type !== "Identifier" || node.init.callee.name !== "require") {
|
|
19693
|
+
return;
|
|
19694
|
+
}
|
|
19695
|
+
const sourceValue = getNodeText(
|
|
19696
|
+
node.init.arguments[0],
|
|
19697
|
+
context.sourceText
|
|
19698
|
+
)?.replace(/^['"]|['"]$/gu, "");
|
|
19699
|
+
if (sourceValue === "electron-store") {
|
|
19700
|
+
constructorNames.add(node.id.name);
|
|
19701
|
+
}
|
|
19702
|
+
});
|
|
19703
|
+
return constructorNames;
|
|
19704
|
+
}
|
|
19705
|
+
function collectElectronStoreInstanceNames(context, constructorNames) {
|
|
19706
|
+
const instanceNames = /* @__PURE__ */ new Set();
|
|
19707
|
+
if (constructorNames.size === 0) {
|
|
19708
|
+
return instanceNames;
|
|
19709
|
+
}
|
|
19710
|
+
walkAst(context.program, (node) => {
|
|
19711
|
+
if (node.type !== "VariableDeclarator" || node.id.type !== "Identifier" || !node.init || node.init.type !== "NewExpression") {
|
|
19712
|
+
return;
|
|
19713
|
+
}
|
|
19714
|
+
const calleeText2 = getNodeText(node.init.callee, context.sourceText);
|
|
19715
|
+
if (calleeText2 && constructorNames.has(calleeText2)) {
|
|
19716
|
+
instanceNames.add(node.id.name);
|
|
19717
|
+
}
|
|
19718
|
+
});
|
|
19719
|
+
return instanceNames;
|
|
19720
|
+
}
|
|
19721
|
+
function hasElectronOriginCheck(handler, sourceText) {
|
|
19722
|
+
const firstParam = handler.params[0];
|
|
19723
|
+
if (!firstParam || firstParam.type !== "Identifier") {
|
|
19724
|
+
return false;
|
|
19725
|
+
}
|
|
19726
|
+
const originExpressions = [
|
|
19727
|
+
`${firstParam.name}.senderFrame.origin`,
|
|
19728
|
+
`${firstParam.name}.senderFrame.url`,
|
|
19729
|
+
`${firstParam.name}.sender.getURL()`
|
|
19730
|
+
];
|
|
19731
|
+
let checked = false;
|
|
19732
|
+
walkAst(handler.body, (node) => {
|
|
19733
|
+
if (checked) {
|
|
19734
|
+
return;
|
|
19735
|
+
}
|
|
19736
|
+
if (node.type === "IfStatement" || node.type === "ConditionalExpression") {
|
|
19737
|
+
const testText = getNodeText(node.test, sourceText)?.replace(/\s+/gu, " ");
|
|
19738
|
+
if (testText && originExpressions.some((expression) => testText.includes(expression))) {
|
|
19739
|
+
checked = true;
|
|
19740
|
+
}
|
|
19741
|
+
return;
|
|
19742
|
+
}
|
|
19743
|
+
if (node.type === "SwitchStatement") {
|
|
19744
|
+
const discriminantText = getNodeText(
|
|
19745
|
+
node.discriminant,
|
|
19746
|
+
sourceText
|
|
19747
|
+
)?.replace(/\s+/gu, " ");
|
|
19748
|
+
if (discriminantText && originExpressions.some(
|
|
19749
|
+
(expression) => discriminantText.includes(expression)
|
|
19750
|
+
)) {
|
|
19751
|
+
checked = true;
|
|
19752
|
+
}
|
|
19753
|
+
return;
|
|
19754
|
+
}
|
|
19755
|
+
if (node.type !== "CallExpression") {
|
|
19756
|
+
return;
|
|
19757
|
+
}
|
|
19758
|
+
const calleeText2 = getCalleeText(node.callee, sourceText);
|
|
19759
|
+
const firstArgumentText = getNodeText(
|
|
19760
|
+
node.arguments[0],
|
|
19761
|
+
sourceText
|
|
19762
|
+
)?.replace(/\s+/gu, " ");
|
|
19763
|
+
if (isElectronTrustedOriginValidatorName(calleeText2) && firstArgumentText && originExpressions.some((expression) => firstArgumentText.includes(expression))) {
|
|
19764
|
+
checked = true;
|
|
19765
|
+
}
|
|
19766
|
+
});
|
|
19767
|
+
return checked;
|
|
19768
|
+
}
|
|
19769
|
+
function collectElectronDangerousWebPreferenceFacts(context) {
|
|
19770
|
+
const objectBindings = collectObjectBindings(context);
|
|
19771
|
+
const facts = [];
|
|
19772
|
+
walkAst(context.program, (node) => {
|
|
19773
|
+
if (node.type !== "NewExpression") {
|
|
19774
|
+
return;
|
|
19775
|
+
}
|
|
19776
|
+
const calleeText2 = getNodeText(node.callee, context.sourceText);
|
|
19777
|
+
if (!calleeText2 || !browserWindowCalleePattern.test(calleeText2)) {
|
|
19778
|
+
return;
|
|
19779
|
+
}
|
|
19780
|
+
const windowOptions = resolveObjectExpression(node.arguments[0], objectBindings);
|
|
19781
|
+
const webPreferences = resolveObjectExpression(
|
|
19782
|
+
getObjectProperty(windowOptions, "webPreferences")?.value,
|
|
19783
|
+
objectBindings
|
|
19784
|
+
);
|
|
19785
|
+
if (!webPreferences) {
|
|
19786
|
+
return;
|
|
19787
|
+
}
|
|
19788
|
+
for (const preference of electronDangerousWebPreferences) {
|
|
19789
|
+
const property = getObjectProperty(webPreferences, preference.name);
|
|
19790
|
+
if (!property || !isBooleanLiteral(
|
|
19791
|
+
property.value,
|
|
19792
|
+
preference.insecureBooleanValue
|
|
19793
|
+
)) {
|
|
19794
|
+
continue;
|
|
19795
|
+
}
|
|
19796
|
+
facts.push(
|
|
19797
|
+
createObservedFact({
|
|
19798
|
+
appliesTo: "block",
|
|
19799
|
+
kind: ELECTRON_DANGEROUS_WEBPREFERENCES_FACT_KIND,
|
|
19800
|
+
node: property,
|
|
19801
|
+
nodeIds: context.nodeIds,
|
|
19802
|
+
props: {
|
|
19803
|
+
preference: preference.name,
|
|
19804
|
+
configuredValue: getNodeText(
|
|
19805
|
+
property.value,
|
|
19806
|
+
context.sourceText
|
|
19807
|
+
),
|
|
19808
|
+
sink: calleeText2
|
|
19809
|
+
},
|
|
19810
|
+
text: `webPreferences.${preference.name}`
|
|
19811
|
+
})
|
|
19812
|
+
);
|
|
19813
|
+
}
|
|
19814
|
+
});
|
|
19815
|
+
return facts;
|
|
19816
|
+
}
|
|
19817
|
+
function collectElectronIpcOriginFacts(context) {
|
|
19818
|
+
const functionBindings = resolveFunctionBindings(context);
|
|
19819
|
+
const facts = [];
|
|
19820
|
+
walkAst(context.program, (node) => {
|
|
19821
|
+
if (node.type !== "CallExpression") {
|
|
19822
|
+
return;
|
|
19823
|
+
}
|
|
19824
|
+
const calleeText2 = getCalleeText(node.callee, context.sourceText);
|
|
19825
|
+
if (!calleeText2 || !ipcMainHandlerPattern.test(calleeText2)) {
|
|
19826
|
+
return;
|
|
19827
|
+
}
|
|
19828
|
+
const handler = resolveFunctionLike(node.arguments[1], functionBindings);
|
|
19829
|
+
const bodyText2 = handler ? getNodeText(handler.body, context.sourceText) : void 0;
|
|
19830
|
+
if (!handler || !isLikelyPrivilegedElectronIpcBody(bodyText2) || hasElectronOriginCheck(handler, context.sourceText)) {
|
|
19831
|
+
return;
|
|
19832
|
+
}
|
|
19833
|
+
facts.push(
|
|
19834
|
+
createObservedFact({
|
|
19835
|
+
appliesTo: "block",
|
|
19836
|
+
kind: ELECTRON_MISSING_IPC_ORIGIN_CHECK_FACT_KIND,
|
|
19837
|
+
node,
|
|
19838
|
+
nodeIds: context.nodeIds,
|
|
19839
|
+
props: {
|
|
19840
|
+
channel: getNodeText(
|
|
19841
|
+
node.arguments[0],
|
|
19842
|
+
context.sourceText
|
|
19843
|
+
),
|
|
19844
|
+
sink: calleeText2
|
|
19845
|
+
},
|
|
19846
|
+
text: `${calleeText2}(${getNodeText(
|
|
19847
|
+
node.arguments[0],
|
|
19848
|
+
context.sourceText
|
|
19849
|
+
) ?? ""})`
|
|
19850
|
+
})
|
|
19851
|
+
);
|
|
19852
|
+
});
|
|
19853
|
+
return facts;
|
|
19854
|
+
}
|
|
19855
|
+
function collectElectronInsecureLocalStateFacts(context) {
|
|
19856
|
+
const constructorNames = collectElectronStoreConstructorNames(context);
|
|
19857
|
+
const instanceNames = collectElectronStoreInstanceNames(
|
|
19858
|
+
context,
|
|
19859
|
+
constructorNames
|
|
19860
|
+
);
|
|
19861
|
+
const facts = [];
|
|
19862
|
+
if (instanceNames.size === 0) {
|
|
19863
|
+
return facts;
|
|
19864
|
+
}
|
|
19865
|
+
walkAst(context.program, (node) => {
|
|
19866
|
+
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
|
|
19867
|
+
return;
|
|
19868
|
+
}
|
|
19869
|
+
if (node.callee.object.type !== "Identifier" || !instanceNames.has(node.callee.object.name) || getMemberPropertyName(node.callee) !== "set") {
|
|
19870
|
+
return;
|
|
19871
|
+
}
|
|
19872
|
+
const storageKeyText = getNodeText(
|
|
19873
|
+
node.arguments[0],
|
|
19874
|
+
context.sourceText
|
|
19875
|
+
);
|
|
19876
|
+
const storageValueText = getNodeText(
|
|
19877
|
+
node.arguments[1],
|
|
19878
|
+
context.sourceText
|
|
19879
|
+
);
|
|
19880
|
+
if (!isElectronSensitiveStorageKey(storageKeyText) && !isElectronSensitiveStorageKey(storageValueText)) {
|
|
19881
|
+
return;
|
|
19882
|
+
}
|
|
19883
|
+
facts.push(
|
|
19884
|
+
createObservedFact({
|
|
19885
|
+
appliesTo: "block",
|
|
19886
|
+
kind: ELECTRON_INSECURE_LOCAL_STATE_FACT_KIND,
|
|
19887
|
+
node,
|
|
19888
|
+
nodeIds: context.nodeIds,
|
|
19889
|
+
props: {
|
|
19890
|
+
key: storageKeyText,
|
|
19891
|
+
sink: `${node.callee.object.name}.set`
|
|
19892
|
+
},
|
|
19893
|
+
text: `${node.callee.object.name}.set(${storageKeyText ?? "unknown"})`
|
|
19894
|
+
})
|
|
19895
|
+
);
|
|
19896
|
+
});
|
|
19897
|
+
return facts;
|
|
19898
|
+
}
|
|
19899
|
+
var collectClientApplicationSecurityFacts = (context) => [
|
|
19900
|
+
...collectElectronDangerousWebPreferenceFacts(context),
|
|
19901
|
+
...collectElectronIpcOriginFacts(context),
|
|
19902
|
+
...collectElectronInsecureLocalStateFacts(context)
|
|
19903
|
+
];
|
|
19904
|
+
|
|
17930
19905
|
// libs/adapters/typescript/src/lib/custom-facts/typescript-async-correctness.ts
|
|
17931
19906
|
var ARRAY_METHODS_EXPECTING_SYNC_CALLBACK = /* @__PURE__ */ new Set([
|
|
17932
19907
|
"every",
|
|
@@ -25380,6 +27355,8 @@ var collectWeakCryptoFacts = (context) => {
|
|
|
25380
27355
|
function collectAdditionalTypeScriptFacts(context) {
|
|
25381
27356
|
const facts = [
|
|
25382
27357
|
...collectAdditionalPublicSecurityFacts(context),
|
|
27358
|
+
...collectClientApplicationSecurityFacts(context),
|
|
27359
|
+
...collectElectronShellOpenExternalUnvalidatedFacts(context),
|
|
25383
27360
|
...collectTypescriptAsyncCorrectnessFacts(context),
|
|
25384
27361
|
...collectTypescriptCoreLanguageCorrectnessFacts(context),
|
|
25385
27362
|
...collectTypescriptCorrectnessLanguageExtendedFacts(context),
|
|
@@ -25562,7 +27539,7 @@ var typescriptSourceAdapter = {
|
|
|
25562
27539
|
analyze: analyzeTypeScriptFile
|
|
25563
27540
|
};
|
|
25564
27541
|
function extensionToLanguage(path) {
|
|
25565
|
-
switch ((0,
|
|
27542
|
+
switch ((0, import_node_path6.extname)(path).toLowerCase()) {
|
|
25566
27543
|
case ".js":
|
|
25567
27544
|
case ".jsx":
|
|
25568
27545
|
return "javascript";
|
|
@@ -25573,7 +27550,7 @@ function extensionToLanguage(path) {
|
|
|
25573
27550
|
}
|
|
25574
27551
|
}
|
|
25575
27552
|
function supportsJsx(path) {
|
|
25576
|
-
return [".jsx", ".tsx"].includes((0,
|
|
27553
|
+
return [".jsx", ".tsx"].includes((0, import_node_path6.extname)(path).toLowerCase());
|
|
25577
27554
|
}
|
|
25578
27555
|
function analyzeTypeScriptFile(path, text) {
|
|
25579
27556
|
try {
|
|
@@ -25629,7 +27606,7 @@ function analyzeTypeScriptFile(path, text) {
|
|
|
25629
27606
|
}
|
|
25630
27607
|
|
|
25631
27608
|
// libs/runtime/check-runner/src/lib/check-runner/registry.ts
|
|
25632
|
-
var
|
|
27609
|
+
var import_node_path9 = require("node:path");
|
|
25633
27610
|
|
|
25634
27611
|
// libs/core/ir/src/lib/ir.ts
|
|
25635
27612
|
var import_node_crypto2 = require("node:crypto");
|
|
@@ -25885,8 +27862,8 @@ function normalizeRuleDocument(validatedRuleDocument) {
|
|
|
25885
27862
|
}
|
|
25886
27863
|
|
|
25887
27864
|
// libs/core/rules-dsl/src/lib/rules-dsl.ts
|
|
25888
|
-
var
|
|
25889
|
-
var
|
|
27865
|
+
var import_node_fs5 = require("node:fs");
|
|
27866
|
+
var import_node_path7 = require("node:path");
|
|
25890
27867
|
var import_zod6 = require("zod");
|
|
25891
27868
|
|
|
25892
27869
|
// libs/core/rules-dsl/src/lib/rules-dsl-schema.ts
|
|
@@ -25906,6 +27883,7 @@ var ruleLanguageSchema = import_zod5.z.enum([
|
|
|
25906
27883
|
"ruby",
|
|
25907
27884
|
"rust",
|
|
25908
27885
|
"dockerfile",
|
|
27886
|
+
"cloudformation",
|
|
25909
27887
|
"all"
|
|
25910
27888
|
]);
|
|
25911
27889
|
var ruleStabilitySchema = import_zod5.z.enum(["stable", "experimental"]);
|
|
@@ -26149,16 +28127,16 @@ var ruleDocumentV0Alpha1Schema = import_zod5.z.object({
|
|
|
26149
28127
|
// libs/core/rules-dsl/src/lib/rules-dsl.ts
|
|
26150
28128
|
function loadRuleDocumentV0Alpha1JsonSchema() {
|
|
26151
28129
|
const candidatePaths = [
|
|
26152
|
-
(0,
|
|
26153
|
-
(0,
|
|
26154
|
-
(0,
|
|
28130
|
+
(0, import_node_path7.resolve)(__dirname, "./schema/rule-document-v0alpha1.schema.json"),
|
|
28131
|
+
(0, import_node_path7.resolve)(__dirname, "../../schema/rule-document-v0alpha1.schema.json"),
|
|
28132
|
+
(0, import_node_path7.resolve)(
|
|
26155
28133
|
__dirname,
|
|
26156
28134
|
"../../../../../workspace_modules/@critiq/core-rules-dsl/schema/rule-document-v0alpha1.schema.json"
|
|
26157
28135
|
)
|
|
26158
28136
|
];
|
|
26159
28137
|
for (const candidatePath of candidatePaths) {
|
|
26160
|
-
if ((0,
|
|
26161
|
-
return JSON.parse((0,
|
|
28138
|
+
if ((0, import_node_fs5.existsSync)(candidatePath)) {
|
|
28139
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(candidatePath, "utf8"));
|
|
26162
28140
|
}
|
|
26163
28141
|
}
|
|
26164
28142
|
throw new Error("Unable to locate rule-document-v0alpha1.schema.json.");
|
|
@@ -26210,7 +28188,7 @@ function summarizeValidatedRuleDocument(validatedRuleDocument) {
|
|
|
26210
28188
|
}
|
|
26211
28189
|
|
|
26212
28190
|
// libs/core/rules-dsl/src/lib/rules-dsl-loader.ts
|
|
26213
|
-
var
|
|
28191
|
+
var import_node_fs6 = require("node:fs");
|
|
26214
28192
|
var import_node_url3 = require("node:url");
|
|
26215
28193
|
function toSourceMap(sourceMap) {
|
|
26216
28194
|
return Object.fromEntries(
|
|
@@ -26272,7 +28250,7 @@ function loadRuleText(text, uri) {
|
|
|
26272
28250
|
}
|
|
26273
28251
|
function loadRuleFile(path) {
|
|
26274
28252
|
try {
|
|
26275
|
-
const text = (0,
|
|
28253
|
+
const text = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
26276
28254
|
const uri = (0, import_node_url3.pathToFileURL)(path).href;
|
|
26277
28255
|
return loadRuleText(text, uri);
|
|
26278
28256
|
} catch (error) {
|
|
@@ -27006,10 +28984,10 @@ function validateRuleDocumentSemantics(validatedRuleDocument) {
|
|
|
27006
28984
|
function validateLoadedRuleDocument(loadedRuleDocument) {
|
|
27007
28985
|
const contractValidation = validateLoadedRuleDocumentContract(loadedRuleDocument);
|
|
27008
28986
|
if (!contractValidation.success) {
|
|
27009
|
-
const
|
|
28987
|
+
const failure2 = contractValidation;
|
|
27010
28988
|
return {
|
|
27011
28989
|
success: false,
|
|
27012
|
-
diagnostics:
|
|
28990
|
+
diagnostics: failure2.diagnostics
|
|
27013
28991
|
};
|
|
27014
28992
|
}
|
|
27015
28993
|
const semanticValidation = validateRuleDocumentSemantics(contractValidation.data);
|
|
@@ -27027,16 +29005,16 @@ function validateLoadedRuleDocument(loadedRuleDocument) {
|
|
|
27027
29005
|
}
|
|
27028
29006
|
|
|
27029
29007
|
// libs/runtime/check-runner/src/lib/check-runner/shared.ts
|
|
27030
|
-
var
|
|
27031
|
-
var
|
|
29008
|
+
var import_node_fs7 = require("node:fs");
|
|
29009
|
+
var import_node_path8 = require("node:path");
|
|
27032
29010
|
var DEFAULT_CATALOG_PACKAGE_NAME = "@critiq/rules";
|
|
27033
29011
|
var RULE_CATALOG_FILENAME = "catalog.yaml";
|
|
27034
29012
|
function toPosixPath(value) {
|
|
27035
|
-
return value.split(
|
|
29013
|
+
return value.split(import_node_path8.sep).join("/");
|
|
27036
29014
|
}
|
|
27037
29015
|
function toDisplayPath(cwd, absolutePath) {
|
|
27038
|
-
const relativePath = toPosixPath((0,
|
|
27039
|
-
if (relativePath.length > 0 && !relativePath.startsWith("..") && !(0,
|
|
29016
|
+
const relativePath = toPosixPath((0, import_node_path8.relative)(cwd, absolutePath));
|
|
29017
|
+
if (relativePath.length > 0 && !relativePath.startsWith("..") && !(0, import_node_path8.isAbsolute)(relativePath)) {
|
|
27040
29018
|
return relativePath;
|
|
27041
29019
|
}
|
|
27042
29020
|
return toPosixPath(absolutePath);
|
|
@@ -27061,7 +29039,7 @@ function isSkippableDirectory(currentDirectory, name) {
|
|
|
27061
29039
|
)) {
|
|
27062
29040
|
return true;
|
|
27063
29041
|
}
|
|
27064
|
-
return name === "cache" && currentDirectory.split(
|
|
29042
|
+
return name === "cache" && currentDirectory.split(import_node_path8.sep).at(-1) === ".yarn";
|
|
27065
29043
|
}
|
|
27066
29044
|
function walkFiles(rootDirectory) {
|
|
27067
29045
|
const files = [];
|
|
@@ -27071,11 +29049,11 @@ function walkFiles(rootDirectory) {
|
|
|
27071
29049
|
if (!currentDirectory) {
|
|
27072
29050
|
continue;
|
|
27073
29051
|
}
|
|
27074
|
-
const entries = (0,
|
|
29052
|
+
const entries = (0, import_node_fs7.readdirSync)(currentDirectory, { withFileTypes: true }).sort(
|
|
27075
29053
|
(left, right) => left.name.localeCompare(right.name)
|
|
27076
29054
|
);
|
|
27077
29055
|
for (const entry of entries) {
|
|
27078
|
-
const absolutePath = (0,
|
|
29056
|
+
const absolutePath = (0, import_node_path8.resolve)(currentDirectory, entry.name);
|
|
27079
29057
|
if (entry.isDirectory()) {
|
|
27080
29058
|
if (!isSkippableDirectory(currentDirectory, entry.name)) {
|
|
27081
29059
|
queue.push(absolutePath);
|
|
@@ -27100,7 +29078,7 @@ function readTextFileSafe(path) {
|
|
|
27100
29078
|
try {
|
|
27101
29079
|
return {
|
|
27102
29080
|
success: true,
|
|
27103
|
-
text: (0,
|
|
29081
|
+
text: (0, import_node_fs7.readFileSync)(path, "utf8")
|
|
27104
29082
|
};
|
|
27105
29083
|
} catch (error) {
|
|
27106
29084
|
return {
|
|
@@ -27125,16 +29103,16 @@ function loadNormalizedRulesForCatalog(absoluteRulePaths) {
|
|
|
27125
29103
|
)) {
|
|
27126
29104
|
const loadResult = loadRuleFile(absolutePath);
|
|
27127
29105
|
if (!loadResult.success) {
|
|
27128
|
-
const
|
|
27129
|
-
diagnostics.push(...
|
|
29106
|
+
const failure2 = loadResult;
|
|
29107
|
+
diagnostics.push(...failure2.diagnostics);
|
|
27130
29108
|
continue;
|
|
27131
29109
|
}
|
|
27132
29110
|
const contractValidation = validateLoadedRuleDocumentContract(
|
|
27133
29111
|
loadResult.data
|
|
27134
29112
|
);
|
|
27135
29113
|
if (!contractValidation.success) {
|
|
27136
|
-
const
|
|
27137
|
-
diagnostics.push(...
|
|
29114
|
+
const failure2 = contractValidation;
|
|
29115
|
+
diagnostics.push(...failure2.diagnostics);
|
|
27138
29116
|
continue;
|
|
27139
29117
|
}
|
|
27140
29118
|
const semanticValidation = validateRuleDocumentSemantics(
|
|
@@ -27173,13 +29151,13 @@ function defaultCatalogPackageRootsFromEnvironment() {
|
|
|
27173
29151
|
if (!rulesRoot) {
|
|
27174
29152
|
return {};
|
|
27175
29153
|
}
|
|
27176
|
-
const directRoot = (0,
|
|
29154
|
+
const directRoot = (0, import_node_path8.resolve)(rulesRoot);
|
|
27177
29155
|
const candidateRoots = [
|
|
27178
29156
|
directRoot,
|
|
27179
|
-
(0,
|
|
29157
|
+
(0, import_node_path8.resolve)(directRoot, "libs/rules/catalog")
|
|
27180
29158
|
];
|
|
27181
29159
|
for (const candidateRoot of candidateRoots) {
|
|
27182
|
-
if ((0,
|
|
29160
|
+
if ((0, import_node_fs7.existsSync)((0, import_node_path8.resolve)(candidateRoot, RULE_CATALOG_FILENAME))) {
|
|
27183
29161
|
return {
|
|
27184
29162
|
[DEFAULT_CATALOG_PACKAGE_NAME]: candidateRoot
|
|
27185
29163
|
};
|
|
@@ -27197,7 +29175,7 @@ function resolveCatalogPackageForRuntime(_displayRoot, packageName, options) {
|
|
|
27197
29175
|
packageRootOverrides,
|
|
27198
29176
|
hasOverrideCatalog(packageRootOverride) {
|
|
27199
29177
|
return Boolean(
|
|
27200
|
-
packageRootOverride && (0,
|
|
29178
|
+
packageRootOverride && (0, import_node_fs7.existsSync)((0, import_node_path8.resolve)(packageRootOverride, RULE_CATALOG_FILENAME))
|
|
27201
29179
|
);
|
|
27202
29180
|
}
|
|
27203
29181
|
};
|
|
@@ -27220,11 +29198,33 @@ function createSourceAdapterRegistry(adapters) {
|
|
|
27220
29198
|
}));
|
|
27221
29199
|
return {
|
|
27222
29200
|
adapters: normalizedAdapters,
|
|
27223
|
-
findAdapterForPath(path) {
|
|
27224
|
-
const extension = normalizeExtension((0,
|
|
27225
|
-
|
|
29201
|
+
findAdapterForPath(path, text) {
|
|
29202
|
+
const extension = normalizeExtension((0, import_node_path9.extname)(path));
|
|
29203
|
+
const candidates = normalizedAdapters.filter(
|
|
27226
29204
|
(adapter) => adapter.supportedExtensions.includes(extension)
|
|
27227
29205
|
);
|
|
29206
|
+
if (candidates.length === 0) {
|
|
29207
|
+
return void 0;
|
|
29208
|
+
}
|
|
29209
|
+
if (candidates.length === 1) {
|
|
29210
|
+
const adapter = candidates[0];
|
|
29211
|
+
if (adapter.canHandle) {
|
|
29212
|
+
if (text === void 0) {
|
|
29213
|
+
return adapter.canHandlePath?.(path) ? adapter : void 0;
|
|
29214
|
+
}
|
|
29215
|
+
return adapter.canHandle(path, text) ? adapter : void 0;
|
|
29216
|
+
}
|
|
29217
|
+
return adapter;
|
|
29218
|
+
}
|
|
29219
|
+
if (text !== void 0) {
|
|
29220
|
+
for (const adapter of candidates) {
|
|
29221
|
+
if (adapter.canHandle?.(path, text)) {
|
|
29222
|
+
return adapter;
|
|
29223
|
+
}
|
|
29224
|
+
}
|
|
29225
|
+
return candidates.find((adapter) => !adapter.canHandle) ?? candidates[0];
|
|
29226
|
+
}
|
|
29227
|
+
return candidates.find((adapter) => !adapter.canHandle) ?? candidates[0];
|
|
27228
29228
|
},
|
|
27229
29229
|
hasAdapterForLanguage(language) {
|
|
27230
29230
|
return normalizedAdapters.some(
|
|
@@ -27249,6 +29249,7 @@ function createSourceAdapterRegistry(adapters) {
|
|
|
27249
29249
|
}
|
|
27250
29250
|
function createDefaultSourceAdapterRegistry() {
|
|
27251
29251
|
return createSourceAdapterRegistry([
|
|
29252
|
+
cloudformationSourceAdapter,
|
|
27252
29253
|
goSourceAdapter,
|
|
27253
29254
|
javaSourceAdapter,
|
|
27254
29255
|
phpSourceAdapter,
|
|
@@ -27261,12 +29262,12 @@ function createDefaultSourceAdapterRegistry() {
|
|
|
27261
29262
|
|
|
27262
29263
|
// libs/runtime/check-runner/src/lib/check-runner/scope.ts
|
|
27263
29264
|
var import_minimatch2 = require("minimatch");
|
|
27264
|
-
var
|
|
27265
|
-
var
|
|
27266
|
-
var
|
|
29265
|
+
var import_node_child_process2 = require("node:child_process");
|
|
29266
|
+
var import_node_fs8 = require("node:fs");
|
|
29267
|
+
var import_node_path11 = require("node:path");
|
|
27267
29268
|
|
|
27268
29269
|
// libs/runtime/check-runner/src/lib/secrets-scanner/eligibility.ts
|
|
27269
|
-
var
|
|
29270
|
+
var import_node_path10 = require("node:path");
|
|
27270
29271
|
var SECRETS_SCAN_MAX_FILE_BYTES = 512 * 1024;
|
|
27271
29272
|
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
27272
29273
|
".png",
|
|
@@ -27371,7 +29372,7 @@ var NO_EXTENSION_NAMES = /* @__PURE__ */ new Set([
|
|
|
27371
29372
|
]);
|
|
27372
29373
|
function isSecretsEligiblePath(displayPath) {
|
|
27373
29374
|
const lower = displayPath.toLowerCase();
|
|
27374
|
-
const ext = (0,
|
|
29375
|
+
const ext = (0, import_node_path10.extname)(lower);
|
|
27375
29376
|
const base = lower.includes("/") ? lower.slice(lower.lastIndexOf("/") + 1) : lower;
|
|
27376
29377
|
if (BINARY_EXTENSIONS.has(ext)) {
|
|
27377
29378
|
return false;
|
|
@@ -27437,13 +29438,13 @@ var defaultIgnoredPathPatterns = [
|
|
|
27437
29438
|
"**/*_generated.go"
|
|
27438
29439
|
];
|
|
27439
29440
|
function isPathWithinDirectory(directoryPath, candidatePath) {
|
|
27440
|
-
const relativePath = (0,
|
|
27441
|
-
return relativePath.length === 0 || !relativePath.startsWith("..") && !(0,
|
|
29441
|
+
const relativePath = (0, import_node_path11.relative)(directoryPath, candidatePath);
|
|
29442
|
+
return relativePath.length === 0 || !relativePath.startsWith("..") && !(0, import_node_path11.isAbsolute)(relativePath);
|
|
27442
29443
|
}
|
|
27443
29444
|
function tryResolveGitRepoRoot(workingDirectory) {
|
|
27444
29445
|
try {
|
|
27445
29446
|
return toPosixPath(
|
|
27446
|
-
(0,
|
|
29447
|
+
(0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
27447
29448
|
cwd: workingDirectory,
|
|
27448
29449
|
encoding: "utf8",
|
|
27449
29450
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -27465,10 +29466,10 @@ function resolveCheckTarget(cwd, target) {
|
|
|
27465
29466
|
]
|
|
27466
29467
|
};
|
|
27467
29468
|
}
|
|
27468
|
-
const candidatePath = (0,
|
|
29469
|
+
const candidatePath = (0, import_node_path11.resolve)(cwd, resolvedTarget);
|
|
27469
29470
|
try {
|
|
27470
|
-
const absolutePath = toPosixPath((0,
|
|
27471
|
-
const stats = (0,
|
|
29471
|
+
const absolutePath = toPosixPath((0, import_node_fs8.realpathSync)(candidatePath));
|
|
29472
|
+
const stats = (0, import_node_fs8.statSync)(absolutePath);
|
|
27472
29473
|
if (!stats.isDirectory() && !stats.isFile()) {
|
|
27473
29474
|
return {
|
|
27474
29475
|
success: false,
|
|
@@ -27479,7 +29480,7 @@ function resolveCheckTarget(cwd, target) {
|
|
|
27479
29480
|
]
|
|
27480
29481
|
};
|
|
27481
29482
|
}
|
|
27482
|
-
const scopeDirectory = stats.isDirectory() ? absolutePath : (0,
|
|
29483
|
+
const scopeDirectory = stats.isDirectory() ? absolutePath : (0, import_node_path11.dirname)(absolutePath);
|
|
27483
29484
|
const repoRoot = tryResolveGitRepoRoot(scopeDirectory);
|
|
27484
29485
|
return {
|
|
27485
29486
|
success: true,
|
|
@@ -27526,10 +29527,10 @@ function normalizeGitDiffPath(value) {
|
|
|
27526
29527
|
}
|
|
27527
29528
|
return value;
|
|
27528
29529
|
}
|
|
27529
|
-
function parseGitDiffChangedRanges(
|
|
29530
|
+
function parseGitDiffChangedRanges(output2) {
|
|
27530
29531
|
const changedRanges = /* @__PURE__ */ new Map();
|
|
27531
29532
|
let currentPath = null;
|
|
27532
|
-
for (const line of
|
|
29533
|
+
for (const line of output2.split(/\r?\n/)) {
|
|
27533
29534
|
if (line.startsWith("+++ ")) {
|
|
27534
29535
|
currentPath = normalizeGitDiffPath(line.slice(4));
|
|
27535
29536
|
if (currentPath && !changedRanges.has(currentPath)) {
|
|
@@ -27559,7 +29560,7 @@ function runGitCommand(cwd, args) {
|
|
|
27559
29560
|
try {
|
|
27560
29561
|
return {
|
|
27561
29562
|
success: true,
|
|
27562
|
-
stdout: (0,
|
|
29563
|
+
stdout: (0, import_node_child_process2.execFileSync)("git", args, {
|
|
27563
29564
|
cwd,
|
|
27564
29565
|
encoding: "utf8",
|
|
27565
29566
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -27617,8 +29618,8 @@ function resolveCheckScope(target, baseRef, headRef, registry) {
|
|
|
27617
29618
|
return diffScope;
|
|
27618
29619
|
}
|
|
27619
29620
|
function readGitStagedFileText(repoRoot, absolutePath) {
|
|
27620
|
-
const relPath = toPosixPath((0,
|
|
27621
|
-
if (!relPath || relPath.startsWith("..") || (0,
|
|
29621
|
+
const relPath = toPosixPath((0, import_node_path11.relative)(repoRoot, absolutePath));
|
|
29622
|
+
if (!relPath || relPath.startsWith("..") || (0, import_node_path11.isAbsolute)(relPath)) {
|
|
27622
29623
|
return {
|
|
27623
29624
|
success: false,
|
|
27624
29625
|
diagnostics: [
|
|
@@ -27634,7 +29635,7 @@ function readGitStagedFileText(repoRoot, absolutePath) {
|
|
|
27634
29635
|
};
|
|
27635
29636
|
}
|
|
27636
29637
|
try {
|
|
27637
|
-
const text = (0,
|
|
29638
|
+
const text = (0, import_node_child_process2.execFileSync)("git", ["show", `:${relPath}`], {
|
|
27638
29639
|
cwd: repoRoot,
|
|
27639
29640
|
encoding: "utf8",
|
|
27640
29641
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -27714,18 +29715,18 @@ function resolveStagedSecretsScope(target) {
|
|
|
27714
29715
|
};
|
|
27715
29716
|
}
|
|
27716
29717
|
const changedRangesByRelativePath = parseGitDiffChangedRanges(diffResult.stdout);
|
|
27717
|
-
const files = changedFilesResult.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => (0,
|
|
27718
|
-
(absolutePath) => target.isDirectory ? isPathWithinDirectory(target.absolutePath, absolutePath) : (0,
|
|
29718
|
+
const files = changedFilesResult.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => (0, import_node_path11.resolve)(target.repoRoot, line)).filter(
|
|
29719
|
+
(absolutePath) => target.isDirectory ? isPathWithinDirectory(target.absolutePath, absolutePath) : (0, import_node_path11.resolve)(absolutePath) === target.absolutePath
|
|
27719
29720
|
).filter((absolutePath) => {
|
|
27720
29721
|
try {
|
|
27721
|
-
return (0,
|
|
29722
|
+
return (0, import_node_fs8.statSync)(absolutePath).isFile();
|
|
27722
29723
|
} catch {
|
|
27723
29724
|
return true;
|
|
27724
29725
|
}
|
|
27725
29726
|
}).sort((left, right) => left.localeCompare(right));
|
|
27726
29727
|
const changedRangesByAbsolutePath = /* @__PURE__ */ new Map();
|
|
27727
29728
|
for (const absolutePath of files) {
|
|
27728
|
-
const relativePath = toPosixPath((0,
|
|
29729
|
+
const relativePath = toPosixPath((0, import_node_path11.relative)(target.repoRoot, absolutePath));
|
|
27729
29730
|
changedRangesByAbsolutePath.set(
|
|
27730
29731
|
absolutePath,
|
|
27731
29732
|
changedRangesByRelativePath.get(relativePath) ?? []
|
|
@@ -27835,18 +29836,18 @@ function resolveRepositoryDiffScope(target, baseRef, headRef, includeAbsolutePat
|
|
|
27835
29836
|
};
|
|
27836
29837
|
}
|
|
27837
29838
|
const changedRangesByRelativePath = parseGitDiffChangedRanges(diffResult.stdout);
|
|
27838
|
-
const files = changedFilesResult.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => (0,
|
|
27839
|
-
(absolutePath) => includeAbsolutePath(absolutePath) && (target.isDirectory ? isPathWithinDirectory(target.absolutePath, absolutePath) : (0,
|
|
29839
|
+
const files = changedFilesResult.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => (0, import_node_path11.resolve)(target.repoRoot, line)).filter(
|
|
29840
|
+
(absolutePath) => includeAbsolutePath(absolutePath) && (target.isDirectory ? isPathWithinDirectory(target.absolutePath, absolutePath) : (0, import_node_path11.resolve)(absolutePath) === target.absolutePath)
|
|
27840
29841
|
).filter((absolutePath) => {
|
|
27841
29842
|
try {
|
|
27842
|
-
return (0,
|
|
29843
|
+
return (0, import_node_fs8.statSync)(absolutePath).isFile();
|
|
27843
29844
|
} catch {
|
|
27844
29845
|
return false;
|
|
27845
29846
|
}
|
|
27846
29847
|
}).sort((left, right) => left.localeCompare(right));
|
|
27847
29848
|
const changedRangesByAbsolutePath = /* @__PURE__ */ new Map();
|
|
27848
29849
|
for (const absolutePath of files) {
|
|
27849
|
-
const relativePath = toPosixPath((0,
|
|
29850
|
+
const relativePath = toPosixPath((0, import_node_path11.relative)(target.repoRoot, absolutePath));
|
|
27850
29851
|
changedRangesByAbsolutePath.set(
|
|
27851
29852
|
absolutePath,
|
|
27852
29853
|
changedRangesByRelativePath.get(relativePath) ?? []
|
|
@@ -28334,13 +30335,13 @@ function packagePolicyId(packageName, versionRange) {
|
|
|
28334
30335
|
}
|
|
28335
30336
|
return void 0;
|
|
28336
30337
|
}
|
|
28337
|
-
function collectNpmDependencies(
|
|
28338
|
-
if ((0, import_posix2.basename)(
|
|
30338
|
+
function collectNpmDependencies(input2) {
|
|
30339
|
+
if ((0, import_posix2.basename)(input2.path) !== "package.json") {
|
|
28339
30340
|
return [];
|
|
28340
30341
|
}
|
|
28341
30342
|
let parsed;
|
|
28342
30343
|
try {
|
|
28343
|
-
parsed = JSON.parse(
|
|
30344
|
+
parsed = JSON.parse(input2.text);
|
|
28344
30345
|
} catch {
|
|
28345
30346
|
return [];
|
|
28346
30347
|
}
|
|
@@ -28364,17 +30365,17 @@ function collectNpmDependencies(input) {
|
|
|
28364
30365
|
ecosystem: "npm",
|
|
28365
30366
|
packageName,
|
|
28366
30367
|
versionRange: rawVersion,
|
|
28367
|
-
manifestPath:
|
|
30368
|
+
manifestPath: input2.path,
|
|
28368
30369
|
policyId: packagePolicyId(packageName, rawVersion)
|
|
28369
30370
|
});
|
|
28370
30371
|
}
|
|
28371
30372
|
}
|
|
28372
30373
|
return facts;
|
|
28373
30374
|
}
|
|
28374
|
-
function collectLineBasedDependencies(
|
|
28375
|
-
const name = (0, import_posix2.basename)(
|
|
30375
|
+
function collectLineBasedDependencies(input2) {
|
|
30376
|
+
const name = (0, import_posix2.basename)(input2.path);
|
|
28376
30377
|
const facts = [];
|
|
28377
|
-
for (const line of
|
|
30378
|
+
for (const line of input2.text.split(/\r?\n/u)) {
|
|
28378
30379
|
const trimmed = line.trim();
|
|
28379
30380
|
let match = null;
|
|
28380
30381
|
let ecosystem;
|
|
@@ -28399,13 +30400,13 @@ function collectLineBasedDependencies(input) {
|
|
|
28399
30400
|
ecosystem,
|
|
28400
30401
|
packageName: match[1],
|
|
28401
30402
|
versionRange: match[2],
|
|
28402
|
-
manifestPath:
|
|
30403
|
+
manifestPath: input2.path
|
|
28403
30404
|
});
|
|
28404
30405
|
}
|
|
28405
30406
|
return facts;
|
|
28406
30407
|
}
|
|
28407
|
-
function collectRegexDependencies(
|
|
28408
|
-
const name = (0, import_posix2.basename)(
|
|
30408
|
+
function collectRegexDependencies(input2) {
|
|
30409
|
+
const name = (0, import_posix2.basename)(input2.path);
|
|
28409
30410
|
const facts = [];
|
|
28410
30411
|
let ecosystem;
|
|
28411
30412
|
let pattern;
|
|
@@ -28422,7 +30423,7 @@ function collectRegexDependencies(input) {
|
|
|
28422
30423
|
if (!ecosystem || !pattern) {
|
|
28423
30424
|
return facts;
|
|
28424
30425
|
}
|
|
28425
|
-
for (const match of
|
|
30426
|
+
for (const match of input2.text.matchAll(pattern)) {
|
|
28426
30427
|
const packageName = match[1] ?? match[3];
|
|
28427
30428
|
const versionRange = match[2] ?? match[4] ?? match[5];
|
|
28428
30429
|
if (!packageName || !versionRange) {
|
|
@@ -28432,7 +30433,7 @@ function collectRegexDependencies(input) {
|
|
|
28432
30433
|
ecosystem,
|
|
28433
30434
|
packageName,
|
|
28434
30435
|
versionRange,
|
|
28435
|
-
manifestPath:
|
|
30436
|
+
manifestPath: input2.path
|
|
28436
30437
|
});
|
|
28437
30438
|
}
|
|
28438
30439
|
return facts;
|
|
@@ -28442,10 +30443,10 @@ function isDependencyManifestPath(path) {
|
|
|
28442
30443
|
return name === "package.json" || name === "package-lock.json" || name === "yarn.lock" || name === "pnpm-lock.yaml" || name === "go.mod" || name === "pom.xml" || name.endsWith(".gradle") || name === "composer.json" || name === "composer.lock" || name === "requirements.txt" || name === "requirements.lock" || name === "Gemfile" || name === "Gemfile.lock" || name === "Cargo.toml" || name === "Cargo.lock";
|
|
28443
30444
|
}
|
|
28444
30445
|
function collectProjectDependencyFacts(manifests) {
|
|
28445
|
-
return manifests.flatMap((
|
|
28446
|
-
...collectNpmDependencies(
|
|
28447
|
-
...collectLineBasedDependencies(
|
|
28448
|
-
...collectRegexDependencies(
|
|
30446
|
+
return manifests.flatMap((input2) => [
|
|
30447
|
+
...collectNpmDependencies(input2),
|
|
30448
|
+
...collectLineBasedDependencies(input2),
|
|
30449
|
+
...collectRegexDependencies(input2)
|
|
28449
30450
|
]);
|
|
28450
30451
|
}
|
|
28451
30452
|
function appendDependencyFacts(analyzedFiles, dependencyFacts) {
|
|
@@ -29279,15 +31280,15 @@ function augmentProjectFacts(analyzedFiles, options) {
|
|
|
29279
31280
|
var CHECK_ENGINE_KIND = "critiq-cli";
|
|
29280
31281
|
function loadCheckEngineVersion() {
|
|
29281
31282
|
const candidatePaths = [
|
|
29282
|
-
(0,
|
|
29283
|
-
(0,
|
|
29284
|
-
(0,
|
|
31283
|
+
(0, import_node_path12.resolve)(__dirname, "./package.json"),
|
|
31284
|
+
(0, import_node_path12.resolve)(__dirname, "../package.json"),
|
|
31285
|
+
(0, import_node_path12.resolve)(__dirname, "../../../package.json")
|
|
29285
31286
|
];
|
|
29286
31287
|
try {
|
|
29287
31288
|
for (const candidatePath of candidatePaths) {
|
|
29288
31289
|
try {
|
|
29289
31290
|
const packageJson = JSON.parse(
|
|
29290
|
-
(0,
|
|
31291
|
+
(0, import_node_fs9.readFileSync)(candidatePath, "utf8")
|
|
29291
31292
|
);
|
|
29292
31293
|
if (packageJson.version && packageJson.version.trim().length > 0) {
|
|
29293
31294
|
return packageJson.version.trim();
|
|
@@ -29352,8 +31353,8 @@ function resolveCatalogPackageRuntime(displayRoot, packageName, options) {
|
|
|
29352
31353
|
data: {
|
|
29353
31354
|
packageName,
|
|
29354
31355
|
packageRoot: packageRootOverride,
|
|
29355
|
-
entryPath: (0,
|
|
29356
|
-
catalogPath: (0,
|
|
31356
|
+
entryPath: (0, import_node_path12.resolve)(packageRootOverride, "package.json"),
|
|
31357
|
+
catalogPath: (0, import_node_path12.resolve)(
|
|
29357
31358
|
packageRootOverride,
|
|
29358
31359
|
RULE_CATALOG_FILENAME
|
|
29359
31360
|
)
|
|
@@ -29619,7 +31620,7 @@ function runCheckCommand(options = {}) {
|
|
|
29619
31620
|
continue;
|
|
29620
31621
|
}
|
|
29621
31622
|
sourceTextsByPath.set(displayPath, textResult.text);
|
|
29622
|
-
const adapter = registry.findAdapterForPath(displayPath);
|
|
31623
|
+
const adapter = registry.findAdapterForPath(displayPath, textResult.text);
|
|
29623
31624
|
if (!adapter) {
|
|
29624
31625
|
diagnostics.push(
|
|
29625
31626
|
createCheckRuntimeDiagnostic(
|
|
@@ -29649,6 +31650,9 @@ function runCheckCommand(options = {}) {
|
|
|
29649
31650
|
});
|
|
29650
31651
|
continue;
|
|
29651
31652
|
}
|
|
31653
|
+
if (analysis.diagnostics?.length) {
|
|
31654
|
+
diagnostics.push(...analysis.diagnostics);
|
|
31655
|
+
}
|
|
29652
31656
|
analyzedFiles.push({
|
|
29653
31657
|
...analysis.data,
|
|
29654
31658
|
changedRanges: filteredScope.changedRangesByAbsolutePath.get(absolutePath)
|
|
@@ -29764,7 +31768,7 @@ function runCheckCommand(options = {}) {
|
|
|
29764
31768
|
}
|
|
29765
31769
|
|
|
29766
31770
|
// libs/runtime/check-runner/src/lib/secrets-scanner/run-secrets-scan.ts
|
|
29767
|
-
var
|
|
31771
|
+
var import_node_fs10 = require("node:fs");
|
|
29768
31772
|
|
|
29769
31773
|
// libs/runtime/check-runner/src/lib/secrets-scanner/detectors.ts
|
|
29770
31774
|
var import_node_crypto3 = require("node:crypto");
|
|
@@ -30097,7 +32101,7 @@ function runSecretsScan(options = {}) {
|
|
|
30097
32101
|
} else {
|
|
30098
32102
|
let size = 0;
|
|
30099
32103
|
try {
|
|
30100
|
-
size = (0,
|
|
32104
|
+
size = (0, import_node_fs10.statSync)(absolutePath).size;
|
|
30101
32105
|
} catch {
|
|
30102
32106
|
continue;
|
|
30103
32107
|
}
|
|
@@ -30175,8 +32179,8 @@ function toCheckSecretsScanPayload(result) {
|
|
|
30175
32179
|
}
|
|
30176
32180
|
|
|
30177
32181
|
// apps/cli/src/commands/audit-secrets.command.ts
|
|
30178
|
-
var
|
|
30179
|
-
var
|
|
32182
|
+
var import_node_fs11 = require("node:fs");
|
|
32183
|
+
var import_node_path13 = require("node:path");
|
|
30180
32184
|
|
|
30181
32185
|
// apps/cli/src/rendering/check/terminal-frames.rendering.ts
|
|
30182
32186
|
var ansi = {
|
|
@@ -30501,9 +32505,9 @@ function collectSourceTextsForFindings(displayRoot, result) {
|
|
|
30501
32505
|
}
|
|
30502
32506
|
const map = /* @__PURE__ */ new Map();
|
|
30503
32507
|
for (const displayPath of paths) {
|
|
30504
|
-
const absolutePath = (0,
|
|
32508
|
+
const absolutePath = (0, import_node_path13.isAbsolute)(displayPath) ? displayPath : (0, import_node_path13.resolve)(displayRoot, displayPath);
|
|
30505
32509
|
try {
|
|
30506
|
-
map.set(displayPath, (0,
|
|
32510
|
+
map.set(displayPath, (0, import_node_fs11.readFileSync)(absolutePath, "utf8"));
|
|
30507
32511
|
} catch {
|
|
30508
32512
|
}
|
|
30509
32513
|
}
|
|
@@ -30803,8 +32807,597 @@ function renderCheckSarif(envelope) {
|
|
|
30803
32807
|
);
|
|
30804
32808
|
}
|
|
30805
32809
|
|
|
32810
|
+
// apps/cli/src/utils/ensure-catalog-package.util.ts
|
|
32811
|
+
var import_node_fs16 = require("node:fs");
|
|
32812
|
+
var import_node_path18 = require("node:path");
|
|
32813
|
+
|
|
32814
|
+
// apps/cli/src/utils/detect-cli-install-scope.util.ts
|
|
32815
|
+
var import_node_fs13 = require("node:fs");
|
|
32816
|
+
var import_node_path15 = require("node:path");
|
|
32817
|
+
|
|
32818
|
+
// apps/cli/src/utils/node-modules-roots.util.ts
|
|
32819
|
+
var import_node_child_process3 = require("node:child_process");
|
|
32820
|
+
var import_node_fs12 = require("node:fs");
|
|
32821
|
+
var import_node_path14 = require("node:path");
|
|
32822
|
+
function tryCommandOutput(command, args) {
|
|
32823
|
+
try {
|
|
32824
|
+
return (0, import_node_child_process3.execFileSync)(command, args, {
|
|
32825
|
+
encoding: "utf8",
|
|
32826
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
32827
|
+
}).trim();
|
|
32828
|
+
} catch {
|
|
32829
|
+
return null;
|
|
32830
|
+
}
|
|
32831
|
+
}
|
|
32832
|
+
function getRepoNodeModulesRoot(cwd) {
|
|
32833
|
+
return (0, import_node_path14.resolve)(cwd, "node_modules");
|
|
32834
|
+
}
|
|
32835
|
+
function getGlobalNodeModulesRoots() {
|
|
32836
|
+
const roots = /* @__PURE__ */ new Set();
|
|
32837
|
+
const npmRoot = tryCommandOutput("npm", ["root", "-g"]);
|
|
32838
|
+
if (npmRoot) {
|
|
32839
|
+
roots.add(npmRoot);
|
|
32840
|
+
}
|
|
32841
|
+
const pnpmRoot = tryCommandOutput("pnpm", ["root", "-g"]);
|
|
32842
|
+
if (pnpmRoot) {
|
|
32843
|
+
roots.add(pnpmRoot);
|
|
32844
|
+
}
|
|
32845
|
+
const yarnGlobalDir = tryCommandOutput("yarn", ["global", "dir"]);
|
|
32846
|
+
if (yarnGlobalDir) {
|
|
32847
|
+
roots.add((0, import_node_path14.resolve)(yarnGlobalDir, "node_modules"));
|
|
32848
|
+
}
|
|
32849
|
+
return [...roots].filter((root) => (0, import_node_fs12.existsSync)(root));
|
|
32850
|
+
}
|
|
32851
|
+
|
|
32852
|
+
// apps/cli/src/utils/detect-cli-install-scope.util.ts
|
|
32853
|
+
function safeRealpath(path) {
|
|
32854
|
+
try {
|
|
32855
|
+
return (0, import_node_fs13.realpathSync)(path);
|
|
32856
|
+
} catch {
|
|
32857
|
+
return null;
|
|
32858
|
+
}
|
|
32859
|
+
}
|
|
32860
|
+
function normalizeEntryPath(entryPath) {
|
|
32861
|
+
if (!entryPath) {
|
|
32862
|
+
return null;
|
|
32863
|
+
}
|
|
32864
|
+
return safeRealpath(entryPath) ?? (0, import_node_path15.resolve)(entryPath);
|
|
32865
|
+
}
|
|
32866
|
+
function detectCliInstallScope(cwd, entryPath = process.argv[1]) {
|
|
32867
|
+
const resolvedEntryPath = normalizeEntryPath(entryPath);
|
|
32868
|
+
if (!resolvedEntryPath) {
|
|
32869
|
+
return "external";
|
|
32870
|
+
}
|
|
32871
|
+
const repoNodeModules = safeRealpath(getRepoNodeModulesRoot(cwd));
|
|
32872
|
+
if (repoNodeModules) {
|
|
32873
|
+
const localCliMain = safeRealpath(
|
|
32874
|
+
(0, import_node_path15.resolve)(repoNodeModules, "@critiq/cli/main.js")
|
|
32875
|
+
);
|
|
32876
|
+
const localBin = safeRealpath((0, import_node_path15.resolve)(repoNodeModules, ".bin/critiq"));
|
|
32877
|
+
if (resolvedEntryPath === localCliMain || resolvedEntryPath === localBin || resolvedEntryPath.startsWith(`${(0, import_node_path15.resolve)(repoNodeModules, "@critiq/cli")}${import_node_path15.sep}`)) {
|
|
32878
|
+
return "local";
|
|
32879
|
+
}
|
|
32880
|
+
}
|
|
32881
|
+
for (const globalRoot of getGlobalNodeModulesRoots()) {
|
|
32882
|
+
const globalCliMain = (0, import_node_path15.resolve)(globalRoot, "@critiq/cli/main.js");
|
|
32883
|
+
const globalBin = (0, import_node_path15.resolve)(globalRoot, ".bin/critiq");
|
|
32884
|
+
if (resolvedEntryPath === safeRealpath(globalCliMain) || resolvedEntryPath === safeRealpath(globalBin) || resolvedEntryPath.startsWith(`${(0, import_node_path15.resolve)(globalRoot, "@critiq/cli")}${import_node_path15.sep}`)) {
|
|
32885
|
+
return "global";
|
|
32886
|
+
}
|
|
32887
|
+
}
|
|
32888
|
+
if (resolvedEntryPath.includes(`${import_node_path15.sep}node_modules${import_node_path15.sep}`)) {
|
|
32889
|
+
return "external";
|
|
32890
|
+
}
|
|
32891
|
+
if ((0, import_node_fs13.existsSync)(resolvedEntryPath)) {
|
|
32892
|
+
return "external";
|
|
32893
|
+
}
|
|
32894
|
+
return "external";
|
|
32895
|
+
}
|
|
32896
|
+
|
|
32897
|
+
// apps/cli/src/utils/detect-package-manager.util.ts
|
|
32898
|
+
var import_node_fs14 = require("node:fs");
|
|
32899
|
+
var import_node_path16 = require("node:path");
|
|
32900
|
+
function readPackageManagerField(cwd) {
|
|
32901
|
+
const packageJsonPath = (0, import_node_path16.join)(cwd, "package.json");
|
|
32902
|
+
if (!(0, import_node_fs14.existsSync)(packageJsonPath)) {
|
|
32903
|
+
return null;
|
|
32904
|
+
}
|
|
32905
|
+
try {
|
|
32906
|
+
const manifest = JSON.parse((0, import_node_fs14.readFileSync)(packageJsonPath, "utf8"));
|
|
32907
|
+
const value = manifest.packageManager?.trim();
|
|
32908
|
+
if (!value) {
|
|
32909
|
+
return null;
|
|
32910
|
+
}
|
|
32911
|
+
if (value.startsWith("pnpm@")) {
|
|
32912
|
+
return "pnpm";
|
|
32913
|
+
}
|
|
32914
|
+
if (value.startsWith("yarn@")) {
|
|
32915
|
+
return "yarn";
|
|
32916
|
+
}
|
|
32917
|
+
if (value.startsWith("npm@")) {
|
|
32918
|
+
return "npm";
|
|
32919
|
+
}
|
|
32920
|
+
if (value.startsWith("bun@")) {
|
|
32921
|
+
return "bun";
|
|
32922
|
+
}
|
|
32923
|
+
} catch {
|
|
32924
|
+
return null;
|
|
32925
|
+
}
|
|
32926
|
+
return null;
|
|
32927
|
+
}
|
|
32928
|
+
function detectPackageManager(cwd) {
|
|
32929
|
+
const fromField = readPackageManagerField(cwd);
|
|
32930
|
+
if (fromField) {
|
|
32931
|
+
return fromField;
|
|
32932
|
+
}
|
|
32933
|
+
if ((0, import_node_fs14.existsSync)((0, import_node_path16.join)(cwd, "bun.lockb")) || (0, import_node_fs14.existsSync)((0, import_node_path16.join)(cwd, "bun.lock"))) {
|
|
32934
|
+
return "bun";
|
|
32935
|
+
}
|
|
32936
|
+
if ((0, import_node_fs14.existsSync)((0, import_node_path16.join)(cwd, "pnpm-lock.yaml"))) {
|
|
32937
|
+
return "pnpm";
|
|
32938
|
+
}
|
|
32939
|
+
if ((0, import_node_fs14.existsSync)((0, import_node_path16.join)(cwd, "yarn.lock"))) {
|
|
32940
|
+
return "yarn";
|
|
32941
|
+
}
|
|
32942
|
+
if ((0, import_node_fs14.existsSync)((0, import_node_path16.join)(cwd, "package-lock.json")) || (0, import_node_fs14.existsSync)((0, import_node_path16.join)(cwd, "npm-shrinkwrap.json"))) {
|
|
32943
|
+
return "npm";
|
|
32944
|
+
}
|
|
32945
|
+
return "npm";
|
|
32946
|
+
}
|
|
32947
|
+
function buildCatalogInstallCommand(packageManager, packageName, scope) {
|
|
32948
|
+
switch (packageManager) {
|
|
32949
|
+
case "yarn":
|
|
32950
|
+
if (scope === "global") {
|
|
32951
|
+
return {
|
|
32952
|
+
executable: "yarn",
|
|
32953
|
+
args: ["global", "add", packageName],
|
|
32954
|
+
display: `yarn global add ${packageName}`
|
|
32955
|
+
};
|
|
32956
|
+
}
|
|
32957
|
+
return {
|
|
32958
|
+
executable: "yarn",
|
|
32959
|
+
args: ["add", "-D", packageName],
|
|
32960
|
+
display: `yarn add -D ${packageName}`
|
|
32961
|
+
};
|
|
32962
|
+
case "pnpm":
|
|
32963
|
+
if (scope === "global") {
|
|
32964
|
+
return {
|
|
32965
|
+
executable: "pnpm",
|
|
32966
|
+
args: ["add", "-g", packageName],
|
|
32967
|
+
display: `pnpm add -g ${packageName}`
|
|
32968
|
+
};
|
|
32969
|
+
}
|
|
32970
|
+
return {
|
|
32971
|
+
executable: "pnpm",
|
|
32972
|
+
args: ["add", "-D", packageName],
|
|
32973
|
+
display: `pnpm add -D ${packageName}`
|
|
32974
|
+
};
|
|
32975
|
+
case "bun":
|
|
32976
|
+
if (scope === "global") {
|
|
32977
|
+
return {
|
|
32978
|
+
executable: "bun",
|
|
32979
|
+
args: ["add", "-g", packageName],
|
|
32980
|
+
display: `bun add -g ${packageName}`
|
|
32981
|
+
};
|
|
32982
|
+
}
|
|
32983
|
+
return {
|
|
32984
|
+
executable: "bun",
|
|
32985
|
+
args: ["add", "-d", packageName],
|
|
32986
|
+
display: `bun add -d ${packageName}`
|
|
32987
|
+
};
|
|
32988
|
+
case "npm":
|
|
32989
|
+
if (scope === "global") {
|
|
32990
|
+
return {
|
|
32991
|
+
executable: "npm",
|
|
32992
|
+
args: ["install", "-g", packageName],
|
|
32993
|
+
display: `npm install -g ${packageName}`
|
|
32994
|
+
};
|
|
32995
|
+
}
|
|
32996
|
+
return {
|
|
32997
|
+
executable: "npm",
|
|
32998
|
+
args: ["install", "-D", packageName],
|
|
32999
|
+
display: `npm install -D ${packageName}`
|
|
33000
|
+
};
|
|
33001
|
+
}
|
|
33002
|
+
}
|
|
33003
|
+
|
|
33004
|
+
// apps/cli/src/utils/format-catalog-not-found-message.util.ts
|
|
33005
|
+
function formatSuggestedInstallCommands(cwd, packageName) {
|
|
33006
|
+
const packageManager = detectPackageManager(cwd);
|
|
33007
|
+
const localCommand = buildCatalogInstallCommand(
|
|
33008
|
+
packageManager,
|
|
33009
|
+
packageName,
|
|
33010
|
+
"local"
|
|
33011
|
+
);
|
|
33012
|
+
const globalCommand = buildCatalogInstallCommand(
|
|
33013
|
+
packageManager,
|
|
33014
|
+
packageName,
|
|
33015
|
+
"global"
|
|
33016
|
+
);
|
|
33017
|
+
const oneShotCommand = `npx -p @critiq/cli -p ${packageName} critiq check .`;
|
|
33018
|
+
return [
|
|
33019
|
+
`Install in this repository: ${localCommand.display}`,
|
|
33020
|
+
`Install globally: ${globalCommand.display}`,
|
|
33021
|
+
`Run once without installing: ${oneShotCommand}`
|
|
33022
|
+
];
|
|
33023
|
+
}
|
|
33024
|
+
function formatCatalogPackageNotFoundMessage(input2) {
|
|
33025
|
+
const lines = [
|
|
33026
|
+
`Critiq could not find the rules catalog package \`${input2.packageName}\`.`
|
|
33027
|
+
];
|
|
33028
|
+
if (input2.cliScope === "local") {
|
|
33029
|
+
lines.push(
|
|
33030
|
+
"The CLI is installed in this repository, but the rules catalog is missing from `./node_modules`."
|
|
33031
|
+
);
|
|
33032
|
+
} else if (input2.cliScope === "global") {
|
|
33033
|
+
lines.push(
|
|
33034
|
+
"The CLI is installed globally. Critiq checked this repository and your global Node modules, but the rules catalog is not installed in either place."
|
|
33035
|
+
);
|
|
33036
|
+
} else {
|
|
33037
|
+
lines.push(
|
|
33038
|
+
"Critiq checked this repository and your global Node modules, but the rules catalog is not installed in either place."
|
|
33039
|
+
);
|
|
33040
|
+
}
|
|
33041
|
+
if (input2.includeInstallSuggestions) {
|
|
33042
|
+
lines.push("", "You can install the default OSS rules catalog with:");
|
|
33043
|
+
for (const suggestion of formatSuggestedInstallCommands(
|
|
33044
|
+
input2.cwd,
|
|
33045
|
+
input2.packageName
|
|
33046
|
+
)) {
|
|
33047
|
+
lines.push(` ${suggestion}`);
|
|
33048
|
+
}
|
|
33049
|
+
} else {
|
|
33050
|
+
lines.push(
|
|
33051
|
+
"",
|
|
33052
|
+
`Install the catalog package manually, or point \`catalog.package\` in \`.critiq/config.yaml\` at the package you want to use.`
|
|
33053
|
+
);
|
|
33054
|
+
}
|
|
33055
|
+
lines.push(
|
|
33056
|
+
"",
|
|
33057
|
+
"For CI or non-interactive runs, install the catalog before running `critiq check`."
|
|
33058
|
+
);
|
|
33059
|
+
return lines.join("\n");
|
|
33060
|
+
}
|
|
33061
|
+
function formatInstallCancelledMessage(packageName) {
|
|
33062
|
+
return `Cancelled. \`${packageName}\` was not installed, so the scan did not run.`;
|
|
33063
|
+
}
|
|
33064
|
+
function formatInstallFailureMessage(command) {
|
|
33065
|
+
return `Failed to install the rules catalog with \`${command.display}\`. Fix the error above and try again.`;
|
|
33066
|
+
}
|
|
33067
|
+
function formatInstallSuccessMessage(command) {
|
|
33068
|
+
return `Installed the rules catalog with \`${command.display}\`. Continuing with the scan...`;
|
|
33069
|
+
}
|
|
33070
|
+
function formatLocalInstallUnavailableMessage() {
|
|
33071
|
+
return "A local install requires a `package.json` in this repository. Choose global install instead, or create a package manifest first.";
|
|
33072
|
+
}
|
|
33073
|
+
|
|
33074
|
+
// apps/cli/src/utils/prompt.util.ts
|
|
33075
|
+
var import_promises = require("node:readline/promises");
|
|
33076
|
+
var import_node_process = require("node:process");
|
|
33077
|
+
async function promptChoice(promptInput) {
|
|
33078
|
+
const lines = [promptInput.title, ""];
|
|
33079
|
+
for (const option of promptInput.options) {
|
|
33080
|
+
const suffix = option.id === promptInput.defaultOptionId ? " (default)" : "";
|
|
33081
|
+
lines.push(` [${option.id}] ${option.label}${suffix}`);
|
|
33082
|
+
}
|
|
33083
|
+
lines.push("", "Enter a choice and press Return.");
|
|
33084
|
+
const readline = (0, import_promises.createInterface)({
|
|
33085
|
+
input: import_node_process.stdin,
|
|
33086
|
+
output: import_node_process.stdout,
|
|
33087
|
+
terminal: true
|
|
33088
|
+
});
|
|
33089
|
+
try {
|
|
33090
|
+
const answer = (await readline.question(`${lines.join("\n")}
|
|
33091
|
+
> `)).trim();
|
|
33092
|
+
if (answer.length === 0 && promptInput.defaultOptionId) {
|
|
33093
|
+
return promptInput.defaultOptionId;
|
|
33094
|
+
}
|
|
33095
|
+
const matched = promptInput.options.find((option) => option.id === answer);
|
|
33096
|
+
return matched?.id ?? null;
|
|
33097
|
+
} finally {
|
|
33098
|
+
readline.close();
|
|
33099
|
+
}
|
|
33100
|
+
}
|
|
33101
|
+
|
|
33102
|
+
// apps/cli/src/utils/probe-catalog-package.util.ts
|
|
33103
|
+
function probeFromBasePath(cwd, packageName, basePath) {
|
|
33104
|
+
const result = resolveCatalogPackage(cwd, packageName, [basePath]);
|
|
33105
|
+
if (!result.success) {
|
|
33106
|
+
return null;
|
|
33107
|
+
}
|
|
33108
|
+
return result.data.packageRoot;
|
|
33109
|
+
}
|
|
33110
|
+
function probeCatalogPackageInRepo(cwd, packageName) {
|
|
33111
|
+
return probeFromBasePath(cwd, packageName, getRepoNodeModulesRoot(cwd));
|
|
33112
|
+
}
|
|
33113
|
+
function probeCatalogPackageGlobally(cwd, packageName) {
|
|
33114
|
+
for (const globalRoot of getGlobalNodeModulesRoots()) {
|
|
33115
|
+
const packageRoot = probeFromBasePath(cwd, packageName, globalRoot);
|
|
33116
|
+
if (packageRoot) {
|
|
33117
|
+
return packageRoot;
|
|
33118
|
+
}
|
|
33119
|
+
}
|
|
33120
|
+
return null;
|
|
33121
|
+
}
|
|
33122
|
+
function probeCatalogPackageResolution(cwd, packageName, options = {}) {
|
|
33123
|
+
const repoPackageRoot = probeCatalogPackageInRepo(cwd, packageName);
|
|
33124
|
+
if (repoPackageRoot) {
|
|
33125
|
+
return {
|
|
33126
|
+
packageName,
|
|
33127
|
+
location: "repo",
|
|
33128
|
+
packageRoot: repoPackageRoot
|
|
33129
|
+
};
|
|
33130
|
+
}
|
|
33131
|
+
if (options.includeGlobal !== false) {
|
|
33132
|
+
const globalPackageRoot = probeCatalogPackageGlobally(cwd, packageName);
|
|
33133
|
+
if (globalPackageRoot) {
|
|
33134
|
+
return {
|
|
33135
|
+
packageName,
|
|
33136
|
+
location: "global",
|
|
33137
|
+
packageRoot: globalPackageRoot
|
|
33138
|
+
};
|
|
33139
|
+
}
|
|
33140
|
+
}
|
|
33141
|
+
return null;
|
|
33142
|
+
}
|
|
33143
|
+
|
|
33144
|
+
// apps/cli/src/utils/resolve-catalog-package-name.util.ts
|
|
33145
|
+
var import_node_fs15 = require("node:fs");
|
|
33146
|
+
var import_node_path17 = require("node:path");
|
|
33147
|
+
function resolveCatalogPackageNameForEnsure(cwd) {
|
|
33148
|
+
const loaded = loadCritiqConfigForDirectory(cwd);
|
|
33149
|
+
if (!loaded.success) {
|
|
33150
|
+
const firstCode = loaded.diagnostics[0]?.code;
|
|
33151
|
+
if (firstCode === "config.file.not-found") {
|
|
33152
|
+
return DEFAULT_CATALOG_PACKAGE_NAME;
|
|
33153
|
+
}
|
|
33154
|
+
return null;
|
|
33155
|
+
}
|
|
33156
|
+
return loaded.data.catalogPackage ?? DEFAULT_CATALOG_PACKAGE_NAME;
|
|
33157
|
+
}
|
|
33158
|
+
function repoHasPackageJson(cwd) {
|
|
33159
|
+
return (0, import_node_fs15.existsSync)((0, import_node_path17.join)(cwd, "package.json"));
|
|
33160
|
+
}
|
|
33161
|
+
function canInstallCatalogLocally(cwd) {
|
|
33162
|
+
return repoHasPackageJson(cwd);
|
|
33163
|
+
}
|
|
33164
|
+
function isDefaultInstallableCatalogPackage(packageName) {
|
|
33165
|
+
return packageName === DEFAULT_CATALOG_PACKAGE_NAME;
|
|
33166
|
+
}
|
|
33167
|
+
|
|
33168
|
+
// apps/cli/src/utils/run-package-install.util.ts
|
|
33169
|
+
var import_node_child_process4 = require("node:child_process");
|
|
33170
|
+
function runPackageInstall(options) {
|
|
33171
|
+
options.writeStdout(`Running: ${options.command.display}`);
|
|
33172
|
+
try {
|
|
33173
|
+
(0, import_node_child_process4.execFileSync)(options.command.executable, [...options.command.args], {
|
|
33174
|
+
cwd: options.cwd,
|
|
33175
|
+
stdio: "inherit"
|
|
33176
|
+
});
|
|
33177
|
+
return true;
|
|
33178
|
+
} catch {
|
|
33179
|
+
options.writeStderr(
|
|
33180
|
+
`Command failed: ${options.command.display}`
|
|
33181
|
+
);
|
|
33182
|
+
return false;
|
|
33183
|
+
}
|
|
33184
|
+
}
|
|
33185
|
+
|
|
33186
|
+
// apps/cli/src/utils/ensure-catalog-package.util.ts
|
|
33187
|
+
function buildCatalogRuntimeOptions(packageName, packageRoot, cwd) {
|
|
33188
|
+
if (probeCatalogPackageInRepo(cwd, packageName)) {
|
|
33189
|
+
return {
|
|
33190
|
+
ok: true,
|
|
33191
|
+
catalogResolverBasePaths: [cwd]
|
|
33192
|
+
};
|
|
33193
|
+
}
|
|
33194
|
+
return {
|
|
33195
|
+
ok: true,
|
|
33196
|
+
catalogPackageRoots: {
|
|
33197
|
+
[packageName]: packageRoot
|
|
33198
|
+
},
|
|
33199
|
+
catalogResolverBasePaths: [cwd]
|
|
33200
|
+
};
|
|
33201
|
+
}
|
|
33202
|
+
function failure(message, exitCode = 1) {
|
|
33203
|
+
return {
|
|
33204
|
+
ok: false,
|
|
33205
|
+
exitCode,
|
|
33206
|
+
message
|
|
33207
|
+
};
|
|
33208
|
+
}
|
|
33209
|
+
function shouldOfferInteractiveInstall(runtime, format) {
|
|
33210
|
+
if (format !== "pretty") {
|
|
33211
|
+
return false;
|
|
33212
|
+
}
|
|
33213
|
+
if (!runtime.isInteractive) {
|
|
33214
|
+
return false;
|
|
33215
|
+
}
|
|
33216
|
+
if (process.env["CI"]?.trim()) {
|
|
33217
|
+
return false;
|
|
33218
|
+
}
|
|
33219
|
+
return true;
|
|
33220
|
+
}
|
|
33221
|
+
async function installCatalogPackage(runtime, packageName, scope) {
|
|
33222
|
+
const packageManager = detectPackageManager(runtime.cwd);
|
|
33223
|
+
const command = buildCatalogInstallCommand(
|
|
33224
|
+
packageManager,
|
|
33225
|
+
packageName,
|
|
33226
|
+
scope
|
|
33227
|
+
);
|
|
33228
|
+
if (runtime.runPackageInstall) {
|
|
33229
|
+
return runtime.runPackageInstall({
|
|
33230
|
+
cwd: scope === "local" ? runtime.cwd : void 0,
|
|
33231
|
+
command
|
|
33232
|
+
});
|
|
33233
|
+
}
|
|
33234
|
+
return runPackageInstall({
|
|
33235
|
+
cwd: scope === "local" ? runtime.cwd : void 0,
|
|
33236
|
+
command,
|
|
33237
|
+
writeStdout: runtime.writeStdout,
|
|
33238
|
+
writeStderr: runtime.writeStderr
|
|
33239
|
+
});
|
|
33240
|
+
}
|
|
33241
|
+
async function promptForInstallChoice(runtime, packageName, cliScope) {
|
|
33242
|
+
if (runtime.promptChoice) {
|
|
33243
|
+
if (cliScope === "local") {
|
|
33244
|
+
return runtime.promptChoice({
|
|
33245
|
+
title: `The rules catalog \`${packageName}\` is not installed in this repository.`,
|
|
33246
|
+
options: [
|
|
33247
|
+
{ id: "local", label: "Install in this repository" },
|
|
33248
|
+
{ id: "cancel", label: "Cancel the scan" }
|
|
33249
|
+
],
|
|
33250
|
+
defaultOptionId: "local"
|
|
33251
|
+
});
|
|
33252
|
+
}
|
|
33253
|
+
const options2 = [
|
|
33254
|
+
...canInstallCatalogLocally(runtime.cwd) ? [{ id: "local", label: "Install in this repository" }] : [],
|
|
33255
|
+
{ id: "global", label: "Install globally" },
|
|
33256
|
+
{ id: "cancel", label: "Cancel the scan" }
|
|
33257
|
+
];
|
|
33258
|
+
return runtime.promptChoice({
|
|
33259
|
+
title: `The rules catalog \`${packageName}\` is not installed.`,
|
|
33260
|
+
options: [...options2],
|
|
33261
|
+
defaultOptionId: canInstallCatalogLocally(runtime.cwd) ? "local" : "global"
|
|
33262
|
+
});
|
|
33263
|
+
}
|
|
33264
|
+
if (cliScope === "local") {
|
|
33265
|
+
const choice2 = await promptChoice({
|
|
33266
|
+
title: `The rules catalog \`${packageName}\` is not installed in this repository.`,
|
|
33267
|
+
options: [
|
|
33268
|
+
{ id: "local", label: "Install in this repository" },
|
|
33269
|
+
{ id: "cancel", label: "Cancel the scan" }
|
|
33270
|
+
],
|
|
33271
|
+
defaultOptionId: "local"
|
|
33272
|
+
});
|
|
33273
|
+
return choice2;
|
|
33274
|
+
}
|
|
33275
|
+
const options = [
|
|
33276
|
+
...canInstallCatalogLocally(runtime.cwd) ? [{ id: "local", label: "Install in this repository" }] : [],
|
|
33277
|
+
{ id: "global", label: "Install globally" },
|
|
33278
|
+
{ id: "cancel", label: "Cancel the scan" }
|
|
33279
|
+
];
|
|
33280
|
+
const choice = await promptChoice({
|
|
33281
|
+
title: `The rules catalog \`${packageName}\` is not installed.`,
|
|
33282
|
+
options,
|
|
33283
|
+
defaultOptionId: canInstallCatalogLocally(runtime.cwd) ? "local" : "global"
|
|
33284
|
+
});
|
|
33285
|
+
return choice;
|
|
33286
|
+
}
|
|
33287
|
+
async function handleMissingCatalogPackage(runtime, packageName, cliScope, includeInstallSuggestions, format) {
|
|
33288
|
+
if (!shouldOfferInteractiveInstall(runtime, format)) {
|
|
33289
|
+
return failure(
|
|
33290
|
+
formatCatalogPackageNotFoundMessage({
|
|
33291
|
+
cwd: runtime.cwd,
|
|
33292
|
+
packageName,
|
|
33293
|
+
cliScope,
|
|
33294
|
+
includeInstallSuggestions
|
|
33295
|
+
})
|
|
33296
|
+
);
|
|
33297
|
+
}
|
|
33298
|
+
const choice = await promptForInstallChoice(runtime, packageName, cliScope);
|
|
33299
|
+
if (choice === null || choice === "cancel") {
|
|
33300
|
+
return failure(formatInstallCancelledMessage(packageName));
|
|
33301
|
+
}
|
|
33302
|
+
if (choice === "local" && !canInstallCatalogLocally(runtime.cwd)) {
|
|
33303
|
+
runtime.writeStderr(formatLocalInstallUnavailableMessage());
|
|
33304
|
+
return failure(formatInstallCancelledMessage(packageName));
|
|
33305
|
+
}
|
|
33306
|
+
const packageManager = detectPackageManager(runtime.cwd);
|
|
33307
|
+
const command = buildCatalogInstallCommand(
|
|
33308
|
+
packageManager,
|
|
33309
|
+
packageName,
|
|
33310
|
+
choice
|
|
33311
|
+
);
|
|
33312
|
+
runtime.writeStdout(`About to run: ${command.display}`);
|
|
33313
|
+
const installed = await installCatalogPackage(runtime, packageName, choice);
|
|
33314
|
+
if (!installed) {
|
|
33315
|
+
return failure(formatInstallFailureMessage(command));
|
|
33316
|
+
}
|
|
33317
|
+
runtime.writeStdout(formatInstallSuccessMessage(command));
|
|
33318
|
+
const packageRoot = choice === "local" ? probeCatalogPackageInRepo(runtime.cwd, packageName) : probeCatalogPackageGlobally(runtime.cwd, packageName);
|
|
33319
|
+
if (!packageRoot) {
|
|
33320
|
+
return failure(
|
|
33321
|
+
formatCatalogPackageNotFoundMessage({
|
|
33322
|
+
cwd: runtime.cwd,
|
|
33323
|
+
packageName,
|
|
33324
|
+
cliScope,
|
|
33325
|
+
includeInstallSuggestions
|
|
33326
|
+
})
|
|
33327
|
+
);
|
|
33328
|
+
}
|
|
33329
|
+
return buildCatalogRuntimeOptions(packageName, packageRoot, runtime.cwd);
|
|
33330
|
+
}
|
|
33331
|
+
function probeCatalogFromEnvironment() {
|
|
33332
|
+
const rulesRoot = process.env["CRITIQ_RULES_ROOT"]?.trim();
|
|
33333
|
+
if (!rulesRoot) {
|
|
33334
|
+
return null;
|
|
33335
|
+
}
|
|
33336
|
+
const candidateRoots = [
|
|
33337
|
+
(0, import_node_path18.resolve)(rulesRoot),
|
|
33338
|
+
(0, import_node_path18.resolve)(rulesRoot, "libs/rules/catalog")
|
|
33339
|
+
];
|
|
33340
|
+
for (const candidateRoot of candidateRoots) {
|
|
33341
|
+
if ((0, import_node_fs16.existsSync)((0, import_node_path18.resolve)(candidateRoot, "catalog.yaml"))) {
|
|
33342
|
+
return candidateRoot;
|
|
33343
|
+
}
|
|
33344
|
+
}
|
|
33345
|
+
return null;
|
|
33346
|
+
}
|
|
33347
|
+
async function ensureCatalogPackageForCheck(runtime, format) {
|
|
33348
|
+
const packageName = resolveCatalogPackageNameForEnsure(runtime.cwd);
|
|
33349
|
+
if (!packageName) {
|
|
33350
|
+
return { ok: true };
|
|
33351
|
+
}
|
|
33352
|
+
const environmentCatalogRoot = probeCatalogFromEnvironment();
|
|
33353
|
+
if (environmentCatalogRoot) {
|
|
33354
|
+
return buildCatalogRuntimeOptions(
|
|
33355
|
+
packageName,
|
|
33356
|
+
environmentCatalogRoot,
|
|
33357
|
+
runtime.cwd
|
|
33358
|
+
);
|
|
33359
|
+
}
|
|
33360
|
+
const cliScope = runtime.cliInstallScope ?? detectCliInstallScope(runtime.cwd);
|
|
33361
|
+
const includeGlobalLookup = cliScope !== "local";
|
|
33362
|
+
const resolved = probeCatalogPackageResolution(runtime.cwd, packageName, {
|
|
33363
|
+
includeGlobal: includeGlobalLookup
|
|
33364
|
+
});
|
|
33365
|
+
if (resolved) {
|
|
33366
|
+
return buildCatalogRuntimeOptions(
|
|
33367
|
+
resolved.packageName,
|
|
33368
|
+
resolved.packageRoot,
|
|
33369
|
+
runtime.cwd
|
|
33370
|
+
);
|
|
33371
|
+
}
|
|
33372
|
+
const includeInstallSuggestions = isDefaultInstallableCatalogPackage(
|
|
33373
|
+
packageName
|
|
33374
|
+
);
|
|
33375
|
+
if (!includeInstallSuggestions || !shouldOfferInteractiveInstall(runtime, format)) {
|
|
33376
|
+
return failure(
|
|
33377
|
+
formatCatalogPackageNotFoundMessage({
|
|
33378
|
+
cwd: runtime.cwd,
|
|
33379
|
+
packageName,
|
|
33380
|
+
cliScope,
|
|
33381
|
+
includeInstallSuggestions
|
|
33382
|
+
})
|
|
33383
|
+
);
|
|
33384
|
+
}
|
|
33385
|
+
return handleMissingCatalogPackage(
|
|
33386
|
+
runtime,
|
|
33387
|
+
packageName,
|
|
33388
|
+
cliScope,
|
|
33389
|
+
includeInstallSuggestions,
|
|
33390
|
+
format
|
|
33391
|
+
);
|
|
33392
|
+
}
|
|
33393
|
+
|
|
30806
33394
|
// apps/cli/src/commands/check.command.ts
|
|
30807
|
-
function handleCheck(target, format, runtime, baseRef, headRef, staged = false) {
|
|
33395
|
+
async function handleCheck(target, format, runtime, baseRef, headRef, staged = false) {
|
|
33396
|
+
const catalogEnsure = await ensureCatalogPackageForCheck(runtime, format);
|
|
33397
|
+
if (catalogEnsure.ok === false) {
|
|
33398
|
+
runtime.writeStderr(catalogEnsure.message);
|
|
33399
|
+
return catalogEnsure.exitCode;
|
|
33400
|
+
}
|
|
30808
33401
|
const progressRenderer = format === "pretty" ? createScanProgressRenderer(runtime) : null;
|
|
30809
33402
|
const runnerFormat = format === "pretty" ? "pretty" : "json";
|
|
30810
33403
|
const result = runCheckCommand({
|
|
@@ -30813,7 +33406,10 @@ function handleCheck(target, format, runtime, baseRef, headRef, staged = false)
|
|
|
30813
33406
|
format: runnerFormat,
|
|
30814
33407
|
baseRef,
|
|
30815
33408
|
headRef,
|
|
30816
|
-
catalogResolverBasePaths: [
|
|
33409
|
+
catalogResolverBasePaths: catalogEnsure.catalogResolverBasePaths ?? [
|
|
33410
|
+
runtime.cwd
|
|
33411
|
+
],
|
|
33412
|
+
catalogPackageRoots: catalogEnsure.catalogPackageRoots,
|
|
30817
33413
|
onProgress: progressRenderer ? (update) => {
|
|
30818
33414
|
progressRenderer.update(update);
|
|
30819
33415
|
} : void 0
|
|
@@ -30855,8 +33451,8 @@ function handleCheck(target, format, runtime, baseRef, headRef, staged = false)
|
|
|
30855
33451
|
}
|
|
30856
33452
|
|
|
30857
33453
|
// tools/testing/harness/src/lib/harness.ts
|
|
30858
|
-
var
|
|
30859
|
-
var
|
|
33454
|
+
var import_node_fs17 = require("node:fs");
|
|
33455
|
+
var import_node_path19 = require("node:path");
|
|
30860
33456
|
var import_node_url4 = require("node:url");
|
|
30861
33457
|
var import_zod8 = require("zod");
|
|
30862
33458
|
var observedRangeSchema = import_zod8.z.object({
|
|
@@ -30967,7 +33563,7 @@ var ruleSpecSchema = import_zod8.z.object({
|
|
|
30967
33563
|
fixtures: import_zod8.z.array(ruleSpecFixtureSchema).min(1)
|
|
30968
33564
|
}).strict();
|
|
30969
33565
|
function readTextFile(path) {
|
|
30970
|
-
return (0,
|
|
33566
|
+
return (0, import_node_fs17.readFileSync)(path, "utf8");
|
|
30971
33567
|
}
|
|
30972
33568
|
function toPointer(path) {
|
|
30973
33569
|
return path.length === 0 ? "/" : `/${path.join("/")}`;
|
|
@@ -31012,7 +33608,7 @@ function validateObservationFixture(fixturePath, text) {
|
|
|
31012
33608
|
return validated.data;
|
|
31013
33609
|
}
|
|
31014
33610
|
function analyzeSourceFixture(fixturePath, text, adapterRegistry) {
|
|
31015
|
-
const extension = (0,
|
|
33611
|
+
const extension = (0, import_node_path19.extname)(fixturePath).toLowerCase();
|
|
31016
33612
|
const adapter = adapterRegistry.findAdapterForPath(fixturePath);
|
|
31017
33613
|
if (!adapter) {
|
|
31018
33614
|
return [
|
|
@@ -31056,8 +33652,8 @@ function analyzeWorkspaceFixture(workspaceRoot, adapterRegistry) {
|
|
|
31056
33652
|
}
|
|
31057
33653
|
const result = entry.adapter.analyze(entry.displayPath, textOrFailure);
|
|
31058
33654
|
if (!result.success) {
|
|
31059
|
-
const
|
|
31060
|
-
diagnostics.push(...
|
|
33655
|
+
const failure2 = result;
|
|
33656
|
+
diagnostics.push(...failure2.diagnostics);
|
|
31061
33657
|
continue;
|
|
31062
33658
|
}
|
|
31063
33659
|
analyzedFiles.push(result.data);
|
|
@@ -31181,20 +33777,20 @@ function assertFixtureExpectations(fixture, emittedFindings) {
|
|
|
31181
33777
|
}
|
|
31182
33778
|
return failures;
|
|
31183
33779
|
}
|
|
31184
|
-
function formatAssertionFailure(
|
|
31185
|
-
const lines = [`- ${
|
|
31186
|
-
if (
|
|
31187
|
-
lines.push(` expected: ${JSON.stringify(
|
|
33780
|
+
function formatAssertionFailure(failure2) {
|
|
33781
|
+
const lines = [`- ${failure2.assertion}: ${failure2.message}`];
|
|
33782
|
+
if (failure2.expected !== void 0) {
|
|
33783
|
+
lines.push(` expected: ${JSON.stringify(failure2.expected)}`);
|
|
31188
33784
|
}
|
|
31189
|
-
if (
|
|
31190
|
-
lines.push(` received: ${JSON.stringify(
|
|
33785
|
+
if (failure2.received !== void 0) {
|
|
33786
|
+
lines.push(` received: ${JSON.stringify(failure2.received)}`);
|
|
31191
33787
|
}
|
|
31192
33788
|
return lines.join("\n");
|
|
31193
33789
|
}
|
|
31194
33790
|
function analyzeFixture(specDirectory, fixture, adapterRegistry) {
|
|
31195
33791
|
const sourceKind = fixture.sourcePath ? "source" : fixture.observationPath ? "observation" : "workspace";
|
|
31196
33792
|
const declaredPath = fixture.sourcePath ?? fixture.observationPath ?? fixture.workspacePath ?? "";
|
|
31197
|
-
const fixturePath = (0,
|
|
33793
|
+
const fixturePath = (0, import_node_path19.resolve)(specDirectory, declaredPath);
|
|
31198
33794
|
if (sourceKind === "workspace") {
|
|
31199
33795
|
const workspaceAnalysis = analyzeWorkspaceFixture(fixturePath, adapterRegistry);
|
|
31200
33796
|
return {
|
|
@@ -31226,8 +33822,8 @@ function analyzeFixture(specDirectory, fixture, adapterRegistry) {
|
|
|
31226
33822
|
}
|
|
31227
33823
|
};
|
|
31228
33824
|
}
|
|
31229
|
-
function validateRuleSpec(
|
|
31230
|
-
const result = ruleSpecSchema.safeParse(
|
|
33825
|
+
function validateRuleSpec(input2) {
|
|
33826
|
+
const result = ruleSpecSchema.safeParse(input2);
|
|
31231
33827
|
if (result.success) {
|
|
31232
33828
|
return {
|
|
31233
33829
|
success: true,
|
|
@@ -31245,10 +33841,10 @@ function loadRuleSpec(path) {
|
|
|
31245
33841
|
const uri = (0, import_node_url4.pathToFileURL)(path).href;
|
|
31246
33842
|
const loaded = loadYamlText(text, uri);
|
|
31247
33843
|
if (!loaded.success) {
|
|
31248
|
-
const
|
|
33844
|
+
const failure2 = loaded;
|
|
31249
33845
|
return {
|
|
31250
33846
|
success: false,
|
|
31251
|
-
diagnostics:
|
|
33847
|
+
diagnostics: failure2.issues.map(
|
|
31252
33848
|
(issue) => createDiagnostic({
|
|
31253
33849
|
code: `harness.rulespec.${issue.kind}`,
|
|
31254
33850
|
message: issue.message,
|
|
@@ -31260,10 +33856,10 @@ function loadRuleSpec(path) {
|
|
|
31260
33856
|
}
|
|
31261
33857
|
const validation = validateRuleSpec(loaded.data);
|
|
31262
33858
|
if (!validation.success) {
|
|
31263
|
-
const
|
|
33859
|
+
const failure2 = validation;
|
|
31264
33860
|
return {
|
|
31265
33861
|
success: false,
|
|
31266
|
-
diagnostics:
|
|
33862
|
+
diagnostics: failure2.issues.map(
|
|
31267
33863
|
(issue) => createDiagnostic({
|
|
31268
33864
|
code: `harness.rulespec.${issue.code}`,
|
|
31269
33865
|
message: issue.message,
|
|
@@ -31297,26 +33893,26 @@ function runRuleSpec(path, options = {}) {
|
|
|
31297
33893
|
const adapterRegistry = options.adapterRegistry ?? createDefaultSourceAdapterRegistry();
|
|
31298
33894
|
const loadedSpec = loadRuleSpec(path);
|
|
31299
33895
|
if (!loadedSpec.success) {
|
|
31300
|
-
const
|
|
33896
|
+
const failure2 = loadedSpec;
|
|
31301
33897
|
return {
|
|
31302
33898
|
specPath: path,
|
|
31303
33899
|
rulePath: "",
|
|
31304
33900
|
success: false,
|
|
31305
33901
|
fixtureResults: [],
|
|
31306
|
-
diagnostics:
|
|
33902
|
+
diagnostics: failure2.diagnostics
|
|
31307
33903
|
};
|
|
31308
33904
|
}
|
|
31309
|
-
const specDirectory = (0,
|
|
31310
|
-
const absoluteRulePath = (0,
|
|
33905
|
+
const specDirectory = (0, import_node_path19.dirname)(loadedSpec.data.path);
|
|
33906
|
+
const absoluteRulePath = (0, import_node_path19.resolve)(specDirectory, loadedSpec.data.spec.rulePath);
|
|
31311
33907
|
const loadedRule = loadRuleFile(absoluteRulePath);
|
|
31312
33908
|
if (!loadedRule.success) {
|
|
31313
|
-
const
|
|
33909
|
+
const failure2 = loadedRule;
|
|
31314
33910
|
return {
|
|
31315
33911
|
specPath: loadedSpec.data.path,
|
|
31316
33912
|
rulePath: absoluteRulePath,
|
|
31317
33913
|
success: false,
|
|
31318
33914
|
fixtureResults: [],
|
|
31319
|
-
diagnostics:
|
|
33915
|
+
diagnostics: failure2.diagnostics
|
|
31320
33916
|
};
|
|
31321
33917
|
}
|
|
31322
33918
|
const validatedRule = validateLoadedRuleDocument(loadedRule.data);
|
|
@@ -31443,7 +34039,7 @@ function formatRuleSpecRunAsJson(result) {
|
|
|
31443
34039
|
}
|
|
31444
34040
|
|
|
31445
34041
|
// apps/cli/src/commands/rules.command.ts
|
|
31446
|
-
var
|
|
34042
|
+
var import_node_path21 = require("node:path");
|
|
31447
34043
|
var import_node_url5 = require("node:url");
|
|
31448
34044
|
|
|
31449
34045
|
// apps/cli/src/rendering/rules.rendering.ts
|
|
@@ -31592,13 +34188,13 @@ function renderSingleFilePretty(heading, state) {
|
|
|
31592
34188
|
|
|
31593
34189
|
// apps/cli/src/commands/rules-targets.ts
|
|
31594
34190
|
var import_minimatch3 = require("minimatch");
|
|
31595
|
-
var
|
|
31596
|
-
var
|
|
34191
|
+
var import_node_fs18 = require("node:fs");
|
|
34192
|
+
var import_node_path20 = require("node:path");
|
|
31597
34193
|
function resolveValidateTargets(cwd, target) {
|
|
31598
|
-
const absoluteCandidate = (0,
|
|
34194
|
+
const absoluteCandidate = (0, import_node_path20.resolve)(cwd, target);
|
|
31599
34195
|
if (!hasGlobMagic(target)) {
|
|
31600
34196
|
try {
|
|
31601
|
-
if (!(0,
|
|
34197
|
+
if (!(0, import_node_fs18.statSync)(absoluteCandidate).isFile()) {
|
|
31602
34198
|
return {
|
|
31603
34199
|
success: false,
|
|
31604
34200
|
diagnostics: [
|
|
@@ -31667,11 +34263,11 @@ function resolveTestTargets(cwd, target) {
|
|
|
31667
34263
|
const resolvedTarget = target ?? "**/*.spec.yaml";
|
|
31668
34264
|
const resolved = resolveValidateTargets(cwd, resolvedTarget);
|
|
31669
34265
|
if (!resolved.success) {
|
|
31670
|
-
const
|
|
34266
|
+
const failure2 = resolved;
|
|
31671
34267
|
return {
|
|
31672
34268
|
success: false,
|
|
31673
34269
|
target: resolvedTarget,
|
|
31674
|
-
diagnostics:
|
|
34270
|
+
diagnostics: failure2.diagnostics
|
|
31675
34271
|
};
|
|
31676
34272
|
}
|
|
31677
34273
|
return {
|
|
@@ -31693,7 +34289,7 @@ function resolveSingleFilePath(cwd, inputPath) {
|
|
|
31693
34289
|
}
|
|
31694
34290
|
return {
|
|
31695
34291
|
success: true,
|
|
31696
|
-
absolutePath: (0,
|
|
34292
|
+
absolutePath: (0, import_node_path20.resolve)(cwd, inputPath)
|
|
31697
34293
|
};
|
|
31698
34294
|
}
|
|
31699
34295
|
|
|
@@ -31724,12 +34320,12 @@ function validateResultForFileSafe(absolutePath, cwd) {
|
|
|
31724
34320
|
const uri = (0, import_node_url5.pathToFileURL)(absolutePath).href;
|
|
31725
34321
|
const loaded = loadRuleFile(absolutePath);
|
|
31726
34322
|
if (!loaded.success) {
|
|
31727
|
-
const
|
|
34323
|
+
const failure2 = loaded;
|
|
31728
34324
|
return {
|
|
31729
34325
|
path: displayPath,
|
|
31730
34326
|
uri,
|
|
31731
34327
|
success: false,
|
|
31732
|
-
diagnostics:
|
|
34328
|
+
diagnostics: failure2.diagnostics
|
|
31733
34329
|
};
|
|
31734
34330
|
}
|
|
31735
34331
|
const validated = validateLoadedRuleDocument(loaded.data);
|
|
@@ -31756,19 +34352,19 @@ function buildSingleFileState(absolutePath, cwd) {
|
|
|
31756
34352
|
const loadResult = loadRuleFile(absolutePath);
|
|
31757
34353
|
parsedSummary.phases.load = loadResult.success ? "success" : "failure";
|
|
31758
34354
|
if (!loadResult.success) {
|
|
31759
|
-
const
|
|
34355
|
+
const failure2 = loadResult;
|
|
31760
34356
|
return {
|
|
31761
34357
|
displayPath,
|
|
31762
34358
|
uri,
|
|
31763
34359
|
parsedSummary,
|
|
31764
34360
|
semanticStatus: {
|
|
31765
34361
|
success: false,
|
|
31766
|
-
diagnostics:
|
|
34362
|
+
diagnostics: failure2.diagnostics
|
|
31767
34363
|
},
|
|
31768
34364
|
normalizedRule: null,
|
|
31769
34365
|
ruleHash: null,
|
|
31770
34366
|
templateVariables,
|
|
31771
|
-
diagnostics:
|
|
34367
|
+
diagnostics: failure2.diagnostics
|
|
31772
34368
|
};
|
|
31773
34369
|
}
|
|
31774
34370
|
const contractValidation = validateLoadedRuleDocumentContract(
|
|
@@ -31776,19 +34372,19 @@ function buildSingleFileState(absolutePath, cwd) {
|
|
|
31776
34372
|
);
|
|
31777
34373
|
parsedSummary.phases.contractValidation = contractValidation.success ? "success" : "failure";
|
|
31778
34374
|
if (!contractValidation.success) {
|
|
31779
|
-
const
|
|
34375
|
+
const failure2 = contractValidation;
|
|
31780
34376
|
return {
|
|
31781
34377
|
displayPath,
|
|
31782
34378
|
uri,
|
|
31783
34379
|
parsedSummary,
|
|
31784
34380
|
semanticStatus: {
|
|
31785
34381
|
success: false,
|
|
31786
|
-
diagnostics:
|
|
34382
|
+
diagnostics: failure2.diagnostics
|
|
31787
34383
|
},
|
|
31788
34384
|
normalizedRule: null,
|
|
31789
34385
|
ruleHash: null,
|
|
31790
34386
|
templateVariables,
|
|
31791
|
-
diagnostics:
|
|
34387
|
+
diagnostics: failure2.diagnostics
|
|
31792
34388
|
};
|
|
31793
34389
|
}
|
|
31794
34390
|
const explainSummary = summarizeValidatedRuleDocument(
|
|
@@ -31838,7 +34434,7 @@ function buildSingleFileState(absolutePath, cwd) {
|
|
|
31838
34434
|
};
|
|
31839
34435
|
}
|
|
31840
34436
|
function createInvalidSingleFileState(cwd, inputPath, diagnostics) {
|
|
31841
|
-
const uri = (0, import_node_url5.pathToFileURL)((0,
|
|
34437
|
+
const uri = (0, import_node_url5.pathToFileURL)((0, import_node_path21.resolve)(cwd, inputPath)).href;
|
|
31842
34438
|
return {
|
|
31843
34439
|
displayPath: inputPath,
|
|
31844
34440
|
uri,
|
|
@@ -31856,15 +34452,15 @@ function createInvalidSingleFileState(cwd, inputPath, diagnostics) {
|
|
|
31856
34452
|
function handleValidate(target, format, runtime) {
|
|
31857
34453
|
const resolved = resolveValidateTargets(runtime.cwd, target);
|
|
31858
34454
|
if (!resolved.success) {
|
|
31859
|
-
const
|
|
31860
|
-
const exitCode2 = determineExitCode(
|
|
34455
|
+
const failure2 = resolved;
|
|
34456
|
+
const exitCode2 = determineExitCode(failure2.diagnostics);
|
|
31861
34457
|
const envelope2 = {
|
|
31862
34458
|
command: "rules.validate",
|
|
31863
34459
|
format,
|
|
31864
34460
|
target,
|
|
31865
34461
|
matchedFileCount: 0,
|
|
31866
34462
|
results: [],
|
|
31867
|
-
diagnostics: aggregateDiagnostics(
|
|
34463
|
+
diagnostics: aggregateDiagnostics(failure2.diagnostics),
|
|
31868
34464
|
exitCode: exitCode2 === 0 ? 1 : exitCode2
|
|
31869
34465
|
};
|
|
31870
34466
|
if (format === "json") {
|
|
@@ -31900,15 +34496,15 @@ function handleValidate(target, format, runtime) {
|
|
|
31900
34496
|
function handleTest(target, format, runtime) {
|
|
31901
34497
|
const resolved = resolveTestTargets(runtime.cwd, target);
|
|
31902
34498
|
if (!resolved.success) {
|
|
31903
|
-
const
|
|
31904
|
-
const exitCode2 = determineExitCode(
|
|
34499
|
+
const failure2 = resolved;
|
|
34500
|
+
const exitCode2 = determineExitCode(failure2.diagnostics) || 1;
|
|
31905
34501
|
const envelope2 = {
|
|
31906
34502
|
command: "rules.test",
|
|
31907
34503
|
format,
|
|
31908
|
-
target:
|
|
34504
|
+
target: failure2.target,
|
|
31909
34505
|
matchedFileCount: 0,
|
|
31910
34506
|
results: [],
|
|
31911
|
-
diagnostics: aggregateDiagnostics(
|
|
34507
|
+
diagnostics: aggregateDiagnostics(failure2.diagnostics),
|
|
31912
34508
|
exitCode: exitCode2
|
|
31913
34509
|
};
|
|
31914
34510
|
if (format === "json") {
|
|
@@ -31957,8 +34553,8 @@ function handleTest(target, format, runtime) {
|
|
|
31957
34553
|
function handleNormalizeOrExplain(command, inputPath, format, runtime) {
|
|
31958
34554
|
const resolved = resolveSingleFilePath(runtime.cwd, inputPath);
|
|
31959
34555
|
if (!resolved.success) {
|
|
31960
|
-
const
|
|
31961
|
-
const diagnostics = aggregateDiagnostics(
|
|
34556
|
+
const failure2 = resolved;
|
|
34557
|
+
const diagnostics = aggregateDiagnostics(failure2.diagnostics);
|
|
31962
34558
|
const exitCode2 = determineExitCode(diagnostics) || 1;
|
|
31963
34559
|
const baseState = createInvalidSingleFileState(
|
|
31964
34560
|
runtime.cwd,
|
|
@@ -32197,7 +34793,7 @@ function isLegacyRulesArgument(value) {
|
|
|
32197
34793
|
function isPrettyOrJson(format) {
|
|
32198
34794
|
return format === "pretty" || format === "json";
|
|
32199
34795
|
}
|
|
32200
|
-
function runCli(args = process.argv.slice(2), runtime = {}) {
|
|
34796
|
+
async function runCli(args = process.argv.slice(2), runtime = {}) {
|
|
32201
34797
|
const resolvedRuntime = resolveRuntime(runtime);
|
|
32202
34798
|
if (args.length === 0 || args[0] === "help" || args[0] === "--help") {
|
|
32203
34799
|
resolvedRuntime.writeStdout(renderHelpMessage());
|
|
@@ -32225,7 +34821,7 @@ function runCli(args = process.argv.slice(2), runtime = {}) {
|
|
|
32225
34821
|
);
|
|
32226
34822
|
return 1;
|
|
32227
34823
|
}
|
|
32228
|
-
return handleCheck(
|
|
34824
|
+
return await handleCheck(
|
|
32229
34825
|
parsed2.positionals[0],
|
|
32230
34826
|
parsed2.format,
|
|
32231
34827
|
resolvedRuntime,
|
|
@@ -32380,10 +34976,12 @@ function isCliEntrypoint() {
|
|
|
32380
34976
|
if (!mainFilename) {
|
|
32381
34977
|
return false;
|
|
32382
34978
|
}
|
|
32383
|
-
return mainFilename === (0,
|
|
34979
|
+
return mainFilename === (0, import_node_path22.resolve)(__dirname, "..", "..", "..", "main.js");
|
|
32384
34980
|
}
|
|
32385
34981
|
if (isCliEntrypoint()) {
|
|
32386
|
-
|
|
34982
|
+
void runCli().then((exitCode) => {
|
|
34983
|
+
process.exitCode = exitCode;
|
|
34984
|
+
});
|
|
32387
34985
|
}
|
|
32388
34986
|
// Annotate the CommonJS export names for ESM import in node:
|
|
32389
34987
|
0 && (module.exports = {
|