git-cli-scanner 1.2.0 → 1.3.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.
Files changed (2) hide show
  1. package/dist/cli.js +127 -2
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -32,6 +32,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
33
 
34
34
  // src/utils/git.ts
35
+ var git_exports = {};
36
+ __export(git_exports, {
37
+ getHistoryDiffs: () => getHistoryDiffs,
38
+ getStagedDiff: () => getStagedDiff
39
+ });
35
40
  async function getStagedDiff() {
36
41
  try {
37
42
  const { stdout: filesOutput } = await execAsync("git diff --cached --name-only");
@@ -50,6 +55,64 @@ async function getStagedDiff() {
50
55
  return [];
51
56
  }
52
57
  }
58
+ async function getHistoryDiffs(options) {
59
+ try {
60
+ let cmd = 'git log -p --pretty=format:"---COMMIT:%H---"';
61
+ if (options.id) {
62
+ cmd += ` -1 ${options.id}`;
63
+ } else {
64
+ if (options.all) cmd += " --all";
65
+ if (options.since) cmd += ` --since="${options.since}"`;
66
+ if (!options.all && !options.since) {
67
+ cmd += " -1";
68
+ }
69
+ }
70
+ const { stdout } = await execAsync(cmd, { maxBuffer: 1024 * 1024 * 50 });
71
+ if (!stdout.trim()) {
72
+ return [];
73
+ }
74
+ const commits = [];
75
+ let currentCommit = null;
76
+ let currentFile = null;
77
+ let currentDiff = "";
78
+ const lines = stdout.split("\n");
79
+ for (const line of lines) {
80
+ if (line.startsWith("---COMMIT:")) {
81
+ if (currentFile && currentCommit) {
82
+ currentCommit.diffs.push({ file: currentFile, content: currentDiff });
83
+ }
84
+ currentFile = null;
85
+ currentDiff = "";
86
+ const match = line.match(/---COMMIT:([a-f0-9]+)---/);
87
+ if (match) {
88
+ currentCommit = { commitHash: match[1], diffs: [] };
89
+ commits.push(currentCommit);
90
+ }
91
+ } else if (line.startsWith("diff --git a/")) {
92
+ if (currentFile && currentCommit) {
93
+ currentCommit.diffs.push({ file: currentFile, content: currentDiff });
94
+ }
95
+ const parts = line.substring(13).split(" b/");
96
+ if (parts.length > 0) {
97
+ currentFile = parts[0];
98
+ }
99
+ currentDiff = "";
100
+ } else if (currentFile) {
101
+ currentDiff += line + "\n";
102
+ }
103
+ }
104
+ if (currentFile && currentCommit) {
105
+ currentCommit.diffs.push({ file: currentFile, content: currentDiff });
106
+ }
107
+ return commits;
108
+ } catch (error2) {
109
+ if (error2.message && error2.message.includes("unknown revision")) {
110
+ throw new Error(`Invalid commit hash: ${options.id}`);
111
+ }
112
+ console.warn("Could not retrieve git history diffs. Are you in a git repository?");
113
+ return [];
114
+ }
115
+ }
53
116
  var import_child_process, import_util, execAsync;
54
117
  var init_git = __esm({
55
118
  "src/utils/git.ts"() {
@@ -477,7 +540,8 @@ var init_dummy = __esm({
477
540
  var scanner_exports = {};
478
541
  __export(scanner_exports, {
479
542
  scanDiff: () => scanDiff,
480
- scanDirectory: () => scanDirectory
543
+ scanDirectory: () => scanDirectory,
544
+ scanHistoryDiffs: () => scanHistoryDiffs
481
545
  });
482
546
  async function scanDiff() {
483
547
  const diffs = await getStagedDiff();
@@ -509,6 +573,28 @@ async function scanDirectory(dirPath) {
509
573
  }
510
574
  return issues;
511
575
  }
576
+ async function scanHistoryDiffs(options) {
577
+ const { getHistoryDiffs: getHistoryDiffs2 } = await Promise.resolve().then(() => (init_git(), git_exports));
578
+ const commitDiffs = await getHistoryDiffs2(options);
579
+ const issues = [];
580
+ for (const commitDiff of commitDiffs) {
581
+ for (const diff of commitDiff.diffs) {
582
+ for (const scanner of allScanners) {
583
+ const scannerIssues = scanner.scan(diff);
584
+ for (const issue of scannerIssues) {
585
+ issue.commitHash = commitDiff.commitHash;
586
+ issues.push(issue);
587
+ }
588
+ }
589
+ }
590
+ }
591
+ for (const issue of issues) {
592
+ if (isDummySecret(issue.match)) {
593
+ issue.severity = "dummy";
594
+ }
595
+ }
596
+ return issues;
597
+ }
512
598
  var init_scanner = __esm({
513
599
  "src/scanner/index.ts"() {
514
600
  "use strict";
@@ -573,6 +659,9 @@ function printIssues(issues, showSolution = false) {
573
659
  console.error(`
574
660
  ${colorFn(indicator)} ${colorFn(import_picocolors.default.bold(label))} ${import_picocolors.default.dim("\xB7")} ${issue.type}`);
575
661
  console.error(` ${import_picocolors.default.dim("File:")} ${issue.file}:${issue.line || "?"}`);
662
+ if (issue.commitHash) {
663
+ console.error(` ${import_picocolors.default.dim("Commit:")} ${import_picocolors.default.cyan(issue.commitHash)}`);
664
+ }
576
665
  console.error(` ${import_picocolors.default.dim("Match:")} ${displayMatch}`);
577
666
  if (issue.risk) {
578
667
  console.error(` ${import_picocolors.default.dim("Risk:")} ${import_picocolors.default.yellow(issue.risk)}`);
@@ -808,10 +897,11 @@ var Spinner = class {
808
897
  };
809
898
 
810
899
  // src/cli.ts
900
+ var import_child_process2 = require("child_process");
811
901
  var fs3 = __toESM(require("fs"));
812
902
  var path3 = __toESM(require("path"));
813
903
  var program = new import_commander.Command();
814
- program.name("git-cli-scanner").description("Interactive Git hooks vulnerability scanner").version("1.2.0");
904
+ program.name("git-cli-scanner").description("Interactive Git hooks vulnerability scanner").version("1.3.0");
815
905
  program.command("init").description("Install pre-commit hook for automatic scanning").action(() => {
816
906
  info("Setting up pre-commit hook...");
817
907
  try {
@@ -832,6 +922,10 @@ exec < /dev/tty
832
922
  npx git-cli-scanner scan --hook
833
923
  `;
834
924
  fs3.writeFileSync(hookPath, hookContent, { mode: 493 });
925
+ try {
926
+ (0, import_child_process2.execSync)("git config --unset core.hooksPath", { stdio: "ignore" });
927
+ } catch (e) {
928
+ }
835
929
  success("Pre-commit hook installed!");
836
930
  info("Every `git commit` will now automatically scan your staged files.");
837
931
  } catch (err) {
@@ -943,6 +1037,37 @@ program.command("scan-all [dir]").description("Scan an entire directory or codeb
943
1037
  process.exit(1);
944
1038
  }
945
1039
  });
1040
+ program.command("scan-history").description("Scan Git commit history for exposed secrets (defaults to last commit)").option("--show-sol", "Show solutions for vulnerabilities").option("--since <date>", 'Scan commits since a specific date (e.g. "30 days ago")').option("--id <hash>", "Scan a specific commit hash").option("--all", "Scan all branches and history").action(async (options) => {
1041
+ info("Git CLI Scanner (History Mode) running...");
1042
+ try {
1043
+ const spinnerMsgs = [
1044
+ "Rewinding Git history...",
1045
+ "Extracting historical diffs...",
1046
+ "Scanning temporal anomalies...",
1047
+ "Looking for buried secrets...",
1048
+ "Thinking..."
1049
+ ];
1050
+ const spinner = new Spinner(spinnerMsgs);
1051
+ spinner.start();
1052
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
1053
+ const issues = await scanHistoryDiffs({
1054
+ since: options.since,
1055
+ id: options.id,
1056
+ all: options.all
1057
+ });
1058
+ if (issues.length > 0) {
1059
+ const blockerIssues = issues.filter((i) => i.severity !== "dummy");
1060
+ spinner.fail(`Found ${issues.length} historical vulnerabilities! (${blockerIssues.length} blockers)`);
1061
+ const { printIssues: printIssues2 } = (init_logger(), __toCommonJS(logger_exports));
1062
+ printIssues2(issues, options.showSol);
1063
+ } else {
1064
+ spinner.succeed("No vulnerabilities found in the scanned history!");
1065
+ }
1066
+ } catch (err) {
1067
+ error(`History scan failed: ${err.message}`);
1068
+ process.exit(1);
1069
+ }
1070
+ });
946
1071
  program.command("explore").description("Launch an interactive Terminal UI to browse and scan files").action(async () => {
947
1072
  try {
948
1073
  const { runExplorer: runExplorer2 } = (init_explorer(), __toCommonJS(explorer_exports));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-cli-scanner",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "A powerful interactive CLI tool that scans your codebase for hardcoded secrets, API keys, passwords, and private keys before they reach your Git history.",
5
5
  "main": "dist/cli.js",
6
6
  "scripts": {