Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9611e48
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
e54c017
Merge remote-tracking branch 'origin/develop' into bolt-tuple-optimiz…
Copilot Jun 16, 2026
d8fa4eb
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
e632684
Merge remote-tracking branch 'origin/bolt-tuple-optimization-25507527…
Copilot Jun 16, 2026
61ae364
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
5e9885e
Apply review feedback: revert to Path.open() and use tuple unpacking
Copilot Jun 16, 2026
5f5bcdf
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
a5bb98f
Merge remote-tracking branch 'origin/develop' into bolt-tuple-optimiz…
Copilot Jun 16, 2026
3d6cd5c
Merge origin/bolt-tuple-optimization branch; keep reviewed improvements
Copilot Jun 16, 2026
bb8e9ea
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
2a663c4
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
9259b68
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
aaf8698
Merge origin/develop: resolve conflicts, fix Path.open() and tuple un…
Copilot Jun 16, 2026
6ac8844
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
3fc34d2
⚡ Bolt: Optimize tight scanning loops using pre-extracted tuples and …
seonghobae Jun 16, 2026
4e81e5e
fix: restore file_path.open(), tuple unpacking, and deleted test file
Copilot Jun 16, 2026
d39e2f4
fix: harden cmd_hook against symlink path traversal on pre_commit_file
Copilot Jun 16, 2026
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
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@
## 2026-06-14 - Deferring Pathlib Operations in Hot Paths
**Learning:** In highly repetitive loops like file scanners (e.g., iterating through thousands of safe files), preemptively calculating `Path.relative_to()` and sanitizing strings adds significant cumulative overhead. Pathlib operations internally parse paths, check parts, and construct new objects, which is extremely expensive when executed on a per-file basis unconditionally.
**Action:** Always defer expensive path computations (like converting paths to relative or string sanitization) until *after* the fast-path condition (like a regex match) triggers. This drastically cuts down on unnecessary string operations for clean files.

