vibesec 0.1.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.
vibesec/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ __version__ = "0.1.0"
2
+ __author__ = "Ayush Khati"
vibesec/cli.py ADDED
@@ -0,0 +1,37 @@
1
+ import click
2
+ from vibesec.scanner import Scanner
3
+ from vibesec.reporter import Reporter
4
+
5
+ @click.group()
6
+ @click.version_option(version="0.1.0")
7
+ def cli():
8
+ """VibeSec — Security scanner for AI-generated code."""
9
+ pass
10
+
11
+ @cli.command()
12
+ @click.argument("path")
13
+ @click.option("--fix", is_flag=True, help="Generate fix suggestions using Groq AI")
14
+ @click.option("--output", type=click.Choice(["terminal", "json"]), default="terminal")
15
+ @click.option("--severity", type=click.Choice(["critical", "high", "medium", "low"]), default=None)
16
+ def scan(path, fix, output, severity):
17
+ """Scan a directory or file for security vulnerabilities."""
18
+
19
+ reporter = Reporter()
20
+ reporter.print_banner()
21
+
22
+ scanner = Scanner(path)
23
+ findings = scanner.run()
24
+
25
+ if severity:
26
+ findings = [f for f in findings if f["severity"].lower() == severity]
27
+
28
+ if output == "json":
29
+ reporter.print_json(findings)
30
+ else:
31
+ reporter.print_report(findings, path, fix)
32
+
33
+ def main():
34
+ cli()
35
+
36
+ if __name__ == "__main__":
37
+ main()
vibesec/fixgen.py ADDED
@@ -0,0 +1,40 @@
1
+ import os
2
+ from groq import Groq
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+
8
+ def generate_fix(finding: dict) -> str:
9
+ """Generate an AI-powered fix suggestion for a security finding."""
10
+
11
+ api_key = os.environ.get("GROQ_API_KEY")
12
+ if not api_key:
13
+ return "Set GROQ_API_KEY environment variable to enable AI fix suggestions."
14
+
15
+ try:
16
+ client = Groq(api_key=api_key)
17
+
18
+ prompt = f"""You are a security expert. A vulnerability was found in code.
19
+
20
+ Rule: {finding['rule']}
21
+ Severity: {finding['severity']}
22
+ File: {finding['file']}
23
+ Issue: {finding['message']}
24
+ Code: {finding.get('code_snippet', 'N/A')}
25
+
26
+ Give a specific, concise fix in 2-3 sentences maximum.
27
+ Show a corrected code example if possible.
28
+ Be direct — no preamble, no "I recommend", just the fix."""
29
+
30
+ response = client.chat.completions.create(
31
+ model="llama-3.1-8b-instant",
32
+ messages=[{"role": "user", "content": prompt}],
33
+ max_tokens=150,
34
+ temperature=0.1,
35
+ )
36
+
37
+ return response.choices[0].message.content.strip()
38
+
39
+ except Exception as e:
40
+ return f"Could not generate fix: {str(e)}"
vibesec/reporter.py ADDED
@@ -0,0 +1,102 @@
1
+ import json
2
+ from rich.console import Console
3
+ from rich.table import Table
4
+ from rich.panel import Panel
5
+ from rich.text import Text
6
+ from rich import box
7
+
8
+ console = Console()
9
+
10
+ SEVERITY_COLORS = {
11
+ "CRITICAL": "bold red",
12
+ "HIGH": "bold yellow",
13
+ "MEDIUM": "bold orange3",
14
+ "LOW": "bold green",
15
+ }
16
+
17
+ SEVERITY_ORDER = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
18
+
19
+
20
+ class Reporter:
21
+
22
+ def print_banner(self):
23
+ console.print()
24
+ console.print(Panel(
25
+ "[bold cyan]VibeSec v0.1.0[/bold cyan] — [dim]AI-Generated Code Security Scanner[/dim]",
26
+ border_style="cyan",
27
+ expand=False
28
+ ))
29
+ console.print()
30
+
31
+ def print_report(self, findings, path, fix=False):
32
+ if not findings:
33
+ console.print(Panel(
34
+ "[bold green]✓ No vulnerabilities found![/bold green]\n"
35
+ "[dim]VibeSec checked 10 vulnerability patterns.[/dim]",
36
+ border_style="green"
37
+ ))
38
+ return
39
+
40
+ # Sort by severity
41
+ findings.sort(key=lambda x: SEVERITY_ORDER.get(x["severity"], 99))
42
+
43
+ # Summary
44
+ counts = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0}
45
+ for f in findings:
46
+ counts[f["severity"]] = counts.get(f["severity"], 0) + 1
47
+
48
+ summary = Table(box=box.SIMPLE, show_header=False, padding=(0, 2))
49
+ summary.add_column(style="bold")
50
+ summary.add_column()
51
+
52
+ for severity, count in counts.items():
53
+ if count > 0:
54
+ color = SEVERITY_COLORS[severity]
55
+ summary.add_row(
56
+ f"[{color}]● {severity}[/{color}]",
57
+ f"[{color}]{count} finding{'s' if count > 1 else ''}[/{color}]"
58
+ )
59
+
60
+ console.print(Panel(summary, title="[bold]FINDINGS SUMMARY[/bold]",
61
+ border_style="dim"))
62
+
63
+ # Generate AI fixes if requested
64
+ if fix:
65
+ from vibesec.fixgen import generate_fix
66
+ console.print("\n [cyan]Generating AI fix suggestions...[/cyan]\n")
67
+ for finding in findings:
68
+ finding["groq_fix"] = generate_fix(finding)
69
+
70
+ # Individual findings
71
+ for i, finding in enumerate(findings, 1):
72
+ severity = finding["severity"]
73
+ color = SEVERITY_COLORS[severity]
74
+
75
+ console.print()
76
+ console.print(f" [{color}]{severity}[/{color}] — "
77
+ f"[bold white]{finding['rule']}[/bold white]")
78
+ console.print(f" [dim]File: {finding['file']} "
79
+ f"Line: {finding.get('line', 'N/A')}[/dim]")
80
+ console.print(f" [red]Found:[/red] {finding['message']}")
81
+ console.print(f" [green]Fix:[/green] {finding['fix_hint']}")
82
+
83
+ if fix and finding.get("groq_fix"):
84
+ console.print(
85
+ f" [cyan]AI Fix:[/cyan] {finding['groq_fix']}"
86
+ )
87
+
88
+ # Footer
89
+ console.print()
90
+ console.print(
91
+ f" [dim]{len(findings)} finding{'s' if len(findings) > 1 else ''} "
92
+ f"in {path}[/dim]"
93
+ )
94
+ if not fix:
95
+ console.print(
96
+ " [dim]Run with [/dim][cyan]--fix[/cyan]"
97
+ "[dim] for AI-powered remediation suggestions[/dim]"
98
+ )
99
+ console.print()
100
+
101
+ def print_json(self, findings):
102
+ console.print(json.dumps(findings, indent=2))
@@ -0,0 +1,23 @@
1
+ from vibesec.rules.secrets import check_secrets
2
+ from vibesec.rules.rls import check_rls
3
+ from vibesec.rules.auth_routes import check_auth_routes
4
+ from vibesec.rules.packages import check_packages
5
+ from vibesec.rules.sourcemaps import check_sourcemaps
6
+ from vibesec.rules.jwt import check_jwt
7
+ from vibesec.rules.xss import check_xss
8
+ from vibesec.rules.roles import check_roles
9
+ from vibesec.rules.webhooks import check_webhooks
10
+ from vibesec.rules.cors import check_cors
11
+
12
+ ALL_RULES = [
13
+ check_secrets,
14
+ check_rls,
15
+ check_auth_routes,
16
+ check_packages,
17
+ check_sourcemaps,
18
+ check_jwt,
19
+ check_xss,
20
+ check_roles,
21
+ check_webhooks,
22
+ check_cors,
23
+ ]
@@ -0,0 +1,67 @@
1
+ import re
2
+
3
+ RULE_NAME = "Missing Route Authentication"
4
+ SEVERITY = "HIGH"
5
+
6
+ # Routes that look like admin or sensitive endpoints
7
+ SENSITIVE_ROUTE_PATTERNS = [
8
+ r'@app\.route\s*\(\s*["\'][^"\']*admin[^"\']*["\']',
9
+ r'@app\.route\s*\(\s*["\'][^"\']*delete[^"\']*["\']',
10
+ r'@app\.route\s*\(\s*["\'][^"\']*\/api\/user[^"\']*["\']',
11
+ r'router\.(post|put|delete|patch)\s*\(\s*["\'][^"\']*admin[^"\']*["\']',
12
+ r'app\.(post|put|delete|patch)\s*\(\s*["\'][^"\']*admin[^"\']*["\']',
13
+ ]
14
+
15
+ # Auth decorators/middleware that make a route safe
16
+ AUTH_INDICATORS = [
17
+ "login_required",
18
+ "jwt_required",
19
+ "auth_required",
20
+ "verify_token",
21
+ "authenticate",
22
+ "authorization",
23
+ "requireAuth",
24
+ "withAuth",
25
+ "middleware",
26
+ "session",
27
+ "getServerSession",
28
+ "currentUser",
29
+ "verifyJWT",
30
+ ]
31
+
32
+
33
+ def check_auth_routes(file_path, content):
34
+ findings = []
35
+
36
+ ext = file_path.split(".")[-1].lower()
37
+ if ext not in {"py", "js", "ts", "jsx", "tsx"}:
38
+ return findings
39
+
40
+ lines = content.splitlines()
41
+
42
+ for line_num, line in enumerate(lines, 1):
43
+ for pattern in SENSITIVE_ROUTE_PATTERNS:
44
+ if re.search(pattern, line, re.IGNORECASE):
45
+ # Check surrounding lines (5 above, 10 below) for auth indicators
46
+ start = max(0, line_num - 5)
47
+ end = min(len(lines), line_num + 10)
48
+ surrounding = "\n".join(lines[start:end]).lower()
49
+
50
+ has_auth = any(
51
+ indicator.lower() in surrounding
52
+ for indicator in AUTH_INDICATORS
53
+ )
54
+
55
+ if not has_auth:
56
+ findings.append({
57
+ "rule": RULE_NAME,
58
+ "severity": SEVERITY,
59
+ "file": file_path,
60
+ "line": line_num,
61
+ "message": "Sensitive route defined without visible auth middleware",
62
+ "fix_hint": "Add authentication decorator or middleware before this route handler.",
63
+ "code_snippet": line.strip()[:80],
64
+ })
65
+ break
66
+
67
+ return findings
vibesec/rules/cors.py ADDED
@@ -0,0 +1,74 @@
1
+ import re
2
+
3
+ RULE_NAME = "Permissive CORS Configuration"
4
+ SEVERITY = "MEDIUM"
5
+
6
+ PATTERNS = [
7
+ (r'origin\s*:\s*["\']?\*["\']?',
8
+ "CORS wildcard origin — any domain can make requests"),
9
+ (r'Access-Control-Allow-Origin.*\*',
10
+ "CORS wildcard in response header"),
11
+ (r'cors\(\s*\)',
12
+ "CORS enabled with no configuration — defaults to wildcard"),
13
+ (r'allowedOrigins\s*[:=]\s*\[?\s*["\']?\*',
14
+ "CORS allowed origins set to wildcard"),
15
+ (r'credentials.*true.*\*|\*.*credentials.*true',
16
+ "CORS wildcard with credentials — critical misconfiguration"),
17
+ ]
18
+
19
+ SAFE_INDICATORS = [
20
+ "process.env",
21
+ "ALLOWED_ORIGINS",
22
+ "whitelist",
23
+ "allowlist",
24
+ "origins.includes",
25
+ "origin ===",
26
+ ]
27
+
28
+ SKIP_FILES = {"README.md", "readme.md"}
29
+
30
+
31
+ def check_cors(file_path, content):
32
+ findings = []
33
+
34
+ filename = file_path.split("/")[-1].split("\\")[-1]
35
+ if filename in SKIP_FILES:
36
+ return findings
37
+
38
+ ext = file_path.split(".")[-1].lower()
39
+ if ext not in {"py", "js", "ts", "jsx", "tsx"}:
40
+ return findings
41
+
42
+ if "cors" not in content.lower() and "origin" not in content.lower():
43
+ return findings
44
+
45
+ lines = content.splitlines()
46
+ for line_num, line in enumerate(lines, 1):
47
+ stripped = line.strip()
48
+ if stripped.startswith("//") or stripped.startswith("#"):
49
+ continue
50
+
51
+ for pattern, description in PATTERNS:
52
+ if re.search(pattern, line, re.IGNORECASE):
53
+ start = max(0, line_num - 5)
54
+ end = min(len(lines), line_num + 5)
55
+ surrounding = "\n".join(lines[start:end])
56
+
57
+ is_safe = any(
58
+ indicator in surrounding
59
+ for indicator in SAFE_INDICATORS
60
+ )
61
+
62
+ if not is_safe:
63
+ findings.append({
64
+ "rule": RULE_NAME,
65
+ "severity": SEVERITY,
66
+ "file": file_path,
67
+ "line": line_num,
68
+ "message": description,
69
+ "fix_hint": "Specify exact allowed origins. Never use wildcard CORS with credentials enabled.",
70
+ "code_snippet": line.strip()[:80],
71
+ })
72
+ break
73
+
74
+ return findings
vibesec/rules/jwt.py ADDED
@@ -0,0 +1,52 @@
1
+ import re
2
+
3
+ RULE_NAME = "Unsafe JWT Handling"
4
+ SEVERITY = "HIGH"
5
+
6
+ PATTERNS = [
7
+ (r'jwt\.decode\s*\([^)]*algorithms\s*=\s*\[["\']none["\']\]',
8
+ "JWT accepts 'none' algorithm — critical auth bypass"),
9
+ (r'verify\s*=\s*False',
10
+ "JWT verification explicitly disabled"),
11
+ (r'localStorage\.setItem\s*\([^)]*token',
12
+ "JWT stored in localStorage — vulnerable to XSS theft"),
13
+ (r'sessionStorage\.setItem\s*\([^)]*token',
14
+ "JWT stored in sessionStorage — vulnerable to XSS theft"),
15
+ (r'algorithm.*none',
16
+ "JWT 'none' algorithm detected"),
17
+ ]
18
+
19
+ SKIP_FILES = {"README.md", "readme.md"}
20
+
21
+
22
+ def check_jwt(file_path, content):
23
+ findings = []
24
+
25
+ filename = file_path.split("/")[-1].split("\\")[-1]
26
+ if filename in SKIP_FILES:
27
+ return findings
28
+
29
+ ext = file_path.split(".")[-1].lower()
30
+ if ext not in {"py", "js", "ts", "jsx", "tsx"}:
31
+ return findings
32
+
33
+ lines = content.splitlines()
34
+ for line_num, line in enumerate(lines, 1):
35
+ stripped = line.strip()
36
+ if stripped.startswith("#") or stripped.startswith("//"):
37
+ continue
38
+
39
+ for pattern, description in PATTERNS:
40
+ if re.search(pattern, line, re.IGNORECASE):
41
+ findings.append({
42
+ "rule": RULE_NAME,
43
+ "severity": SEVERITY,
44
+ "file": file_path,
45
+ "line": line_num,
46
+ "message": description,
47
+ "fix_hint": "Always verify JWT signature. Use httpOnly cookies instead of localStorage.",
48
+ "code_snippet": line.strip()[:80],
49
+ })
50
+ break
51
+
52
+ return findings
@@ -0,0 +1,83 @@
1
+ import json
2
+ import re
3
+ import requests
4
+
5
+ RULE_NAME = "Hallucinated Package"
6
+ SEVERITY = "HIGH"
7
+
8
+ # Known hallucinated package names LLMs commonly generate
9
+ KNOWN_HALLUCINATED = {
10
+ "react-auth-handler",
11
+ "supabase-helpers",
12
+ "express-middleware-auth",
13
+ "nextjs-utils",
14
+ "react-secure-storage",
15
+ "express-auth-jwt",
16
+ "node-security-utils",
17
+ "react-api-handler",
18
+ "next-auth-helpers",
19
+ "prisma-utils",
20
+ }
21
+
22
+
23
+ def check_npm_exists(package_name):
24
+ """Check if package exists on npm registry."""
25
+ try:
26
+ url = f"https://registry.npmjs.org/{package_name}"
27
+ response = requests.get(url, timeout=3)
28
+ return response.status_code == 200
29
+ except Exception:
30
+ return True # If we can't check, assume it exists (avoid false positives)
31
+
32
+
33
+ def check_packages(file_path, content):
34
+ findings = []
35
+
36
+ filename = file_path.split("/")[-1].split("\\")[-1]
37
+
38
+ if filename != "package.json":
39
+ return findings
40
+
41
+ try:
42
+ data = json.loads(content)
43
+ except json.JSONDecodeError:
44
+ return findings
45
+
46
+ all_deps = {}
47
+ all_deps.update(data.get("dependencies", {}))
48
+ all_deps.update(data.get("devDependencies", {}))
49
+
50
+ for package_name in all_deps:
51
+ # First check known hallucinated list (fast, no API call)
52
+ if package_name.lower() in KNOWN_HALLUCINATED:
53
+ findings.append({
54
+ "rule": RULE_NAME,
55
+ "severity": SEVERITY,
56
+ "file": file_path,
57
+ "line": "N/A",
58
+ "message": f"'{package_name}' is a known hallucinated package name",
59
+ "fix_hint": f"Remove '{package_name}' — this package does not exist. Find the correct package on npmjs.com.",
60
+ "code_snippet": package_name,
61
+ })
62
+ continue
63
+
64
+ # Check for suspicious patterns
65
+ suspicious = (
66
+ re.search(r'(helper|util|handler|wrapper)s?$', package_name, re.I)
67
+ and not package_name.startswith("@")
68
+ and len(package_name) > 15
69
+ )
70
+
71
+ if suspicious:
72
+ if not check_npm_exists(package_name):
73
+ findings.append({
74
+ "rule": RULE_NAME,
75
+ "severity": SEVERITY,
76
+ "file": file_path,
77
+ "line": "N/A",
78
+ "message": f"'{package_name}' does not exist on npm registry",
79
+ "fix_hint": "This package may be hallucinated by an AI tool. Verify on npmjs.com before using.",
80
+ "code_snippet": package_name,
81
+ })
82
+
83
+ return findings
vibesec/rules/rls.py ADDED
@@ -0,0 +1,51 @@
1
+ import re
2
+
3
+ RULE_NAME = "Supabase RLS Disabled"
4
+ SEVERITY = "CRITICAL"
5
+
6
+ PATTERNS = [
7
+ (r'alter\s+table\s+\w+\s+disable\s+row\s+level\s+security',
8
+ "RLS explicitly disabled on table"),
9
+ (r'row\s+level\s+security.*disabled',
10
+ "Row level security disabled"),
11
+ (r'\.from\(["\'](\w+)["\']\)\s*\.select',
12
+ "Supabase query — verify RLS is enabled on this table"),
13
+ ]
14
+
15
+ SKIP_FILES = {"README.md", "readme.md"}
16
+
17
+
18
+ def check_rls(file_path, content):
19
+ findings = []
20
+
21
+ filename = file_path.split("/")[-1].split("\\")[-1]
22
+ if filename in SKIP_FILES:
23
+ return findings
24
+
25
+ ext = file_path.split(".")[-1].lower()
26
+
27
+ lines = content.splitlines()
28
+ for line_num, line in enumerate(lines, 1):
29
+ stripped = line.strip()
30
+ if stripped.startswith("--") or stripped.startswith("#"):
31
+ continue
32
+
33
+ for pattern, description in PATTERNS:
34
+ if re.search(pattern, line, re.IGNORECASE):
35
+ # For .select queries, only flag in non-SQL files
36
+ # SQL files legitimately define RLS
37
+ if "select" in pattern and ext == "sql":
38
+ continue
39
+
40
+ findings.append({
41
+ "rule": RULE_NAME,
42
+ "severity": SEVERITY,
43
+ "file": file_path,
44
+ "line": line_num,
45
+ "message": description,
46
+ "fix_hint": "Enable RLS: ALTER TABLE table_name ENABLE ROW LEVEL SECURITY; then add policies.",
47
+ "code_snippet": line.strip()[:80],
48
+ })
49
+ break
50
+
51
+ return findings
vibesec/rules/roles.py ADDED
@@ -0,0 +1,58 @@
1
+ import re
2
+
3
+ RULE_NAME = "Client-Side Role Trust"
4
+ SEVERITY = "HIGH"
5
+
6
+ PATTERNS = [
7
+ (r'localStorage\.getItem\s*\([^)]*role',
8
+ "Role read from localStorage — can be tampered by user"),
9
+ (r'localStorage\.getItem\s*\([^)]*admin',
10
+ "Admin flag read from localStorage — can be tampered by user"),
11
+ (r'localStorage\.getItem\s*\([^)]*permission',
12
+ "Permission read from localStorage — can be tampered by user"),
13
+ (r'if\s*\([^)]*localStorage[^)]*admin',
14
+ "Admin check using localStorage value — client-side trust"),
15
+ (r'params\.(role|admin|isAdmin|permission)\s*===',
16
+ "Role/permission check using URL params — can be manipulated"),
17
+ (r'searchParams\.(get|role|admin)',
18
+ "Role read from URL search params — easily manipulated"),
19
+ (r'isAdmin\s*=\s*.*localStorage',
20
+ "isAdmin derived from localStorage — insecure"),
21
+ (r'userRole\s*=\s*.*localStorage',
22
+ "userRole derived from localStorage — insecure"),
23
+ ]
24
+
25
+ SKIP_FILES = {"README.md", "readme.md"}
26
+
27
+
28
+ def check_roles(file_path, content):
29
+ findings = []
30
+
31
+ filename = file_path.split("/")[-1].split("\\")[-1]
32
+ if filename in SKIP_FILES:
33
+ return findings
34
+
35
+ ext = file_path.split(".")[-1].lower()
36
+ if ext not in {"js", "ts", "jsx", "tsx", "py"}:
37
+ return findings
38
+
39
+ lines = content.splitlines()
40
+ for line_num, line in enumerate(lines, 1):
41
+ stripped = line.strip()
42
+ if stripped.startswith("//") or stripped.startswith("#"):
43
+ continue
44
+
45
+ for pattern, description in PATTERNS:
46
+ if re.search(pattern, line, re.IGNORECASE):
47
+ findings.append({
48
+ "rule": RULE_NAME,
49
+ "severity": SEVERITY,
50
+ "file": file_path,
51
+ "line": line_num,
52
+ "message": description,
53
+ "fix_hint": "Always verify roles server-side. Never trust client-provided role values.",
54
+ "code_snippet": line.strip()[:80],
55
+ })
56
+ break
57
+
58
+ return findings
@@ -0,0 +1,59 @@
1
+ import re
2
+
3
+ RULE_NAME = "Hardcoded Secret"
4
+ SEVERITY = "CRITICAL"
5
+
6
+ # Patterns that indicate hardcoded secrets
7
+ PATTERNS = [
8
+ (r'api_key\s*=\s*["\'][a-zA-Z0-9_\-]{16,}["\']', "Hardcoded API key"),
9
+ (r'api_secret\s*=\s*["\'][a-zA-Z0-9_\-]{16,}["\']', "Hardcoded API secret"),
10
+ (r'password\s*=\s*["\'][^"\']{6,}["\']', "Hardcoded password"),
11
+ (r'secret_key\s*=\s*["\'][^"\']{8,}["\']', "Hardcoded secret key"),
12
+ (r'sk-[a-zA-Z0-9]{48}', "OpenAI API key"),
13
+ (r'ghp_[a-zA-Z0-9]{36}', "GitHub personal access token"),
14
+ (r'SUPABASE_SERVICE_KEY\s*=\s*["\'][^"\']+["\']', "Supabase service key exposed"),
15
+ (r'SUPABASE_SECRET\s*=\s*["\'][^"\']+["\']', "Supabase secret exposed"),
16
+ (r'stripe[_\s]secret\s*=\s*["\'][^"\']+["\']', "Stripe secret key"),
17
+ (r'sk_live_[a-zA-Z0-9]{24,}', "Stripe live secret key"),
18
+ (r'AUTH_SECRET\s*=\s*["\'][^"\']{8,}["\']', "Auth secret hardcoded"),
19
+ (r'DATABASE_URL\s*=\s*["\']postgresql://[^"\']+["\']', "Database URL with credentials"),
20
+ ]
21
+
22
+ # Files to skip — these are expected to have these patterns
23
+ SKIP_FILES = {
24
+ ".env.example", ".env.sample", ".env.template",
25
+ "README.md", "readme.md", ".gitignore"
26
+ }
27
+
28
+
29
+ def check_secrets(file_path, content):
30
+ findings = []
31
+
32
+ # Skip example/template files
33
+ filename = file_path.split("/")[-1].split("\\")[-1]
34
+ if filename in SKIP_FILES:
35
+ return findings
36
+
37
+ # Skip .env files that are in .gitignore — but still flag if exposed
38
+ lines = content.splitlines()
39
+
40
+ for line_num, line in enumerate(lines, 1):
41
+ # Skip comments
42
+ stripped = line.strip()
43
+ if stripped.startswith("#") or stripped.startswith("//"):
44
+ continue
45
+
46
+ for pattern, description in PATTERNS:
47
+ if re.search(pattern, line, re.IGNORECASE):
48
+ findings.append({
49
+ "rule": RULE_NAME,
50
+ "severity": SEVERITY,
51
+ "file": file_path,
52
+ "line": line_num,
53
+ "message": f"{description} detected in source code",
54
+ "fix_hint": "Move to environment variables. Never commit secrets to git.",
55
+ "code_snippet": line.strip()[:80],
56
+ })
57
+ break # One finding per line is enough
58
+
59
+ return findings
@@ -0,0 +1,66 @@
1
+ import re
2
+ import os
3
+
4
+ RULE_NAME = "Source Map Exposure"
5
+ SEVERITY = "HIGH"
6
+
7
+ PATTERNS = [
8
+ (r'["\']?sourceMap["\']?\s*[:=]\s*true', "Source maps enabled in build config"),
9
+ (r'GENERATE_SOURCEMAP\s*=\s*true', "Create React App source maps enabled"),
10
+ (r'devtool\s*:\s*["\']source-map["\']', "Webpack source-map devtool enabled"),
11
+ (r'\"source-map\"', "Source map configuration detected"),
12
+ ]
13
+
14
+
15
+ def check_sourcemaps(file_path, content):
16
+ findings = []
17
+
18
+ filename = file_path.split("/")[-1].split("\\")[-1]
19
+ ext = file_path.split(".")[-1].lower()
20
+
21
+ # Check .map files committed to repo
22
+ if filename.endswith(".map"):
23
+ # Check if it's in a build/dist directory
24
+ if any(d in file_path for d in ["dist/", "build/", ".next/", "out/"]):
25
+ findings.append({
26
+ "rule": RULE_NAME,
27
+ "severity": SEVERITY,
28
+ "file": file_path,
29
+ "line": "N/A",
30
+ "message": "Source map file committed to repository — exposes full source code",
31
+ "fix_hint": "Add *.map to .gitignore. Set sourceMap: false in production builds.",
32
+ "code_snippet": filename,
33
+ })
34
+ return findings
35
+
36
+ # Check build config files
37
+ config_files = {
38
+ "webpack.config.js", "webpack.config.ts",
39
+ "next.config.js", "next.config.ts",
40
+ "vite.config.js", "vite.config.ts",
41
+ ".env", ".env.production", ".env.prod"
42
+ }
43
+
44
+ if filename not in config_files and ext not in {"json"}:
45
+ return findings
46
+
47
+ lines = content.splitlines()
48
+ for line_num, line in enumerate(lines, 1):
49
+ stripped = line.strip()
50
+ if stripped.startswith("//") or stripped.startswith("#"):
51
+ continue
52
+
53
+ for pattern, description in PATTERNS:
54
+ if re.search(pattern, line, re.IGNORECASE):
55
+ findings.append({
56
+ "rule": RULE_NAME,
57
+ "severity": SEVERITY,
58
+ "file": file_path,
59
+ "line": line_num,
60
+ "message": description,
61
+ "fix_hint": "Set sourceMap: false in production. Use hidden-source-map if debugging is needed.",
62
+ "code_snippet": line.strip()[:80],
63
+ })
64
+ break
65
+
66
+ return findings
@@ -0,0 +1,77 @@
1
+ import re
2
+
3
+ RULE_NAME = "Missing Webhook Verification"
4
+ SEVERITY = "MEDIUM"
5
+
6
+ PATTERNS = [
7
+ (r'stripe\.webhooks(?!.*constructEvent)',
8
+ "Stripe webhook used without constructEvent signature verification"),
9
+ (r'req\.body.*stripe(?!.*stripe-signature)',
10
+ "Stripe webhook body read without signature header check"),
11
+ (r'x-github-event(?!.*x-hub-signature)',
12
+ "GitHub webhook received without hub signature verification"),
13
+ (r'webhook.*payload(?!.*secret)',
14
+ "Webhook payload processed without secret verification"),
15
+ ]
16
+
17
+ # Positive indicators that webhook IS being verified
18
+ SAFE_INDICATORS = [
19
+ "constructEvent",
20
+ "stripe-signature",
21
+ "x-hub-signature",
22
+ "webhook_secret",
23
+ "WEBHOOK_SECRET",
24
+ "verifySignature",
25
+ "crypto.timingSafeEqual",
26
+ "hmac",
27
+ ]
28
+
29
+ SKIP_FILES = {"README.md", "readme.md"}
30
+
31
+
32
+ def check_webhooks(file_path, content):
33
+ findings = []
34
+
35
+ filename = file_path.split("/")[-1].split("\\")[-1]
36
+ if filename in SKIP_FILES:
37
+ return findings
38
+
39
+ ext = file_path.split(".")[-1].lower()
40
+ if ext not in {"py", "js", "ts", "jsx", "tsx"}:
41
+ return findings
42
+
43
+ # Only scan files that mention webhooks
44
+ if "webhook" not in content.lower() and "stripe" not in content.lower():
45
+ return findings
46
+
47
+ lines = content.splitlines()
48
+ for line_num, line in enumerate(lines, 1):
49
+ stripped = line.strip()
50
+ if stripped.startswith("//") or stripped.startswith("#"):
51
+ continue
52
+
53
+ for pattern, description in PATTERNS:
54
+ if re.search(pattern, line, re.IGNORECASE):
55
+ # Check surrounding context for safe indicators
56
+ start = max(0, line_num - 10)
57
+ end = min(len(lines), line_num + 10)
58
+ surrounding = "\n".join(lines[start:end])
59
+
60
+ is_safe = any(
61
+ indicator in surrounding
62
+ for indicator in SAFE_INDICATORS
63
+ )
64
+
65
+ if not is_safe:
66
+ findings.append({
67
+ "rule": RULE_NAME,
68
+ "severity": SEVERITY,
69
+ "file": file_path,
70
+ "line": line_num,
71
+ "message": description,
72
+ "fix_hint": "Verify webhook signatures using the provider's SDK. For Stripe use stripe.webhooks.constructEvent().",
73
+ "code_snippet": line.strip()[:80],
74
+ })
75
+ break
76
+
77
+ return findings
vibesec/rules/xss.py ADDED
@@ -0,0 +1,56 @@
1
+ import re
2
+
3
+ RULE_NAME = "Unsafe HTML Injection (XSS)"
4
+ SEVERITY = "MEDIUM"
5
+
6
+ PATTERNS = [
7
+ (r'dangerouslySetInnerHTML\s*=\s*\{\s*\{',
8
+ "dangerouslySetInnerHTML used — potential XSS vulnerability"),
9
+ (r'dangerouslySetInnerHTML.*\$\{',
10
+ "dangerouslySetInnerHTML with template literal — XSS risk"),
11
+ (r'dangerouslySetInnerHTML.*props\.',
12
+ "dangerouslySetInnerHTML with props value — XSS risk"),
13
+ (r'dangerouslySetInnerHTML.*state\.',
14
+ "dangerouslySetInnerHTML with state value — XSS risk"),
15
+ (r'innerHTML\s*=\s*[^"\'`][^\n]*\+',
16
+ "innerHTML set with concatenated value — XSS risk"),
17
+ (r'document\.write\s*\(',
18
+ "document.write used — XSS risk"),
19
+ (r'eval\s*\(\s*[^"\'`]',
20
+ "eval() called with dynamic value — code injection risk"),
21
+ ]
22
+
23
+ SKIP_FILES = {"README.md", "readme.md"}
24
+
25
+
26
+ def check_xss(file_path, content):
27
+ findings = []
28
+
29
+ filename = file_path.split("/")[-1].split("\\")[-1]
30
+ if filename in SKIP_FILES:
31
+ return findings
32
+
33
+ ext = file_path.split(".")[-1].lower()
34
+ if ext not in {"js", "ts", "jsx", "tsx"}:
35
+ return findings
36
+
37
+ lines = content.splitlines()
38
+ for line_num, line in enumerate(lines, 1):
39
+ stripped = line.strip()
40
+ if stripped.startswith("//"):
41
+ continue
42
+
43
+ for pattern, description in PATTERNS:
44
+ if re.search(pattern, line, re.IGNORECASE):
45
+ findings.append({
46
+ "rule": RULE_NAME,
47
+ "severity": SEVERITY,
48
+ "file": file_path,
49
+ "line": line_num,
50
+ "message": description,
51
+ "fix_hint": "Sanitize HTML with DOMPurify before rendering. Avoid dangerouslySetInnerHTML with user input.",
52
+ "code_snippet": line.strip()[:80],
53
+ })
54
+ break
55
+
56
+ return findings
vibesec/scanner.py ADDED
@@ -0,0 +1,25 @@
1
+ from vibesec.utils import walk_files, read_file
2
+ from vibesec.rules import ALL_RULES
3
+
4
+
5
+ class Scanner:
6
+
7
+ def __init__(self, path):
8
+ self.path = path
9
+
10
+ def run(self):
11
+ findings = []
12
+ files = list(walk_files(self.path))
13
+
14
+ for file_path in files:
15
+ content = read_file(file_path)
16
+ if not content:
17
+ continue
18
+ for rule in ALL_RULES:
19
+ try:
20
+ results = rule(file_path, content)
21
+ findings.extend(results)
22
+ except Exception:
23
+ pass
24
+
25
+ return findings
vibesec/utils.py ADDED
@@ -0,0 +1,31 @@
1
+ import os
2
+
3
+ SUPPORTED_EXTENSIONS = {
4
+ ".py", ".js", ".ts", ".jsx", ".tsx",
5
+ ".json", ".yaml", ".yml", ".env", ".sql"
6
+ }
7
+
8
+ def walk_files(path):
9
+ """Recursively yield all supported files in a directory."""
10
+ if os.path.isfile(path):
11
+ yield path
12
+ return
13
+
14
+ for root, dirs, files in os.walk(path):
15
+ # Skip common non-code directories
16
+ dirs[:] = [d for d in dirs if d not in {
17
+ "node_modules", ".git", "venv", "__pycache__",
18
+ ".next", "dist", "build", ".venv"
19
+ }]
20
+ for file in files:
21
+ ext = os.path.splitext(file)[1].lower()
22
+ if ext in SUPPORTED_EXTENSIONS:
23
+ yield os.path.join(root, file)
24
+
25
+ def read_file(path):
26
+ """Read file content safely."""
27
+ try:
28
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
29
+ return f.read()
30
+ except Exception:
31
+ return ""
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: vibesec
3
+ Version: 0.1.0
4
+ Summary: Security scanner for AI-generated code
5
+ Author-email: Ayush Khati <ayushiskhati305@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Ayush Khati
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/AyushkhatiDev/vibesec
29
+ Project-URL: Bug Tracker, https://github.com/AyushkhatiDev/vibesec/issues
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Topic :: Security
34
+ Classifier: Topic :: Software Development :: Quality Assurance
35
+ Requires-Python: >=3.8
36
+ Description-Content-Type: text/markdown
37
+ License-File: LICENSE
38
+ Requires-Dist: click>=8.0
39
+ Requires-Dist: rich>=13.0
40
+ Requires-Dist: requests>=2.28
41
+ Requires-Dist: groq>=0.4.0
42
+ Requires-Dist: python-dotenv>=1.0.0
43
+ Dynamic: license-file
44
+
45
+ # 🔒 VibeSec
46
+
47
+ **Security scanner for AI-generated code.**
48
+
49
+ [![PyPI version](https://badge.fury.io/py/vibesec.svg)](https://badge.fury.io/py/vibesec)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
51
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
52
+ [![GitHub stars](https://img.shields.io/github/stars/AyushkhatiDev/vibesec?style=social)](https://github.com/AyushkhatiDev/vibesec)
53
+
54
+ 45% of AI-generated code ships with critical vulnerabilities. Cursor, Claude Code, Bolt, and Lovable generate insecure patterns that existing tools miss. VibeSec catches them before you deploy.
55
+
56
+ ```
57
+ $ vibesec scan ./my-cursor-app
58
+
59
+ VibeSec v0.1.0 — AI-Generated Code Security Scanner
60
+
61
+ ● CRITICAL 7 findings
62
+ ● HIGH 2 findings
63
+
64
+ CRITICAL — Hardcoded Secret
65
+ File: src/lib/supabase.ts Line: 12
66
+ Found: SUPABASE_SERVICE_KEY hardcoded in source code
67
+ Fix: Move to environment variables. Never commit secrets to git.
68
+
69
+ CRITICAL — Supabase RLS Disabled
70
+ File: supabase/migrations/001_init.sql Line: 34
71
+ Found: ALTER TABLE users DISABLE ROW LEVEL SECURITY
72
+ Fix: Enable RLS + add user isolation policies.
73
+
74
+ 9 findings in ./my-cursor-app
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Why VibeSec
80
+
81
+ Existing tools like Semgrep, Snyk, and CodeQL are great — but they were built for human-written code. AI tools generate specific anti-patterns that these scanners miss:
82
+
83
+ | Pattern | Semgrep | Snyk | VibeSec |
84
+ |---|---|---|---|
85
+ | Hardcoded secrets | ✓ | ✓ | ✓ |
86
+ | Supabase RLS disabled | ✗ | ✗ | ✓ |
87
+ | Hallucinated npm packages | ✗ | ✗ | ✓ |
88
+ | Missing auth on scaffolded routes | Partial | ✗ | ✓ |
89
+ | Source map exposure in build config | ✗ | ✗ | ✓ |
90
+ | AI-specific JWT misuse | ✗ | ✗ | ✓ |
91
+
92
+ ---
93
+
94
+ ## Install
95
+
96
+ ```bash
97
+ pip install vibesec
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Usage
103
+
104
+ **Scan a directory:**
105
+ ```bash
106
+ vibesec scan ./my-project
107
+ ```
108
+
109
+ **Scan and get AI-powered fix suggestions:**
110
+ ```bash
111
+ vibesec scan ./my-project --fix
112
+ ```
113
+
114
+ **Export results as JSON (for CI/CD):**
115
+ ```bash
116
+ vibesec scan ./my-project --output json
117
+ ```
118
+
119
+ **Filter by severity:**
120
+ ```bash
121
+ vibesec scan ./my-project --severity critical
122
+ ```
123
+
124
+ **Ignore specific checks:**
125
+ ```bash
126
+ vibesec scan ./my-project --ignore rls,cors
127
+ ```
128
+
129
+ ---
130
+
131
+ ## What VibeSec Checks
132
+
133
+ ### 🔴 CRITICAL
134
+
135
+ **1. Hardcoded Secrets**
136
+ API keys, passwords, tokens, and database URLs hardcoded in source files. LLMs replicate tutorial patterns where secrets are hardcoded.
137
+
138
+ ```python
139
+ # VibeSec catches this
140
+ api_key = "sk-abc123..."
141
+ SUPABASE_SERVICE_KEY = "eyJhbGci..."
142
+ stripe_secret = "sk_live_..."
143
+ ```
144
+
145
+ **2. Supabase RLS Disabled**
146
+ Row Level Security disabled — any authenticated user can read or modify all data. LLMs skip RLS to make queries work quickly in scaffolding.
147
+
148
+ ```sql
149
+ -- VibeSec catches this
150
+ ALTER TABLE users DISABLE ROW LEVEL SECURITY;
151
+ ```
152
+
153
+ ### 🟡 HIGH
154
+
155
+ **3. Missing Route Authentication**
156
+ Admin and sensitive API routes scaffolded without authentication middleware. LLMs build the happy path without thinking about access control.
157
+
158
+ **4. Hallucinated Packages**
159
+ npm packages that don't exist — a typosquatting attack surface. LLMs generate plausible-sounding package names that aren't real.
160
+
161
+ ```json
162
+ // VibeSec catches this
163
+ "react-auth-handler": "^1.0.0",
164
+ "supabase-helpers": "^2.1.0"
165
+ ```
166
+
167
+ **5. Source Map Exposure**
168
+ Build config exposes full source code via `.map` files in production.
169
+
170
+ ### 🟠 MEDIUM
171
+
172
+ **6. Unsafe JWT Handling** — JWT decoded without verification, or `none` algorithm accepted
173
+
174
+ **7. dangerouslySetInnerHTML** — Direct HTML injection without sanitization
175
+
176
+ **8. Client-Side Role Trust** — Admin checks done using `localStorage` values
177
+
178
+ **9. Missing Webhook Verification** — Stripe/GitHub webhooks without signature check
179
+
180
+ **10. Permissive CORS** — Wildcard CORS with credentials enabled
181
+
182
+ ---
183
+
184
+ ## GitHub Actions Integration
185
+
186
+ Add VibeSec to your CI/CD pipeline:
187
+
188
+ ```yaml
189
+ # .github/workflows/vibesec.yml
190
+ name: VibeSec Security Scan
191
+
192
+ on: [push, pull_request]
193
+
194
+ jobs:
195
+ security:
196
+ runs-on: ubuntu-latest
197
+ steps:
198
+ - uses: actions/checkout@v3
199
+ - uses: actions/setup-python@v4
200
+ with:
201
+ python-version: '3.11'
202
+ - name: Install VibeSec
203
+ run: pip install vibesec
204
+ - name: Run Security Scan
205
+ run: vibesec scan . --output json --severity high
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Development
211
+
212
+ ```bash
213
+ git clone https://github.com/AyushkhatiDev/vibesec
214
+ cd vibesec
215
+ python -m venv venv
216
+ source venv/bin/activate
217
+ pip install -e ".[dev]"
218
+ pytest tests/
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Contributing
224
+
225
+ VibeSec is open source and contributions are welcome.
226
+
227
+ **Adding a new rule:**
228
+ 1. Create `vibesec/rules/your_rule.py`
229
+ 2. Implement `check_your_rule(file_path, content) -> list[dict]`
230
+ 3. Register it in `vibesec/rules/__init__.py`
231
+ 4. Add test cases in `tests/corpus/`
232
+ 5. Open a PR
233
+
234
+ Each finding must return:
235
+ ```python
236
+ {
237
+ "rule": "Rule Name",
238
+ "severity": "CRITICAL|HIGH|MEDIUM|LOW",
239
+ "file": file_path,
240
+ "line": line_number,
241
+ "message": "What was found",
242
+ "fix_hint": "How to fix it",
243
+ "code_snippet": "offending line"
244
+ }
245
+ ```
246
+
247
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for full guide.
248
+
249
+ ---
250
+
251
+ ## Roadmap
252
+
253
+ - [x] Secrets detection
254
+ - [x] Supabase RLS checker
255
+ - [x] Missing auth on routes
256
+ - [x] Hallucinated package detector
257
+ - [x] Source map exposure
258
+ - [ ] JWT misuse rules
259
+ - [ ] dangerouslySetInnerHTML
260
+ - [ ] Client-side role trust
261
+ - [ ] Webhook verification
262
+ - [ ] Permissive CORS
263
+ - [ ] GitHub Action marketplace listing
264
+ - [ ] Web app (paste URL → get report)
265
+ - [ ] SARIF output for GitHub Security tab
266
+ - [ ] VS Code extension
267
+
268
+ ---
269
+
270
+ ## Built By
271
+
272
+ [Ayush Khati](https://github.com/AyushkhatiDev) — BCA student building real tools for real problems.
273
+
274
+ Found a bug? [Open an issue](https://github.com/AyushkhatiDev/vibesec/issues).
275
+ Want a rule added? [Start a discussion](https://github.com/AyushkhatiDev/vibesec/discussions).
276
+
277
+ ---
278
+
279
+ ## License
280
+
281
+ MIT — free to use, modify, and distribute.
282
+
283
+ ---
284
+
285
+ <p align="center">
286
+ <sub>Built because 45% of vibe-coded apps ship with critical vulnerabilities. Someone had to fix that.</sub>
287
+ </p>
@@ -0,0 +1,23 @@
1
+ vibesec/__init__.py,sha256=q31sV8RecGVcenSqZBLbtYG4iVMxR56N-gYs2to9Se8,49
2
+ vibesec/cli.py,sha256=i1B54upXZC0edXgSDYOFvl8rSk1b6eKSbQBGF-4qKGE,1050
3
+ vibesec/fixgen.py,sha256=CU0HFSqVLJvPrNTT1aum8oeTD6J2cG7_YCNV0ISTgsU,1153
4
+ vibesec/reporter.py,sha256=yfr1VQ3Bm58YhNyjFe0htCNZDqw_4VC5MEOe6Mamxuc,3435
5
+ vibesec/scanner.py,sha256=r8NGOBF1SFDUFH0Z5Puf8XvguGrvgr6-94TgVa2cvts,612
6
+ vibesec/utils.py,sha256=Cgpv0zbNeiGH7rQ-n9YOfySLoeTErB60QJ5ObRmihXE,905
7
+ vibesec/rules/__init__.py,sha256=F69yiHp72wnogMO8JNhYOwAguRqkeG45MrEklUe2SuM,633
8
+ vibesec/rules/auth_routes.py,sha256=qcu0yE6mnqkYt9CuPFyb79Zpnqcf3RlJqfeEBYp0HdI,2159
9
+ vibesec/rules/cors.py,sha256=nwsdt-38L9ZancdIpn1GrVBn_HYQbbWYDDrLzjuZB_0,2293
10
+ vibesec/rules/jwt.py,sha256=gJ_0GPIGHXYkIpsYBlEFTJ1yepR6SeTFkTWJcHE2duY,1663
11
+ vibesec/rules/packages.py,sha256=tGrJtlOKH79qdBbxkZalg3mzX89LtKTtFF1uajF8ayo,2594
12
+ vibesec/rules/rls.py,sha256=dSLefkEZPka2Xv-dpGDQbnffI42_XNgJoHlKn8cHxyU,1609
13
+ vibesec/rules/roles.py,sha256=0cSTUaupIvQAacaSNsh1wl9tpoVEc47NwbP0fJKqkCQ,2058
14
+ vibesec/rules/secrets.py,sha256=oMyQMjievSOek9UYEOovAEQAHfjur4YrdaRnkND3z6g,2286
15
+ vibesec/rules/sourcemaps.py,sha256=-7UsB_EcC-CEavj_v3pmGwMoRjgDRROr0hpRB_M97RQ,2316
16
+ vibesec/rules/webhooks.py,sha256=NXZy32ykHlX7R3i-Vs9eylBkQfXCZ9vmvjvd7dPGZhA,2501
17
+ vibesec/rules/xss.py,sha256=N6w_736r0YsTRoMvFadQ6YX9rNj4Eymnd8MU1Mg6nEo,1869
18
+ vibesec-0.1.0.dist-info/licenses/LICENSE,sha256=C85Ww3BMTD0gQt9J9N7MP_SbWl9EyLyp9yjc5eg0doQ,1068
19
+ vibesec-0.1.0.dist-info/METADATA,sha256=yfg6sphnIwhCkQ7ku6r2MPtQetLeE0HUcRlxFLIGSPs,8135
20
+ vibesec-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
21
+ vibesec-0.1.0.dist-info/entry_points.txt,sha256=MLazU6qKZaH2PHfOE1g_ybPvEKwDIBp51WGH4LMFmF0,45
22
+ vibesec-0.1.0.dist-info/top_level.txt,sha256=8Ivz0xZCNXO4NwrGs5xoUaYa_sQXqkq94Xg9yyvEU0g,8
23
+ vibesec-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ vibesec = vibesec.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ayush Khati
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ vibesec