credlock 2.0.0__tar.gz → 2.0.2__tar.gz
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.
- {credlock-2.0.0 → credlock-2.0.2}/PKG-INFO +1 -1
- {credlock-2.0.0 → credlock-2.0.2}/credlock/__init__.py +1 -1
- {credlock-2.0.0 → credlock-2.0.2}/credlock/cli.py +71 -5
- credlock-2.0.2/credlock/formatter.py +172 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock/patterns.py +7 -7
- {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/PKG-INFO +1 -1
- {credlock-2.0.0 → credlock-2.0.2}/setup.py +1 -1
- credlock-2.0.0/credlock/formatter.py +0 -152
- {credlock-2.0.0 → credlock-2.0.2}/LICENSE +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/README.md +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock/config.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock/entropy.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock/git_hook.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock/gitignore_parser.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock/scanner.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock/storage.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/SOURCES.txt +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/dependency_links.txt +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/entry_points.txt +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/requires.txt +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/top_level.txt +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/setup.cfg +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/tests/__init__.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/tests/test_cli.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/tests/test_entropy.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/tests/test_gitignore.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/tests/test_patterns.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/tests/test_scanner.py +0 -0
- {credlock-2.0.0 → credlock-2.0.2}/tests/test_scanner_improved.py +0 -0
|
@@ -4,6 +4,7 @@ Command-line interface for CredLock.
|
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
6
|
import typer
|
|
7
|
+
import json
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Optional
|
|
9
10
|
from rich.console import Console
|
|
@@ -19,6 +20,14 @@ app = typer.Typer(help="🔒 CredLock - Prevent accidental credential commits")
|
|
|
19
20
|
console = Console()
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def count_files_in_directory(path: str) -> int:
|
|
24
|
+
"""Count total files in directory"""
|
|
25
|
+
try:
|
|
26
|
+
return len(list(Path(path).rglob("*")))
|
|
27
|
+
except:
|
|
28
|
+
return 0
|
|
29
|
+
|
|
30
|
+
|
|
22
31
|
@app.command()
|
|
23
32
|
def scan(
|
|
24
33
|
path: str = typer.Argument("."),
|
|
@@ -27,17 +36,74 @@ def scan(
|
|
|
27
36
|
):
|
|
28
37
|
"""Scan for secrets"""
|
|
29
38
|
console = Console()
|
|
39
|
+
start_time = time.time()
|
|
40
|
+
|
|
41
|
+
# Print start message
|
|
30
42
|
console.print(f"🔍 Scanning {path} for secrets...")
|
|
31
|
-
|
|
43
|
+
print_scan_start()
|
|
32
44
|
|
|
45
|
+
# Count files
|
|
46
|
+
files_scanned = count_files_in_directory(path)
|
|
47
|
+
|
|
48
|
+
# Scan directory
|
|
33
49
|
if staged:
|
|
34
|
-
|
|
50
|
+
# For staged files, we'd need git integration
|
|
51
|
+
findings = scan_directory(path)
|
|
35
52
|
else:
|
|
36
53
|
findings = scan_directory(path)
|
|
37
54
|
|
|
38
|
-
|
|
55
|
+
elapsed = time.time() - start_time
|
|
56
|
+
|
|
57
|
+
# Display results
|
|
58
|
+
if not findings:
|
|
59
|
+
print_no_secrets()
|
|
60
|
+
else:
|
|
61
|
+
print_secrets_found(findings)
|
|
39
62
|
|
|
40
|
-
#
|
|
63
|
+
# Print summary
|
|
64
|
+
print_scan_summary(len(findings), elapsed, files_scanned)
|
|
65
|
+
|
|
66
|
+
# Save to history - pass findings list, not count!
|
|
67
|
+
try:
|
|
68
|
+
# Convert Finding objects to dicts for storage
|
|
69
|
+
findings_dicts = [
|
|
70
|
+
{
|
|
71
|
+
"file_path": f.file_path,
|
|
72
|
+
"line_number": f.line_number,
|
|
73
|
+
"pattern_name": f.pattern_name,
|
|
74
|
+
"confidence": f.confidence,
|
|
75
|
+
"matched_string": f.matched_string[:50] + "..." if len(f.matched_string) > 50 else f.matched_string,
|
|
76
|
+
}
|
|
77
|
+
for f in findings
|
|
78
|
+
]
|
|
79
|
+
save_scan(findings_dicts)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
# Silently fail if history save fails (doesn't block scan)
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
# JSON output if requested
|
|
85
|
+
if json_output:
|
|
86
|
+
output = {
|
|
87
|
+
"path": path,
|
|
88
|
+
"findings_count": len(findings),
|
|
89
|
+
"files_scanned": files_scanned,
|
|
90
|
+
"findings": [
|
|
91
|
+
{
|
|
92
|
+
"file_path": f.file_path,
|
|
93
|
+
"line_number": f.line_number,
|
|
94
|
+
"pattern_name": f.pattern_name,
|
|
95
|
+
"confidence": f.confidence,
|
|
96
|
+
"matched_string": f.matched_string[:50] + "..." if len(f.matched_string) > 50 else f.matched_string,
|
|
97
|
+
}
|
|
98
|
+
for f in findings
|
|
99
|
+
],
|
|
100
|
+
"elapsed_seconds": elapsed,
|
|
101
|
+
}
|
|
102
|
+
console.print(json.dumps(output, indent=2))
|
|
103
|
+
|
|
104
|
+
# Exit with code 1 if secrets found (for git hook)
|
|
105
|
+
if findings:
|
|
106
|
+
raise typer.Exit(code=1)
|
|
41
107
|
|
|
42
108
|
|
|
43
109
|
@app.command()
|
|
@@ -98,7 +164,7 @@ def setup(force: bool = typer.Option(False, "--force", "-f", help="Force reinsta
|
|
|
98
164
|
raise typer.Exit(code=1)
|
|
99
165
|
|
|
100
166
|
setup_hook(force=force)
|
|
101
|
-
console.print("[green]✅
|
|
167
|
+
console.print("[green]✅ Git hook installed![/green]")
|
|
102
168
|
except typer.Exit:
|
|
103
169
|
raise
|
|
104
170
|
except Exception as e:
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Terminal output formatting for CredLock
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from typing import List, Dict
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from credlock.scanner import Finding
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def print_scan_start():
|
|
14
|
+
"""Print scan start message"""
|
|
15
|
+
console.print("[cyan]🔍 Scanning current directory...[/cyan]")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def print_no_secrets():
|
|
19
|
+
"""Print success message when no secrets found"""
|
|
20
|
+
console.print("[green]✅ No secrets found. Scan clean![/green]")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_secrets_found(findings: List[Finding], verbose: bool = False):
|
|
24
|
+
"""
|
|
25
|
+
Print findings grouped by file (NOT as table)
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
findings: List of Finding objects
|
|
29
|
+
verbose: Show detailed output (not used, but kept for compatibility)
|
|
30
|
+
"""
|
|
31
|
+
if not findings:
|
|
32
|
+
print_no_secrets()
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# Group findings by file
|
|
36
|
+
by_file = defaultdict(list)
|
|
37
|
+
for finding in findings:
|
|
38
|
+
by_file[finding.file_path].append(finding)
|
|
39
|
+
|
|
40
|
+
# Print header
|
|
41
|
+
console.print("\n[red]⛔ SECRETS DETECTED! Found {} potential secrets[/red]".format(len(findings)))
|
|
42
|
+
console.print()
|
|
43
|
+
|
|
44
|
+
# Print grouped by file
|
|
45
|
+
for file_path in sorted(by_file.keys()):
|
|
46
|
+
file_findings = by_file[file_path]
|
|
47
|
+
console.print(f"[cyan]📄 {file_path}[/cyan] [yellow]({len(file_findings)} secret{'s' if len(file_findings) > 1 else ''})[/yellow]")
|
|
48
|
+
|
|
49
|
+
for finding in file_findings:
|
|
50
|
+
# Extract just the pattern name (without "pattern_" prefix if present)
|
|
51
|
+
pattern_display = finding.pattern_name
|
|
52
|
+
|
|
53
|
+
# Truncate matched string to 40 chars for display
|
|
54
|
+
matched_display = finding.matched_string[:40]
|
|
55
|
+
if len(finding.matched_string) > 40:
|
|
56
|
+
matched_display += "..."
|
|
57
|
+
|
|
58
|
+
console.print(
|
|
59
|
+
f" [yellow]Line {finding.line_number}:[/yellow] "
|
|
60
|
+
f"[magenta][{pattern_display}][/magenta] "
|
|
61
|
+
f"{matched_display}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
console.print()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def print_dangerous_files(files: List[str]):
|
|
68
|
+
"""Print dangerous files found"""
|
|
69
|
+
if not files:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
console.print("\n[red]⚠️ Dangerous Files Found:[/red]")
|
|
73
|
+
for file in files:
|
|
74
|
+
console.print(f" [red]•[/red] {file}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def print_scan_summary(findings_count: int, duration: float, files_scanned: int = 0):
|
|
78
|
+
"""Print scan summary"""
|
|
79
|
+
if findings_count > 0:
|
|
80
|
+
console.print("[red]❌ PUSH BLOCKED - {} SECRET{} FOUND[/red]".format(
|
|
81
|
+
findings_count,
|
|
82
|
+
'S' if findings_count > 1 else ''
|
|
83
|
+
))
|
|
84
|
+
console.print()
|
|
85
|
+
console.print("[cyan]Recommendations:[/cyan]")
|
|
86
|
+
console.print("1. Move secrets to .env file")
|
|
87
|
+
console.print("2. Add .env to .gitignore")
|
|
88
|
+
console.print("3. Use environment variables in code")
|
|
89
|
+
console.print()
|
|
90
|
+
console.print("[yellow]Git commands to fix:[/yellow]")
|
|
91
|
+
console.print(" $ echo '.env' >> .gitignore")
|
|
92
|
+
console.print(" $ git rm --cached .env")
|
|
93
|
+
console.print(" $ git commit --amend")
|
|
94
|
+
else:
|
|
95
|
+
console.print("[green]✅ SCAN PASSED - Safe to push![/green]")
|
|
96
|
+
|
|
97
|
+
console.print(f"\n[cyan]Scan Summary:[/cyan]")
|
|
98
|
+
console.print(f" Files scanned: {files_scanned}")
|
|
99
|
+
console.print(f" Secrets found: {findings_count}")
|
|
100
|
+
console.print(f" Duration: {duration:.2f}s")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def print_history(scans: List[Dict]):
|
|
104
|
+
"""Print scan history"""
|
|
105
|
+
if not scans:
|
|
106
|
+
console.print("[yellow]No scan history found[/yellow]")
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
console.print("[cyan]Scan History:[/cyan]")
|
|
110
|
+
for i, scan in enumerate(scans, 1):
|
|
111
|
+
ts = scan.get("timestamp", "")[:10]
|
|
112
|
+
count = scan.get("findings_count", 0)
|
|
113
|
+
console.print(f" {i}. {ts} - {count} findings")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def print_config_info(config: Dict):
|
|
117
|
+
"""Print configuration information"""
|
|
118
|
+
console.print("[cyan]Current Configuration:[/cyan]")
|
|
119
|
+
console.print(f" Built-in patterns: 50+")
|
|
120
|
+
console.print(f" Custom patterns: {len(config.get('custom_patterns', {}))}")
|
|
121
|
+
console.print(f" Ignored files: {len(config.get('ignored_files', []))}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def print_setup_success():
|
|
125
|
+
"""Print setup success message"""
|
|
126
|
+
console.print("\n[green]✅ Git pre-commit hook installed successfully![/green]")
|
|
127
|
+
console.print("[cyan]The hook will now run automatically before each commit.[/cyan]")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def print_error(message: str):
|
|
131
|
+
"""Print error message"""
|
|
132
|
+
console.print(f"[red]❌ ERROR: {message}[/red]")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def print_success(message: str):
|
|
136
|
+
"""Print success message"""
|
|
137
|
+
console.print(f"[green]✅ {message}[/green]")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def format_findings(findings: List[Finding], verbose: bool = False) -> str:
|
|
141
|
+
"""
|
|
142
|
+
Format findings as string (alternative to print_secrets_found)
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
findings: List of Finding objects
|
|
146
|
+
verbose: Show detailed output
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Formatted string
|
|
150
|
+
"""
|
|
151
|
+
if not findings:
|
|
152
|
+
return "No secrets found."
|
|
153
|
+
|
|
154
|
+
# Group by file
|
|
155
|
+
by_file = defaultdict(list)
|
|
156
|
+
for finding in findings:
|
|
157
|
+
by_file[finding.file_path].append(finding)
|
|
158
|
+
|
|
159
|
+
output = f"\n⛔ SECRETS DETECTED! Found {len(findings)} potential secret(s)\n"
|
|
160
|
+
|
|
161
|
+
for file_path in sorted(by_file.keys()):
|
|
162
|
+
file_findings = by_file[file_path]
|
|
163
|
+
output += f"\n📄 {file_path} ({len(file_findings)} secret{'s' if len(file_findings) > 1 else ''})\n"
|
|
164
|
+
|
|
165
|
+
for finding in file_findings:
|
|
166
|
+
matched_display = finding.matched_string[:40]
|
|
167
|
+
if len(finding.matched_string) > 40:
|
|
168
|
+
matched_display += "..."
|
|
169
|
+
|
|
170
|
+
output += f" Line {finding.line_number}: [{finding.pattern_name}] {matched_display}\n"
|
|
171
|
+
|
|
172
|
+
return output
|
|
@@ -235,7 +235,7 @@ PATTERNS = {
|
|
|
235
235
|
|
|
236
236
|
"slack_bot_token": SecretPattern(
|
|
237
237
|
name="slack_bot_token",
|
|
238
|
-
pattern=r"xoxb-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-
|
|
238
|
+
pattern=r"xoxb-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9_\-]{12,}", # FIXED: 12+ instead of 20+
|
|
239
239
|
confidence="HIGH",
|
|
240
240
|
entropy_threshold=3.5,
|
|
241
241
|
exclude_files=[]
|
|
@@ -254,7 +254,7 @@ PATTERNS = {
|
|
|
254
254
|
name="password_assignment",
|
|
255
255
|
pattern=r"['\"]?password['\"]?\s*[:=]\s*['\"]([^'\"]{8,})['\"]",
|
|
256
256
|
confidence="MEDIUM",
|
|
257
|
-
entropy_threshold=3.5,
|
|
257
|
+
entropy_threshold=3.5,
|
|
258
258
|
exclude_files=[]
|
|
259
259
|
),
|
|
260
260
|
|
|
@@ -281,7 +281,7 @@ PATTERNS = {
|
|
|
281
281
|
name="heroku_key",
|
|
282
282
|
pattern=r"[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}",
|
|
283
283
|
confidence="MEDIUM",
|
|
284
|
-
entropy_threshold=4.2,
|
|
284
|
+
entropy_threshold=4.2,
|
|
285
285
|
exclude_files=["package-lock.json", "yarn.lock"]
|
|
286
286
|
),
|
|
287
287
|
|
|
@@ -295,9 +295,9 @@ PATTERNS = {
|
|
|
295
295
|
|
|
296
296
|
"azure_key": SecretPattern(
|
|
297
297
|
name="azure_key",
|
|
298
|
-
pattern=r"[a-zA-Z0-9+/]{88}==",
|
|
298
|
+
pattern=r"[a-zA-Z0-9+/]{88}==",
|
|
299
299
|
confidence="LOW",
|
|
300
|
-
entropy_threshold=4.5,
|
|
300
|
+
entropy_threshold=4.5,
|
|
301
301
|
exclude_files=["package-lock.json", "yarn.lock", "composer.lock"]
|
|
302
302
|
),
|
|
303
303
|
|
|
@@ -321,7 +321,7 @@ PATTERNS = {
|
|
|
321
321
|
name="twilio_auth_token",
|
|
322
322
|
pattern=r"[a-zA-Z0-9]{32}",
|
|
323
323
|
confidence="LOW",
|
|
324
|
-
entropy_threshold=4.0,
|
|
324
|
+
entropy_threshold=4.0,
|
|
325
325
|
exclude_files=[]
|
|
326
326
|
),
|
|
327
327
|
|
|
@@ -329,7 +329,7 @@ PATTERNS = {
|
|
|
329
329
|
name="datadog_key",
|
|
330
330
|
pattern=r"[a-f0-9]{32}",
|
|
331
331
|
confidence="LOW",
|
|
332
|
-
entropy_threshold=4.5,
|
|
332
|
+
entropy_threshold=4.5,
|
|
333
333
|
exclude_files=["package-lock.json", "yarn.lock"]
|
|
334
334
|
),
|
|
335
335
|
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Terminal output formatting for CredLock
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from rich.console import Console
|
|
6
|
-
from rich.table import Table
|
|
7
|
-
from typing import List, Dict
|
|
8
|
-
from credlock.scanner import Finding
|
|
9
|
-
|
|
10
|
-
console = Console()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def print_scan_start():
|
|
14
|
-
"""Print scan start message"""
|
|
15
|
-
console.print("[cyan]🔍 Scanning for secrets...[/cyan]")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def print_no_secrets():
|
|
19
|
-
"""Print success message when no secrets found"""
|
|
20
|
-
console.print("[green]✅ No secrets found. Scan clean![/green]")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def print_secrets_found(findings: List[Finding], verbose: bool = False):
|
|
24
|
-
"""
|
|
25
|
-
Print findings in formatted table
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
findings: List of Finding objects
|
|
29
|
-
verbose: Show detailed output
|
|
30
|
-
"""
|
|
31
|
-
console.print("\n[red]⛔ SECRETS DETECTED![/red]")
|
|
32
|
-
console.print(f"[red]Found {len(findings)} potential secret(s)[/red]\n")
|
|
33
|
-
|
|
34
|
-
# Create table
|
|
35
|
-
table = Table(title="Secret Findings")
|
|
36
|
-
table.add_column("File", style="cyan")
|
|
37
|
-
table.add_column("Line", style="yellow")
|
|
38
|
-
table.add_column("Pattern", style="magenta")
|
|
39
|
-
table.add_column("Confidence", style="red")
|
|
40
|
-
|
|
41
|
-
for finding in findings:
|
|
42
|
-
table.add_row(
|
|
43
|
-
finding.file_path,
|
|
44
|
-
str(finding.line_number),
|
|
45
|
-
finding.pattern_name,
|
|
46
|
-
finding.confidence
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
console.print(table)
|
|
50
|
-
|
|
51
|
-
# Show details if verbose
|
|
52
|
-
if verbose:
|
|
53
|
-
console.print("\n[bold]Detailed Output:[/bold]")
|
|
54
|
-
for finding in findings:
|
|
55
|
-
console.print(f"\n[yellow]File:[/yellow] {finding.file_path}")
|
|
56
|
-
console.print(f"[yellow]Line {finding.line_number}:[/yellow] {finding.line_content}")
|
|
57
|
-
console.print(f"[yellow]Pattern:[/yellow] {finding.pattern_name}")
|
|
58
|
-
console.print(f"[yellow]Confidence:[/yellow] {finding.confidence}")
|
|
59
|
-
console.print(f"[yellow]Entropy:[/yellow] {finding.entropy:.2f}")
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def print_dangerous_files(files: List[str]):
|
|
63
|
-
"""Print dangerous files found"""
|
|
64
|
-
if not files:
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
console.print("\n[red]⚠️ Dangerous Files Found:[/red]")
|
|
68
|
-
for file in files:
|
|
69
|
-
console.print(f" [red]•[/red] {file}")
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def print_scan_summary(findings_count: int, duration: float, files_scanned: int = 0):
|
|
73
|
-
"""Print scan summary"""
|
|
74
|
-
console.print(f"\n[cyan]Scan Summary:[/cyan]")
|
|
75
|
-
console.print(f" Files scanned: {files_scanned}")
|
|
76
|
-
console.print(f" Secrets found: {findings_count}")
|
|
77
|
-
console.print(f" Duration: {duration:.2f}s")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def print_history(scans: List[Dict]):
|
|
81
|
-
"""Print scan history"""
|
|
82
|
-
if not scans:
|
|
83
|
-
console.print("[yellow]No scan history found[/yellow]")
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
table = Table(title="Scan History")
|
|
87
|
-
table.add_column("Scan ID", style="cyan")
|
|
88
|
-
table.add_column("Timestamp", style="yellow")
|
|
89
|
-
table.add_column("Secrets Found", style="red")
|
|
90
|
-
table.add_column("Duration", style="green")
|
|
91
|
-
|
|
92
|
-
for scan in scans:
|
|
93
|
-
table.add_row(
|
|
94
|
-
scan.get("scan_id", "unknown"),
|
|
95
|
-
scan.get("timestamp", "unknown"),
|
|
96
|
-
str(scan.get("secrets_found", 0)),
|
|
97
|
-
scan.get("duration", "0s")
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
console.print(table)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def print_config_info(config: Dict):
|
|
104
|
-
"""Print configuration information"""
|
|
105
|
-
console.print("[cyan]Current Configuration:[/cyan]")
|
|
106
|
-
console.print(f" Built-in patterns: 50+")
|
|
107
|
-
console.print(f" Custom patterns: {len(config.get('custom_patterns', {}))}")
|
|
108
|
-
console.print(f" Ignored files: {len(config.get('ignored_files', []))}")
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def print_setup_success():
|
|
112
|
-
"""Print setup success message"""
|
|
113
|
-
console.print("\n[green]✅ Git pre-commit hook installed successfully![/green]")
|
|
114
|
-
console.print("[cyan]The hook will now run automatically before each commit.[/cyan]")
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def print_error(message: str):
|
|
118
|
-
"""Print error message"""
|
|
119
|
-
console.print(f"[red]❌ ERROR: {message}[/red]")
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def print_success(message: str):
|
|
123
|
-
"""Print success message"""
|
|
124
|
-
console.print(f"[green]✅ {message}[/green]")
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def format_findings(findings: List[Finding], verbose: bool = False) -> str:
|
|
128
|
-
"""
|
|
129
|
-
Format findings as string (alternative to print_secrets_found)
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
findings: List of Finding objects
|
|
133
|
-
verbose: Show detailed output
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
Formatted string
|
|
137
|
-
"""
|
|
138
|
-
if not findings:
|
|
139
|
-
return "No secrets found."
|
|
140
|
-
|
|
141
|
-
output = f"\n⛔ SECRETS DETECTED! Found {len(findings)} potential secret(s)\n"
|
|
142
|
-
|
|
143
|
-
for finding in findings:
|
|
144
|
-
output += f"\n📄 {finding.file_path} (Line {finding.line_number})\n"
|
|
145
|
-
output += f" Pattern: {finding.pattern_name}\n"
|
|
146
|
-
output += f" Confidence: {finding.confidence}\n"
|
|
147
|
-
|
|
148
|
-
if verbose:
|
|
149
|
-
output += f" Content: {finding.line_content}\n"
|
|
150
|
-
output += f" Entropy: {finding.entropy:.2f}\n"
|
|
151
|
-
|
|
152
|
-
return output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|