solana-privacy-scanner-core 0.6.1 → 0.6.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/index.cjs +93 -11
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +96 -14
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -82,7 +82,7 @@ var import_web3 = require("@solana/web3.js");
|
|
|
82
82
|
// src/constants.ts
|
|
83
83
|
var _RPC_ENCODED = "aHR0cHM6Ly9zZXJlbmUtcm91Z2gtcG9vbC5zb2xhbmEtbWFpbm5ldC5xdWlrbm9kZS5wcm8vYTliM2RkNGRkMzc0MzYwYzQzNzY4YzQyMTI2NmE2ZGNlZDU4MTI3Ny8=";
|
|
84
84
|
var DEFAULT_RPC_URL = /* @__PURE__ */ Buffer.from(_RPC_ENCODED, "base64").toString("utf-8");
|
|
85
|
-
var VERSION = "0.6.
|
|
85
|
+
var VERSION = "0.6.2";
|
|
86
86
|
|
|
87
87
|
// src/rpc/client.ts
|
|
88
88
|
var RateLimiter = class {
|
|
@@ -2697,6 +2697,7 @@ function createDefaultLabelProvider() {
|
|
|
2697
2697
|
var fs = __toESM(require("fs/promises"), 1);
|
|
2698
2698
|
var path = __toESM(require("path"), 1);
|
|
2699
2699
|
var import_glob = require("glob");
|
|
2700
|
+
var CODE_EXTENSIONS = ["ts", "tsx", "js", "jsx"];
|
|
2700
2701
|
async function readFile2(filePath) {
|
|
2701
2702
|
try {
|
|
2702
2703
|
return await fs.readFile(filePath, "utf-8");
|
|
@@ -2704,15 +2705,29 @@ async function readFile2(filePath) {
|
|
|
2704
2705
|
throw new Error(`Failed to read file ${filePath}: ${error}`);
|
|
2705
2706
|
}
|
|
2706
2707
|
}
|
|
2708
|
+
async function expandPattern(pattern) {
|
|
2709
|
+
try {
|
|
2710
|
+
const cleaned = pattern.replace(/\/+$/, "");
|
|
2711
|
+
const stat2 = await fs.stat(cleaned);
|
|
2712
|
+
if (stat2.isDirectory()) {
|
|
2713
|
+
return CODE_EXTENSIONS.map((ext) => path.join(cleaned, "**", `*.${ext}`));
|
|
2714
|
+
}
|
|
2715
|
+
} catch {
|
|
2716
|
+
}
|
|
2717
|
+
return [pattern];
|
|
2718
|
+
}
|
|
2707
2719
|
async function findFiles(patterns, options) {
|
|
2708
2720
|
const files = [];
|
|
2709
2721
|
for (const pattern of patterns) {
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2722
|
+
const expanded = await expandPattern(pattern);
|
|
2723
|
+
for (const p of expanded) {
|
|
2724
|
+
const matches = await (0, import_glob.glob)(p, {
|
|
2725
|
+
ignore: options?.exclude || [],
|
|
2726
|
+
nodir: true,
|
|
2727
|
+
absolute: true
|
|
2728
|
+
});
|
|
2729
|
+
files.push(...matches);
|
|
2730
|
+
}
|
|
2716
2731
|
}
|
|
2717
2732
|
return [...new Set(files)];
|
|
2718
2733
|
}
|
|
@@ -2802,7 +2817,7 @@ function detectFeePayerReuseInCode(content, filePath) {
|
|
|
2802
2817
|
traverse(ast, (node) => {
|
|
2803
2818
|
if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && node.init && isKeypairGenerate(node.init)) {
|
|
2804
2819
|
const varName = node.id.name;
|
|
2805
|
-
if (
|
|
2820
|
+
if (isFeePayerName(varName)) {
|
|
2806
2821
|
const isInsideLoop = loops.some((loop) => {
|
|
2807
2822
|
return isNodeInsideNode(node, loop.body);
|
|
2808
2823
|
});
|
|
@@ -2819,6 +2834,22 @@ function detectFeePayerReuseInCode(content, filePath) {
|
|
|
2819
2834
|
}
|
|
2820
2835
|
}
|
|
2821
2836
|
}
|
|
2837
|
+
if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.params) {
|
|
2838
|
+
for (const param of node.params) {
|
|
2839
|
+
const paramName = param.type === "Identifier" ? param.name : param.type === "AssignmentPattern" && param.left.type === "Identifier" ? param.left.name : null;
|
|
2840
|
+
if (paramName && isFeePayerName(paramName)) {
|
|
2841
|
+
feePayerVars.set(paramName, {
|
|
2842
|
+
name: paramName,
|
|
2843
|
+
declaration: {
|
|
2844
|
+
line: param.loc.start.line,
|
|
2845
|
+
column: param.loc.start.column,
|
|
2846
|
+
file: filePath
|
|
2847
|
+
},
|
|
2848
|
+
usages: []
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2822
2853
|
});
|
|
2823
2854
|
for (const [varName, varInfo] of feePayerVars) {
|
|
2824
2855
|
for (const loop of loops) {
|
|
@@ -2833,6 +2864,34 @@ function detectFeePayerReuseInCode(content, filePath) {
|
|
|
2833
2864
|
});
|
|
2834
2865
|
}
|
|
2835
2866
|
}
|
|
2867
|
+
if (node.type === "CallExpression" && !isTransactionCall(node)) {
|
|
2868
|
+
for (const arg of node.arguments) {
|
|
2869
|
+
if (arg.type === "Identifier" && arg.name === varName) {
|
|
2870
|
+
const alreadyTracked = varInfo.usages.some(
|
|
2871
|
+
(u) => u.line === node.loc.start.line && u.column === node.loc.start.column
|
|
2872
|
+
);
|
|
2873
|
+
if (!alreadyTracked) {
|
|
2874
|
+
varInfo.usages.push({
|
|
2875
|
+
line: node.loc.start.line,
|
|
2876
|
+
column: node.loc.start.column,
|
|
2877
|
+
file: filePath
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
break;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
if (node.type === "AssignmentExpression" && node.left.type === "MemberExpression" && node.left.property.type === "Identifier" && node.left.property.name === "feePayer") {
|
|
2885
|
+
const right = node.right;
|
|
2886
|
+
const assignedName = right.type === "MemberExpression" && right.object.type === "Identifier" ? right.object.name : right.type === "Identifier" ? right.name : null;
|
|
2887
|
+
if (assignedName === varName) {
|
|
2888
|
+
varInfo.usages.push({
|
|
2889
|
+
line: node.loc.start.line,
|
|
2890
|
+
column: node.loc.start.column,
|
|
2891
|
+
file: filePath
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2836
2895
|
});
|
|
2837
2896
|
}
|
|
2838
2897
|
if (varInfo.usages.length > 0) {
|
|
@@ -2904,6 +2963,10 @@ function traverse(node, visitor) {
|
|
|
2904
2963
|
}
|
|
2905
2964
|
}
|
|
2906
2965
|
}
|
|
2966
|
+
function isFeePayerName(name) {
|
|
2967
|
+
const lower = name.toLowerCase();
|
|
2968
|
+
return lower.includes("fee") || lower.includes("payer");
|
|
2969
|
+
}
|
|
2907
2970
|
function isKeypairGenerate(node) {
|
|
2908
2971
|
if (node.type === "CallExpression") {
|
|
2909
2972
|
const callee = node.callee;
|
|
@@ -2935,10 +2998,15 @@ function findFeePayerArgument(node) {
|
|
|
2935
2998
|
for (const arg of node.arguments) {
|
|
2936
2999
|
if (arg.type === "ArrayExpression") {
|
|
2937
3000
|
const elements = arg.elements.filter((e) => e !== null);
|
|
3001
|
+
for (const el of elements) {
|
|
3002
|
+
if (el && el.type === "Identifier" && isFeePayerName(el.name)) {
|
|
3003
|
+
return el;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
2938
3006
|
if (elements.length > 0) {
|
|
2939
|
-
const
|
|
2940
|
-
if (
|
|
2941
|
-
return
|
|
3007
|
+
const first = elements[0];
|
|
3008
|
+
if (first && first.type !== "SpreadElement") {
|
|
3009
|
+
return first;
|
|
2942
3010
|
}
|
|
2943
3011
|
}
|
|
2944
3012
|
}
|
|
@@ -3027,6 +3095,20 @@ function detectMemoPII(content, filePath) {
|
|
|
3027
3095
|
});
|
|
3028
3096
|
}
|
|
3029
3097
|
}
|
|
3098
|
+
const piiVarMatches = memoValue.matchAll(/\$\{(\w*(?:email|name|user|phone|ssn|address|identity|account)\w*)\}/gi);
|
|
3099
|
+
for (const match of piiVarMatches) {
|
|
3100
|
+
issues.push({
|
|
3101
|
+
type: "memo-pii",
|
|
3102
|
+
severity: "HIGH",
|
|
3103
|
+
file: filePath,
|
|
3104
|
+
line: index + 1,
|
|
3105
|
+
column: columnOffset,
|
|
3106
|
+
message: `Variable '${match[1]}' interpolated into memo likely contains PII`,
|
|
3107
|
+
suggestion: "Do not interpolate user-identifying variables into memo fields. Memos are permanently public on-chain.",
|
|
3108
|
+
codeSnippet: extractSnippet(content, index + 1),
|
|
3109
|
+
identifier: `pii-variable: ${match[1]}`
|
|
3110
|
+
});
|
|
3111
|
+
}
|
|
3030
3112
|
if (isDescriptiveContent(memoValue)) {
|
|
3031
3113
|
issues.push({
|
|
3032
3114
|
type: "memo-pii",
|