## 2024-06-16 - Optimizing tight loops with pre-extracted tuples
**Learning:** In python tight loops, checking `rule["search"](line)` on a list of dictionaries requires a dictionary lookup (`__getitem__`) on every iteration which introduces unnecessary overhead. While `tuple(applicable_rules)` is fast to iterate, we can extract the function and avoid dict lookup.
**Action:** In `_scan_file`, cache `applicable_rules` as a tuple of `(search_function, rule_dict)` tuples and use tuple indexing (`tup[0](line)`) to invoke the function. Also, `open(file_path)` is marginally faster than `file_path.open()`.
9 changes: 0 additions & 9 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,3 @@
**Vulnerability:** The CLI file scanner `vibesec scan` lacked detection rules for fundamental security vulnerabilities in JavaScript/TypeScript ecosystems, specifically arbitrary code execution via `eval()` and Cross-Site Scripting (XSS) via React's `dangerouslySetInnerHTML`.
**Learning:** Even specialized "vibe-coding" static analysis tools must include detection for standard, catastrophic security anti-patterns (like eval and XSS injection vectors) to provide complete coverage.
**Prevention:** Two new rules, `dangerous-eval` and `react-dangerously-set-inner-html`, were added to `SCAN_RULES` to flag these patterns.
## 2025-02-12 - Prevent ReDoS by avoiding capturing groups in scanner rules
**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.
94 changes: 57 additions & 37 deletions scanner/cli/vibesec.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
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 @@ -117,16 +116,16 @@
SCAN_RULES = [
{
"id": "hardcoded-stripe-secret",
"pattern": re.compile(r'sk_(?:live|test)_[A-Za-z0-9]{24,}'),
"pattern": re.compile(r'sk_(live|test)_[A-Za-z0-9]{24,}'),
"severity": "CRITICAL",
"message": "Hardcoded Stripe secret key detected. Rotate this key immediately. [OWASP A07:2021 - Identification and Authentication Failures]",
"message": "Hardcoded Stripe secret key detected. Rotate this key immediately.",
"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. [OWASP A07:2021 - Identification and Authentication Failures]",
"message": "Possible hardcoded OpenAI API key detected.",
"extensions": None,
},
{
Expand All @@ -136,62 +135,71 @@
re.IGNORECASE,
),
"severity": "CRITICAL",
"message": "Secret environment variable uses NEXT_PUBLIC_ prefix — this exposes it to the browser bundle. [OWASP A05:2021 - Security Misconfiguration]",
"message": (
"Secret environment variable uses NEXT_PUBLIC_ prefix — "
"this exposes it to the browser bundle."
),
"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. [OWASP A05:2021 - Security Misconfiguration]",
"message": "Supabase service role key exposed to the client via NEXT_PUBLIC_ prefix.",
"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'),
"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. [OWASP A01:2021 - Broken Access Control]",
"message": (
"Firebase/Firestore rule allows unrestricted read/write access. "
"Add authentication and ownership checks."
),
"extensions": [".rules"],
},
{
"id": "todo-skip-auth",
"pattern": re.compile(
r'(?i)(?:todo|fixme|hack|temp)[^\n]{0,50}(?:auth|security|permission|check|protect)',
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. [OWASP A01:2021 - Broken Access Control]",
"message": (
"Comment suggests auth/security check was deferred. "
"Verify this is not deployed to production."
),
"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. [OWASP A05:2021 - Security Misconfiguration]",
"message": "CORS set to allow all origins (*). Restrict to known domains.",
"extensions": [".ts", ".tsx", ".js", ".jsx", ".py"],
},
{
"id": "hardcoded-database-url",
"pattern": re.compile(
r'(?i)(?:DATABASE_URL|POSTGRES_URL)\s*[=:]\s*["\x27](?:postgres|postgresql|mysql)://\S+',
r'(?i)(DATABASE_URL|POSTGRES_URL)\s*[=:]\s*["\x27](postgres|postgresql|mysql)://\S+',
),
"severity": "CRITICAL",
"message": "Hardcoded database connection string detected. [OWASP A07:2021 - Identification and Authentication Failures]",
"message": "Hardcoded database connection string detected.",
"extensions": None,
},
{
"id": "hardcoded-jwt-secret",
"pattern": re.compile(
r'(?i)(?:JWT_SECRET|NEXTAUTH_SECRET)\s*[=:]\s*["\x27][^"\x27\s]{8,}["\x27]',
r'(?i)(JWT_SECRET|NEXTAUTH_SECRET)\s*[=:]\s*["\x27][^"\x27\s]{8,}["\x27]',
),
"severity": "CRITICAL",
"message": "Hardcoded JWT/NextAuth secret detected. [OWASP A02:2021 - Cryptographic Failures]",
"message": "Hardcoded JWT/NextAuth secret detected.",
"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. [OWASP A08:2021 - Software and Data Integrity Failures]",
"message": "Stripe constructEvent called with empty/undefined webhook secret.",
"extensions": [".ts", ".tsx", ".js", ".jsx"],
},
{
Expand All @@ -200,21 +208,21 @@
r'const\s+(?:session|user)\s*=\s*\{\s*(?:user\s*:\s*)?\{\s*id\s*:\s*["\x27]',
),
"severity": "HIGH",
"message": "Mock or hardcoded session/user object detected in route handler. [OWASP A01:2021 - Broken Access Control]",
"message": "Hardcoded mock session/user in what may be a production handler. Verify this is test-only code.",
"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. [OWASP A03:2021 - Injection]",
"message": "Use of eval() detected. This is a critical risk for arbitrary code execution and injection attacks.",
"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. [OWASP A03:2021 - Injection]",
"message": "Use of dangerouslySetInnerHTML detected. This can lead to Cross-Site Scripting (XSS) if input is not sanitized.",
"extensions": [".jsx", ".tsx"],
},
{
Expand All @@ -223,7 +231,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. [OWASP A03:2021 - Injection]",
"message": "Potential SQL injection detected: string concatenation or template literal in database query.",
"extensions": [".ts", ".tsx", ".js", ".jsx", ".py"],
},
]
Expand Down Expand Up @@ -440,9 +448,21 @@ def cmd_hook(args):
hooks_dir.mkdir(parents=True, exist_ok=True)
pre_commit_file = hooks_dir / "pre-commit"

