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 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.1";
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 matches = await (0, import_glob.glob)(pattern, {
2711
- ignore: options?.exclude || [],
2712
- nodir: true,
2713
- absolute: true
2714
- });
2715
- files.push(...matches);
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 (varName.toLowerCase().includes("fee") || varName.toLowerCase().includes("payer")) {
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 lastElement = elements[elements.length - 1];
2940
- if (lastElement && lastElement.type !== "SpreadElement") {
2941
- return lastElement;
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",