Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@
**Vulnerability:** Regular Expression Denial of Service (ReDoS) vulnerability caused by tracking capturing group matches `(...)` during line-by-line file scanning in `SCAN_RULES`.
**Learning:** In a highly repetitive inner loop (line-by-line file scanning), tracking backreferences and capturing group matches incurs unnecessary regex engine overhead. While unbounded quantifiers are the primary ReDoS cause, capturing groups exacerbate the tracking state, increasing scan time significantly on long lines or adversarial payloads.
**Prevention:** Always use non-capturing groups `(?:...)` instead of capturing groups `(...)` when adding or modifying regular expressions in `SCAN_RULES` to prevent unnecessary performance overhead and ReDoS vulnerabilities.

## 2026-06-16 - Add OWASP mapping and pre-commit hook integration
**Vulnerability:** The VibeSec scanner lacked explicit mapping to standard vulnerability frameworks (like OWASP Top 10) and relied on manual invocation, meaning vulnerabilities could easily bypass detection and be committed by developers or AI agents (like Claude Code or Codex).
**Learning:** To enforce security guardrails effectively, static analysis tools should intercept the workflow at commit time. Mapping findings to OWASP categories improves the clarity and actionability of the scanner output.
**Prevention:** Updated `SCAN_RULES` messages to include relevant OWASP classifications (e.g., A01, A03). Added a `vibesec hook` command that automatically installs a `pre-commit` script to block commits if critical or high vulnerabilities are detected.
88 changes: 65 additions & 23 deletions scanner/cli/vibesec.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
init Install security rules into your project
scan Run a lightweight security scan on a directory
review Generate an AI review prompt for your stack
hook Install a pre-commit hook to block vulnerabilities

Options:
--tool AI coding tool: cursor, claude-code, windsurf, lovable (default: cursor)
Expand Down Expand Up @@ -118,14 +119,14 @@
"id": "hardcoded-stripe-secret",
"pattern": re.compile(r'sk_(?:live|test)_[A-Za-z0-9]{24,}'),
"severity": "CRITICAL",
"message": "Hardcoded Stripe secret key detected. Rotate this key immediately.",
"message": "Hardcoded Stripe secret key detected. Rotate this key immediately. [OWASP A07:2021 - Identification and Authentication Failures]",
"extensions": None,
},
{
"id": "hardcoded-openai-key",
"pattern": re.compile(r'sk-[A-Za-z0-9]{32,}'),
"severity": "CRITICAL",
"message": "Possible hardcoded OpenAI API key detected.",
"message": "Possible hardcoded OpenAI API key detected. [OWASP A07:2021 - Identification and Authentication Failures]",
"extensions": None,
},
{
Expand All @@ -135,27 +136,21 @@
re.IGNORECASE,
),
"severity": "CRITICAL",
"message": (
"Secret environment variable uses NEXT_PUBLIC_ prefix — "
"this exposes it to the browser bundle."
),
"message": "Secret environment variable uses NEXT_PUBLIC_ prefix — this exposes it to the browser bundle. [OWASP A05:2021 - Security Misconfiguration]",
"extensions": None,
},
{
"id": "supabase-service-role-client",
"pattern": re.compile(r'NEXT_PUBLIC_.*SERVICE_ROLE', re.IGNORECASE),
"severity": "CRITICAL",
"message": "Supabase service role key exposed to the client via NEXT_PUBLIC_ prefix.",
"message": "Supabase service role key exposed to the client via NEXT_PUBLIC_ prefix. [OWASP A05:2021 - Security Misconfiguration]",
"extensions": [".ts", ".tsx", ".js", ".jsx", ".env", ".env.local", ".env.production"],
},
{
"id": "firebase-allow-all",
"pattern": re.compile(r'allow\s+(?:read|write|read,\s*write)\s*:\s*if\s+true'),
"severity": "CRITICAL",
"message": (
"Firebase/Firestore rule allows unrestricted read/write access. "
"Add authentication and ownership checks."
),
"message": "Firebase/Firestore rule allows unrestricted read/write access. Add authentication and ownership checks. [OWASP A01:2021 - Broken Access Control]",
"extensions": [".rules"],
},
{
Expand All @@ -164,17 +159,14 @@
r'(?i)(?:todo|fixme|hack|temp)[^\n]{0,50}(?:auth|security|permission|check|protect)',
),
"severity": "HIGH",
"message": (
"Comment suggests auth/security check was deferred. "
"Verify this is not deployed to production."
),
"message": "Comment suggests auth/security check was deferred. Verify this is not deployed to production. [OWASP A01:2021 - Broken Access Control]",
"extensions": [".ts", ".tsx", ".js", ".jsx", ".py"],
},
{
"id": "dangerous-cors",
"pattern": re.compile(r"Access-Control-Allow-Origin['\",\s]*[*]"),
"severity": "HIGH",
"message": "CORS set to allow all origins (*). Restrict to known domains.",
"message": "CORS set to allow all origins (*). Restrict to known domains. [OWASP A05:2021 - Security Misconfiguration]",
"extensions": [".ts", ".tsx", ".js", ".jsx", ".py"],
},
{
Expand All @@ -183,7 +175,7 @@
r'(?i)(?:DATABASE_URL|POSTGRES_URL)\s*[=:]\s*["\x27](?:postgres|postgresql|mysql)://\S+',
),
"severity": "CRITICAL",
"message": "Hardcoded database connection string detected.",
"message": "Hardcoded database connection string detected. [OWASP A07:2021 - Identification and Authentication Failures]",
"extensions": None,
},
{
Expand All @@ -192,14 +184,14 @@
r'(?i)(?:JWT_SECRET|NEXTAUTH_SECRET)\s*[=:]\s*["\x27][^"\x27\s]{8,}["\x27]',
),
"severity": "CRITICAL",
"message": "Hardcoded JWT/NextAuth secret detected.",
"message": "Hardcoded JWT/NextAuth secret detected. [OWASP A02:2021 - Cryptographic Failures]",
"extensions": None,
},
{
"id": "stripe-webhook-no-verify",
"pattern": re.compile(r'constructEvent\s*\([^)]*(?:undefined|""|\'\')\s*\)'),
"severity": "CRITICAL",
"message": "Stripe constructEvent called with empty/undefined webhook secret.",
"message": "Stripe constructEvent called with empty/undefined webhook secret. [OWASP A08:2021 - Software and Data Integrity Failures]",
"extensions": [".ts", ".tsx", ".js", ".jsx"],
},
{
Expand All @@ -208,21 +200,21 @@
r'const\s+(?:session|user)\s*=\s*\{\s*(?:user\s*:\s*)?\{\s*id\s*:\s*["\x27]',
),
"severity": "HIGH",
"message": "Hardcoded mock session/user in what may be a production handler. Verify this is test-only code.",
"message": "Mock or hardcoded session/user object detected in route handler. [OWASP A01:2021 - Broken Access Control]",
"extensions": [".ts", ".tsx", ".js", ".jsx"],
},
{
"id": "dangerous-eval",
"pattern": re.compile(r'\beval\s*\('),
"severity": "CRITICAL",
"message": "Use of eval() detected. This is a critical risk for arbitrary code execution and injection attacks.",
"message": "Use of eval() detected. This is a critical risk for arbitrary code execution and injection attacks. [OWASP A03:2021 - Injection]",
"extensions": [".js", ".jsx", ".ts", ".tsx", ".py"],
},
{
"id": "react-dangerously-set-inner-html",
"pattern": re.compile(r'dangerouslySetInnerHTML\s*='),
"severity": "HIGH",
"message": "Use of dangerouslySetInnerHTML detected. This can lead to Cross-Site Scripting (XSS) if input is not sanitized.",
"message": "Use of dangerouslySetInnerHTML detected. This can lead to Cross-Site Scripting (XSS) if input is not sanitized. [OWASP A03:2021 - Injection]",
"extensions": [".jsx", ".tsx"],
},
{
Expand All @@ -231,7 +223,7 @@
r'(?i)(?:query|execute|raw)\s*\(\s*(?:`[^`]*\$\{[^}]+\}[^`]*`|["\'].*?["\']\s*\+\s*[a-zA-Z0-9_]+)'
),
"severity": "CRITICAL",
"message": "Potential SQL injection detected: string concatenation or template literal in database query.",
"message": "Potential SQL injection detected: string concatenation or template literal in database query. [OWASP A03:2021 - Injection]",
"extensions": [".ts", ".tsx", ".js", ".jsx", ".py"],
},
]
Expand Down Expand Up @@ -430,6 +422,50 @@ def cmd_scan(args):
return 1 if any(f["severity"] in ("CRITICAL", "HIGH") for f in findings) else 0


