brainblast 0.5.0 → 0.5.2
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-5LJXC66F.js → chunk-EYFKA33G.js} +114 -37
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -662,6 +662,44 @@ var taintToSink = (c, p) => {
|
|
|
662
662
|
return { result: "pass", detail: "No tracked source value flows to a sink within the analyzed call graph." };
|
|
663
663
|
};
|
|
664
664
|
|
|
665
|
+
// src/checkers/literalMultiplierWrongConstant.ts
|
|
666
|
+
import { SyntaxKind as SyntaxKind8 } from "ts-morph";
|
|
667
|
+
function callName4(call) {
|
|
668
|
+
const exp = call.getExpression();
|
|
669
|
+
if (exp.getKind() === SyntaxKind8.Identifier) return exp.getText();
|
|
670
|
+
if (exp.getKind() === SyntaxKind8.PropertyAccessExpression) {
|
|
671
|
+
return exp.asKind(SyntaxKind8.PropertyAccessExpression).getName();
|
|
672
|
+
}
|
|
673
|
+
return "";
|
|
674
|
+
}
|
|
675
|
+
function containsIdentifier(node, name) {
|
|
676
|
+
if (node.getKind() === SyntaxKind8.Identifier && node.getText() === name) return true;
|
|
677
|
+
return node.getDescendantsOfKind(SyntaxKind8.Identifier).some((id) => id.getText() === name);
|
|
678
|
+
}
|
|
679
|
+
var literalMultiplierWrongConstant = (c, p) => {
|
|
680
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind8.CallExpression).filter((x) => callName4(x) === p.call);
|
|
681
|
+
if (calls.length === 0) {
|
|
682
|
+
return { result: "cant_tell", detail: p.absentCallDetail };
|
|
683
|
+
}
|
|
684
|
+
const arg = calls[0].getArguments()[p.argIndex];
|
|
685
|
+
if (!arg) {
|
|
686
|
+
return { result: "cant_tell", detail: p.absentCallDetail };
|
|
687
|
+
}
|
|
688
|
+
const forbidden = Array.isArray(p.forbiddenIdentifiers) ? p.forbiddenIdentifiers : [];
|
|
689
|
+
for (const name of forbidden) {
|
|
690
|
+
if (containsIdentifier(arg, name)) {
|
|
691
|
+
return { result: "fail", detail: String(p.failDetail).replace("{got}", name) };
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const expected = Array.isArray(p.expectedIdentifiers) ? p.expectedIdentifiers : [];
|
|
695
|
+
for (const name of expected) {
|
|
696
|
+
if (containsIdentifier(arg, name)) {
|
|
697
|
+
return { result: "pass", detail: String(p.passDetail) };
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return { result: "cant_tell", detail: p.cantTellDetail };
|
|
701
|
+
};
|
|
702
|
+
|
|
665
703
|
// src/checkers/index.ts
|
|
666
704
|
var registry = {
|
|
667
705
|
"positional-arg-identity": positionalArgIdentity,
|
|
@@ -671,7 +709,8 @@ var registry = {
|
|
|
671
709
|
"object-arg-property-literal-equals": objectArgPropertyLiteralEquals,
|
|
672
710
|
"anchor-init-if-needed-guarded": anchorInitIfNeededGuarded,
|
|
673
711
|
"env-secrets-committed": envSecretsCommitted,
|
|
674
|
-
"taint-to-sink": taintToSink
|
|
712
|
+
"taint-to-sink": taintToSink,
|
|
713
|
+
"literal-multiplier-wrong-constant": literalMultiplierWrongConstant
|
|
675
714
|
};
|
|
676
715
|
function runChecker(kind, c, params) {
|
|
677
716
|
const fn = registry[kind];
|
|
@@ -887,7 +926,7 @@ function findRustCandidates(targetDir, rule) {
|
|
|
887
926
|
}
|
|
888
927
|
|
|
889
928
|
// src/fixers/positionalArgIdentity.ts
|
|
890
|
-
import { SyntaxKind as
|
|
929
|
+
import { SyntaxKind as SyntaxKind9 } from "ts-morph";
|
|
891
930
|
|
|
892
931
|
// src/fixers/diffUtil.ts
|
|
893
932
|
function buildDiff(node, replacement) {
|
|
@@ -912,9 +951,9 @@ function buildDiff(node, replacement) {
|
|
|
912
951
|
// src/fixers/positionalArgIdentity.ts
|
|
913
952
|
var fixPositionalArgIdentity = (c, p, outcome) => {
|
|
914
953
|
if (outcome.result !== "fail") return void 0;
|
|
915
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
954
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind9.CallExpression).filter((call) => {
|
|
916
955
|
const exp = call.getExpression();
|
|
917
|
-
return exp.getKind() ===
|
|
956
|
+
return exp.getKind() === SyntaxKind9.PropertyAccessExpression && exp.asKind(SyntaxKind9.PropertyAccessExpression).getName() === p.call;
|
|
918
957
|
});
|
|
919
958
|
if (calls.length === 0) {
|
|
920
959
|
const wantParam2 = c.params[p.paramIndex] ?? "<rawBodyParam>";
|
|
@@ -929,7 +968,7 @@ Do not call JSON.parse() on the body before this verification step.`
|
|
|
929
968
|
}
|
|
930
969
|
const arg = calls[0].getArguments()[p.argIndex];
|
|
931
970
|
const wantParam = c.params[p.paramIndex];
|
|
932
|
-
if (arg && wantParam && arg.getKind() ===
|
|
971
|
+
if (arg && wantParam && arg.getKind() === SyntaxKind9.CallExpression) {
|
|
933
972
|
return {
|
|
934
973
|
summary: `Pass the raw body parameter '${wantParam}' to ${p.call} instead of a parsed value`,
|
|
935
974
|
diff: buildDiff(arg, wantParam)
|
|
@@ -939,12 +978,12 @@ Do not call JSON.parse() on the body before this verification step.`
|
|
|
939
978
|
};
|
|
940
979
|
|
|
941
980
|
// src/fixers/requiredCallWithOptions.ts
|
|
942
|
-
import { SyntaxKind as
|
|
943
|
-
function
|
|
981
|
+
import { SyntaxKind as SyntaxKind10 } from "ts-morph";
|
|
982
|
+
function callName5(call) {
|
|
944
983
|
const exp = call.getExpression();
|
|
945
|
-
if (exp.getKind() ===
|
|
946
|
-
if (exp.getKind() ===
|
|
947
|
-
return exp.asKind(
|
|
984
|
+
if (exp.getKind() === SyntaxKind10.Identifier) return exp.getText();
|
|
985
|
+
if (exp.getKind() === SyntaxKind10.PropertyAccessExpression) {
|
|
986
|
+
return exp.asKind(SyntaxKind10.PropertyAccessExpression).getName();
|
|
948
987
|
}
|
|
949
988
|
return "";
|
|
950
989
|
}
|
|
@@ -962,15 +1001,15 @@ function placeholderFor(propName) {
|
|
|
962
1001
|
}
|
|
963
1002
|
var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
964
1003
|
if (outcome.result !== "fail") return void 0;
|
|
965
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
966
|
-
const verify = calls.filter((x) => p.verifyCalls.includes(
|
|
1004
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind10.CallExpression);
|
|
1005
|
+
const verify = calls.filter((x) => p.verifyCalls.includes(callName5(x)));
|
|
967
1006
|
if (verify.length > 0) {
|
|
968
1007
|
const call = verify[0];
|
|
969
1008
|
const args = call.getArguments();
|
|
970
1009
|
const lastArg = args[args.length - 1];
|
|
971
|
-
const obj = lastArg?.asKind(
|
|
1010
|
+
const obj = lastArg?.asKind(SyntaxKind10.ObjectLiteralExpression);
|
|
972
1011
|
const presentNames = obj ? obj.getProperties().map((pr) => {
|
|
973
|
-
const pa = pr.asKind(
|
|
1012
|
+
const pa = pr.asKind(SyntaxKind10.PropertyAssignment) ?? pr.asKind(SyntaxKind10.ShorthandPropertyAssignment);
|
|
974
1013
|
return pa?.getName() ?? "";
|
|
975
1014
|
}) : [];
|
|
976
1015
|
const missingGroups = p.requiredProps.filter(
|
|
@@ -978,7 +1017,7 @@ var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
|
978
1017
|
);
|
|
979
1018
|
if (missingGroups.length === 0) return void 0;
|
|
980
1019
|
const newProps = missingGroups.map((g) => placeholderFor(g[0])).join(", ");
|
|
981
|
-
const summary = `Add ${missingGroups.map((g) => g[0]).join(" and ")} to the ${
|
|
1020
|
+
const summary = `Add ${missingGroups.map((g) => g[0]).join(" and ")} to the ${callName5(call)} call`;
|
|
982
1021
|
if (obj) {
|
|
983
1022
|
const inner = obj.getText().slice(1, -1).trim();
|
|
984
1023
|
const newText = inner.length > 0 ? `{ ${inner}, ${newProps} }` : `{ ${newProps} }`;
|
|
@@ -990,7 +1029,7 @@ var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
|
990
1029
|
}
|
|
991
1030
|
return {
|
|
992
1031
|
summary,
|
|
993
|
-
suggestion: `Add an options object ({ ${newProps} }) as an argument to ${
|
|
1032
|
+
suggestion: `Add an options object ({ ${newProps} }) as an argument to ${callName5(call)}.`
|
|
994
1033
|
};
|
|
995
1034
|
}
|
|
996
1035
|
return {
|
|
@@ -1003,10 +1042,48 @@ JWKS must come from Privy's published JWKS endpoint for your app.`
|
|
|
1003
1042
|
};
|
|
1004
1043
|
};
|
|
1005
1044
|
|
|
1045
|
+
// src/fixers/literalMultiplierWrongConstant.ts
|
|
1046
|
+
import { SyntaxKind as SyntaxKind11 } from "ts-morph";
|
|
1047
|
+
function callName6(call) {
|
|
1048
|
+
const exp = call.getExpression();
|
|
1049
|
+
if (exp.getKind() === SyntaxKind11.Identifier) return exp.getText();
|
|
1050
|
+
if (exp.getKind() === SyntaxKind11.PropertyAccessExpression) {
|
|
1051
|
+
return exp.asKind(SyntaxKind11.PropertyAccessExpression).getName();
|
|
1052
|
+
}
|
|
1053
|
+
return "";
|
|
1054
|
+
}
|
|
1055
|
+
function findIdentifier(node, name) {
|
|
1056
|
+
if (node.getKind() === SyntaxKind11.Identifier && node.getText() === name) return node;
|
|
1057
|
+
return node.getDescendantsOfKind(SyntaxKind11.Identifier).find((id) => id.getText() === name);
|
|
1058
|
+
}
|
|
1059
|
+
var fixLiteralMultiplierWrongConstant = (c, p, outcome) => {
|
|
1060
|
+
if (outcome.result !== "fail") return void 0;
|
|
1061
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind11.CallExpression).filter((call) => callName6(call) === p.call);
|
|
1062
|
+
if (calls.length === 0) return void 0;
|
|
1063
|
+
const arg = calls[0].getArguments()[p.argIndex];
|
|
1064
|
+
if (!arg) return void 0;
|
|
1065
|
+
const forbidden = Array.isArray(p.forbiddenIdentifiers) ? p.forbiddenIdentifiers : [];
|
|
1066
|
+
const forbiddenNode = forbidden.map((name) => findIdentifier(arg, name)).find((n) => n);
|
|
1067
|
+
if (!forbiddenNode) return void 0;
|
|
1068
|
+
const expected = Array.isArray(p.expectedIdentifiers) ? p.expectedIdentifiers : [];
|
|
1069
|
+
const expectedName = expected.find((name) => c.params.includes(name));
|
|
1070
|
+
if (!expectedName) {
|
|
1071
|
+
return {
|
|
1072
|
+
summary: `Scale by the mint's decimals instead of ${forbiddenNode.getText()}`,
|
|
1073
|
+
suggestion: `'${forbiddenNode.getText()}' is the native-SOL lamports constant (1e9), not the mint's decimals. Add a 'decimals: number' parameter to this function (threaded from the mint's configuration) and scale the amount with '10 ** decimals' instead of '${forbiddenNode.getText()}'.`
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
summary: `Scale by 10 ** ${expectedName} instead of ${forbiddenNode.getText()}`,
|
|
1078
|
+
diff: buildDiff(forbiddenNode, `10 ** ${expectedName}`)
|
|
1079
|
+
};
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1006
1082
|
// src/fixers/index.ts
|
|
1007
1083
|
var registry2 = {
|
|
1008
1084
|
"positional-arg-identity": fixPositionalArgIdentity,
|
|
1009
|
-
"required-call-with-options": fixRequiredCallWithOptions
|
|
1085
|
+
"required-call-with-options": fixRequiredCallWithOptions,
|
|
1086
|
+
"literal-multiplier-wrong-constant": fixLiteralMultiplierWrongConstant
|
|
1010
1087
|
};
|
|
1011
1088
|
function runFixer(kind, c, params, outcome) {
|
|
1012
1089
|
if (outcome.result !== "fail") return void 0;
|
|
@@ -1956,7 +2033,7 @@ function renderTrustGraphMd(g) {
|
|
|
1956
2033
|
}
|
|
1957
2034
|
|
|
1958
2035
|
// src/costAnalysis.ts
|
|
1959
|
-
import { Project as Project2, SyntaxKind as
|
|
2036
|
+
import { Project as Project2, SyntaxKind as SyntaxKind12 } from "ts-morph";
|
|
1960
2037
|
var LAMPORTS_PER_BYTE_YEAR = 3480;
|
|
1961
2038
|
var EXEMPTION_THRESHOLD = 2;
|
|
1962
2039
|
var OVERHEAD_BYTES = 128;
|
|
@@ -2037,11 +2114,11 @@ var KNOWN_FLOWS = [
|
|
|
2037
2114
|
}
|
|
2038
2115
|
];
|
|
2039
2116
|
var LOOP_NODE_KINDS = /* @__PURE__ */ new Set([
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2117
|
+
SyntaxKind12.ForStatement,
|
|
2118
|
+
SyntaxKind12.ForOfStatement,
|
|
2119
|
+
SyntaxKind12.ForInStatement,
|
|
2120
|
+
SyntaxKind12.WhileStatement,
|
|
2121
|
+
SyntaxKind12.DoStatement
|
|
2045
2122
|
]);
|
|
2046
2123
|
var ARRAY_METHOD_LOOPS = /* @__PURE__ */ new Set(["map", "forEach", "flatMap", "reduce", "filter"]);
|
|
2047
2124
|
function isInsideLoop(node) {
|
|
@@ -2049,12 +2126,12 @@ function isInsideLoop(node) {
|
|
|
2049
2126
|
while (cur) {
|
|
2050
2127
|
const k = cur.getKind?.();
|
|
2051
2128
|
if (k !== void 0 && LOOP_NODE_KINDS.has(k)) {
|
|
2052
|
-
return { scalable: true, note: `call is inside a ${
|
|
2129
|
+
return { scalable: true, note: `call is inside a ${SyntaxKind12[k]} \u2014 cost scales with loop iterations` };
|
|
2053
2130
|
}
|
|
2054
|
-
if (k ===
|
|
2131
|
+
if (k === SyntaxKind12.CallExpression) {
|
|
2055
2132
|
const expr = cur.getExpression?.();
|
|
2056
|
-
if (expr?.getKind?.() ===
|
|
2057
|
-
const name = expr.asKind?.(
|
|
2133
|
+
if (expr?.getKind?.() === SyntaxKind12.PropertyAccessExpression) {
|
|
2134
|
+
const name = expr.asKind?.(SyntaxKind12.PropertyAccessExpression)?.getName?.();
|
|
2058
2135
|
if (name && ARRAY_METHOD_LOOPS.has(name)) {
|
|
2059
2136
|
return { scalable: true, note: `call is inside .${name}() \u2014 cost scales with array length` };
|
|
2060
2137
|
}
|
|
@@ -2068,7 +2145,7 @@ function detectPriorityFee(targetDir) {
|
|
|
2068
2145
|
const project = new Project2({ skipAddingFilesFromTsConfig: true });
|
|
2069
2146
|
for (const file of walk(targetDir)) {
|
|
2070
2147
|
const sf = project.addSourceFileAtPath(file);
|
|
2071
|
-
const calls = sf.getDescendantsOfKind(
|
|
2148
|
+
const calls = sf.getDescendantsOfKind(SyntaxKind12.CallExpression);
|
|
2072
2149
|
for (const ce of calls) {
|
|
2073
2150
|
const expr = ce.getExpression();
|
|
2074
2151
|
const text = expr.getText();
|
|
@@ -2096,22 +2173,22 @@ function detectAccountFlows(targetDir) {
|
|
|
2096
2173
|
const importedModules = new Set(
|
|
2097
2174
|
sf.getImportDeclarations().map((d) => d.getModuleSpecifierValue())
|
|
2098
2175
|
);
|
|
2099
|
-
for (const ce of sf.getDescendantsOfKind(
|
|
2176
|
+
for (const ce of sf.getDescendantsOfKind(SyntaxKind12.CallExpression)) {
|
|
2100
2177
|
const expr = ce.getExpression();
|
|
2101
|
-
let
|
|
2102
|
-
if (expr.getKind() ===
|
|
2103
|
-
|
|
2104
|
-
} else if (expr.getKind() ===
|
|
2105
|
-
|
|
2178
|
+
let callName7 = null;
|
|
2179
|
+
if (expr.getKind() === SyntaxKind12.Identifier) {
|
|
2180
|
+
callName7 = expr.getText();
|
|
2181
|
+
} else if (expr.getKind() === SyntaxKind12.PropertyAccessExpression) {
|
|
2182
|
+
callName7 = expr.asKind(SyntaxKind12.PropertyAccessExpression).getName();
|
|
2106
2183
|
}
|
|
2107
|
-
if (!
|
|
2108
|
-
const known = callIndex.get(
|
|
2184
|
+
if (!callName7) continue;
|
|
2185
|
+
const known = callIndex.get(callName7);
|
|
2109
2186
|
if (!known) continue;
|
|
2110
2187
|
if (!importedModules.has(known.module)) continue;
|
|
2111
2188
|
const lamports = rentExemptMinimum(known.dataLen);
|
|
2112
2189
|
const { scalable, note } = isInsideLoop(ce);
|
|
2113
2190
|
flows.push({
|
|
2114
|
-
call:
|
|
2191
|
+
call: callName7,
|
|
2115
2192
|
module: known.module,
|
|
2116
2193
|
accountType: known.accountType,
|
|
2117
2194
|
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.2",
|
|
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": [
|