git-cli-scanner 1.1.5 → 1.1.6

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 +137 -53
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -69,32 +69,44 @@ var init_apiKeys = __esm({
69
69
  {
70
70
  id: "stripe-key",
71
71
  description: "Stripe Secret/Restricted Key",
72
- pattern: /(?:sk|rk)_(?:test|live)_[0-9a-zA-Z]{24}/
72
+ pattern: /(?:sk|rk)_(?:test|live)_[0-9a-zA-Z]{24}/,
73
+ risk: "An attacker can issue unauthorized refunds, access customer payment data, or disrupt billing operations.",
74
+ solution: "Revoke in Stripe Dashboard (Developers > API keys). Use a secret manager (e.g. AWS Secrets Manager, Doppler) or .env files, and load via process.env."
73
75
  },
74
76
  {
75
77
  id: "sendgrid-key",
76
78
  description: "SendGrid API Key",
77
- pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/
79
+ pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/,
80
+ risk: "Allows unauthorized sending of emails from your domains, leading to phishing campaigns and domain blacklisting.",
81
+ solution: "Revoke in Twilio SendGrid UI (Settings > API Keys). Store the new key securely using environment variables."
78
82
  },
79
83
  {
80
84
  id: "mailgun-key",
81
85
  description: "Mailgun API Key",
82
- pattern: /key-[0-9a-zA-Z]{32}/
86
+ pattern: /key-[0-9a-zA-Z]{32}/,
87
+ risk: "Allows an attacker to read incoming emails, send spam, and permanently ruin your email domain reputation.",
88
+ solution: "Rotate the key immediately in Mailgun Settings. Hardcoded Mailgun keys can lead to severe email spoofing and domain reputation loss."
83
89
  },
84
90
  {
85
91
  id: "twilio-key",
86
92
  description: "Twilio API Key",
87
- pattern: /SK[0-9a-fA-F]{32}/
93
+ pattern: /SK[0-9a-fA-F]{32}/,
94
+ risk: "Attackers can incur massive financial charges via SMS toll fraud or hijack phone numbers.",
95
+ solution: "Delete this key in the Twilio Console (Account > API keys & tokens). Never hardcode communication API keys."
88
96
  },
89
97
  {
90
98
  id: "generic-api-key",
91
99
  description: "Generic API Key / Secret / Password / Passphrase",
92
- pattern: /(?:api[_\-]?key|secret|token|password|pwd|passphrase)[\s:=]+["'][a-zA-Z0-9\-_!@#$%^&*()=+]{8,}["']/i
100
+ pattern: /(?:api[_\-]?key|secret|token|password|pwd|passphrase)[\s:=]+["'][a-zA-Z0-9\-_!@#$%^&*()=+]{8,}["']/i,
101
+ risk: "Generic secrets often grant unrestricted access to databases, third-party APIs, or internal microservices.",
102
+ solution: "Extract this secret into a local .env file. Ensure .env is added to your .gitignore so it never enters source control."
93
103
  },
94
104
  {
95
105
  id: "generic-bearer-token",
96
106
  description: "Generic Bearer Token",
97
- pattern: /bearer\s+[a-zA-Z0-9\-_.]{20,}/i
107
+ pattern: /bearer\s+[a-zA-Z0-9\-_.]{20,}/i,
108
+ risk: "Bearer tokens allow impersonation of users or service accounts to access protected REST APIs directly.",
109
+ solution: "Bearer tokens grant direct access to APIs. Remove it from code, revoke the session, and inject it securely at runtime."
98
110
  }
99
111
  ];
100
112
  apiKeyScanner = {
@@ -114,7 +126,8 @@ var init_apiKeys = __esm({
114
126
  line: lineNumber,
115
127
  match: cleanLine.trim().substring(0, 50) + "...",
116
128
  severity: "high",
117
- solution: "Store this key in a secret manager or environment variable. Never hardcode it."
129
+ solution: rule.solution,
130
+ risk: rule.risk
118
131
  });
119
132
  }
120
133
  }
@@ -136,17 +149,23 @@ var init_cloudProviders = __esm({
136
149
  {
137
150
  id: "aws-access-key",
138
151
  description: "AWS Access Key ID",
139
- pattern: /(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/
152
+ pattern: /(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/,
153
+ risk: "An attacker can use this to spin up thousands of crypto-mining EC2 instances, costing you massive amounts of money.",
154
+ solution: "Deactivate the key in AWS IAM Console immediately. Switch to using short-lived credentials via AWS STS or IAM Roles whenever possible."
140
155
  },
141
156
  {
142
157
  id: "aws-secret-key",
143
158
  description: "AWS Secret Access Key (heuristics)",
144
- pattern: /aws_?(?:secret)?_?(?:access)?_?key[\s:=]+["'][a-zA-Z0-9\/+]{40}["']/i
159
+ pattern: /aws_?(?:secret)?_?(?:access)?_?key[\s:=]+["'][a-zA-Z0-9\/+]{40}["']/i,
160
+ risk: "Compromised AWS Secret Keys grant direct access to your entire cloud infrastructure, databases, and storage buckets.",
161
+ solution: "This secret grants access to your AWS infrastructure. Remove it from the codebase and inject it using a secure CI/CD secrets manager or HashiCorp Vault."
145
162
  },
146
163
  {
147
164
  id: "gcp-api-key",
148
165
  description: "Google Cloud API Key",
149
- pattern: /AIza[0-9A-Za-z\-_]{35}/
166
+ pattern: /AIza[0-9A-Za-z\-_]{35}/,
167
+ risk: "Attackers can bypass quotas to abuse GCP services like Maps API or Vertex AI, causing high billing charges.",
168
+ solution: "Restrict the key in Google Cloud Console (APIs & Services > Credentials) to specific IP addresses/apps, or revoke it and use GCP Service Accounts."
150
169
  }
151
170
  ];
152
171
  cloudProviderScanner = {
@@ -166,7 +185,8 @@ var init_cloudProviders = __esm({
166
185
  line: lineNumber,
167
186
  match: cleanLine.trim().substring(0, 50) + "...",
168
187
  severity: "high",
169
- solution: "Move this cloud credential to IAM roles, Vault, or environment variables."
188
+ solution: rule.solution,
189
+ risk: rule.risk
170
190
  });
171
191
  }
172
192
  }
@@ -188,22 +208,30 @@ var init_collaboration = __esm({
188
208
  {
189
209
  id: "github-pat",
190
210
  description: "GitHub Personal Access Token",
191
- pattern: /ghp_[a-zA-Z0-9]{36}/
211
+ pattern: /ghp_[a-zA-Z0-9]{36}/,
212
+ risk: "Attackers can read, modify, or delete your entire private source code and infrastructure configurations.",
213
+ solution: "Revoke this token immediately in your GitHub Developer Settings. For automated workflows, use a fine-grained PAT or GitHub Apps."
192
214
  },
193
215
  {
194
216
  id: "github-oauth",
195
217
  description: "GitHub OAuth Access Token",
196
- pattern: /gho_[a-zA-Z0-9]{36}/
218
+ pattern: /gho_[a-zA-Z0-9]{36}/,
219
+ risk: "Grants third-party level access to GitHub accounts, leading to source code theft or supply chain attacks.",
220
+ solution: "Revoke the OAuth token via GitHub Settings > Authorized OAuth Apps. Never hardcode OAuth tokens."
197
221
  },
198
222
  {
199
223
  id: "slack-token",
200
224
  description: "Slack Token",
201
- pattern: /xox[pboar]-[a-zA-Z0-9]{10,13}-[a-zA-Z0-9]{10,13}-[a-zA-Z0-9]{10,13}-[a-zA-Z0-9]{32}/
225
+ pattern: /xox[pboar]-[a-zA-Z0-9]{10,13}-[a-zA-Z0-9]{10,13}-[a-zA-Z0-9]{10,13}-[a-zA-Z0-9]{32}/,
226
+ risk: "An attacker can read private messages, extract company secrets, and socially engineer employees using internal channels.",
227
+ solution: "Regenerate the Slack App Token in your Slack API Dashboard. If pushed, an attacker can access your internal team communications."
202
228
  },
203
229
  {
204
230
  id: "slack-webhook",
205
231
  description: "Slack Webhook",
206
- pattern: /https:\/\/hooks\.slack\.com\/services\/T[a-zA-Z0-9_]{8,10}\/B[a-zA-Z0-9_]{8,10}\/[a-zA-Z0-9_]{24}/
232
+ pattern: /https:\/\/hooks\.slack\.com\/services\/T[a-zA-Z0-9_]{8,10}\/B[a-zA-Z0-9_]{8,10}\/[a-zA-Z0-9_]{24}/,
233
+ risk: "Allows unauthenticated actors to spam or spoof messages into your internal team channels.",
234
+ solution: "Delete the webhook in Slack and create a new one. Load the webhook URL via environment variables in production."
207
235
  }
208
236
  ];
209
237
  collaborationScanner = {
@@ -223,7 +251,8 @@ var init_collaboration = __esm({
223
251
  line: lineNumber,
224
252
  match: cleanLine.trim().substring(0, 50) + "...",
225
253
  severity: "high",
226
- solution: "Use OAuth tokens or environment variables. Do not commit team tokens."
254
+ solution: rule.solution,
255
+ risk: rule.risk
227
256
  });
228
257
  }
229
258
  }
@@ -245,17 +274,23 @@ var init_privateKeys = __esm({
245
274
  {
246
275
  id: "rsa-private-key",
247
276
  description: "RSA Private Key",
248
- pattern: /-----BEGIN RSA PRIVATE KEY-----/
277
+ pattern: /-----BEGIN RSA PRIVATE KEY-----/,
278
+ risk: "Allows attackers to decrypt intercepted traffic, forge digital signatures, or authenticate to your infrastructure.",
279
+ solution: "RSA keys should never enter source control. Rotate this key on all associated servers/services and use a secure keystore like AWS KMS or HashiCorp Vault."
249
280
  },
250
281
  {
251
282
  id: "openssh-private-key",
252
283
  description: "OpenSSH Private Key",
253
- pattern: /-----BEGIN OPENSSH PRIVATE KEY-----/
284
+ pattern: /-----BEGIN OPENSSH PRIVATE KEY-----/,
285
+ risk: "Attackers can use this to SSH directly into your production servers, clone private repos, or pivot into your internal network.",
286
+ solution: "This SSH key could grant direct server access. Generate a new SSH keypair (`ssh-keygen`), replace the public key on your servers, and delete this leaked private key."
254
287
  },
255
288
  {
256
289
  id: "pgp-private-key",
257
290
  description: "PGP Private Key",
258
- pattern: /-----BEGIN PGP PRIVATE KEY BLOCK-----/
291
+ pattern: /-----BEGIN PGP PRIVATE KEY BLOCK-----/,
292
+ risk: "An attacker can impersonate you to sign malicious software releases or decrypt highly sensitive PGP-encrypted messages.",
293
+ solution: "Revoke this PGP key via your keyserver immediately, as it can be used to forge digital signatures or decrypt sensitive payloads."
259
294
  }
260
295
  ];
261
296
  privateKeyScanner = {
@@ -275,7 +310,8 @@ var init_privateKeys = __esm({
275
310
  line: lineNumber,
276
311
  match: cleanLine.trim().substring(0, 50) + "...",
277
312
  severity: "high",
278
- solution: "Never commit private keys. Revoke this key immediately if it was ever public, and use a secure key manager."
313
+ solution: rule.solution,
314
+ risk: rule.risk
279
315
  });
280
316
  }
281
317
  }
@@ -330,7 +366,8 @@ var init_envFiles = __esm({
330
366
  line: 0,
331
367
  match: matchMsg,
332
368
  severity,
333
- solution: "Add this file to .gitignore. If it is a template, rename it to .env.example."
369
+ solution: `Run 'echo "${filename}" >> .gitignore' to safely ignore this file type moving forward. If it's meant to be a template, rename it to '${filename}.example'.`,
370
+ risk: "Contains highly sensitive environment variables, database passwords, or SSL certificates that grant instant system access if leaked."
334
371
  });
335
372
  }
336
373
  return issues;
@@ -500,31 +537,55 @@ function error(message) {
500
537
  console.error(import_picocolors.default.red("\u2716 error ") + message);
501
538
  }
502
539
  function printIssues(issues, showSolution = false) {
540
+ if (issues.length === 0) return;
503
541
  console.error("\n" + import_picocolors.default.bold("Scan Results:"));
504
- issues.forEach((issue) => {
505
- let indicator = "\u25CF";
506
- let label = "HIGH";
507
- let colorFn = import_picocolors.default.red;
508
- if (issue.severity === "medium") {
509
- label = "MEDIUM";
510
- colorFn = import_picocolors.default.yellow;
511
- } else if (issue.severity === "dummy") {
512
- indicator = "\u25CB";
513
- label = "IGNORED (DUMMY)";
514
- colorFn = import_picocolors.default.dim;
515
- }
516
- let displayMatch = issue.match.replace(/\n/g, " ").trim();
517
- if (displayMatch.length > 60) {
518
- displayMatch = displayMatch.substring(0, 57) + "...";
542
+ const severityWeight = { high: 3, medium: 2, dummy: 1 };
543
+ const sortedIssues = [...issues].sort((a, b) => {
544
+ return (severityWeight[b.severity] || 0) - (severityWeight[a.severity] || 0);
545
+ });
546
+ const grouped = /* @__PURE__ */ new Map();
547
+ for (const issue of sortedIssues) {
548
+ const key = `${issue.file}:::${issue.type}`;
549
+ if (!grouped.has(key)) {
550
+ grouped.set(key, []);
519
551
  }
520
- console.error(`
552
+ grouped.get(key).push(issue);
553
+ }
554
+ for (const [key, group] of grouped.entries()) {
555
+ const displayCount = Math.min(group.length, 3);
556
+ for (let i = 0; i < displayCount; i++) {
557
+ const issue = group[i];
558
+ let indicator = "\u25CF";
559
+ let label = "HIGH";
560
+ let colorFn = import_picocolors.default.red;
561
+ if (issue.severity === "medium") {
562
+ label = "MEDIUM";
563
+ colorFn = import_picocolors.default.yellow;
564
+ } else if (issue.severity === "dummy") {
565
+ indicator = "\u25CB";
566
+ label = "IGNORED (DUMMY)";
567
+ colorFn = import_picocolors.default.dim;
568
+ }
569
+ let displayMatch = issue.match.replace(/\n/g, " ").trim();
570
+ if (displayMatch.length > 60) {
571
+ displayMatch = displayMatch.substring(0, 57) + "...";
572
+ }
573
+ console.error(`
521
574
  ${colorFn(indicator)} ${colorFn(import_picocolors.default.bold(label))} ${import_picocolors.default.dim("\xB7")} ${issue.type}`);
522
- console.error(` ${import_picocolors.default.dim("File:")} ${issue.file}:${issue.line || "?"}`);
523
- console.error(` ${import_picocolors.default.dim("Match:")} ${displayMatch}`);
524
- if (showSolution && issue.solution) {
525
- console.error(` ${import_picocolors.default.dim("Fix:")} ${import_picocolors.default.green(issue.solution)}`);
575
+ console.error(` ${import_picocolors.default.dim("File:")} ${issue.file}:${issue.line || "?"}`);
576
+ console.error(` ${import_picocolors.default.dim("Match:")} ${displayMatch}`);
577
+ if (issue.risk) {
578
+ console.error(` ${import_picocolors.default.dim("Risk:")} ${import_picocolors.default.yellow(issue.risk)}`);
579
+ }
580
+ if (showSolution && issue.solution) {
581
+ console.error(` ${import_picocolors.default.dim("Fix:")} ${import_picocolors.default.green(issue.solution)}`);
582
+ }
526
583
  }
527
- });
584
+ if (group.length > 3) {
585
+ console.error(`
586
+ ${import_picocolors.default.cyan(`... and ${group.length - 3} more similar issues in ${group[0].file}`)}`);
587
+ }
588
+ }
528
589
  console.error();
529
590
  }
530
591
  var import_picocolors;
@@ -555,17 +616,40 @@ ${import_picocolors3.default.dim(" Scanning")} ${import_picocolors3.default.cya
555
616
  console.log(`
556
617
  ${import_picocolors3.default.bold(`Found ${fileIssues.length} issue(s):`)}
557
618
  `);
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();
619
+ const severityWeight = { high: 3, medium: 2, dummy: 1 };
620
+ const sortedIssues = [...fileIssues].sort((a, b) => {
621
+ return (severityWeight[b.severity] || 0) - (severityWeight[a.severity] || 0);
568
622
  });
623
+ const grouped = /* @__PURE__ */ new Map();
624
+ for (const issue of sortedIssues) {
625
+ if (!grouped.has(issue.type)) {
626
+ grouped.set(issue.type, []);
627
+ }
628
+ grouped.get(issue.type).push(issue);
629
+ }
630
+ for (const [type, group] of grouped.entries()) {
631
+ const displayCount = Math.min(group.length, 3);
632
+ for (let i = 0; i < displayCount; i++) {
633
+ const issue = group[i];
634
+ const severity = issue.severity === "high" ? import_picocolors3.default.red("HIGH") : issue.severity === "medium" ? import_picocolors3.default.yellow("MEDIUM") : import_picocolors3.default.dim("DUMMY");
635
+ const colorFn = issue.severity === "high" ? import_picocolors3.default.red : issue.severity === "medium" ? import_picocolors3.default.yellow : import_picocolors3.default.dim;
636
+ const indicator = issue.severity === "dummy" ? import_picocolors3.default.dim("\u25CB") : colorFn("\u25CF");
637
+ console.log(` ${indicator} ${severity} ${import_picocolors3.default.dim("\xB7")} ${issue.type}`);
638
+ console.log(` ${import_picocolors3.default.dim("Line:")} ${issue.line || "?"}`);
639
+ console.log(` ${import_picocolors3.default.dim("Match:")} ${issue.match.substring(0, 60)}`);
640
+ if (issue.risk) {
641
+ console.log(` ${import_picocolors3.default.dim("Risk:")} ${import_picocolors3.default.yellow(issue.risk)}`);
642
+ }
643
+ if (issue.solution) {
644
+ console.log(` ${import_picocolors3.default.dim("Fix:")} ${import_picocolors3.default.green(issue.solution)}`);
645
+ }
646
+ console.log();
647
+ }
648
+ if (group.length > 3) {
649
+ console.log(` ${import_picocolors3.default.cyan(`... and ${group.length - 3} more similar issues in this file.`)}
650
+ `);
651
+ }
652
+ }
569
653
  }
570
654
  }
571
655
  function buildChoices(dirPath) {
@@ -727,7 +811,7 @@ var Spinner = class {
727
811
  var fs3 = __toESM(require("fs"));
728
812
  var path3 = __toESM(require("path"));
729
813
  var program = new import_commander.Command();
730
- program.name("git-cli-scanner").description("Interactive Git hooks vulnerability scanner").version("1.1.5");
814
+ program.name("git-cli-scanner").description("Interactive Git hooks vulnerability scanner").version("1.1.6");
731
815
  program.command("init").description("Install pre-commit hook for automatic scanning").action(() => {
732
816
  info("Setting up pre-commit hook...");
733
817
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-cli-scanner",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
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": {