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.
- package/dist/cli.js +127 -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.
|
|
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.
|
|
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": {
|