brainblast 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EYFKA33G.js → chunk-LHP6HFMS.js} +154 -63
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -421,6 +421,66 @@ var objectArgPropertyLiteralEquals = (c, p) => {
|
|
|
421
421
|
};
|
|
422
422
|
};
|
|
423
423
|
|
|
424
|
+
// src/checkers/objectArgPropertyForbiddenLiteral.ts
|
|
425
|
+
import { SyntaxKind as SyntaxKind7 } from "ts-morph";
|
|
426
|
+
var objectArgPropertyForbiddenLiteral = (c, p) => {
|
|
427
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind7.CallExpression).filter((ce2) => {
|
|
428
|
+
const expr = ce2.getExpression();
|
|
429
|
+
if (expr.getKind() === SyntaxKind7.Identifier) return expr.getText() === p.call;
|
|
430
|
+
if (expr.getKind() === SyntaxKind7.PropertyAccessExpression) {
|
|
431
|
+
return expr.asKind(SyntaxKind7.PropertyAccessExpression).getName() === p.call;
|
|
432
|
+
}
|
|
433
|
+
return false;
|
|
434
|
+
});
|
|
435
|
+
if (calls.length === 0) {
|
|
436
|
+
return {
|
|
437
|
+
result: "cant_tell",
|
|
438
|
+
detail: p.absentCallDetail ?? `no ${p.call} call found`
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
const ce = calls[0];
|
|
442
|
+
const args = ce.getArguments();
|
|
443
|
+
const arg = args[p.argIndex];
|
|
444
|
+
const objLit = arg?.asKind(SyntaxKind7.ObjectLiteralExpression);
|
|
445
|
+
if (!objLit) {
|
|
446
|
+
return {
|
|
447
|
+
result: "cant_tell",
|
|
448
|
+
detail: p.absentArgDetail ?? `${p.call} arg[${p.argIndex}] is not an inline object literal \u2014 cannot statically inspect ${p.propName}`
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const propAssignment = objLit.getProperties().map((prop) => prop.asKind(SyntaxKind7.PropertyAssignment)).find((pa) => pa?.getName() === p.propName);
|
|
452
|
+
if (!propAssignment) {
|
|
453
|
+
return {
|
|
454
|
+
result: "cant_tell",
|
|
455
|
+
detail: p.absentArgDetail ?? `${p.propName} is absent from the ${p.call} options`
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const init = propAssignment.getInitializer();
|
|
459
|
+
if (!init) {
|
|
460
|
+
return { result: "cant_tell", detail: `${p.propName} has no initializer` };
|
|
461
|
+
}
|
|
462
|
+
const kind = init.getKind();
|
|
463
|
+
const text = init.getText();
|
|
464
|
+
const forbidden = JSON.stringify(p.forbiddenValue);
|
|
465
|
+
const isForbidden = (kind === SyntaxKind7.NumericLiteral || kind === SyntaxKind7.StringLiteral) && (text === forbidden || text === String(p.forbiddenValue));
|
|
466
|
+
if (isForbidden) {
|
|
467
|
+
return {
|
|
468
|
+
result: "fail",
|
|
469
|
+
detail: p.failDetail ?? `${p.propName} is ${p.forbiddenValue}`
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
if (kind === SyntaxKind7.NumericLiteral || kind === SyntaxKind7.StringLiteral) {
|
|
473
|
+
return {
|
|
474
|
+
result: "pass",
|
|
475
|
+
detail: p.passDetail ?? `${p.propName} is ${text}`
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
result: "cant_tell",
|
|
480
|
+
detail: `${p.propName} is a non-literal expression \u2014 cannot determine statically`
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
|
|
424
484
|
// src/checkers/anchorInitIfNeededGuarded.ts
|
|
425
485
|
var GUARD_PATTERNS = [
|
|
426
486
|
/\brequire!\s*\(/,
|
|
@@ -488,18 +548,18 @@ var envSecretsCommitted = (c, p) => {
|
|
|
488
548
|
};
|
|
489
549
|
|
|
490
550
|
// src/checkers/taintToSink.ts
|
|
491
|
-
import { SyntaxKind as
|
|
551
|
+
import { SyntaxKind as SyntaxKind8 } from "ts-morph";
|
|
492
552
|
function calleeName(call) {
|
|
493
553
|
const exp = call.getExpression();
|
|
494
|
-
if (exp.getKind() ===
|
|
495
|
-
if (exp.getKind() ===
|
|
496
|
-
return exp.asKind(
|
|
554
|
+
if (exp.getKind() === SyntaxKind8.Identifier) return exp.getText();
|
|
555
|
+
if (exp.getKind() === SyntaxKind8.PropertyAccessExpression) {
|
|
556
|
+
return exp.asKind(SyntaxKind8.PropertyAccessExpression).getName();
|
|
497
557
|
}
|
|
498
558
|
return "";
|
|
499
559
|
}
|
|
500
560
|
function calleeIdentifierName(call) {
|
|
501
561
|
const exp = call.getExpression();
|
|
502
|
-
return exp.getKind() ===
|
|
562
|
+
return exp.getKind() === SyntaxKind8.Identifier ? exp.getText() : void 0;
|
|
503
563
|
}
|
|
504
564
|
function wordIn(text, name) {
|
|
505
565
|
return new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(text);
|
|
@@ -509,14 +569,14 @@ function matchesSource(text, sourceRes) {
|
|
|
509
569
|
}
|
|
510
570
|
function localTaintedNames(fn, sourceRes) {
|
|
511
571
|
const names = /* @__PURE__ */ new Set();
|
|
512
|
-
for (const decl of fn.getDescendantsOfKind(
|
|
572
|
+
for (const decl of fn.getDescendantsOfKind(SyntaxKind8.VariableDeclaration)) {
|
|
513
573
|
const init = decl.getInitializer();
|
|
514
574
|
if (init && matchesSource(init.getText(), sourceRes)) names.add(decl.getName());
|
|
515
575
|
}
|
|
516
576
|
return names;
|
|
517
577
|
}
|
|
518
578
|
function findDirectLeak(fn, sinkCalls, sourceRes, taintedNames) {
|
|
519
|
-
for (const call of fn.getDescendantsOfKind(
|
|
579
|
+
for (const call of fn.getDescendantsOfKind(SyntaxKind8.CallExpression)) {
|
|
520
580
|
const name = calleeName(call);
|
|
521
581
|
if (!sinkCalls.has(name)) continue;
|
|
522
582
|
for (const arg of call.getArguments()) {
|
|
@@ -548,7 +608,7 @@ function resolveFunction(sourceFile, name) {
|
|
|
548
608
|
}
|
|
549
609
|
function findForwardLeak(fn, rootName, sinkCalls, sourceRes, taintedNames, hopsLeft, visited) {
|
|
550
610
|
if (hopsLeft <= 0) return void 0;
|
|
551
|
-
for (const call of fn.getDescendantsOfKind(
|
|
611
|
+
for (const call of fn.getDescendantsOfKind(SyntaxKind8.CallExpression)) {
|
|
552
612
|
const name = calleeIdentifierName(call);
|
|
553
613
|
if (!name || name === rootName) continue;
|
|
554
614
|
const args = call.getArguments();
|
|
@@ -590,7 +650,7 @@ function findForwardLeak(fn, rootName, sinkCalls, sourceRes, taintedNames, hopsL
|
|
|
590
650
|
function paramsUsedInSink(fn, sinkCalls) {
|
|
591
651
|
const params = new Set(fn.getParameters().map((p) => p.getName()));
|
|
592
652
|
const sinked = /* @__PURE__ */ new Set();
|
|
593
|
-
for (const call of fn.getDescendantsOfKind(
|
|
653
|
+
for (const call of fn.getDescendantsOfKind(SyntaxKind8.CallExpression)) {
|
|
594
654
|
if (!sinkCalls.has(calleeName(call))) continue;
|
|
595
655
|
for (const arg of call.getArguments()) {
|
|
596
656
|
const text = arg.getText();
|
|
@@ -603,13 +663,13 @@ function paramsUsedInSink(fn, sinkCalls) {
|
|
|
603
663
|
}
|
|
604
664
|
function enclosingFunction(node) {
|
|
605
665
|
return node.getFirstAncestor(
|
|
606
|
-
(a) => a.getKind() ===
|
|
666
|
+
(a) => a.getKind() === SyntaxKind8.FunctionDeclaration || a.getKind() === SyntaxKind8.ArrowFunction
|
|
607
667
|
);
|
|
608
668
|
}
|
|
609
669
|
function findBackwardLeak(candidateFn, fnName, candidateFile, params, sinkedParams, sourceRes) {
|
|
610
670
|
const project = candidateFn.getProject();
|
|
611
671
|
for (const sf of project.getSourceFiles()) {
|
|
612
|
-
for (const call of sf.getDescendantsOfKind(
|
|
672
|
+
for (const call of sf.getDescendantsOfKind(SyntaxKind8.CallExpression)) {
|
|
613
673
|
if (calleeIdentifierName(call) !== fnName) continue;
|
|
614
674
|
if (call.getFirstAncestor((a) => a === candidateFn)) continue;
|
|
615
675
|
const args = call.getArguments();
|
|
@@ -621,7 +681,7 @@ function findBackwardLeak(candidateFn, fnName, candidateFile, params, sinkedPara
|
|
|
621
681
|
if (matchesSource(text, sourceRes)) {
|
|
622
682
|
return `'${fnName}' is called from ${sf.getFilePath()}:${call.getStartLineNumber()} with '${text}' as '${pname}', which this function passes to a sink.`;
|
|
623
683
|
}
|
|
624
|
-
if (arg.getKind() ===
|
|
684
|
+
if (arg.getKind() === SyntaxKind8.Identifier) {
|
|
625
685
|
const callerFn = enclosingFunction(arg);
|
|
626
686
|
if (callerFn) {
|
|
627
687
|
const callerTainted = localTaintedNames(callerFn, sourceRes);
|
|
@@ -663,21 +723,21 @@ var taintToSink = (c, p) => {
|
|
|
663
723
|
};
|
|
664
724
|
|
|
665
725
|
// src/checkers/literalMultiplierWrongConstant.ts
|
|
666
|
-
import { SyntaxKind as
|
|
726
|
+
import { SyntaxKind as SyntaxKind9 } from "ts-morph";
|
|
667
727
|
function callName4(call) {
|
|
668
728
|
const exp = call.getExpression();
|
|
669
|
-
if (exp.getKind() ===
|
|
670
|
-
if (exp.getKind() ===
|
|
671
|
-
return exp.asKind(
|
|
729
|
+
if (exp.getKind() === SyntaxKind9.Identifier) return exp.getText();
|
|
730
|
+
if (exp.getKind() === SyntaxKind9.PropertyAccessExpression) {
|
|
731
|
+
return exp.asKind(SyntaxKind9.PropertyAccessExpression).getName();
|
|
672
732
|
}
|
|
673
733
|
return "";
|
|
674
734
|
}
|
|
675
735
|
function containsIdentifier(node, name) {
|
|
676
|
-
if (node.getKind() ===
|
|
677
|
-
return node.getDescendantsOfKind(
|
|
736
|
+
if (node.getKind() === SyntaxKind9.Identifier && node.getText() === name) return true;
|
|
737
|
+
return node.getDescendantsOfKind(SyntaxKind9.Identifier).some((id) => id.getText() === name);
|
|
678
738
|
}
|
|
679
739
|
var literalMultiplierWrongConstant = (c, p) => {
|
|
680
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
740
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind9.CallExpression).filter((x) => callName4(x) === p.call);
|
|
681
741
|
if (calls.length === 0) {
|
|
682
742
|
return { result: "cant_tell", detail: p.absentCallDetail };
|
|
683
743
|
}
|
|
@@ -700,6 +760,35 @@ var literalMultiplierWrongConstant = (c, p) => {
|
|
|
700
760
|
return { result: "cant_tell", detail: p.cantTellDetail };
|
|
701
761
|
};
|
|
702
762
|
|
|
763
|
+
// src/checkers/forbiddenCallReplacement.ts
|
|
764
|
+
import { SyntaxKind as SyntaxKind10 } from "ts-morph";
|
|
765
|
+
function callName5(call) {
|
|
766
|
+
const exp = call.getExpression();
|
|
767
|
+
if (exp.getKind() === SyntaxKind10.Identifier) return exp.getText();
|
|
768
|
+
if (exp.getKind() === SyntaxKind10.PropertyAccessExpression) {
|
|
769
|
+
return exp.asKind(SyntaxKind10.PropertyAccessExpression).getName();
|
|
770
|
+
}
|
|
771
|
+
return "";
|
|
772
|
+
}
|
|
773
|
+
var forbiddenCallReplacement = (c, p) => {
|
|
774
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind10.CallExpression);
|
|
775
|
+
const names = calls.map(callName5);
|
|
776
|
+
const forbidden = p.forbiddenCalls ?? [];
|
|
777
|
+
const safer = p.saferCalls ?? [];
|
|
778
|
+
const usesSafer = names.some((n) => safer.includes(n));
|
|
779
|
+
if (usesSafer) {
|
|
780
|
+
return { result: "pass", detail: p.passDetail ?? `uses ${safer.join("/")}` };
|
|
781
|
+
}
|
|
782
|
+
const usesForbidden = names.some((n) => forbidden.includes(n));
|
|
783
|
+
if (usesForbidden) {
|
|
784
|
+
return { result: "fail", detail: p.failDetail ?? `uses ${forbidden.join("/")}` };
|
|
785
|
+
}
|
|
786
|
+
return {
|
|
787
|
+
result: "cant_tell",
|
|
788
|
+
detail: p.absentDetail ?? `neither ${forbidden.join("/")} nor ${safer.join("/")} found`
|
|
789
|
+
};
|
|
790
|
+
};
|
|
791
|
+
|
|
703
792
|
// src/checkers/index.ts
|
|
704
793
|
var registry = {
|
|
705
794
|
"positional-arg-identity": positionalArgIdentity,
|
|
@@ -707,10 +796,12 @@ var registry = {
|
|
|
707
796
|
"fee-allocation-shape": feeAllocationShape,
|
|
708
797
|
"arg-equals-constant-identifier": argEqualsConstantIdentifier,
|
|
709
798
|
"object-arg-property-literal-equals": objectArgPropertyLiteralEquals,
|
|
799
|
+
"object-arg-property-forbidden-literal": objectArgPropertyForbiddenLiteral,
|
|
710
800
|
"anchor-init-if-needed-guarded": anchorInitIfNeededGuarded,
|
|
711
801
|
"env-secrets-committed": envSecretsCommitted,
|
|
712
802
|
"taint-to-sink": taintToSink,
|
|
713
|
-
"literal-multiplier-wrong-constant": literalMultiplierWrongConstant
|
|
803
|
+
"literal-multiplier-wrong-constant": literalMultiplierWrongConstant,
|
|
804
|
+
"forbidden-call-replacement": forbiddenCallReplacement
|
|
714
805
|
};
|
|
715
806
|
function runChecker(kind, c, params) {
|
|
716
807
|
const fn = registry[kind];
|
|
@@ -926,7 +1017,7 @@ function findRustCandidates(targetDir, rule) {
|
|
|
926
1017
|
}
|
|
927
1018
|
|
|
928
1019
|
// src/fixers/positionalArgIdentity.ts
|
|
929
|
-
import { SyntaxKind as
|
|
1020
|
+
import { SyntaxKind as SyntaxKind11 } from "ts-morph";
|
|
930
1021
|
|
|
931
1022
|
// src/fixers/diffUtil.ts
|
|
932
1023
|
function buildDiff(node, replacement) {
|
|
@@ -951,9 +1042,9 @@ function buildDiff(node, replacement) {
|
|
|
951
1042
|
// src/fixers/positionalArgIdentity.ts
|
|
952
1043
|
var fixPositionalArgIdentity = (c, p, outcome) => {
|
|
953
1044
|
if (outcome.result !== "fail") return void 0;
|
|
954
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
1045
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind11.CallExpression).filter((call) => {
|
|
955
1046
|
const exp = call.getExpression();
|
|
956
|
-
return exp.getKind() ===
|
|
1047
|
+
return exp.getKind() === SyntaxKind11.PropertyAccessExpression && exp.asKind(SyntaxKind11.PropertyAccessExpression).getName() === p.call;
|
|
957
1048
|
});
|
|
958
1049
|
if (calls.length === 0) {
|
|
959
1050
|
const wantParam2 = c.params[p.paramIndex] ?? "<rawBodyParam>";
|
|
@@ -968,7 +1059,7 @@ Do not call JSON.parse() on the body before this verification step.`
|
|
|
968
1059
|
}
|
|
969
1060
|
const arg = calls[0].getArguments()[p.argIndex];
|
|
970
1061
|
const wantParam = c.params[p.paramIndex];
|
|
971
|
-
if (arg && wantParam && arg.getKind() ===
|
|
1062
|
+
if (arg && wantParam && arg.getKind() === SyntaxKind11.CallExpression) {
|
|
972
1063
|
return {
|
|
973
1064
|
summary: `Pass the raw body parameter '${wantParam}' to ${p.call} instead of a parsed value`,
|
|
974
1065
|
diff: buildDiff(arg, wantParam)
|
|
@@ -978,12 +1069,12 @@ Do not call JSON.parse() on the body before this verification step.`
|
|
|
978
1069
|
};
|
|
979
1070
|
|
|
980
1071
|
// src/fixers/requiredCallWithOptions.ts
|
|
981
|
-
import { SyntaxKind as
|
|
982
|
-
function
|
|
1072
|
+
import { SyntaxKind as SyntaxKind12 } from "ts-morph";
|
|
1073
|
+
function callName6(call) {
|
|
983
1074
|
const exp = call.getExpression();
|
|
984
|
-
if (exp.getKind() ===
|
|
985
|
-
if (exp.getKind() ===
|
|
986
|
-
return exp.asKind(
|
|
1075
|
+
if (exp.getKind() === SyntaxKind12.Identifier) return exp.getText();
|
|
1076
|
+
if (exp.getKind() === SyntaxKind12.PropertyAccessExpression) {
|
|
1077
|
+
return exp.asKind(SyntaxKind12.PropertyAccessExpression).getName();
|
|
987
1078
|
}
|
|
988
1079
|
return "";
|
|
989
1080
|
}
|
|
@@ -1001,15 +1092,15 @@ function placeholderFor(propName) {
|
|
|
1001
1092
|
}
|
|
1002
1093
|
var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
1003
1094
|
if (outcome.result !== "fail") return void 0;
|
|
1004
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
1005
|
-
const verify = calls.filter((x) => p.verifyCalls.includes(
|
|
1095
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind12.CallExpression);
|
|
1096
|
+
const verify = calls.filter((x) => p.verifyCalls.includes(callName6(x)));
|
|
1006
1097
|
if (verify.length > 0) {
|
|
1007
1098
|
const call = verify[0];
|
|
1008
1099
|
const args = call.getArguments();
|
|
1009
1100
|
const lastArg = args[args.length - 1];
|
|
1010
|
-
const obj = lastArg?.asKind(
|
|
1101
|
+
const obj = lastArg?.asKind(SyntaxKind12.ObjectLiteralExpression);
|
|
1011
1102
|
const presentNames = obj ? obj.getProperties().map((pr) => {
|
|
1012
|
-
const pa = pr.asKind(
|
|
1103
|
+
const pa = pr.asKind(SyntaxKind12.PropertyAssignment) ?? pr.asKind(SyntaxKind12.ShorthandPropertyAssignment);
|
|
1013
1104
|
return pa?.getName() ?? "";
|
|
1014
1105
|
}) : [];
|
|
1015
1106
|
const missingGroups = p.requiredProps.filter(
|
|
@@ -1017,7 +1108,7 @@ var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
|
1017
1108
|
);
|
|
1018
1109
|
if (missingGroups.length === 0) return void 0;
|
|
1019
1110
|
const newProps = missingGroups.map((g) => placeholderFor(g[0])).join(", ");
|
|
1020
|
-
const summary = `Add ${missingGroups.map((g) => g[0]).join(" and ")} to the ${
|
|
1111
|
+
const summary = `Add ${missingGroups.map((g) => g[0]).join(" and ")} to the ${callName6(call)} call`;
|
|
1021
1112
|
if (obj) {
|
|
1022
1113
|
const inner = obj.getText().slice(1, -1).trim();
|
|
1023
1114
|
const newText = inner.length > 0 ? `{ ${inner}, ${newProps} }` : `{ ${newProps} }`;
|
|
@@ -1029,7 +1120,7 @@ var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
|
1029
1120
|
}
|
|
1030
1121
|
return {
|
|
1031
1122
|
summary,
|
|
1032
|
-
suggestion: `Add an options object ({ ${newProps} }) as an argument to ${
|
|
1123
|
+
suggestion: `Add an options object ({ ${newProps} }) as an argument to ${callName6(call)}.`
|
|
1033
1124
|
};
|
|
1034
1125
|
}
|
|
1035
1126
|
return {
|
|
@@ -1043,22 +1134,22 @@ JWKS must come from Privy's published JWKS endpoint for your app.`
|
|
|
1043
1134
|
};
|
|
1044
1135
|
|
|
1045
1136
|
// src/fixers/literalMultiplierWrongConstant.ts
|
|
1046
|
-
import { SyntaxKind as
|
|
1047
|
-
function
|
|
1137
|
+
import { SyntaxKind as SyntaxKind13 } from "ts-morph";
|
|
1138
|
+
function callName7(call) {
|
|
1048
1139
|
const exp = call.getExpression();
|
|
1049
|
-
if (exp.getKind() ===
|
|
1050
|
-
if (exp.getKind() ===
|
|
1051
|
-
return exp.asKind(
|
|
1140
|
+
if (exp.getKind() === SyntaxKind13.Identifier) return exp.getText();
|
|
1141
|
+
if (exp.getKind() === SyntaxKind13.PropertyAccessExpression) {
|
|
1142
|
+
return exp.asKind(SyntaxKind13.PropertyAccessExpression).getName();
|
|
1052
1143
|
}
|
|
1053
1144
|
return "";
|
|
1054
1145
|
}
|
|
1055
1146
|
function findIdentifier(node, name) {
|
|
1056
|
-
if (node.getKind() ===
|
|
1057
|
-
return node.getDescendantsOfKind(
|
|
1147
|
+
if (node.getKind() === SyntaxKind13.Identifier && node.getText() === name) return node;
|
|
1148
|
+
return node.getDescendantsOfKind(SyntaxKind13.Identifier).find((id) => id.getText() === name);
|
|
1058
1149
|
}
|
|
1059
1150
|
var fixLiteralMultiplierWrongConstant = (c, p, outcome) => {
|
|
1060
1151
|
if (outcome.result !== "fail") return void 0;
|
|
1061
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
1152
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind13.CallExpression).filter((call) => callName7(call) === p.call);
|
|
1062
1153
|
if (calls.length === 0) return void 0;
|
|
1063
1154
|
const arg = calls[0].getArguments()[p.argIndex];
|
|
1064
1155
|
if (!arg) return void 0;
|
|
@@ -2033,7 +2124,7 @@ function renderTrustGraphMd(g) {
|
|
|
2033
2124
|
}
|
|
2034
2125
|
|
|
2035
2126
|
// src/costAnalysis.ts
|
|
2036
|
-
import { Project as Project2, SyntaxKind as
|
|
2127
|
+
import { Project as Project2, SyntaxKind as SyntaxKind14 } from "ts-morph";
|
|
2037
2128
|
var LAMPORTS_PER_BYTE_YEAR = 3480;
|
|
2038
2129
|
var EXEMPTION_THRESHOLD = 2;
|
|
2039
2130
|
var OVERHEAD_BYTES = 128;
|
|
@@ -2114,11 +2205,11 @@ var KNOWN_FLOWS = [
|
|
|
2114
2205
|
}
|
|
2115
2206
|
];
|
|
2116
2207
|
var LOOP_NODE_KINDS = /* @__PURE__ */ new Set([
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2208
|
+
SyntaxKind14.ForStatement,
|
|
2209
|
+
SyntaxKind14.ForOfStatement,
|
|
2210
|
+
SyntaxKind14.ForInStatement,
|
|
2211
|
+
SyntaxKind14.WhileStatement,
|
|
2212
|
+
SyntaxKind14.DoStatement
|
|
2122
2213
|
]);
|
|
2123
2214
|
var ARRAY_METHOD_LOOPS = /* @__PURE__ */ new Set(["map", "forEach", "flatMap", "reduce", "filter"]);
|
|
2124
2215
|
function isInsideLoop(node) {
|
|
@@ -2126,12 +2217,12 @@ function isInsideLoop(node) {
|
|
|
2126
2217
|
while (cur) {
|
|
2127
2218
|
const k = cur.getKind?.();
|
|
2128
2219
|
if (k !== void 0 && LOOP_NODE_KINDS.has(k)) {
|
|
2129
|
-
return { scalable: true, note: `call is inside a ${
|
|
2220
|
+
return { scalable: true, note: `call is inside a ${SyntaxKind14[k]} \u2014 cost scales with loop iterations` };
|
|
2130
2221
|
}
|
|
2131
|
-
if (k ===
|
|
2222
|
+
if (k === SyntaxKind14.CallExpression) {
|
|
2132
2223
|
const expr = cur.getExpression?.();
|
|
2133
|
-
if (expr?.getKind?.() ===
|
|
2134
|
-
const name = expr.asKind?.(
|
|
2224
|
+
if (expr?.getKind?.() === SyntaxKind14.PropertyAccessExpression) {
|
|
2225
|
+
const name = expr.asKind?.(SyntaxKind14.PropertyAccessExpression)?.getName?.();
|
|
2135
2226
|
if (name && ARRAY_METHOD_LOOPS.has(name)) {
|
|
2136
2227
|
return { scalable: true, note: `call is inside .${name}() \u2014 cost scales with array length` };
|
|
2137
2228
|
}
|
|
@@ -2145,7 +2236,7 @@ function detectPriorityFee(targetDir) {
|
|
|
2145
2236
|
const project = new Project2({ skipAddingFilesFromTsConfig: true });
|
|
2146
2237
|
for (const file of walk(targetDir)) {
|
|
2147
2238
|
const sf = project.addSourceFileAtPath(file);
|
|
2148
|
-
const calls = sf.getDescendantsOfKind(
|
|
2239
|
+
const calls = sf.getDescendantsOfKind(SyntaxKind14.CallExpression);
|
|
2149
2240
|
for (const ce of calls) {
|
|
2150
2241
|
const expr = ce.getExpression();
|
|
2151
2242
|
const text = expr.getText();
|
|
@@ -2173,22 +2264,22 @@ function detectAccountFlows(targetDir) {
|
|
|
2173
2264
|
const importedModules = new Set(
|
|
2174
2265
|
sf.getImportDeclarations().map((d) => d.getModuleSpecifierValue())
|
|
2175
2266
|
);
|
|
2176
|
-
for (const ce of sf.getDescendantsOfKind(
|
|
2267
|
+
for (const ce of sf.getDescendantsOfKind(SyntaxKind14.CallExpression)) {
|
|
2177
2268
|
const expr = ce.getExpression();
|
|
2178
|
-
let
|
|
2179
|
-
if (expr.getKind() ===
|
|
2180
|
-
|
|
2181
|
-
} else if (expr.getKind() ===
|
|
2182
|
-
|
|
2269
|
+
let callName8 = null;
|
|
2270
|
+
if (expr.getKind() === SyntaxKind14.Identifier) {
|
|
2271
|
+
callName8 = expr.getText();
|
|
2272
|
+
} else if (expr.getKind() === SyntaxKind14.PropertyAccessExpression) {
|
|
2273
|
+
callName8 = expr.asKind(SyntaxKind14.PropertyAccessExpression).getName();
|
|
2183
2274
|
}
|
|
2184
|
-
if (!
|
|
2185
|
-
const known = callIndex.get(
|
|
2275
|
+
if (!callName8) continue;
|
|
2276
|
+
const known = callIndex.get(callName8);
|
|
2186
2277
|
if (!known) continue;
|
|
2187
2278
|
if (!importedModules.has(known.module)) continue;
|
|
2188
2279
|
const lamports = rentExemptMinimum(known.dataLen);
|
|
2189
2280
|
const { scalable, note } = isInsideLoop(ce);
|
|
2190
2281
|
flows.push({
|
|
2191
|
-
call:
|
|
2282
|
+
call: callName8,
|
|
2192
2283
|
module: known.module,
|
|
2193
2284
|
accountType: known.accountType,
|
|
2194
2285
|
file,
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainblast",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deterministic auditor for catastrophic AI-integration bugs: scan a repo, find the silent money/auth traps, and generate the behavioral test that proves they're fixed.",
|
|
6
6
|
"keywords": [
|