# SECURITY: Prevent Arbitrary File Write via symlink — reject if the
# pre-commit path is a symlink pointing outside the project root.
if pre_commit_file.is_symlink():
symlink_target = pre_commit_file.resolve()
if not symlink_target.is_relative_to(project_root):
print(f"Error: {pre_commit_file} is a symlink pointing outside the project root. Aborting.", file=sys.stderr)
return 1
pre_commit_file.unlink()

# SECURITY: Validate the resolved path of the hook file itself is within
# the project root before writing, guarding against any TOCTOU races.
if pre_commit_file.exists() and not pre_commit_file.resolve().is_relative_to(project_root):
print(f"Error: Target path {pre_commit_file} escapes the project root. Aborting.", file=sys.stderr)
return 1

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

Expand Down Expand Up @@ -480,16 +500,21 @@ def _get_applicable_rules(ext: str):
_LAST_SCAN_RULES_ID = current_id

if ext not in _RULES_CACHE:
_RULES_CACHE[ext] = [
{
"id": rule["id"],
"severity": rule["severity"],
"message": rule["message"],
"search": rule["pattern"].search
}
# ⚡ Bolt: Cache as a tuple of (search_fn, rule_meta) pairs.
# Pre-extracting the .search method avoids a dictionary key lookup
# on every line during the tight scan loop.
_RULES_CACHE[ext] = tuple(
(
rule["pattern"].search,
{
"id": rule["id"],
"severity": rule["severity"],
"message": rule["message"],
}
)
for rule in SCAN_RULES
if not rule["extensions"] or ext in rule["extensions"]
]
)
return _RULES_CACHE[ext]


Expand Down Expand Up @@ -564,9 +589,10 @@ def _scan_file(file_path: Path, base_path: Path):
try:
with file_path.open("r", encoding="utf-8", errors="ignore") as f:
for line_num, line in enumerate(f, start=1):
for rule in applicable_rules:
match = rule["search"](line)
if match:
# ⚡ Bolt: Unpack each (search_fn, rule) tuple; search_fn is the
# pre-extracted regex .search method, avoiding a dict lookup per line.
for search_fn, rule in applicable_rules:
if search_fn(line):
if rel_path_str is None:
rel_path = file_path.relative_to(base_path) if base_path.is_dir() else file_path
rel_path_str = _sanitize_terminal_output(str(rel_path))
Expand Down Expand Up @@ -701,10 +727,6 @@ 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 @@ -713,8 +735,6 @@ 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
Empty file removed tests/scripts/ci/__init__.py
Empty file.
46 changes: 0 additions & 46 deletions tests/scripts/ci/test_opencode_review_normalize_output.py

This file was deleted.

19 changes: 19 additions & 0 deletions tests/scripts/ci/test_pr_review_merge_scheduler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pytest
from scripts.ci.pr_review_merge_scheduler import split_repo

def test_split_repo_valid():
assert split_repo("owner/name") == ("owner", "name")
assert split_repo("owner/name/extra") == ("owner", "name/extra")

def test_split_repo_invalid():
with pytest.raises(ValueError, match="repo must be owner/name, got 'owner'"):
split_repo("owner")

with pytest.raises(ValueError, match="repo must be owner/name, got '/name'"):
split_repo("/name")

with pytest.raises(ValueError, match="repo must be owner/name, got 'owner/'"):
split_repo("owner/")

with pytest.raises(ValueError, match="repo must be owner/name, got '/'"):
split_repo("/")
Loading