def cmd_hook(args):
"""Install a pre-commit hook to block commits with vulnerabilities."""
project_root = Path(".").resolve()
git_dir = project_root / ".git"

if not git_dir.is_dir():
print("Error: Not a git repository. Run 'git init' first.", file=sys.stderr)
return 1

hooks_dir = git_dir / "hooks"
# SECURITY: Prevent Arbitrary File Write via symlink path traversal
if not hooks_dir.resolve().is_relative_to(project_root):
print(f"Error: Target path {hooks_dir} escapes the project root. Aborting.", file=sys.stderr)
return 1

hooks_dir.mkdir(parents=True, exist_ok=True)
pre_commit_file = hooks_dir / "pre-commit"

if pre_commit_file.is_symlink():
pre_commit_file.unlink()

hook_content = """#!/bin/sh
# VibeSec Pre-Commit Hook

echo "\\n🔍 Running VibeSec scan..."
vibesec scan .

if [ $? -ne 0 ]; then
echo "\\n❌ VibeSec scan failed! Critical or high vulnerabilities found."
echo "Please fix the issues or use '--no-verify' to bypass (not recommended)."
exit 1
fi

echo "✅ VibeSec scan passed."
"""

pre_commit_file.write_text(hook_content)
pre_commit_file.chmod(pre_commit_file.stat().st_mode | stat.S_IEXEC)

print("\n✅ VibeSec pre-commit hook installed successfully at .git/hooks/pre-commit!\n")
print("This will run 'vibesec scan .' before every commit and block commits if vulnerabilities are found.")
return 0


# ⚡ Bolt: Cache applicable rules per file extension to avoid redundant list
# comprehensions and pre-extract the search method to avoid dictionary and
# attribute lookups in the tight scanning loop.
Expand Down Expand Up @@ -665,6 +701,10 @@ def main():
review_parser.add_argument("--db", help="Database/backend (e.g. supabase, firebase)")
review_parser.add_argument("--payments", help="Payment provider (e.g. stripe)")

# hook
hook_parser = subparsers.add_parser("hook", help="Install a pre-commit hook to block commits with vulnerabilities")


args = parser.parse_args()

if args.command == "init":
Expand All @@ -673,6 +713,8 @@ def main():
sys.exit(cmd_scan(args))
elif args.command == "review":
cmd_review(args)
elif args.command == "hook":
sys.exit(cmd_hook(args))
else:
parser.print_help()
sys.exit(0)
Expand Down
Loading