solana-privacy-scanner 0.3.2 → 0.4.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/dist/index.js +426 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -325,6 +325,430 @@ async function scanProgram(programId, options) {
|
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
// src/commands/analyze.ts
|
|
329
|
+
import chalk2 from "chalk";
|
|
330
|
+
import { analyze } from "solana-privacy-scanner-core";
|
|
331
|
+
import * as path from "path";
|
|
332
|
+
import * as fs from "fs";
|
|
333
|
+
async function analyzeCommand(paths, options) {
|
|
334
|
+
try {
|
|
335
|
+
if (!options.quiet) {
|
|
336
|
+
console.log(chalk2.blue("\u{1F512} Running Solana Privacy Analyzer...\n"));
|
|
337
|
+
}
|
|
338
|
+
const result = await analyze(paths, {
|
|
339
|
+
includeLow: options.low !== false
|
|
340
|
+
});
|
|
341
|
+
if (options.json) {
|
|
342
|
+
const output = JSON.stringify(result, null, 2);
|
|
343
|
+
if (options.output) {
|
|
344
|
+
fs.writeFileSync(options.output, output);
|
|
345
|
+
console.log(chalk2.green(`\u2705 Results written to ${options.output}`));
|
|
346
|
+
} else {
|
|
347
|
+
console.log(output);
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
printResults(result, options.quiet || false);
|
|
351
|
+
if (options.output) {
|
|
352
|
+
const jsonOutput = JSON.stringify(result, null, 2);
|
|
353
|
+
fs.writeFileSync(options.output, jsonOutput);
|
|
354
|
+
console.log(chalk2.green(`
|
|
355
|
+
\u2705 Results also written to ${options.output}`));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (result.summary.critical > 0 || result.summary.high > 0) {
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error(chalk2.red("\u274C Analysis failed:"), error);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function printResults(result, quiet) {
|
|
367
|
+
console.log(chalk2.bold("\u{1F4CA} Scan Summary\n"));
|
|
368
|
+
console.log(`Files analyzed: ${result.filesAnalyzed}`);
|
|
369
|
+
console.log(`Total issues: ${result.summary.total}
|
|
370
|
+
`);
|
|
371
|
+
if (result.summary.critical > 0) {
|
|
372
|
+
console.log(chalk2.red(` \u{1F534} CRITICAL: ${result.summary.critical}`));
|
|
373
|
+
}
|
|
374
|
+
if (result.summary.high > 0) {
|
|
375
|
+
console.log(chalk2.yellow(` \u{1F7E1} HIGH: ${result.summary.high}`));
|
|
376
|
+
}
|
|
377
|
+
if (result.summary.medium > 0) {
|
|
378
|
+
console.log(chalk2.blue(` \u{1F535} MEDIUM: ${result.summary.medium}`));
|
|
379
|
+
}
|
|
380
|
+
if (result.summary.low > 0) {
|
|
381
|
+
console.log(chalk2.gray(` \u26AA LOW: ${result.summary.low}`));
|
|
382
|
+
}
|
|
383
|
+
if (result.issues.length === 0) {
|
|
384
|
+
console.log(chalk2.green("\n\u2705 No issues found!"));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (quiet) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const byFile = result.issues.reduce((acc, issue) => {
|
|
391
|
+
if (!acc[issue.file]) acc[issue.file] = [];
|
|
392
|
+
acc[issue.file].push(issue);
|
|
393
|
+
return acc;
|
|
394
|
+
}, {});
|
|
395
|
+
console.log(chalk2.bold("\n\u{1F4CB} Detailed Issues\n"));
|
|
396
|
+
for (const [file, issues] of Object.entries(byFile)) {
|
|
397
|
+
console.log(chalk2.bold(`
|
|
398
|
+
\u{1F4C1} ${path.relative(process.cwd(), file)}`));
|
|
399
|
+
console.log("\u2501".repeat(80));
|
|
400
|
+
issues.forEach((issue, index) => {
|
|
401
|
+
const severityColor = issue.severity === "CRITICAL" ? chalk2.red : issue.severity === "HIGH" ? chalk2.yellow : issue.severity === "MEDIUM" ? chalk2.blue : chalk2.gray;
|
|
402
|
+
const severityEmoji = issue.severity === "CRITICAL" ? "\u{1F534}" : issue.severity === "HIGH" ? "\u{1F7E1}" : issue.severity === "MEDIUM" ? "\u{1F535}" : "\u26AA";
|
|
403
|
+
console.log(`${index + 1}. ${severityEmoji} ${severityColor(issue.severity)} ${issue.message}`);
|
|
404
|
+
console.log(` Line ${issue.line}:${issue.column}`);
|
|
405
|
+
if (issue.suggestion) {
|
|
406
|
+
console.log(chalk2.dim(` Suggestion: ${issue.suggestion}`));
|
|
407
|
+
}
|
|
408
|
+
if (issue.codeSnippet) {
|
|
409
|
+
console.log(chalk2.dim("\n Code:"));
|
|
410
|
+
console.log(chalk2.dim(issue.codeSnippet.split("\n").map((l) => ` ${l}`).join("\n")));
|
|
411
|
+
}
|
|
412
|
+
console.log();
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
if (result.summary.critical > 0 || result.summary.high > 0) {
|
|
416
|
+
console.log(chalk2.yellow("\n\u{1F4A1} Recommendations\n"));
|
|
417
|
+
console.log("Critical and high severity issues should be fixed before deployment.");
|
|
418
|
+
console.log("Run with --json to get machine-readable output for CI/CD integration.");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/commands/init.ts
|
|
423
|
+
import { writeFileSync as writeFileSync5, existsSync, mkdirSync, chmodSync } from "fs";
|
|
424
|
+
import { join } from "path";
|
|
425
|
+
import prompts from "prompts";
|
|
426
|
+
import chalk3 from "chalk";
|
|
427
|
+
async function initCommand() {
|
|
428
|
+
console.log(chalk3.bold.blue("\n\u{1F50D} Solana Privacy Scanner - Setup Wizard\n"));
|
|
429
|
+
const configPath = join(process.cwd(), ".privacyrc");
|
|
430
|
+
if (existsSync(configPath)) {
|
|
431
|
+
const { overwrite } = await prompts({
|
|
432
|
+
type: "confirm",
|
|
433
|
+
name: "overwrite",
|
|
434
|
+
message: "Configuration file already exists. Overwrite?",
|
|
435
|
+
initial: false
|
|
436
|
+
});
|
|
437
|
+
if (!overwrite) {
|
|
438
|
+
console.log(chalk3.yellow("\nSetup cancelled."));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const responses = await prompts([
|
|
443
|
+
{
|
|
444
|
+
type: "select",
|
|
445
|
+
name: "preset",
|
|
446
|
+
message: "Choose a configuration preset:",
|
|
447
|
+
choices: [
|
|
448
|
+
{
|
|
449
|
+
title: "Development (Permissive)",
|
|
450
|
+
value: "dev",
|
|
451
|
+
description: "Relaxed rules for development"
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
title: "Production (Strict)",
|
|
455
|
+
value: "prod",
|
|
456
|
+
description: "Strict rules for production code"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
title: "Custom",
|
|
460
|
+
value: "custom",
|
|
461
|
+
description: "Configure manually"
|
|
462
|
+
}
|
|
463
|
+
],
|
|
464
|
+
initial: 0
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
type: (prev) => prev === "custom" ? "select" : null,
|
|
468
|
+
name: "maxRiskLevel",
|
|
469
|
+
message: "Maximum acceptable risk level:",
|
|
470
|
+
choices: [
|
|
471
|
+
{ title: "LOW", value: "LOW" },
|
|
472
|
+
{ title: "MEDIUM", value: "MEDIUM" },
|
|
473
|
+
{ title: "HIGH", value: "HIGH" }
|
|
474
|
+
],
|
|
475
|
+
initial: 1
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
type: (_, values) => values.preset === "custom" ? "confirm" : null,
|
|
479
|
+
name: "enforceInCI",
|
|
480
|
+
message: "Enforce privacy checks in CI/CD?",
|
|
481
|
+
initial: true
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
type: (_, values) => values.preset === "custom" ? "confirm" : null,
|
|
485
|
+
name: "blockOnFailure",
|
|
486
|
+
message: "Block builds/commits on privacy policy violations?",
|
|
487
|
+
initial: false
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
type: "text",
|
|
491
|
+
name: "devnetWallet",
|
|
492
|
+
message: "Test wallet address (devnet):",
|
|
493
|
+
validate: (value) => !value || value.length === 44 || "Invalid Solana address"
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
type: "multiselect",
|
|
497
|
+
name: "integrations",
|
|
498
|
+
message: "Which integrations would you like to set up?",
|
|
499
|
+
choices: [
|
|
500
|
+
{ title: "GitHub Actions", value: "github", selected: true },
|
|
501
|
+
{ title: "Pre-commit Hook", value: "precommit", selected: true },
|
|
502
|
+
{ title: "Testing Matchers", value: "testing", selected: true }
|
|
503
|
+
]
|
|
504
|
+
}
|
|
505
|
+
]);
|
|
506
|
+
if (!responses.preset) {
|
|
507
|
+
console.log(chalk3.yellow("\nSetup cancelled."));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
let config2;
|
|
511
|
+
switch (responses.preset) {
|
|
512
|
+
case "dev":
|
|
513
|
+
config2 = {
|
|
514
|
+
maxRiskLevel: "HIGH",
|
|
515
|
+
enforceInCI: false,
|
|
516
|
+
blockOnFailure: false,
|
|
517
|
+
thresholds: {
|
|
518
|
+
maxHighSeverity: 5
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
break;
|
|
522
|
+
case "prod":
|
|
523
|
+
config2 = {
|
|
524
|
+
maxRiskLevel: "LOW",
|
|
525
|
+
enforceInCI: true,
|
|
526
|
+
blockOnFailure: true,
|
|
527
|
+
thresholds: {
|
|
528
|
+
maxHighSeverity: 0,
|
|
529
|
+
maxMediumSeverity: 2,
|
|
530
|
+
minPrivacyScore: 80
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
break;
|
|
534
|
+
case "custom":
|
|
535
|
+
config2 = {
|
|
536
|
+
maxRiskLevel: responses.maxRiskLevel || "MEDIUM",
|
|
537
|
+
enforceInCI: responses.enforceInCI ?? true,
|
|
538
|
+
blockOnFailure: responses.blockOnFailure ?? false,
|
|
539
|
+
thresholds: {
|
|
540
|
+
maxHighSeverity: 0
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
break;
|
|
544
|
+
default:
|
|
545
|
+
config2 = {
|
|
546
|
+
maxRiskLevel: "MEDIUM",
|
|
547
|
+
enforceInCI: true,
|
|
548
|
+
blockOnFailure: false,
|
|
549
|
+
thresholds: {
|
|
550
|
+
maxHighSeverity: 0
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
if (responses.devnetWallet) {
|
|
555
|
+
config2.testWallets = {
|
|
556
|
+
devnet: responses.devnetWallet
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
writeFileSync5(configPath, JSON.stringify(config2, null, 2));
|
|
560
|
+
console.log(chalk3.green(`
|
|
561
|
+
\u2713 Created ${configPath}`));
|
|
562
|
+
const integrations = responses.integrations || [];
|
|
563
|
+
if (integrations.includes("github")) {
|
|
564
|
+
setupGitHubActions();
|
|
565
|
+
}
|
|
566
|
+
if (integrations.includes("precommit")) {
|
|
567
|
+
setupPreCommitHook();
|
|
568
|
+
}
|
|
569
|
+
if (integrations.includes("testing")) {
|
|
570
|
+
setupTestingMatchers();
|
|
571
|
+
}
|
|
572
|
+
console.log(chalk3.bold.green("\n\u2713 Setup complete!\n"));
|
|
573
|
+
console.log(chalk3.bold("Next steps:\n"));
|
|
574
|
+
console.log(" 1. Review your configuration in " + chalk3.cyan(".privacyrc"));
|
|
575
|
+
console.log(" 2. Add a test wallet address to scan");
|
|
576
|
+
console.log(" 3. Run " + chalk3.cyan("npx solana-privacy-scanner scan-wallet <ADDRESS>"));
|
|
577
|
+
console.log("\nFor more info: " + chalk3.cyan("https://sps.guide"));
|
|
578
|
+
}
|
|
579
|
+
function setupGitHubActions() {
|
|
580
|
+
const workflowDir = join(process.cwd(), ".github", "workflows");
|
|
581
|
+
const workflowPath = join(workflowDir, "privacy-check.yml");
|
|
582
|
+
if (!existsSync(workflowDir)) {
|
|
583
|
+
mkdirSync(workflowDir, { recursive: true });
|
|
584
|
+
}
|
|
585
|
+
const workflow = `name: Privacy Check
|
|
586
|
+
|
|
587
|
+
on:
|
|
588
|
+
pull_request:
|
|
589
|
+
push:
|
|
590
|
+
branches: [main, develop]
|
|
591
|
+
|
|
592
|
+
jobs:
|
|
593
|
+
privacy-scan:
|
|
594
|
+
runs-on: ubuntu-latest
|
|
595
|
+
|
|
596
|
+
steps:
|
|
597
|
+
- uses: actions/checkout@v3
|
|
598
|
+
|
|
599
|
+
- name: Setup Node.js
|
|
600
|
+
uses: actions/setup-node@v3
|
|
601
|
+
with:
|
|
602
|
+
node-version: '20'
|
|
603
|
+
|
|
604
|
+
- name: Install Dependencies
|
|
605
|
+
run: npm ci
|
|
606
|
+
|
|
607
|
+
- name: Install Privacy Scanner
|
|
608
|
+
run: npm install -g solana-privacy-scanner
|
|
609
|
+
|
|
610
|
+
- name: Run Privacy Check
|
|
611
|
+
run: |
|
|
612
|
+
# Get test wallet from config
|
|
613
|
+
TEST_WALLET=$(node -e "console.log(require('./.privacyrc').testWallets?.devnet || '')")
|
|
614
|
+
|
|
615
|
+
if [ -n "$TEST_WALLET" ]; then
|
|
616
|
+
solana-privacy-scanner scan-wallet $TEST_WALLET --json --output privacy-report.json
|
|
617
|
+
else
|
|
618
|
+
echo "No test wallet configured, skipping scan"
|
|
619
|
+
exit 0
|
|
620
|
+
fi
|
|
621
|
+
|
|
622
|
+
- name: Check Privacy Policy
|
|
623
|
+
run: |
|
|
624
|
+
# Parse report and check against policy
|
|
625
|
+
RISK_LEVEL=$(node -e "console.log(require('./privacy-report.json').overallRisk)")
|
|
626
|
+
MAX_RISK=$(node -e "console.log(require('./.privacyrc').maxRiskLevel)")
|
|
627
|
+
|
|
628
|
+
echo "Risk Level: $RISK_LEVEL"
|
|
629
|
+
echo "Max Allowed: $MAX_RISK"
|
|
630
|
+
|
|
631
|
+
# Simple comparison (can be enhanced)
|
|
632
|
+
if [ "$RISK_LEVEL" = "HIGH" ] && [ "$MAX_RISK" != "HIGH" ]; then
|
|
633
|
+
echo "Privacy policy violated!"
|
|
634
|
+
exit 1
|
|
635
|
+
fi
|
|
636
|
+
|
|
637
|
+
- name: Upload Report
|
|
638
|
+
if: always()
|
|
639
|
+
uses: actions/upload-artifact@v3
|
|
640
|
+
with:
|
|
641
|
+
name: privacy-report
|
|
642
|
+
path: privacy-report.json
|
|
643
|
+
`;
|
|
644
|
+
writeFileSync5(workflowPath, workflow);
|
|
645
|
+
console.log(chalk3.green(`
|
|
646
|
+
\u2713 Created ${workflowPath}`));
|
|
647
|
+
}
|
|
648
|
+
function setupPreCommitHook() {
|
|
649
|
+
const hookPath = join(process.cwd(), ".husky", "pre-commit");
|
|
650
|
+
const huskyDir = join(process.cwd(), ".husky");
|
|
651
|
+
if (!existsSync(huskyDir)) {
|
|
652
|
+
mkdirSync(huskyDir, { recursive: true });
|
|
653
|
+
}
|
|
654
|
+
const hook = `#!/bin/sh
|
|
655
|
+
. "$(dirname "$0")/_/husky.sh"
|
|
656
|
+
|
|
657
|
+
# Load config
|
|
658
|
+
CONFIG_FILE=".privacyrc"
|
|
659
|
+
|
|
660
|
+
if [ ! -f "$CONFIG_FILE" ]; then
|
|
661
|
+
echo "No .privacyrc found, skipping privacy check"
|
|
662
|
+
exit 0
|
|
663
|
+
fi
|
|
664
|
+
|
|
665
|
+
# Get test wallet
|
|
666
|
+
TEST_WALLET=$(node -e "const config = require('./.privacyrc'); console.log(config.testWallets?.devnet || '');" 2>/dev/null)
|
|
667
|
+
|
|
668
|
+
if [ -z "$TEST_WALLET" ]; then
|
|
669
|
+
echo "No test wallet configured, skipping privacy check"
|
|
670
|
+
exit 0
|
|
671
|
+
fi
|
|
672
|
+
|
|
673
|
+
echo "\u{1F50D} Running privacy check..."
|
|
674
|
+
|
|
675
|
+
# Run privacy scanner
|
|
676
|
+
npx solana-privacy-scanner scan-wallet $TEST_WALLET --json > /tmp/privacy-report.json 2>&1
|
|
677
|
+
|
|
678
|
+
# Check result
|
|
679
|
+
RISK_LEVEL=$(node -e "const report = require('/tmp/privacy-report.json'); console.log(report.overallRisk);" 2>/dev/null)
|
|
680
|
+
|
|
681
|
+
if [ "$RISK_LEVEL" = "HIGH" ]; then
|
|
682
|
+
echo "\u274C HIGH privacy risk detected!"
|
|
683
|
+
echo "Run 'npx solana-privacy-scanner scan-wallet $TEST_WALLET' for details"
|
|
684
|
+
echo ""
|
|
685
|
+
echo "To bypass this check, use: git commit --no-verify"
|
|
686
|
+
exit 1
|
|
687
|
+
fi
|
|
688
|
+
|
|
689
|
+
echo "\u2713 Privacy check passed"
|
|
690
|
+
exit 0
|
|
691
|
+
`;
|
|
692
|
+
writeFileSync5(hookPath, hook);
|
|
693
|
+
try {
|
|
694
|
+
chmodSync(hookPath, "755");
|
|
695
|
+
} catch (error) {
|
|
696
|
+
console.warn(chalk3.yellow("Could not make hook executable. Run: chmod +x .husky/pre-commit"));
|
|
697
|
+
}
|
|
698
|
+
console.log(chalk3.green(`
|
|
699
|
+
\u2713 Created ${hookPath}`));
|
|
700
|
+
console.log(chalk3.yellow(" Note: Install husky with: npm install --save-dev husky && npx husky install"));
|
|
701
|
+
}
|
|
702
|
+
function setupTestingMatchers() {
|
|
703
|
+
const testSetupPath = join(process.cwd(), "tests", "setup.ts");
|
|
704
|
+
const testDir = join(process.cwd(), "tests");
|
|
705
|
+
if (!existsSync(testDir)) {
|
|
706
|
+
mkdirSync(testDir, { recursive: true });
|
|
707
|
+
}
|
|
708
|
+
const setup = `// Privacy testing setup
|
|
709
|
+
import '@solana-privacy-scanner/ci-tools/matchers';
|
|
710
|
+
|
|
711
|
+
// This file is automatically loaded by Vitest
|
|
712
|
+
// Add any additional test setup here
|
|
713
|
+
`;
|
|
714
|
+
writeFileSync5(testSetupPath, setup);
|
|
715
|
+
console.log(chalk3.green(`
|
|
716
|
+
\u2713 Created ${testSetupPath}`));
|
|
717
|
+
const exampleTestPath = join(testDir, "privacy.example.test.ts");
|
|
718
|
+
const exampleTest = `import { describe, it, expect } from 'vitest';
|
|
719
|
+
import { Connection, Transaction, SystemProgram, Keypair } from '@solana/web3.js';
|
|
720
|
+
import { simulateTransactionPrivacy } from '@solana-privacy-scanner/ci-tools/simulator';
|
|
721
|
+
|
|
722
|
+
describe('Privacy Tests Example', () => {
|
|
723
|
+
const connection = new Connection('https://api.devnet.solana.com');
|
|
724
|
+
|
|
725
|
+
it('should maintain privacy for basic transfer', async () => {
|
|
726
|
+
// Create a simple transfer transaction
|
|
727
|
+
const from = Keypair.generate();
|
|
728
|
+
const to = Keypair.generate();
|
|
729
|
+
|
|
730
|
+
const tx = new Transaction().add(
|
|
731
|
+
SystemProgram.transfer({
|
|
732
|
+
fromPubkey: from.publicKey,
|
|
733
|
+
toPubkey: to.publicKey,
|
|
734
|
+
lamports: 1000000,
|
|
735
|
+
})
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
// Simulate and analyze privacy
|
|
739
|
+
const report = await simulateTransactionPrivacy(tx, connection);
|
|
740
|
+
|
|
741
|
+
// Make privacy assertions
|
|
742
|
+
expect(report).toHavePrivacyRisk('LOW');
|
|
743
|
+
expect(report).toHaveNoHighRiskSignals();
|
|
744
|
+
expect(report).toHaveAtMostSignals(2);
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
`;
|
|
748
|
+
writeFileSync5(exampleTestPath, exampleTest);
|
|
749
|
+
console.log(chalk3.green(`\u2713 Created ${exampleTestPath}`));
|
|
750
|
+
}
|
|
751
|
+
|
|
328
752
|
// src/index.ts
|
|
329
753
|
import { VERSION } from "solana-privacy-scanner-core";
|
|
330
754
|
dotenv.config({ path: ".env.local" });
|
|
@@ -334,5 +758,7 @@ program.name("solana-privacy-scanner").description("Privacy risk scanner for Sol
|
|
|
334
758
|
program.command("scan-wallet").alias("wallet").description("Scan a Solana wallet address for privacy risks").argument("<address>", "Wallet address to scan").option("--rpc <url>", "Custom RPC endpoint URL (optional)", process.env.SOLANA_RPC).option("--json", "Output as JSON", false).option("--max-signatures <number>", "Maximum number of signatures to fetch", "100").option("--output <file>", "Write output to file").action(scanWallet);
|
|
335
759
|
program.command("scan-transaction").alias("tx").description("Scan a single Solana transaction for privacy risks").argument("<signature>", "Transaction signature to scan").option("--rpc <url>", "Custom RPC endpoint URL (optional)", process.env.SOLANA_RPC).option("--json", "Output as JSON", false).option("--output <file>", "Write output to file").action(scanTransaction);
|
|
336
760
|
program.command("scan-program").alias("program").description("Scan a Solana program for privacy risks").argument("<programId>", "Program ID to scan").option("--rpc <url>", "Custom RPC endpoint URL (optional)", process.env.SOLANA_RPC).option("--json", "Output as JSON", false).option("--max-accounts <number>", "Maximum number of accounts to fetch", "100").option("--max-transactions <number>", "Maximum number of transactions to fetch", "50").option("--output <file>", "Write output to file").action(scanProgram);
|
|
761
|
+
program.command("analyze").description("Analyze source code for privacy vulnerabilities").argument("<paths...>", "Files or directories to analyze").option("--json", "Output as JSON", false).option("--no-low", "Exclude low severity issues").option("--quiet", "Only show summary").option("--output <file>", "Write output to file").action(analyzeCommand);
|
|
762
|
+
program.command("init").description("Interactive setup wizard for privacy configuration").action(initCommand);
|
|
337
763
|
program.parse();
|
|
338
764
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-privacy-scanner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "CLI tool for analyzing Solana
|
|
5
|
+
"description": "CLI tool for analyzing Solana privacy - scan wallets/transactions/programs, analyze source code, and set up CI/CD privacy checks",
|
|
6
6
|
"bin": {
|
|
7
7
|
"solana-privacy-scanner": "./dist/index.js"
|
|
8
8
|
},
|
|
@@ -38,10 +38,11 @@
|
|
|
38
38
|
"url": "https://github.com/taylorferran/solana-privacy-scanner/issues"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"solana-privacy-scanner-core": "^0.
|
|
41
|
+
"solana-privacy-scanner-core": "^0.5.0",
|
|
42
42
|
"chalk": "^5.3.0",
|
|
43
43
|
"commander": "^12.1.0",
|
|
44
|
-
"dotenv": "^16.4.7"
|
|
44
|
+
"dotenv": "^16.4.7",
|
|
45
|
+
"prompts": "^2.4.2"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"typescript": "^5.7.2",
|