github-guardian 1.0.0__py3-none-any.whl
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.
- core/__init__.py +1 -0
- core/hook.py +49 -0
- core/remote.py +76 -0
- core/scanner.py +97 -0
- github_guardian-1.0.0.dist-info/METADATA +11 -0
- github_guardian-1.0.0.dist-info/RECORD +10 -0
- github_guardian-1.0.0.dist-info/WHEEL +5 -0
- github_guardian-1.0.0.dist-info/entry_points.txt +2 -0
- github_guardian-1.0.0.dist-info/top_level.txt +2 -0
- guardian.py +34 -0
core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marks the core directory as a Python package
|
core/hook.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import stat
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
HOOK_SCRIPT = """#!/usr/bin/env bash
|
|
6
|
+
# GitHub Guardian Pre-Commit Shield
|
|
7
|
+
|
|
8
|
+
echo "š”ļø GitHub Guardian: Running Pre-Commit Shield Audit..."
|
|
9
|
+
|
|
10
|
+
# We use the isolated virtual environment from the CLI directory
|
|
11
|
+
CLI_DIR="$(git rev-parse --show-toplevel)/github-guardian-cli"
|
|
12
|
+
$CLI_DIR/venv/bin/python $CLI_DIR/guardian.py scan-local . --hook
|
|
13
|
+
|
|
14
|
+
if [ $? -ne 0 ]; then
|
|
15
|
+
echo "ā COMMIT BLOCKED! Secrets or semantic vulnerabilities were detected in your staging area."
|
|
16
|
+
echo "Please fix the issues. To bypass the shield (NOT RECOMMENDED), use 'git commit --no-verify'."
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
echo "ā
Code looks clean. Proceeding with commit."
|
|
21
|
+
exit 0
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import subprocess
|
|
25
|
+
|
|
26
|
+
def install_pre_commit_hook(console):
|
|
27
|
+
try:
|
|
28
|
+
git_root_proc = subprocess.run(["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True, check=True)
|
|
29
|
+
git_root = git_root_proc.stdout.strip()
|
|
30
|
+
except Exception:
|
|
31
|
+
console.print("[bold red]Error:[/bold red] Not inside a git repository. Cannot install hook.")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
git_dir = os.path.join(git_root, ".git")
|
|
35
|
+
hook_path = os.path.join(git_dir, "hooks", "pre-commit")
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
with open(hook_path, "w") as f:
|
|
39
|
+
f.write(HOOK_SCRIPT)
|
|
40
|
+
|
|
41
|
+
# Make the bash script executable (Unix/macOS only)
|
|
42
|
+
if platform.system() != 'Windows':
|
|
43
|
+
st = os.stat(hook_path)
|
|
44
|
+
os.chmod(hook_path, st.st_mode | stat.S_IEXEC)
|
|
45
|
+
|
|
46
|
+
console.print(f"[bold green]ā
Success![/bold green] Pre-commit shield installed at: {hook_path}")
|
|
47
|
+
console.print("[italic]Your commits will now be securely blocked if secrets or SAST vulnerabilities are staged.[/italic]")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
console.print(f"[bold red]Failed to install hook:[/bold red] {str(e)}")
|
core/remote.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import time
|
|
3
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
|
|
6
|
+
BACKEND_URL = "http://localhost:8000/api/v1"
|
|
7
|
+
|
|
8
|
+
def run_remote_scan(owner: str, repo: str, console):
|
|
9
|
+
try:
|
|
10
|
+
# 1. Trigger the asynchronous scan task on FastAPI
|
|
11
|
+
with console.status("[bold cyan]Triggering remote forensic scan on Guardian Core...[/bold cyan]") as status:
|
|
12
|
+
res = httpx.post(f"{BACKEND_URL}/scan", json={"owner": owner, "repo_name": repo}, timeout=10.0)
|
|
13
|
+
if res.status_code != 200:
|
|
14
|
+
console.print(f"[bold red]Failed to trigger scan. Status Code: {res.status_code}[/bold red]")
|
|
15
|
+
console.print(res.text)
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
task_id = res.json().get("task_id")
|
|
19
|
+
console.print(f"[green]Scan initiated successfully. Task ID:[/green] {task_id}\n")
|
|
20
|
+
|
|
21
|
+
# 2. Poll the FastAPI backend for status updates
|
|
22
|
+
with Progress(
|
|
23
|
+
SpinnerColumn(),
|
|
24
|
+
TextColumn("[progress.description]{task.description}"),
|
|
25
|
+
transient=True,
|
|
26
|
+
) as progress:
|
|
27
|
+
task = progress.add_task(description="Initializing scanners...", total=None)
|
|
28
|
+
|
|
29
|
+
while True:
|
|
30
|
+
status_res = httpx.get(f"{BACKEND_URL}/scan/status/{task_id}", timeout=10.0)
|
|
31
|
+
if status_res.status_code != 200:
|
|
32
|
+
time.sleep(2)
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
data = status_res.json()
|
|
36
|
+
|
|
37
|
+
if data.get("status") == "completed":
|
|
38
|
+
progress.update(task, description="[bold green]Scan Completed! Processing AI Report...[/bold green]")
|
|
39
|
+
break
|
|
40
|
+
elif data.get("status") == "failed":
|
|
41
|
+
progress.update(task, description=f"[bold red]Scan Failed: {data.get('error')}[/bold red]")
|
|
42
|
+
return
|
|
43
|
+
else:
|
|
44
|
+
msg = data.get("message", "Processing pipelines...")
|
|
45
|
+
progress.update(task, description=f"[cyan]Engine Status:[/cyan] {msg}")
|
|
46
|
+
|
|
47
|
+
time.sleep(2)
|
|
48
|
+
|
|
49
|
+
# 3. Download and Render the final AI enriched report
|
|
50
|
+
res_data = httpx.get(f"{BACKEND_URL}/scan/status/{task_id}").json()
|
|
51
|
+
report = res_data.get("report", {})
|
|
52
|
+
|
|
53
|
+
score = report.get("score", 0.0)
|
|
54
|
+
verdict = report.get("verdict", "Unknown")
|
|
55
|
+
|
|
56
|
+
console.print("\n[bold underline]š”ļø GitHub Guardian Audit Report[/bold underline]")
|
|
57
|
+
console.print(f"\nFinal AI Dampened Score: [bold {'red' if score > 5 else 'green'}]{score} / 10.0[/bold]")
|
|
58
|
+
console.print(f"Verdict: [italic]{verdict}[/italic]\n")
|
|
59
|
+
|
|
60
|
+
# Table of Actionable Findings
|
|
61
|
+
if report.get("negatives"):
|
|
62
|
+
vuln_table = Table(title="Actionable Architectural Threats", show_header=True, header_style="bold red")
|
|
63
|
+
vuln_table.add_column("Issue Detected", style="white")
|
|
64
|
+
|
|
65
|
+
for neg in report.get("negatives"):
|
|
66
|
+
vuln_table.add_row(neg)
|
|
67
|
+
|
|
68
|
+
console.print(vuln_table)
|
|
69
|
+
|
|
70
|
+
if report.get("positives"):
|
|
71
|
+
console.print("\n[bold green]Security Positives:[/bold green]")
|
|
72
|
+
for pos in report.get("positives"):
|
|
73
|
+
console.print(f" [green]ā[/green] {pos}")
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
console.print(f"[bold red]Critical Error during remote scan orchestration:[/bold red] {str(e)}")
|
core/scanner.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
# Re-using the professional-grade patterns from the backend
|
|
6
|
+
SECRET_PATTERNS = {
|
|
7
|
+
"AWS Access Key": r'AKIA[0-9A-Z]{16}',
|
|
8
|
+
"GitHub Token": r'ghp_[0-9a-zA-Z]{36}',
|
|
9
|
+
"Slack Webhook": r'https://hooks\.slack\.com/services/T[0-9A-Z]{8}/B[0-9A-Z]{8}/[0-9a-zA-Z]{24}',
|
|
10
|
+
"Stripe API Key": r'sk_live_[0-9a-zA-Z]{24}',
|
|
11
|
+
"Private Key": r'-----BEGIN (?:RSA|OPENSSH) PRIVATE KEY-----',
|
|
12
|
+
"Google API Key": r'AIza[0-9A-Za-z\-_]{35}'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
SAST_PATTERNS = {
|
|
16
|
+
"SQL Injection (Raw Query)": r"\.execute\(\".*%\s*\"",
|
|
17
|
+
"Insecure Rendering (XSS)": r"dangerouslySetInnerHTML",
|
|
18
|
+
"Hardcoded Auth/Secret": r"password\s*=\s*['\"][^'\"]+['\"]"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def ask_gitignore(files):
|
|
22
|
+
import sys
|
|
23
|
+
try:
|
|
24
|
+
with open("/dev/tty", "w") as tty_out, open("/dev/tty", "r") as tty_in:
|
|
25
|
+
tty_out.write("\nā ļø Would you like to automatically add these vulnerable files to .gitignore? (y/N): ")
|
|
26
|
+
tty_out.flush()
|
|
27
|
+
ans = tty_in.readline().strip().lower()
|
|
28
|
+
return ans in ['y', 'yes']
|
|
29
|
+
except Exception:
|
|
30
|
+
ans = input("\nā ļø Would you like to automatically add these vulnerable files to .gitignore? (y/N): ")
|
|
31
|
+
return ans.lower() in ['y', 'yes']
|
|
32
|
+
|
|
33
|
+
def run_local_scan(path: str, console, hook_mode: bool = False) -> bool:
|
|
34
|
+
console.print(f"[*] Scanning local directory: [yellow]{os.path.abspath(path)}[/yellow]...\n")
|
|
35
|
+
findings = []
|
|
36
|
+
|
|
37
|
+
for root, _, files in os.walk(path):
|
|
38
|
+
# Ignore common build/dependency directories
|
|
39
|
+
if any(x in root for x in [".git", "node_modules", "venv", "__pycache__"]):
|
|
40
|
+
continue
|
|
41
|
+
for file in files:
|
|
42
|
+
ext = os.path.splitext(file)[1]
|
|
43
|
+
if ext in [".png", ".jpg", ".pdf", ".zip", ".pyc"]:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
# Skip the scanner scripts themselves to prevent false positives!
|
|
47
|
+
if file in ["scanner.py", "leak_forensics.py", "sast_analyzer.py"]:
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
filepath = os.path.join(root, file)
|
|
51
|
+
try:
|
|
52
|
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
|
53
|
+
lines = f.readlines()
|
|
54
|
+
|
|
55
|
+
for line_idx, line in enumerate(lines, 1):
|
|
56
|
+
# Check for Secrets
|
|
57
|
+
for name, pat in SECRET_PATTERNS.items():
|
|
58
|
+
if re.search(pat, line):
|
|
59
|
+
findings.append((filepath, line_idx, "SECRET LEAK", name))
|
|
60
|
+
|
|
61
|
+
# Check for Semantic vulnerabilities
|
|
62
|
+
for name, pat in SAST_PATTERNS.items():
|
|
63
|
+
if re.search(pat, line):
|
|
64
|
+
findings.append((filepath, line_idx, "SAST VULN", name))
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
if not findings:
|
|
69
|
+
console.print("[bold green]ā
No local leaks or vulnerabilities detected. Ready to push![/bold green]")
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
table = Table(title="Guardian Local Shield Findings")
|
|
73
|
+
table.add_column("File Path", style="cyan")
|
|
74
|
+
table.add_column("Line", justify="right", style="magenta")
|
|
75
|
+
table.add_column("Threat Class", style="yellow")
|
|
76
|
+
table.add_column("Pattern Matched", style="red")
|
|
77
|
+
|
|
78
|
+
for f in findings:
|
|
79
|
+
table.add_row(os.path.relpath(f[0], path), str(f[1]), f[2], f[3])
|
|
80
|
+
|
|
81
|
+
console.print(table)
|
|
82
|
+
|
|
83
|
+
if hook_mode:
|
|
84
|
+
vulnerable_files = list(set([os.path.relpath(f[0], path) for f in findings]))
|
|
85
|
+
if ask_gitignore(vulnerable_files):
|
|
86
|
+
gitignore_path = os.path.join(path, ".gitignore")
|
|
87
|
+
with open(gitignore_path, "a") as gf:
|
|
88
|
+
gf.write("\n# GitHub Guardian: Auto-ignored vulnerable files\n")
|
|
89
|
+
for vf in vulnerable_files:
|
|
90
|
+
gf.write(f"{vf}\n")
|
|
91
|
+
console.print("\n[bold green]ā
Vulnerable files appended to .gitignore![/bold green]")
|
|
92
|
+
console.print("[yellow]Note: If the files were already tracked by git, you must also run 'git rm --cached <file>'.[/yellow]")
|
|
93
|
+
console.print("[yellow]Please review your changes and try committing again.[/yellow]")
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
console.print("\n[bold red]ā SCAN FAILED! Active vulnerabilities were detected locally.[/bold red]")
|
|
97
|
+
return False
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: github-guardian
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Deep Forensic Security Audit Engine & Pre-Commit Shield
|
|
5
|
+
Author: GitHub Guardian Team
|
|
6
|
+
Requires-Dist: typer>=0.9.0
|
|
7
|
+
Requires-Dist: rich>=13.7.0
|
|
8
|
+
Requires-Dist: httpx>=0.27.0
|
|
9
|
+
Dynamic: author
|
|
10
|
+
Dynamic: requires-dist
|
|
11
|
+
Dynamic: summary
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
guardian.py,sha256=JelEnNRwGoAEMJcf0wJLiZCK2BeOyBK8RWVwzQfD7vA,1456
|
|
2
|
+
core/__init__.py,sha256=Ugc5jnfm_Bl5kgVR4UaOoOdlv9fUr5yP3nGGy9kg_GA,47
|
|
3
|
+
core/hook.py,sha256=arqBruVkmSER6Z9M5SEMIjXAU6QY6LKJSNPH5NzhZ4U,1831
|
|
4
|
+
core/remote.py,sha256=RTmPp42XGeixC8cIz-SEIZjNOI-1Sz2TD1IEI2darcA,3544
|
|
5
|
+
core/scanner.py,sha256=m6yazu4qz9q8pWMpzCLCPihP-ne4ayKqouXhEkkkaN8,4345
|
|
6
|
+
github_guardian-1.0.0.dist-info/METADATA,sha256=y4LP2ERTWKrmFqyFii-I-Kciu5xaa_oQQiMzvcTFEGs,294
|
|
7
|
+
github_guardian-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
github_guardian-1.0.0.dist-info/entry_points.txt,sha256=aaXgoBHQZpeppnCgqM0-1Bm4lYN9kBnXgC-KYo79M3o,42
|
|
9
|
+
github_guardian-1.0.0.dist-info/top_level.txt,sha256=ZMNVow5B-nJtHwT3irGO0hUFjolR2TXv0IDD0dMeS_8,14
|
|
10
|
+
github_guardian-1.0.0.dist-info/RECORD,,
|
guardian.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
from core.scanner import run_local_scan
|
|
5
|
+
from core.remote import run_remote_scan
|
|
6
|
+
from core.hook import install_pre_commit_hook
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(help="GitHub Guardian - Forensic Security Audit CLI")
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
@app.command()
|
|
12
|
+
def scan_local(
|
|
13
|
+
path: str = typer.Argument(".", help="Path to scan locally"),
|
|
14
|
+
hook_mode: bool = typer.Option(False, "--hook", help="Run in git hook mode (prompts to gitignore)")
|
|
15
|
+
):
|
|
16
|
+
"""Scan local directory for exposed secrets and vulnerabilities before committing."""
|
|
17
|
+
console.print(Panel.fit("[bold cyan]GitHub Guardian[/bold cyan] - Local Shield", border_style="cyan"))
|
|
18
|
+
run_local_scan(path, console, hook_mode)
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def scan_remote(owner: str = typer.Argument(..., help="GitHub Repository Owner"),
|
|
22
|
+
repo: str = typer.Argument(..., help="GitHub Repository Name")):
|
|
23
|
+
"""Trigger an AI-driven forensic scan on the Guardian backend."""
|
|
24
|
+
console.print(Panel.fit(f"[bold magenta]GitHub Guardian[/bold magenta] - Remote Scan: {owner}/{repo}", border_style="magenta"))
|
|
25
|
+
run_remote_scan(owner, repo, console)
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
def hook_install():
|
|
29
|
+
"""Install the Pre-Commit Shield to block insecure commits."""
|
|
30
|
+
console.print(Panel.fit("[bold green]GitHub Guardian[/bold green] - Pre-Commit Hook", border_style="green"))
|
|
31
|
+
install_pre_commit_hook(console)
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
app()
|