rootscan — Logic-Driven Bug Hunting Mode
Don't wait for bugs to bite. Find them before they escape to production. When this skill activates, run 2-phase scan: Phase 1 (pattern detection) finds known anti-patterns, Phase 2 (logic reasoning) finds structural defects that only appear when understanding code flow.
Activation
Explicit: /rootscan
Auto-triggers (when these phrases appear in user message):
- "버그 찾아줘", "버그 있는지 확인"
- "취약점 찾아", "보안 검사"
- "코드 검사", "잠재적 버그"
- "성능 문제", "메모리 누수"
- "find bugs", "scan bugs", "vulnerability scan"
What's Different (vs general code review)
| General Review | rootscan Mode |
|---|---|
| Read code + spot issues | 2-phase: pattern scan (fast) + logic reasoning (deep) |
| Subjective "looks OK" | Severity-rated findings (🔴 HIGH / 🟡 MEDIUM / 🟢 LOW) |
| Mixed findings | Categorized report (Logic / Security / Performance / Type) |
| Text feedback | Actionable items (file:line + trace + fix suggestion) |
| One-pass read | Multi-pass: pattern → logic → cross-file → report |
| Finds known bugs | Finds known bugs + structural defects + design flaws |
4-Category Bug Hunt
Category 1: Logic Bugs 🧩
What to find: Edge cases, race conditions, null/undefined handling gaps, off-by-one errors, async coordination issues.
Patterns to detect:
- Null/Undefined:
obj.propwithoutobj?.propor null check, array access without bounds check - Edge Cases: loop with
i < arr.lengthbutarr[i+1]access, division without zero check, string operations without empty check - Race Conditions: multiple
asyncio.create_taskorPromise.allwith shared state mutation, no lock/event for concurrent access - Async Coordination:
awaitinside loop without considering parallelism, missing error handling in async tasks, dangling promises - Off-by-One:
range(len(arr))thenarr[i+1],slice(0, n)logic errors - Boolean Logic: complex
ifconditions with mixedand/or, De Morgan's law violations, redundant checks
Detection method:
- Grep for risky patterns:
\.\w+\(without null check,arr\[.*\+.*\],asyncio.create_task,Promise.all - Read functions with >3 branches or >2 levels of nesting
- Check all async functions for error handling + coordination
Severity rating:
- 🔴 HIGH: Null dereference in hot path, race on shared state, async coordination gap
- 🟡 MEDIUM: Missing edge case handling, off-by-one in non-critical path
- 🟢 LOW: Redundant checks, minor boolean logic simplification
Category 2: Security Vulnerabilities 🔒
What to find: SQL injection, XSS, command injection, path traversal, authentication bypass, authorization gaps, secrets in code.
Patterns to detect:
- Injection:
- SQL: string concatenation in queries (
f"SELECT * FROM {table}"or"SELECT * FROM " + table) - Command:
os.system(user_input),subprocess.callwith shell=True + user input - Path Traversal:
open(user_path)without validation,../not blocked - XSS: HTML output with user input without escaping (
innerHTML = userInput)
- SQL: string concatenation in queries (
- Authentication/Authorization:
- Missing auth check: endpoints without
@require_authdecorator or similar - JWT: hardcoded secrets, no expiration check, no signature verification
- Session: session ID in URL, no CSRF token
- Missing auth check: endpoints without
- Secrets:
- Hardcoded:
password = "...",API_KEY = "...",token = "..." - Logged:
logger.info(f"password: {pw}"), sensitive data in error messages
- Hardcoded:
- Cryptography:
- Weak hash: MD5, SHA1 for passwords (instead of bcrypt/argon2)
- ECB mode, hardcoded IV/salt
Detection method:
- Grep for injection patterns:
f".*SELECT.*{,os.system,subprocess.*shell=True,innerHTML - Grep for secrets:
password\s*=\s*["'],api_key\s*=,token\s*=,secret\s*= - Check all route handlers for auth decorators
- Check crypto usage:
hashlib.md5,Cipher.*MODE_ECB
Severity rating:
- 🔴 HIGH: SQL injection, command injection, hardcoded secrets in production code, auth bypass
- 🟡 MEDIUM: Path traversal without validation, weak hash, missing CSRF
- 🟢 LOW: Secrets in test fixtures, verbose error messages
Category 3: Performance Issues ⚡
What to find: N+1 queries, memory leaks, infinite loops, O(n²) algorithms, redundant API calls, unindexed DB queries.
Patterns to detect:
- N+1 Query:
- Loop with DB query inside:
for item in items: db.query(...) - ORM lazy load in loop:
for user in users: user.profile.name
- Loop with DB query inside:
- Memory Leaks:
- Unclosed resources:
open(file)withoutwith,requests.getwithout.close() - Event listeners without cleanup:
addEventListenerwithoutremoveEventListener - Growing cache without eviction: unbounded dict/map used as cache
- Unclosed resources:
- Algorithmic:
- Nested loops on same data:
for i in arr: for j in arr: - Linear search in loop:
for item in big_list: if item == x - Repeated sorting:
sorted(arr)called multiple times on same data
- Nested loops on same data:
- Redundant Work:
- Same API call in loop:
for id in ids: api.get(id) - Repeated computation: same calculation in loop without memoization
- Same API call in loop:
Detection method:
- Grep for N+1:
for.*in.*:.*\.(query|get|fetch) - Grep for unclosed:
open\(not inwith,requests\.(get|post)without context manager - Read nested loops (>2 levels)
- Check functions with >50 lines for repeated patterns
Severity rating:
- 🔴 HIGH: N+1 query in production hot path, memory leak in long-running service
- 🟡 MEDIUM: O(n²) algorithm on medium-size data, unclosed file handle
- 🟢 LOW: Minor redundant computation, non-critical repeated API call
Category 4: Type Safety Holes 🏷️
What to find: any type abuse, type assertion without validation, runtime type mismatch, missing null checks in typed code.
Patterns to detect (TypeScript/Python typed context):
- Type Escape Hatches:
- TypeScript:
as any,@ts-ignore,// @ts-expect-errorwithout explanation - Python:
cast()without runtime check,# type: ignorewithout reason
- TypeScript:
- Runtime Mismatch:
- JSON parsing without validation:
JSON.parse(data)then direct property access - API response without schema check:
response.data.fieldwithout validating structure - User input cast:
int(user_input),Number(input)without try-catch
- JSON parsing without validation:
- Null Unsafety (in strict-null-check context):
- Non-null assertion without check:
obj!.prop,obj.prop!in TypeScript - Optional chaining overuse masking real issue:
a?.b?.c?.d?.e(design smell)
- Non-null assertion without check:
- Type Declaration Drift:
- Interface/type definition doesn't match actual usage
- Function signature says
Xbut returnsX | nullin some paths
Detection method:
- Grep for escape hatches:
as any,@ts-ignore,cast\(,# type: ignore - Grep for non-null assertions:
!\\.,\\..*! - Check JSON.parse call sites for validation
- Run type checker and check for
anyin inferred types
Severity rating:
- 🔴 HIGH:
as anyin production code bypassing critical type check, JSON parse without validation in API handler - 🟡 MEDIUM:
@ts-ignorewithout explanation, missing null check in typed code - 🟢 LOW: Type declaration comment out of sync, harmless
anyin test code
6-Step Scan Workflow
Step 1: Scope Definition
- What to scan: user specifies file/directory/entire project
- Focus area: user can request specific category (e.g., "security only") or all 4
- Depth:
- shallow = entry points only (API handlers, main functions) + pattern scan
- deep = all files + pattern scan + logic reasoning
Ask user if not clear:
Scanning scope:
- Files: [which files/directories?]
- Categories: [all 4, or specific ones?]
- Depth: [shallow = pattern scan only, deep = pattern + logic reasoning]
Default: If user says "버그 찾아줘" without specifying, use deep scan on current directory.
Step 2a: Pattern Detection (Phase 1 - Fast)
Purpose: Find known anti-patterns via automated checks.
For each category, run the detection method:
- Grep for known patterns
- Read flagged files
- Cross-reference with checklist
Output format per finding:
[Category] Severity: Issue Title
File: path/to/file.py:42
Pattern: [pattern name]
Code:
41 | def process(data):
42 | result = data.field # ← no null check
43 | return result
Why risky: [explanation]
Fix suggestion: [actionable fix]
Time estimate: ~30 seconds per 1000 LOC
Step 2b: Logic Reasoning (Phase 2 - Deep)
Purpose: Find bugs that pattern matching can't catch — structural defects, design flaws, edge cases that only appear when understanding code flow.
When to run: Only when depth = deep. Skip if shallow.
7-Point Logic Reasoning Checklist
For each critical function (API handlers, core business logic, async coordinators):
1. State Flow Tracing
Question: "If I trace all possible state transitions, where does it break?"
Method:
- Identify all mutable state (class vars, DB fields, cache keys, async events)
- Trace state changes through function calls
- Look for paths where state becomes inconsistent
Example findings:
🔴 HIGH: State inconsistency in order processing
File: services/order.py:67
Logic: order.status = 'paid' but order.payment_id is None
Why risky: Later code assumes payment_id exists when status='paid'
Trace:
- Line 45: status set to 'paid'
- Line 52: payment_id set only if payment_method == 'card'
- Line 67: returns order (payment_id may be None)
Fix: Set payment_id before setting status, or add validation
2. Auth/Permission Boundary Check
Question: "Who can access what, and is it enforced everywhere?"
Method:
- Map all resources (DB rows, files, API data)
- Check if access control is consistent across read/write/delete
- Look for "owner checks" that only check user_id but not resource_id
Example findings:
🔴 HIGH: Authorization bypass in analysis results
File: api/analysis.py:34
Logic: Only checks user_id match, but analysis can be shared
Why risky: User A shares analysis with User B → both have same user_id in sharing table
→ User C can read if they guess analysis_id
Boundary: Should check (user_id OR analysis_id in user's shared list)
Fix: Add analysis ownership/sharing check in middleware
3. Edge Case Enumeration
Question: "What inputs/states would make this function do something unexpected?"
Method:
- For each function, enumerate input dimensions: empty, zero, null, negative, very large, concurrent
- Check if each edge is handled
Example findings:
🟡 MEDIUM: Unhandled empty list in batch processor
File: workers/batch.py:23
Logic: Assumes items list is non-empty
Edge cases:
- items = [] → range(len(items)) = range(0) → loop never runs
- Line 45: result[0] access → IndexError
Why risky: Batch queue can send empty list during backpressure
Fix: Add early return if not items
4. Async Coordination Gaps
Question: "What happens if two async tasks run this code at the same time?"
Method:
- Find all
asyncio.create_task/Promise.all/ background jobs - Check shared resources (DB, cache, file, state)
- Look for read-modify-write without locks
Example findings:
🔴 HIGH: Race condition in token refresh
File: auth/token.py:56
Logic:
T=0: Task A reads token (expires_at = T+5)
T=1: Task B reads token (expires_at = T+5)
T=2: Task A refreshes → new token written
T=3: Task B refreshes → overwrites A's token with older one
Why risky: Token becomes invalid, all requests fail
Fix: Use lock or atomic compare-and-swap
5. Error Propagation Analysis
Question: "If step 3 fails, does step 5 know about it?"
Method:
- Trace error paths (try-except, return None, error callbacks)
- Check if errors propagate or get silently swallowed
- Look for "optimistic" code that assumes success
Example findings:
🟡 MEDIUM: Silent failure in notification sender
File: notifications/email.py:78
Logic:
- send_email() catches all exceptions, logs, returns None
- Caller doesn't check return value
- User thinks notification sent, but it failed
Why risky: Users miss critical alerts (payment failed, account locked)
Fix: Return success/failure status, or raise exception
6. Data Schema Drift Detection
Question: "Does code assume a schema that may not be true?"
Method:
- Check API response handling: does code assume fields exist?
- Check DB queries: does code assume row shape matches model?
- Look for dict access
data['field']without checking key existence
Example findings:
🔴 HIGH: Missing field handling in external API response
File: integrations/payment.py:45
Logic: response.data.transaction_id accessed directly
Schema risk:
- External API docs say transaction_id is optional
- Code assumes it always exists
- Error: KeyError when payment pending (no transaction_id yet)
Why risky: Breaks checkout flow
Fix: Use response.data.get('transaction_id') with fallback
7. Implicit Assumptions Hunt
Question: "What does this code assume that might not be true?"
Method:
- Read function comments/docstrings for assumptions
- Check "obvious" behavior that's not validated
- Look for magic constants without explanation
Example findings:
🟡 MEDIUM: Unvalidated assumption about list order
File: ranking/scorer.py:34
Logic: Assumes items are sorted by score descending
Assumption: Caller sorts before passing
Risk: If caller doesn't sort, top 10 may not be actual top 10
Evidence: No sorting in this function, no assertion
Fix: Add assert items == sorted(items, key=...) or sort here
When to flag as finding vs. when to skip
Flag if:
- Assumption can be violated in real usage
- Failure mode is data loss / security breach / user-facing error
- Code in critical path (auth, payment, core business logic)
Skip if:
- Assumption is guaranteed by framework/library contract
- Only breaks in test scenarios
- Already covered by existing validation layer
Step 3: Severity Triage
Group findings by severity:
- 🔴 HIGH: Must fix before merge (blocks PR)
- 🟡 MEDIUM: Should fix soon (follow-up ticket)
- 🟢 LOW: Optional improvement (backlog)
Step 4: Cross-File Analysis
Some bugs only appear when reading multiple files together:
- Inconsistent Error Handling: one file throws, another returns null for same error
- Shared State Race: file A writes
_cache[key], file B reads it without lock - API Contract Drift: caller expects
{status, data}, but handler returns{ok, result}
Method:
- Identify shared state (global vars, class attributes, DB tables)
- Trace access patterns across files
- Flag inconsistencies
This step is enhanced by Step 2b logic reasoning — cross-file issues are often caught during state flow tracing and async coordination checks.
Step 5: Report Generation
Summary:
🔴 HIGH: N findings (must fix)
🟡 MEDIUM: M findings (should fix)
🟢 LOW: L findings (optional)
Top 3 risks:
1. [most critical finding]
2. [second most critical]
3. [third most critical]
Detailed Findings (grouped by category → severity):
## 🧩 Logic Bugs
### 🔴 HIGH
- [finding 1]
- [finding 2]
### 🟡 MEDIUM
- [finding 3]
## 🔒 Security Vulnerabilities
### 🔴 HIGH
- [finding 4]
...
Actionable Next Steps:
Immediate (before merge):
1. Fix [file:line] - [issue]
2. Fix [file:line] - [issue]
Follow-up (next sprint):
1. Add [test/validation] for [pattern]
2. Refactor [area] to eliminate [risk]
Optional (backlog):
1. Consider [improvement]
Output Format
[rootscan — <scope>]
Scan Summary:
- Scope: [files/directories]
- Categories: [Logic / Security / Performance / Type]
- Depth: [shallow = pattern only / deep = pattern + logic reasoning]
- Phase 1 (Pattern): N findings
- Phase 2 (Logic): M findings (only if deep scan)
- Total Findings: 🔴 X HIGH / 🟡 Y MEDIUM / 🟢 Z LOW
Top 3 Risks:
1. [most critical]
2. [second most critical]
3. [third most critical]
---
## 🧩 Logic Bugs
### 🔴 HIGH
[findings...]
### 🟡 MEDIUM
[findings...]
### 🟢 LOW
[findings...]
---
## 🔒 Security Vulnerabilities
[same structure...]
---
## ⚡ Performance Issues
[same structure...]
---
## 🏷️ Type Safety Holes
[same structure...]
---
## Next Actions
🚨 Immediate (before merge):
1. [action]
2. [action]
📋 Follow-up (next sprint):
1. [action]
💡 Optional (backlog):
1. [action]
---
Confidence: HIGH | MEDIUM | LOW
Reason: [why this confidence level]
Termination Conditions
Scan complete → report delivered when ALL of:
- Scope clearly defined (files + categories + depth)
- Phase 1: Pattern detection run for all requested categories
- Phase 2: Logic reasoning performed (only if depth = deep)
- State flow traced for critical functions
- Auth/permission boundaries checked
- Edge cases enumerated
- Async coordination verified
- Error propagation analyzed
- Data schema drift checked
- Implicit assumptions hunted
- Each finding has: file:line + severity + fix suggestion (+ trace if from Phase 2)
- Findings grouped by category → severity → phase
- Cross-file analysis performed (for deep scan)
- Top 3 risks identified
- Next actions classified (Immediate / Follow-up / Optional)
- Confidence declared (HIGH/MEDIUM/LOW + reason)
Any unchecked item → scan incomplete.
Confidence rating guide:
- HIGH: Deep scan completed, all 7 logic checks passed, cross-file analysis done
- MEDIUM: Pattern scan completed, partial logic reasoning (some checks skipped due to code complexity)
- LOW: Shallow scan only, or deep scan blocked by missing context (unclear requirements, external dependencies)
Anti-Patterns (forbidden in this mode)
- "Looks OK to me" — skipping systematic checklist, relying on intuition
- Pattern-only when user wants deep — skipping Phase 2 logic reasoning when depth = deep
- Unfocused scan — reading code without specific pattern/logic check in mind
- No severity — listing findings without priority (user can't triage)
- Vague fix — "improve error handling" instead of "add try-catch at line 42"
- False negatives — missing patterns because grep wasn't run, or missing logic flaws because trace wasn't done
- Category mixing — security + performance in one finding (separate them)
- Claiming HIGH confidence without Phase 2 — can't be HIGH if you skipped logic reasoning
Notes
- Derived from real-world code review sessions where critical bugs were missed because review was unfocused or pattern-only.
- Phase 2 logic reasoning is the differentiator — finds bugs that static analyzers can't catch.
- Pairs with rootfix: scan → find issue → rootfix → fix root cause.
- Works in any language. Adjust patterns per language (e.g., Python vs TypeScript vs Go).
- Can be run as pre-commit hook (shallow), PR gate (deep), or periodic audit (deep).
- Time estimate: shallow ~1 min per 1000 LOC, deep ~5 min per 1000 LOC (depends on complexity).
Example Usage
User: "버그 찾아줘 이 API 핸들러에"
Agent (rootscan activated):
[rootscan — api/handlers/]
Scan Summary:
- Scope: api/handlers/*.py
- Categories: All (Logic, Security, Performance, Type)
- Depth: Deep (pattern + logic reasoning)
- Phase 1 (Pattern): 3 findings
- Phase 2 (Logic): 2 findings
- Total Findings: 🔴 3 HIGH / 🟡 2 MEDIUM / 🟢 1 LOW
Top 3 Risks:
1. Authorization bypass in analysis results (logic reasoning - no pattern match)
2. SQL injection in user_search() - no parameterized query
3. Race condition in async cache update - no lock
---
## 🧩 Logic Bugs
### 🔴 HIGH (Phase 1 - Pattern)
**Race condition in cache update**
File: api/cache.py:34
Pattern: Shared state mutation without lock
Code:
33 | async def update_cache(key, value):
34 | _cache[key] = value # ← no lock, multiple tasks can race
35 | await notify_subscribers(key)
Why risky: Multiple concurrent calls can interleave, leading to lost updates
Fix suggestion: Use asyncio.Lock() or single-writer pattern
### 🟡 MEDIUM (Phase 1 - Pattern)
**Missing null check in profile access**
File: api/user.py:67
Pattern: Null dereference
Code:
66 | user = get_user(id)
67 | return user.profile.bio # ← user could be None
Why risky: If user not found, AttributeError crashes handler
Fix suggestion: Add `if user is None: return 404` before access
---
## 🔒 Security Vulnerabilities
### 🔴 HIGH (Phase 1 - Pattern)
**SQL injection in search**
File: api/search.py:23
Pattern: String concatenation in SQL
Code:
22 | query = request.args.get('q')
23 | sql = f"SELECT * FROM users WHERE name LIKE '%{query}%'" # ← injection!
24 | results = db.execute(sql)
Why risky: Attacker can inject `'; DROP TABLE users; --`
Fix suggestion: Use parameterized query: `db.execute("... WHERE name LIKE ?", (f"%{query}%",))`
### 🔴 HIGH (Phase 2 - Logic Reasoning)
**Authorization bypass in analysis results**
File: api/analysis.py:34
Logic Check: Auth/Permission Boundary
Code:
33 | def get_analysis(analysis_id):
34 | analysis = db.query(Analysis).filter_by(id=analysis_id).first()
35 | if analysis.user_id == current_user.id:
36 | return analysis
Why risky: Only checks user_id match, but analysis can be shared
Trace:
- User A creates analysis (owner)
- User A shares with User B via sharing_table
- User C guesses analysis_id → Line 34 succeeds
- Line 35 fails (not owner) → but C already saw analysis in line 34 query
Boundary violation: Should check (user_id == current OR analysis_id in shared_with_user)
Fix suggestion: Add sharing check in query filter or middleware
---
## ⚡ Performance Issues
### 🟡 MEDIUM (Phase 1 - Pattern)
**N+1 query in user list**
File: api/users.py:45
Pattern: DB query inside loop
Code:
44 | for user in users:
45 | user.profile = db.query(Profile).filter_by(user_id=user.id).first() # ← N queries
Why risky: 1000 users = 1000 DB queries, scales poorly
Fix suggestion: Use `joinedload(User.profile)` to fetch in one query
---
## 🏷️ Type Safety Holes
### 🟢 LOW (Phase 1 - Pattern)
**Type ignore without reason**
File: api/utils.py:12
Pattern: Type escape hatch
Code:
11 | def parse(data):
12 | return json.loads(data) # type: ignore
Why risky: Bypasses type checker, actual issue could be signature mismatch
Fix suggestion: Add proper return type annotation or explain why ignore is needed
---
## Next Actions
🚨 Immediate (before merge):
1. Fix authorization bypass in api/analysis.py:34 [rootfix]
2. Fix SQL injection in api/search.py:23 [rootfix]
3. Fix race condition in api/cache.py:34 [rootfix]
📋 Follow-up (next sprint):
1. Add integration test for concurrent cache updates [rootbuild]
2. Add sharing matrix test for analysis permissions [rootbuild]
3. Refactor user list to use joinedload [rootclean]
💡 Optional (backlog):
1. Add type validation for JSON.parse calls [rootclean]
---
Confidence: HIGH
Reason: Deep scan completed - all 4 categories + 7 logic checks performed. Phase 2 found authorization bypass that pattern matching missed.