Add code-audit skill for systematic anti-pattern detection
Project-specific skill to audit codebase for: - Error handling: silent failures, swallowed exceptions, hidden errors - Security: missing auth, data exposure, injection risks - Architecture: DI violations, import boundaries, coupling Pattern definitions in YAML with verdict hints (ISSUE_IF/OK_IF) for context-aware analysis. Tailored to Mantimon TCG architecture patterns (core independence, stateless backend, repository pattern). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
55e02ceb21
commit
1346853869
148
.claude/skills/code-audit/SKILL.md
Normal file
148
.claude/skills/code-audit/SKILL.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Code Audit Skill
|
||||||
|
|
||||||
|
Perform systematic code audits to find anti-patterns, hidden errors, security issues, and architecture violations.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/code-audit [category] [path]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Categories:**
|
||||||
|
- `errors` - Silent failures, swallowed exceptions, hidden errors
|
||||||
|
- `security` - Input validation, auth checks, data exposure
|
||||||
|
- `architecture` - DI violations, import boundaries, coupling
|
||||||
|
- `all` - Run all audit categories (default)
|
||||||
|
|
||||||
|
**Path:** Optional path to audit (defaults to `backend/app/`)
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/code-audit # Full audit of backend/app/
|
||||||
|
/code-audit errors # Just error handling patterns
|
||||||
|
/code-audit security services/ # Security audit of services/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
When this skill is invoked:
|
||||||
|
|
||||||
|
1. **Load Pattern Definitions**
|
||||||
|
Read the YAML files in `patterns/` directory for the requested category.
|
||||||
|
|
||||||
|
2. **Search Codebase**
|
||||||
|
For each pattern, use Grep with the specified regex to find matches.
|
||||||
|
Focus on production code (skip test files unless pattern specifies otherwise).
|
||||||
|
|
||||||
|
3. **Analyze Context**
|
||||||
|
For each match, read surrounding code to determine verdict:
|
||||||
|
- **ISSUE**: Code should be changed (explain why)
|
||||||
|
- **WARNING**: Potential problem, needs review
|
||||||
|
- **OK**: Pattern detected but implementation is correct (explain why)
|
||||||
|
|
||||||
|
4. **Generate Report**
|
||||||
|
Output structured report with:
|
||||||
|
- Summary stats (issues/warnings/ok by category)
|
||||||
|
- Detailed findings with file:line references
|
||||||
|
- Recommendations for each issue
|
||||||
|
|
||||||
|
## Verdict Guidelines
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
**ISSUE** when:
|
||||||
|
- Exception caught, logged, but execution continues with corrupted/partial state
|
||||||
|
- `None` returned for error condition without documentation
|
||||||
|
- Error logged but caller has no way to detect failure
|
||||||
|
|
||||||
|
**OK** when:
|
||||||
|
- Error logged AND exception re-raised with context
|
||||||
|
- Method returns explicit failure indicator (False, Result type) that caller checks
|
||||||
|
- Graceful degradation is intentional and documented
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
**ISSUE** when:
|
||||||
|
- User input used directly without validation
|
||||||
|
- Auth check missing on endpoint that modifies data
|
||||||
|
- Sensitive data (passwords, tokens) logged or exposed in errors
|
||||||
|
|
||||||
|
**OK** when:
|
||||||
|
- Input validated at API boundary before reaching service
|
||||||
|
- Auth handled by dependency injection (get_current_user)
|
||||||
|
- Sensitive fields excluded from serialization
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
**ISSUE** when:
|
||||||
|
- `app/core/` imports from `app/services/` or `app/api/`
|
||||||
|
- Service directly accesses ORM models instead of repository
|
||||||
|
- Global state modified without DI support for testing
|
||||||
|
|
||||||
|
**OK** when:
|
||||||
|
- Import is type-only (`if TYPE_CHECKING:`)
|
||||||
|
- Cross-layer access is through injected dependency
|
||||||
|
- Global has DI override capability in constructor
|
||||||
|
|
||||||
|
## Pattern File Format
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Category Name
|
||||||
|
description: What this category audits
|
||||||
|
|
||||||
|
patterns:
|
||||||
|
- id: unique-pattern-id
|
||||||
|
description: Human-readable description
|
||||||
|
grep_pattern: 'regex pattern for Grep tool'
|
||||||
|
file_glob: '*.py' # Optional, defaults to *.py
|
||||||
|
exclude_tests: true # Optional, defaults to true
|
||||||
|
multiline: false # Optional, for cross-line patterns
|
||||||
|
context_lines: 3 # Lines to read around match
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "condition that makes this an issue"
|
||||||
|
OK_IF: "condition that makes this acceptable"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Code Audit Report
|
||||||
|
|
||||||
|
**Scope:** backend/app/
|
||||||
|
**Categories:** errors, security, architecture
|
||||||
|
**Date:** YYYY-MM-DD
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Category | Issues | Warnings | OK |
|
||||||
|
|----------|--------|----------|-----|
|
||||||
|
| Errors | 2 | 1 | 5 |
|
||||||
|
| Security | 0 | 2 | 3 |
|
||||||
|
| Architecture | 1 | 0 | 4 |
|
||||||
|
|
||||||
|
## Issues (Fix Required)
|
||||||
|
|
||||||
|
### [ISSUE] error-hiding.log-and-continue
|
||||||
|
**File:** app/services/card_service.py:209
|
||||||
|
**Pattern:** Log and continue on error
|
||||||
|
|
||||||
|
```python
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load card: {e}")
|
||||||
|
return None # Caller silently gets None
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem:** Caller has no way to detect failure. Cards silently missing.
|
||||||
|
**Recommendation:** Raise exception or return Result type with error info.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Warnings (Review Needed)
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## OK (Verified Correct)
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
122
.claude/skills/code-audit/patterns/architecture.yaml
Normal file
122
.claude/skills/code-audit/patterns/architecture.yaml
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
# Architecture Anti-Patterns
|
||||||
|
# Patterns that violate Mantimon TCG architecture principles
|
||||||
|
|
||||||
|
name: Architecture
|
||||||
|
description: |
|
||||||
|
Detects violations of the project's architecture principles:
|
||||||
|
- Core engine independence (offline fork support)
|
||||||
|
- Dependency injection (no monkey patching)
|
||||||
|
- Stateless backend (config from request)
|
||||||
|
- Repository pattern (services don't touch ORM directly)
|
||||||
|
|
||||||
|
patterns:
|
||||||
|
# Core Independence Violations
|
||||||
|
- id: core-imports-services
|
||||||
|
description: Core module importing from services layer
|
||||||
|
grep_pattern: 'from app\.services'
|
||||||
|
file_glob: 'app/core/**/*.py'
|
||||||
|
context_lines: 2
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Core imports service - breaks offline fork capability"
|
||||||
|
OK_IF: "Never OK in core module"
|
||||||
|
|
||||||
|
- id: core-imports-api
|
||||||
|
description: Core module importing from API layer
|
||||||
|
grep_pattern: 'from app\.api'
|
||||||
|
file_glob: 'app/core/**/*.py'
|
||||||
|
context_lines: 2
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Core imports API - breaks offline fork capability"
|
||||||
|
OK_IF: "Never OK in core module"
|
||||||
|
|
||||||
|
- id: core-imports-db
|
||||||
|
description: Core module importing database dependencies
|
||||||
|
grep_pattern: 'from (app\.db|sqlalchemy)'
|
||||||
|
file_glob: 'app/core/**/*.py'
|
||||||
|
context_lines: 2
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Core has database dependency - breaks offline fork"
|
||||||
|
OK_IF: "Never OK in core module"
|
||||||
|
|
||||||
|
# Dependency Injection Violations
|
||||||
|
- id: service-global-in-method
|
||||||
|
description: Service accessing global inside method body
|
||||||
|
grep_pattern: '(get_db|get_session|get_redis)\(\)'
|
||||||
|
file_glob: 'app/services/**/*.py'
|
||||||
|
context_lines: 4
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Global accessed in method - should be injected via constructor"
|
||||||
|
OK_IF: "In factory function or module-level singleton creation"
|
||||||
|
|
||||||
|
- id: patch-in-test
|
||||||
|
description: Using patch/monkeypatch instead of DI
|
||||||
|
grep_pattern: '(patch|monkeypatch)\.(object|setattr)'
|
||||||
|
file_glob: 'tests/**/*.py'
|
||||||
|
exclude_tests: false
|
||||||
|
context_lines: 4
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Patching production code - use constructor injection"
|
||||||
|
OK_IF: "Patching external library; or testing legacy code marked for refactor"
|
||||||
|
|
||||||
|
- id: import-in-function
|
||||||
|
description: Import statement inside function body
|
||||||
|
grep_pattern: '^\s{4,}(from|import) '
|
||||||
|
context_lines: 3
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Late import to avoid circular dep - restructure modules"
|
||||||
|
OK_IF: "Conditional import for optional dependency; TYPE_CHECKING guard"
|
||||||
|
|
||||||
|
# Stateless Backend Violations
|
||||||
|
- id: config-in-constructor
|
||||||
|
description: RulesConfig or DeckConfig stored in service constructor
|
||||||
|
grep_pattern: 'def __init__.*:\n.*self\.\w*(config|rules)\s*='
|
||||||
|
multiline: true
|
||||||
|
file_glob: 'app/services/**/*.py'
|
||||||
|
context_lines: 5
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Config stored at construction - should come from request"
|
||||||
|
OK_IF: "Infrastructure config (timeouts, URLs) not game rules"
|
||||||
|
|
||||||
|
- id: default-config-creation
|
||||||
|
description: Creating default config instead of requiring it
|
||||||
|
grep_pattern: '(config|rules)\s*=\s*(config|rules)\s*or\s*(DeckConfig|RulesConfig)\(\)'
|
||||||
|
context_lines: 3
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Hidden default config - caller should provide explicitly"
|
||||||
|
OK_IF: "In test fixtures or documented convenience wrapper"
|
||||||
|
|
||||||
|
# Repository Pattern Violations
|
||||||
|
- id: service-direct-orm
|
||||||
|
description: Service directly using SQLAlchemy session
|
||||||
|
grep_pattern: '(db|session)\.(execute|add|delete|commit)'
|
||||||
|
file_glob: 'app/services/**/*.py'
|
||||||
|
context_lines: 4
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Service directly manipulates ORM - use repository"
|
||||||
|
OK_IF: "GameStateManager (special case); or transitional code"
|
||||||
|
|
||||||
|
- id: orm-model-in-service-return
|
||||||
|
description: Service returning ORM model instead of DTO
|
||||||
|
grep_pattern: 'return.*(User|Deck|Collection|ActiveGame)\b'
|
||||||
|
file_glob: 'app/services/**/*.py'
|
||||||
|
context_lines: 3
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "ORM model leaked to caller - return DTO/dataclass"
|
||||||
|
OK_IF: "Explicitly documented; or service in transition"
|
||||||
|
|
||||||
|
# Coupling Issues
|
||||||
|
- id: circular-import-guard
|
||||||
|
description: TYPE_CHECKING import guard (potential circular dep)
|
||||||
|
grep_pattern: 'if TYPE_CHECKING:'
|
||||||
|
context_lines: 5
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Indicates circular dependency - consider restructuring"
|
||||||
|
OK_IF: "Type hints for complex generics; documented design choice"
|
||||||
|
|
||||||
|
- id: god-class
|
||||||
|
description: Class with too many methods (>15)
|
||||||
|
grep_pattern: 'class \w+.*:'
|
||||||
|
context_lines: 0
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Class has >15 public methods - consider splitting"
|
||||||
|
OK_IF: "Cohesive responsibility; methods are related"
|
||||||
79
.claude/skills/code-audit/patterns/error-handling.yaml
Normal file
79
.claude/skills/code-audit/patterns/error-handling.yaml
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Error Handling Anti-Patterns
|
||||||
|
# Patterns that hide errors instead of surfacing them
|
||||||
|
|
||||||
|
name: Error Handling
|
||||||
|
description: |
|
||||||
|
Detects patterns where errors are silently swallowed, logged but ignored,
|
||||||
|
or hidden behind default return values. These patterns make debugging hard
|
||||||
|
and can lead to systems running with corrupted or partial state.
|
||||||
|
|
||||||
|
patterns:
|
||||||
|
- id: log-and-continue
|
||||||
|
description: Logging error then continuing with default/None
|
||||||
|
grep_pattern: 'logger\.(warning|error|exception).*\n.*return (None|\[\]|\{\}|0|False)'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 5
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Caller cannot detect failure; continues with partial data"
|
||||||
|
OK_IF: "Method documents None as valid return; caller handles it"
|
||||||
|
|
||||||
|
- id: catch-log-pass
|
||||||
|
description: Exception caught, logged, but silently swallowed
|
||||||
|
grep_pattern: 'except.*:\n\s*(pass|logger\.(warning|error))'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 5
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Exception swallowed without re-raise or failure indicator"
|
||||||
|
OK_IF: "Exception is expected case (e.g., cache miss) with proper handling"
|
||||||
|
|
||||||
|
- id: bare-except
|
||||||
|
description: Bare except clause catches everything including SystemExit
|
||||||
|
grep_pattern: 'except:\s*$'
|
||||||
|
context_lines: 3
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Catches KeyboardInterrupt, SystemExit - use Exception instead"
|
||||||
|
OK_IF: "Never OK - always specify exception type"
|
||||||
|
|
||||||
|
- id: broad-exception-catch
|
||||||
|
description: Catching Exception without re-raising
|
||||||
|
grep_pattern: 'except Exception.*:\n(?!.*raise)'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 7
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Broad catch without re-raise hides unexpected errors"
|
||||||
|
OK_IF: "Re-raises as domain exception with context preserved"
|
||||||
|
|
||||||
|
- id: silent-none-return
|
||||||
|
description: Returning None/empty on lookup failure without logging
|
||||||
|
grep_pattern: 'if .* is None:\n\s*return (None|\[\]|\{\})'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 4
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "None indicates error condition but no logging/metric"
|
||||||
|
OK_IF: "None is valid business case (e.g., optional field not set)"
|
||||||
|
|
||||||
|
- id: continue-on-error
|
||||||
|
description: Loop continues past errors without tracking failures
|
||||||
|
grep_pattern: 'except.*:\n.*continue'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 5
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Errors silently skipped; loop completes with partial results"
|
||||||
|
OK_IF: "Failures tracked and reported after loop; caller informed"
|
||||||
|
|
||||||
|
- id: default-on-keyerror
|
||||||
|
description: Using .get() with default to hide missing keys
|
||||||
|
grep_pattern: '\.get\([^,]+,\s*(None|\[\]|\{\}|0|"")\)'
|
||||||
|
context_lines: 2
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Missing key indicates data corruption; should raise"
|
||||||
|
OK_IF: "Key genuinely optional with documented default behavior"
|
||||||
|
|
||||||
|
- id: suppress-validation-error
|
||||||
|
description: Catching validation errors without surfacing to user
|
||||||
|
grep_pattern: 'except.*(ValidationError|ValueError|TypeError).*:\n.*return'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 5
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "User provided bad input but gets generic response"
|
||||||
|
OK_IF: "Error converted to proper HTTP 400/422 with details"
|
||||||
96
.claude/skills/code-audit/patterns/security.yaml
Normal file
96
.claude/skills/code-audit/patterns/security.yaml
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Security Anti-Patterns
|
||||||
|
# Patterns that may expose security vulnerabilities
|
||||||
|
|
||||||
|
name: Security
|
||||||
|
description: |
|
||||||
|
Detects patterns that could lead to security vulnerabilities including
|
||||||
|
missing authorization, data exposure, injection risks, and unsafe operations.
|
||||||
|
Specific to Mantimon TCG patterns and FastAPI/Python conventions.
|
||||||
|
|
||||||
|
patterns:
|
||||||
|
- id: missing-auth-dependency
|
||||||
|
description: Endpoint without authentication dependency
|
||||||
|
grep_pattern: '@router\.(post|put|patch|delete)\([^)]*\)\n(async )?def \w+\([^)]*\):'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 5
|
||||||
|
file_glob: 'app/api/**/*.py'
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Mutating endpoint without get_current_user dependency"
|
||||||
|
OK_IF: "Public endpoint (login, register, health) or has auth in deps"
|
||||||
|
|
||||||
|
- id: password-in-log
|
||||||
|
description: Password or secret potentially logged
|
||||||
|
grep_pattern: 'logger\.\w+\(.*["\']?.*(password|secret|token|key|credential).*["\']?'
|
||||||
|
context_lines: 2
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Sensitive value included in log message"
|
||||||
|
OK_IF: "Just logging field name, not value; or intentionally masked"
|
||||||
|
|
||||||
|
- id: sql-string-format
|
||||||
|
description: SQL query built with string formatting (injection risk)
|
||||||
|
grep_pattern: '(execute|text)\([^)]*(%|\.format|f["\'])'
|
||||||
|
context_lines: 3
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "User input could be interpolated into SQL"
|
||||||
|
OK_IF: "Only constant values interpolated; user input uses params"
|
||||||
|
|
||||||
|
- id: hidden-game-info-exposure
|
||||||
|
description: Deck/hand contents potentially exposed to wrong player
|
||||||
|
grep_pattern: '(deck|hand|prize).*\.(cards|contents|order)'
|
||||||
|
context_lines: 5
|
||||||
|
file_glob: 'app/services/game_service.py'
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Opponent's hidden zone contents sent to client"
|
||||||
|
OK_IF: "Only counts sent, or filtered through visibility layer"
|
||||||
|
|
||||||
|
- id: missing-ownership-check
|
||||||
|
description: Resource access without verifying ownership
|
||||||
|
grep_pattern: 'await.*(get_deck|get_collection|get_game).*\n(?!.*user_id|player_id)'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 5
|
||||||
|
file_glob: 'app/api/**/*.py'
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Resource fetched without checking user owns it"
|
||||||
|
OK_IF: "Ownership check in service layer or resource is public"
|
||||||
|
|
||||||
|
- id: hardcoded-secret
|
||||||
|
description: Hardcoded secret or API key in code
|
||||||
|
grep_pattern: '(api_key|secret|password|token)\s*=\s*["\'][^"\']{8,}["\']'
|
||||||
|
context_lines: 2
|
||||||
|
exclude_tests: false
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Real credential hardcoded in source"
|
||||||
|
OK_IF: "Placeholder/example value; real value from environment"
|
||||||
|
|
||||||
|
- id: debug-mode-check
|
||||||
|
description: Debug mode check that might expose info in production
|
||||||
|
grep_pattern: 'if.*(debug|DEBUG|dev_mode).*:\n.*print|logger'
|
||||||
|
multiline: true
|
||||||
|
context_lines: 4
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Sensitive data logged only when debug flag set"
|
||||||
|
OK_IF: "Debug info is non-sensitive; flag properly controlled"
|
||||||
|
|
||||||
|
- id: unsafe-pickle
|
||||||
|
description: Pickle usage (arbitrary code execution risk)
|
||||||
|
grep_pattern: 'pickle\.(load|loads)'
|
||||||
|
context_lines: 3
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Unpickling data from untrusted source"
|
||||||
|
OK_IF: "Never OK for external data; use JSON/msgpack instead"
|
||||||
|
|
||||||
|
- id: eval-usage
|
||||||
|
description: eval() or exec() usage
|
||||||
|
grep_pattern: '(eval|exec)\s*\('
|
||||||
|
context_lines: 3
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Evaluating user-controlled input"
|
||||||
|
OK_IF: "Extremely rare valid use; usually should refactor"
|
||||||
|
|
||||||
|
- id: cors-wildcard
|
||||||
|
description: CORS allowing all origins
|
||||||
|
grep_pattern: 'allow_origins\s*=\s*\["\*"\]|allow_origin_regex.*\.\*'
|
||||||
|
context_lines: 3
|
||||||
|
verdict_hints:
|
||||||
|
ISSUE_IF: "Production API allows any origin"
|
||||||
|
OK_IF: "Development only; production has specific origins"
|
||||||
Loading…
Reference in New Issue
Block a user