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.
Files changed (29) hide show
  1. {credlock-2.0.0 → credlock-2.0.2}/PKG-INFO +1 -1
  2. {credlock-2.0.0 → credlock-2.0.2}/credlock/__init__.py +1 -1
  3. {credlock-2.0.0 → credlock-2.0.2}/credlock/cli.py +71 -5
  4. credlock-2.0.2/credlock/formatter.py +172 -0
  5. {credlock-2.0.0 → credlock-2.0.2}/credlock/patterns.py +7 -7
  6. {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/PKG-INFO +1 -1
  7. {credlock-2.0.0 → credlock-2.0.2}/setup.py +1 -1
  8. credlock-2.0.0/credlock/formatter.py +0 -152
  9. {credlock-2.0.0 → credlock-2.0.2}/LICENSE +0 -0
  10. {credlock-2.0.0 → credlock-2.0.2}/README.md +0 -0
  11. {credlock-2.0.0 → credlock-2.0.2}/credlock/config.py +0 -0
  12. {credlock-2.0.0 → credlock-2.0.2}/credlock/entropy.py +0 -0
  13. {credlock-2.0.0 → credlock-2.0.2}/credlock/git_hook.py +0 -0
  14. {credlock-2.0.0 → credlock-2.0.2}/credlock/gitignore_parser.py +0 -0
  15. {credlock-2.0.0 → credlock-2.0.2}/credlock/scanner.py +0 -0
  16. {credlock-2.0.0 → credlock-2.0.2}/credlock/storage.py +0 -0
  17. {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/SOURCES.txt +0 -0
  18. {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/dependency_links.txt +0 -0
  19. {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/entry_points.txt +0 -0
  20. {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/requires.txt +0 -0
  21. {credlock-2.0.0 → credlock-2.0.2}/credlock.egg-info/top_level.txt +0 -0
  22. {credlock-2.0.0 → credlock-2.0.2}/setup.cfg +0 -0
  23. {credlock-2.0.0 → credlock-2.0.2}/tests/__init__.py +0 -0
  24. {credlock-2.0.0 → credlock-2.0.2}/tests/test_cli.py +0 -0
  25. {credlock-2.0.0 → credlock-2.0.2}/tests/test_entropy.py +0 -0
  26. {credlock-2.0.0 → credlock-2.0.2}/tests/test_gitignore.py +0 -0
  27. {credlock-2.0.0 → credlock-2.0.2}/tests/test_patterns.py +0 -0
  28. {credlock-2.0.0 → credlock-2.0.2}/tests/test_scanner.py +0 -0
  29. {credlock-2.0.0 → credlock-2.0.2}/tests/test_scanner_improved.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: credlock
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: 🔐 Prevent accidental credential commits to Git
5
5
  Home-page: https://github.com/VedhaSriD/credlock
6
6
  Author: Vedha Sri
@@ -1,6 +1,6 @@
1
1
  """CredLock - Prevent accidental secret commits to Git"""
2
2
 
3
- __version__ = "2.0.0"
3
+ __version__ = "2.0.2"
4
4
  __author__ = "Your Name"
5
5
  __description__ = "Automatic credential detection in your code"
6
6
 
@@ -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
- console.print(f"[DEBUG] Using entropy filtering: YES") # DEBUG LINE
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
- findings = scan_staged_files()
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
- console.print(f"[DEBUG] Total findings BEFORE filtering: {len(findings)}") # DEBUG LINE
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
- # Rest of code...
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]✅ Hook installed[/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-9]{20,}",
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, # RAISED to filter messages like "Enter your password"
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, # HIGH: filters UUID-like strings in npm
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}==", # Base64 ~88 chars
298
+ pattern=r"[a-zA-Z0-9+/]{88}==",
299
299
  confidence="LOW",
300
- entropy_threshold=4.5, # VERY HIGH: filters npm integrity hashes
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, # HIGH: filters random strings
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, # VERY HIGH: filters hex strings like npm integrity
332
+ entropy_threshold=4.5,
333
333
  exclude_files=["package-lock.json", "yarn.lock"]
334
334
  ),
335
335
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: credlock
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: 🔐 Prevent accidental credential commits to Git
5
5
  Home-page: https://github.com/VedhaSriD/credlock
6
6
  Author: Vedha Sri
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
9
9
 
10
10
  setup(
11
11
  name="credlock",
12
- version="2.0.0",
12
+ version="2.0.2",
13
13
  author="Vedha Sri",
14
14
  author_email="your.email@example.com",
15
15
  description="🔐 Prevent accidental credential commits to Git",
@@ -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