git-cli-scanner 1.1.1 → 1.1.3
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 +122 -101
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -538,110 +538,131 @@ var init_logger = __esm({
|
|
|
538
538
|
// src/utils/explorer.ts
|
|
539
539
|
var explorer_exports = {};
|
|
540
540
|
__export(explorer_exports, {
|
|
541
|
-
|
|
541
|
+
runExplorer: () => runExplorer
|
|
542
542
|
});
|
|
543
|
-
|
|
543
|
+
async function scanFile(filePath, baseDir) {
|
|
544
|
+
console.log(`
|
|
545
|
+
${import_picocolors3.default.dim(" Scanning")} ${import_picocolors3.default.cyan(path2.basename(filePath))}${import_picocolors3.default.dim("...")}`);
|
|
546
|
+
const issues = await scanDirectory(path2.dirname(filePath));
|
|
547
|
+
const relativePath = path2.relative(baseDir, filePath).replace(/\\/g, "/");
|
|
548
|
+
const fileIssues = issues.filter((i) => {
|
|
549
|
+
const issuePath = i.file.replace(/\\/g, "/");
|
|
550
|
+
return issuePath === relativePath || issuePath === path2.basename(filePath);
|
|
551
|
+
});
|
|
552
|
+
if (fileIssues.length === 0) {
|
|
553
|
+
console.log(import_picocolors3.default.green("\n No vulnerabilities found in this file.\n"));
|
|
554
|
+
} else {
|
|
555
|
+
console.log(`
|
|
556
|
+
${import_picocolors3.default.bold(`Found ${fileIssues.length} issue(s):`)}
|
|
557
|
+
`);
|
|
558
|
+
fileIssues.forEach((issue) => {
|
|
559
|
+
const severity = issue.severity === "high" ? import_picocolors3.default.red("HIGH") : issue.severity === "medium" ? import_picocolors3.default.yellow("MEDIUM") : import_picocolors3.default.dim("DUMMY");
|
|
560
|
+
const indicator = issue.severity === "dummy" ? import_picocolors3.default.dim("\u25CB") : "\u25CF";
|
|
561
|
+
console.log(` ${indicator} ${severity} ${import_picocolors3.default.dim("\xB7")} ${issue.type}`);
|
|
562
|
+
console.log(` ${import_picocolors3.default.dim("Line:")} ${issue.line || "?"}`);
|
|
563
|
+
console.log(` ${import_picocolors3.default.dim("Match:")} ${issue.match.substring(0, 60)}`);
|
|
564
|
+
if (issue.solution) {
|
|
565
|
+
console.log(` ${import_picocolors3.default.dim("Fix:")} ${import_picocolors3.default.green(issue.solution)}`);
|
|
566
|
+
}
|
|
567
|
+
console.log();
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function buildChoices(dirPath) {
|
|
572
|
+
const choices = [];
|
|
573
|
+
choices.push({
|
|
574
|
+
name: import_picocolors3.default.dim(".. (go back)"),
|
|
575
|
+
value: "__BACK__"
|
|
576
|
+
});
|
|
577
|
+
let entries = [];
|
|
578
|
+
try {
|
|
579
|
+
entries = fs2.readdirSync(dirPath, { withFileTypes: true });
|
|
580
|
+
} catch (err) {
|
|
581
|
+
return choices;
|
|
582
|
+
}
|
|
583
|
+
entries = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules");
|
|
584
|
+
entries.sort((a, b) => {
|
|
585
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
586
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
587
|
+
return a.name.localeCompare(b.name);
|
|
588
|
+
});
|
|
589
|
+
for (const entry of entries) {
|
|
590
|
+
if (entry.isDirectory()) {
|
|
591
|
+
choices.push({
|
|
592
|
+
name: `${import_picocolors3.default.blue("/")}${entry.name}`,
|
|
593
|
+
value: path2.join(dirPath, entry.name),
|
|
594
|
+
description: "folder"
|
|
595
|
+
});
|
|
596
|
+
} else {
|
|
597
|
+
choices.push({
|
|
598
|
+
name: ` ${entry.name}`,
|
|
599
|
+
value: path2.join(dirPath, entry.name),
|
|
600
|
+
description: `${(fs2.statSync(path2.join(dirPath, entry.name)).size / 1024).toFixed(1)} KB`
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
choices.push({
|
|
605
|
+
name: import_picocolors3.default.dim("Exit explorer"),
|
|
606
|
+
value: "__EXIT__"
|
|
607
|
+
});
|
|
608
|
+
return choices;
|
|
609
|
+
}
|
|
610
|
+
async function runExplorer(startDir) {
|
|
611
|
+
let currentDir = path2.resolve(startDir);
|
|
612
|
+
const baseDir = currentDir;
|
|
613
|
+
console.log(`
|
|
614
|
+
${import_picocolors3.default.bold("Git CLI Scanner")} ${import_picocolors3.default.dim("\xB7 Interactive Explorer")}`);
|
|
615
|
+
console.log(import_picocolors3.default.dim(" Use arrow keys to navigate, Enter to select\n"));
|
|
616
|
+
while (true) {
|
|
617
|
+
const choices = buildChoices(currentDir);
|
|
618
|
+
let selected;
|
|
619
|
+
try {
|
|
620
|
+
selected = await (0, import_prompts2.select)({
|
|
621
|
+
message: `${import_picocolors3.default.cyan(currentDir)}`,
|
|
622
|
+
choices,
|
|
623
|
+
pageSize: 20,
|
|
624
|
+
loop: false
|
|
625
|
+
});
|
|
626
|
+
} catch (err) {
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
if (selected === "__EXIT__") {
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
if (selected === "__BACK__") {
|
|
633
|
+
currentDir = path2.dirname(currentDir);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
const stat = fs2.statSync(selected);
|
|
638
|
+
if (stat.isDirectory()) {
|
|
639
|
+
currentDir = selected;
|
|
640
|
+
} else {
|
|
641
|
+
await scanFile(selected, baseDir);
|
|
642
|
+
const next = await (0, import_prompts2.select)({
|
|
643
|
+
message: "What next?",
|
|
644
|
+
choices: [
|
|
645
|
+
{ name: "Continue browsing", value: "continue" },
|
|
646
|
+
{ name: "Exit explorer", value: "exit" }
|
|
647
|
+
]
|
|
648
|
+
});
|
|
649
|
+
if (next === "exit") break;
|
|
650
|
+
}
|
|
651
|
+
} catch (err) {
|
|
652
|
+
console.log(import_picocolors3.default.red(` Cannot access: ${selected}`));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
console.log(import_picocolors3.default.dim("\n Explorer closed.\n"));
|
|
656
|
+
}
|
|
657
|
+
var import_prompts2, import_picocolors3, fs2, path2;
|
|
544
658
|
var init_explorer = __esm({
|
|
545
659
|
"src/utils/explorer.ts"() {
|
|
546
660
|
"use strict";
|
|
547
|
-
|
|
661
|
+
import_prompts2 = require("@inquirer/prompts");
|
|
548
662
|
import_picocolors3 = __toESM(require("picocolors"));
|
|
549
663
|
fs2 = __toESM(require("fs"));
|
|
550
664
|
path2 = __toESM(require("path"));
|
|
551
665
|
init_scanner();
|
|
552
|
-
explorePrompt = (0, import_core.createPrompt)(
|
|
553
|
-
(config, done) => {
|
|
554
|
-
const [currentPath, setCurrentPath] = (0, import_core.useState)(config.currentDir);
|
|
555
|
-
const [selectedIndex, setSelectedIndex] = (0, import_core.useState)(0);
|
|
556
|
-
const [scanResult, setScanResult] = (0, import_core.useState)(null);
|
|
557
|
-
const [scanningFile, setScanningFile] = (0, import_core.useState)(null);
|
|
558
|
-
const prefix = (0, import_core.usePrefix)({ status: "idle" });
|
|
559
|
-
let items = [];
|
|
560
|
-
try {
|
|
561
|
-
items = fs2.readdirSync(currentPath, { withFileTypes: true });
|
|
562
|
-
} catch (err) {
|
|
563
|
-
}
|
|
564
|
-
items.sort((a, b) => {
|
|
565
|
-
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
566
|
-
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
567
|
-
return a.name.localeCompare(b.name);
|
|
568
|
-
});
|
|
569
|
-
const choices = [
|
|
570
|
-
{ name: "\u{1F519} Go Back (or press Left Arrow)", type: "back" },
|
|
571
|
-
...items.map((i) => ({
|
|
572
|
-
name: i.isDirectory() ? `\u{1F4C1} ${i.name}` : `\u{1F4C4} ${i.name}`,
|
|
573
|
-
type: i.isDirectory() ? "dir" : "file",
|
|
574
|
-
nameRaw: i.name
|
|
575
|
-
}))
|
|
576
|
-
];
|
|
577
|
-
(0, import_core.useKeypress)(async (key, rl) => {
|
|
578
|
-
if ((0, import_core.isEnterKey)(key) || key.name === "right") {
|
|
579
|
-
const selected = choices[selectedIndex];
|
|
580
|
-
if (selected.type === "back") {
|
|
581
|
-
setCurrentPath(path2.dirname(currentPath));
|
|
582
|
-
setSelectedIndex(0);
|
|
583
|
-
setScanResult(null);
|
|
584
|
-
} else if (selected.type === "dir") {
|
|
585
|
-
setCurrentPath(path2.join(currentPath, selected.nameRaw));
|
|
586
|
-
setSelectedIndex(0);
|
|
587
|
-
setScanResult(null);
|
|
588
|
-
} else if (selected.type === "file") {
|
|
589
|
-
const fullPath = path2.join(currentPath, selected.nameRaw);
|
|
590
|
-
setScanningFile(fullPath);
|
|
591
|
-
const issues = await scanDirectory(currentPath);
|
|
592
|
-
const fileIssues = issues.filter((i) => i.file === path2.relative(process.cwd(), fullPath).replace(/\\/g, "/"));
|
|
593
|
-
setScanResult(fileIssues);
|
|
594
|
-
setScanningFile(null);
|
|
595
|
-
}
|
|
596
|
-
} else if (key.name === "left") {
|
|
597
|
-
setCurrentPath(path2.dirname(currentPath));
|
|
598
|
-
setSelectedIndex(0);
|
|
599
|
-
setScanResult(null);
|
|
600
|
-
} else if ((0, import_core.isUpKey)(key)) {
|
|
601
|
-
setSelectedIndex((prev) => prev > 0 ? prev - 1 : choices.length - 1);
|
|
602
|
-
} else if ((0, import_core.isDownKey)(key)) {
|
|
603
|
-
setSelectedIndex((prev) => prev < choices.length - 1 ? prev + 1 : 0);
|
|
604
|
-
} else if (key.name === "c" && key.ctrl) {
|
|
605
|
-
done("");
|
|
606
|
-
}
|
|
607
|
-
});
|
|
608
|
-
let message = `${prefix} ${import_picocolors3.default.bold("Exploring:")} ${import_picocolors3.default.cyan(currentPath)}
|
|
609
|
-
|
|
610
|
-
`;
|
|
611
|
-
const startIndex = Math.max(0, selectedIndex - 10);
|
|
612
|
-
const endIndex = Math.min(choices.length, startIndex + 20);
|
|
613
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
614
|
-
const choice = choices[i];
|
|
615
|
-
if (i === selectedIndex) {
|
|
616
|
-
message += import_picocolors3.default.cyan(`\u276F ${choice.name}
|
|
617
|
-
`);
|
|
618
|
-
} else {
|
|
619
|
-
message += ` ${choice.name}
|
|
620
|
-
`;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
if (scanResult) {
|
|
624
|
-
message += `
|
|
625
|
-
${import_picocolors3.default.bold("Scan Results for " + choices[selectedIndex].nameRaw + ":")}
|
|
626
|
-
`;
|
|
627
|
-
if (scanResult.length === 0) {
|
|
628
|
-
message += import_picocolors3.default.green("\u2714 No vulnerabilities found in this file.\n");
|
|
629
|
-
} else {
|
|
630
|
-
scanResult.forEach((issue) => {
|
|
631
|
-
message += ` ${issue.severity === "high" ? import_picocolors3.default.red("\u25CF HIGH") : issue.severity === "medium" ? import_picocolors3.default.yellow("\u25CF MEDIUM") : import_picocolors3.default.dim("\u25CB DUMMY")} \xB7 ${issue.type}
|
|
632
|
-
`;
|
|
633
|
-
message += ` Fix: ${import_picocolors3.default.green(issue.solution || "No solution provided")}
|
|
634
|
-
`;
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
} else if (scanningFile) {
|
|
638
|
-
message += `
|
|
639
|
-
${import_picocolors3.default.yellow("Scanning...")} (Press Right Arrow to scan)
|
|
640
|
-
`;
|
|
641
|
-
}
|
|
642
|
-
return message;
|
|
643
|
-
}
|
|
644
|
-
);
|
|
645
666
|
}
|
|
646
667
|
});
|
|
647
668
|
|
|
@@ -706,7 +727,7 @@ var Spinner = class {
|
|
|
706
727
|
var fs3 = __toESM(require("fs"));
|
|
707
728
|
var path3 = __toESM(require("path"));
|
|
708
729
|
var program = new import_commander.Command();
|
|
709
|
-
program.name("git-cli-scanner").description("Interactive Git hooks vulnerability scanner").version("1.1.
|
|
730
|
+
program.name("git-cli-scanner").description("Interactive Git hooks vulnerability scanner").version("1.1.2");
|
|
710
731
|
program.command("init").description("Install pre-commit hook for automatic scanning").action(() => {
|
|
711
732
|
info("Setting up pre-commit hook...");
|
|
712
733
|
try {
|
|
@@ -766,7 +787,7 @@ program.command("scan").description("Interactive vulnerability scan for staged f
|
|
|
766
787
|
"Thinking..."
|
|
767
788
|
]);
|
|
768
789
|
spinner.start();
|
|
769
|
-
await new Promise((
|
|
790
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
770
791
|
const issues = await scanDiff();
|
|
771
792
|
if (issues.length > 0) {
|
|
772
793
|
const blockerIssues = issues.filter((i) => i.severity !== "dummy");
|
|
@@ -811,7 +832,7 @@ program.command("scan-all [dir]").description("Scan an entire directory or codeb
|
|
|
811
832
|
]);
|
|
812
833
|
spinner.start();
|
|
813
834
|
const { scanDirectory: scanDirectory2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
|
|
814
|
-
await new Promise((
|
|
835
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
815
836
|
const issues = await scanDirectory2(scanDir);
|
|
816
837
|
if (issues.length > 0) {
|
|
817
838
|
const blockerIssues = issues.filter((i) => i.severity !== "dummy");
|
|
@@ -834,8 +855,8 @@ program.command("scan-all [dir]").description("Scan an entire directory or codeb
|
|
|
834
855
|
});
|
|
835
856
|
program.command("explore").description("Launch an interactive Terminal UI to browse and scan files").action(async () => {
|
|
836
857
|
try {
|
|
837
|
-
const {
|
|
838
|
-
await
|
|
858
|
+
const { runExplorer: runExplorer2 } = (init_explorer(), __toCommonJS(explorer_exports));
|
|
859
|
+
await runExplorer2(process.cwd());
|
|
839
860
|
process.exit(0);
|
|
840
861
|
} catch (err) {
|
|
841
862
|
if (err.name === "ExitPromptError" || err.message?.includes("closed")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-cli-scanner",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
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": {
|