Skills Catalog
All 113 Claude Code skills — searchable reference
- Core Skills
- Business Skills
- Design Skills
- Document Skills
- Specialized Patterns (sp-*)
- /sp-brainstorm
- /sp-brainstorming
- /sp-dispatching-parallel-agents
- /sp-execute-plan
- /sp-executing-plans
- /sp-finishing-a-development-branch
- /sp-receiving-code-review
- /sp-requesting-code-review
- /sp-subagent-driven-development
- /sp-systematic-debugging
- /sp-test-driven-development
- /sp-using-git-worktrees
- /sp-using-superpowers
- /sp-verification-before-completion
- /sp-write-plan
- /sp-writing-plans
- /sp-writing-skills
- Sentry Skills
- /sentry-agents-md
- /sentry-brand-guidelines
- /sentry-claude-settings-audit
- /sentry-code-review
- /sentry-code-simplifier
- /sentry-commit
- /sentry-create-pr
- /sentry-django-access-review
- /sentry-django-perf-review
- /sentry-doc-coauthoring
- /sentry-find-bugs
- /sentry-iterate-pr
- /sentry-security-review
- /sentry-skill-creator
- /sentry-skill-scanner
- Trail of Bits Skills
- /ask-questions-if-underspecified
- /audit-context-building
- /algorand-vulnerability-scanner
- /audit-prep-assistant
- /cairo-vulnerability-scanner
- /code-maturity-assessor
- /cosmos-vulnerability-scanner
- /guidelines-advisor
- /secure-workflow-guide
- /solana-vulnerability-scanner
- /substrate-vulnerability-scanner
- /token-integration-analyzer
- /ton-vulnerability-scanner
- /claude-in-chrome-troubleshooting
- /constant-time-analysis
- /interpreting-culture-index
- /devcontainer-setup
- /differential-review
- /dwarf-expert
- /entry-point-analyzer
- /firebase-apk-scanner
- /fix-review
- /insecure-defaults
- /modern-python
- /property-based-testing
- /second-opinion
- /semgrep-rule-creator
- /semgrep-rule-variant-creator
- /sharp-edges
- /spec-to-code-compliance
- /codeql
- /sarif-parsing
- /semgrep
- /address-sanitizer
- /aflpp
- /atheris
- /cargo-fuzz
- /constant-time-testing
- /coverage-analysis
- /fuzzing-dictionary
- /fuzzing-obstacles
- /harness-writing
- /libafl
- /libfuzzer
- /ossfuzz
- /ruzzy
- /testing-handbook-generator
- /wycheproof
- /variant-analysis
- /yara-rule-authoring
- Other Skills
- /algorithmic-art
- /internal-comms
- /mcp-builder
- /skill-creator
- /slack-gif-creator
- /theme-factory
- /web-artifacts-builder
- /webapp-testing
- /plan-build-test
- /sentinel
- /qa-doc-review
- /debugging
- Mobile UAT Test
- mobile-uat Skill
- mobile-uat — Responsive Regression Detector
Core Skills
/build-plan
Source: ~/.claude/skills/build-plan/SKILL.md
name: build-plan version: 2.0 description: > Execute an approved plan using TaskList with builder/validator teams. Use after /plan-with-team creates an approved plan. Triggers: "execute the plan", "build this", "implement the plan", "run the plan", "/build-plan". argument-hint: "[path to plan file, or leave empty for latest in ~/system/specs/]" level: 3
/build-plan — Execute Approved Plan
Purpose
Executes a pre-approved plan file using parallel builder/validator TaskList agents.
Run AFTER /plan-with-team — never execute without an approved plan.
Variables
| Variable | Type | Description | Default |
|---|---|---|---|
plan_path |
path | Path to plan file | latest in ~/system/specs/ |
concurrency |
number | Parallel builders | 3 |
yolo |
flag | Skip browser testing | false |
Workflow
Step 1: Load Plan
- if
$ARGUMENTSprovided → use as plan_path - else → find latest plan:
ls -t ~/system/specs/*.md | head -1 - Read plan file, verify it has: [ ] task checkboxes + acceptance criteria
Step 2: Validate Prerequisites
- Check: does plan have approval marker? (
## APPROVEDsection orstatus: approved) - if not approved → STOP, tell user to run
/plan-with-teamfirst - Check: are required services running? (DB, relevant tools)
Step 3: Activate Build Mode
node ~/system/tools/build-mode.js start <project_dir> --concurrency <N>
Builder completion flow
Builders call node ~/system/tools/mc.js ready <id> NOT mc.js done.
Only after Proveo/validator verification can tasks be marked done.
Build-plan CANNOT report COMPLETE until all tasks pass Proveo verification.
Step 4: Execute Tasks (parallel where independent)
For each [ ] task in plan:
- if task has no dependencies → spawn in parallel with builder agent
- if task has dependencies → wait for dep completion, then spawn
- Each builder: reads task spec, implements, marks
[x]when done
Step 5: Validate Each Task
After each task completes → spawn validator agent:
- Runs tests relevant to task
- Checks acceptance criteria
- if FAIL → send back to builder with failure context
- if PASS → proceed
Step 6: Final Check
- All tasks
[x]? → run full test suite - if PASS →
node ~/system/tools/build-mode.js stop --status completed - if FAIL →
node ~/system/tools/build-mode.js stop --status failed+ report
Report Format
BUILD-PLAN EXECUTION REPORT
Plan: [filename]
Status: [COMPLETE|PARTIAL|FAILED]
Tasks: X/Y completed
Tests: [passing/total]
Duration: [time]
Issues: [any blockers or failures]
$ARGUMENTS
/plan-with-team
Source: ~/.claude/skills/plan-with-team/SKILL.md
name: plan-with-team description: Create implementation plans with builder/validator agent teams. Use for major features, refactoring, or system components. argument-hint: "[what to build]"
Plan With Team
Create an implementation plan with builder/validator agent teams.
Related Skills (pick the right one)
/plan-with-team(this skill) — Multi-expert planning for complex features, refactors, system components./build-plan— Execute the plan produced here. Run AFTER plan is approved./hop-build <task_id>— For a SINGLE task (not a multi-task plan)./build— Toggle session into Build Mode./prime-build— Load lightweight build context into session.
Instructions
You are creating a detailed implementation plan that assigns work to specialized agent teams.
Step 1: Research (MANDATORY)
Before planning, research:
- If building a company/org: Find 2-3 existing examples, analyze their structure
- If building software: Explore the codebase, understand existing patterns
- If building process: Find industry standards and best practices
Use Glob, Grep, Read, WebSearch as needed. Document findings.
Step 2: Analyze
Based on research:
- What patterns should we copy?
- What should we adapt for our needs?
- What are the key components?
Step 3: Define Team
For each major task, assign:
- Builder — Creates/implements
- Validator — Verifies the work
Reference agents from ~/.claude/agents/:
builder.md— Implementation agentvalidator.md— Verification agent (read-only)
Step 4: Create Plan
Write plan to ~/system/specs/<name>-plan.md with this structure:
# Plan: [Name]
## Research Summary
[What we learned from existing examples]
## Objective
[1-2 sentences]
## Team Orchestration
### Team Members
| ID | Name | Role | Agent Type |
|----|------|------|------------|
| B1 | [name]-builder | Build [what] | builder |
| V1 | [name]-validator | Validate [what] | validator |
### Step-by-Step Tasks
#### Phase 1: [Name]
**Task 1:** [Description]
- Owner: B1
- BlockedBy: none
- Acceptance:
- [ ] Criterion 1
- [ ] Criterion 2
**Task 2:** Validate [above]
- Owner: V1
- BlockedBy: 1
- Acceptance: [criteria]
## Validation Commands
[How to verify the work]
Step 5: Validate Plan
Self-validate the plan:
- All tasks have acceptance criteria
- Dependencies make sense (validators blocked by builders)
- No circular dependencies
MANDATORY CHECK — plan is INCOMPLETE without both:
- Validation task exists — owner: Proveo/Angie Jones, end-to-end test with real evidence (not dry-run), BlockedBy all build tasks
- Documentation task exists — owner: Skillforge, BookStack page for every system built or changed, BlockedBy validation task
If either is missing → add them before presenting to CEO. Do not ask. Just add.
Step 6: Present for Approval
Show the user:
- Research summary
- Plan overview
- Number of tasks and phases
- Ask: "Approve plan? Then run
/build-planto execute."
Output
The plan file path: ~/system/specs/<name>-plan.md
Ready for execution with /build-plan.
$ARGUMENTS
/learning-opportunity
Source: ~/.claude/skills/learning-opportunity/SKILL.md
name: learning-opportunity description: Self-improving feedback loop. When something goes wrong, analyze root cause, patch the system, and ensure it never happens again. argument-hint: "[describe what went wrong]"
Learning Opportunity
Turn every mistake into a permanent system improvement.
Instructions
You are analyzing a mistake or failure and patching the system so it never recurs.
Principle: AI bez enforcement-a ne radi. Markdown rules = suggestions. Hooks/scripts = enforcement. Always prefer deterministic fixes over documentation fixes.
Step 1: Identify the Failure
If argument provided, use it. Otherwise, ask:
- What went wrong?
- When did it happen?
- What was the expected vs actual outcome?
Classify the failure type:
- HALLUCINATION — AI invented something that doesn't exist (tool, path, port, import)
- PROCESS_SKIP — AI skipped a required step (no boot, no backup, no task)
- WRONG_OUTPUT — AI produced incorrect content (wrong data, bad code, broken logic)
- KNOWLEDGE_GAP — AI didn't know something it should have known
- REPEAT_MISTAKE — Same error as a previous session (worst category)
Step 2: Root Cause Analysis
Trace the failure through GOTCHA layers:
-
Goals — Was there a spec/rule that should have prevented this?
# Check existing rules ls ~/system/rules/ grep -r "relevant keyword" ~/system/rules/ -
Tools — Did a tool fail or was a phantom tool used?
# Check manifest grep "relevant tool" ~/system/tools/manifest.md -
Context — Was the context missing or wrong?
# Check HiveMind for prior knowledge node ~/system/agents/hivemind/hivemind.js query "relevant keyword" -
Hooks — Should an enforcement hook have caught this?
# Check existing hooks ls ~/.claude/hooks/ -
Memory — Was this a known issue that was forgotten?
# Check memory files grep "relevant keyword" ~/.claude/projects/-Users-makinja/memory/MEMORY.md
Document: Which layer failed? Why?
Step 3: Determine Fix Type
Choose the STRONGEST fix available (top = strongest):
| Priority | Fix Type | When to Use |
|---|---|---|
| 1 | Hook (Python enforcement) | Hallucinations, phantom tools, security violations |
| 2 | Tool update (deterministic code) | Missing validation, wrong behavior |
| 3 | Rule addition (~/system/rules/) | New process requirement, agent behavior |
| 4 | CLAUDE.md update | Missing instruction, wrong priority |
| 5 | Memory update | Lesson learned, context for future |
NEVER use only option 5 alone. Memory without enforcement = ZAKON #1 violation.
Step 4: Apply the Patch
Based on fix type, apply changes:
If HALLUCINATION → Update hallucination-detector.py
# Read current blocklist
grep -A 50 "PHANTOM_TOOLS" ~/.claude/hooks/hallucination-detector.py
Add the hallucinated item to the appropriate blocklist (PHANTOM_TOOLS, KNOWN_PORTS, etc.)
If PROCESS_SKIP → Update/create enforcement hook
Check if gotcha-enforcer.py can be extended, or create new hook.
If WRONG_OUTPUT → Update tool or add validation
Fix the tool that produced wrong output. Add input validation.
If KNOWLEDGE_GAP → Add to context + memory
# Add to HiveMind
node ~/system/agents/hivemind/hivemind.js post john lesson "description"
If REPEAT_MISTAKE → Escalate enforcement
If this mistake happened before, the previous fix was too weak. Go UP the priority list (e.g., if rule exists but wasn't followed → add hook).
Step 5: Verify the Fix
Test that the fix actually works:
- If hook: test with a simulated bad input
- If tool: run the tool and verify output
- If rule: check it's in the right location and formatted correctly
Step 6: Log Everything
# 1. Log to CHANGELOG
bash ~/system/tools/syslog.sh add "LEARNING: [description] — fix: [what was changed]"
# 2. Log to HiveMind
node ~/system/agents/hivemind/hivemind.js post john lesson "[failure type]: [what happened] → [what was fixed]"
# 3. Update lessons-learned if exists
# ~/system/rules/lessons-learned.md
Step 7: Report
Show the user:
## Learning Opportunity Report
### Failure
- **Type:** [HALLUCINATION|PROCESS_SKIP|WRONG_OUTPUT|KNOWLEDGE_GAP|REPEAT_MISTAKE]
- **Description:** [what went wrong]
- **Root Cause:** [which GOTCHA layer failed and why]
### Fix Applied
- **Fix Type:** [Hook|Tool|Rule|CLAUDE.md|Memory]
- **File Changed:** [path]
- **What Changed:** [description]
### Enforcement Level
- [ ] Deterministic (hook/script blocks bad behavior)
- [ ] Documented (rule/instruction guides good behavior)
- [ ] Remembered (memory/HiveMind for context)
### Verification
- [ ] Fix tested and working
- [ ] Logged to CHANGELOG
- [ ] Logged to HiveMind
Rules
- Deterministic > Documented — A hook that blocks is worth 100 markdown rules
- ZAKON #1 applies — If the fix is "write more markdown", it's NOT a fix
- Escalate repeats — Same mistake twice = previous fix was too weak
- Always log — CHANGELOG + HiveMind, no exceptions
- Backup first —
setup-backup.shbefore any hook/tool changes
$ARGUMENTS
/code-review
Source: ~/.claude/skills/code-review/SKILL.md
name: code-review version: "2.0" level: 3 trigger: "code review, review this code, check my code, pre-commit review, security review of code" author: john updated: 2026-03-16
Code Review — Level 3 Supervised Skill
Sistematski code review sa if/then control flow. Security-first, actionable feedback.
WHEN TO USE
- IF "review", "code review", "check code", "pre-commit" → activate this skill
- IF security-specific request → prioritize Security section, run sentry-security-review first
WORKFLOW
Step 1: Scope Check
IF large PR (>500 lines):
→ Split: delegate security to securion sub-agent, delegate logic to code-reviewer sub-agent
→ Merge reports before final output
ELSE:
→ Single-pass review, continue to Step 2
Step 2: RAG Context
node ~/system/tools/rag-router.js query "code review patterns [tech stack]" --top 3
Check HiveMind for prior decisions on this codebase.
Step 3: Security Scan (ALWAYS FIRST)
IF bash/shell code detected → check for injection patterns
IF database queries → check for SQL injection
IF user input handling → check XSS, validation
IF credentials/keys visible → STOP, report immediately (Level 5 block)
Step 4: Quality Checklist
[ ] Correctness: edge cases, error handling, null safety
[ ] Security: OWASP Top 10, no hardcoded secrets, input validation
[ ] Performance: N+1 queries, unnecessary loops, memory leaks
[ ] Maintainability: DRY, naming, dead code
[ ] Tests: coverage for happy path + 2 edge cases minimum
[ ] GOTCHA: does this introduce regressions?
Step 5: Report Format
IF critical security issue → status: BLOCKED, stop review, escalate
IF blocking bugs (>3) → status: NEEDS_WORK
IF minor issues only → status: APPROVED_WITH_COMMENTS
IF clean → status: APPROVED
OUTPUT FORMAT (report to John, not user)
CODE REVIEW REPORT
Status: APPROVED | APPROVED_WITH_COMMENTS | NEEDS_WORK | BLOCKED
Files reviewed: [list]
Lines reviewed: [count]
🔴 CRITICAL (must fix before merge):
- [issue]: [file:line] — [fix suggestion]
🟡 SHOULD FIX:
- [issue]: [file:line] — [suggestion]
🟢 OPTIONAL:
- [suggestion]
Security: PASS | WARN | FAIL
Tests: [coverage%] | [missing]
Verdict: [one sentence summary]
ESCALATION
- Security FAIL → delegate to securion agent immediately
- Architectural concern → delegate to sentinel-architect agent
- Performance concern → query HiveMind for prior benchmarks first
/security-audit
Source: ~/.claude/skills/security-audit/SKILL.md
name: security-audit version: 2.0 description: > Run comprehensive security audit following OWASP and ALAI LAWS. Use for: "security review", "audit this code", "check for vulnerabilities", "OWASP check", "before deploying", "security scan", "/security-audit". level: 3 company: Securion
/security-audit — Security Review
Purpose
Systematic security review covering OWASP Top 10, ALAI internal LAWS, and code-specific vulnerabilities.
Variables
| Variable | Type | Description | Default |
|---|---|---|---|
target |
path/scope | File, directory, or "full system" | current project |
depth |
string | quick | standard | deep | standard |
focus |
string | owasp | laws | auth | api | all | all |
Workflow
Step 1: Scope
- Read $ARGUMENTS to determine target and depth
- if no target → audit current working directory
- if depth=quick → run only LAWS + auth checks
- if depth=deep → run all + tob-* skill checks
Step 2: ALAI LAWS Compliance
Check each LAW:
- ZAKON 0 (Tajnost): No secrets in code, no internal URLs exposed, no employee data hardcoded
- ZAKON 1 (Ne škodi): No destructive ops without confirm, backups exist for critical data
- ZAKON 2 (Slušaj): Auth on all endpoints, RBAC, admin routes protected
- ZAKON 3 (Čuvaj sebe): Error handling, graceful degradation, no crash on bad input
Step 3: OWASP Top 10 Check
For each category, scan target:
- Injection (SQL, NoSQL, command injection)
- Broken Authentication (weak JWT, no rate limit, session issues)
- Sensitive Data Exposure (logs, responses, hardcoded secrets)
- Security Misconfiguration (CORS, headers, default credentials)
- XSS (reflected, stored, DOM-based)
- Broken Access Control (IDOR, privilege escalation)
- Vulnerable Dependencies (
npm auditor equivalent) - Insecure Deserialization
- Logging & Monitoring gaps
- SSRF
Step 4: Run Available Tools
- if tob-static-analysis available → run on target
- if tob-insecure-defaults available → check configs
- if tob-sharp-edges available → check dangerous patterns
npm audit --audit-level=highif package.json exists
Step 5: Report
- if CRITICAL found → flag for immediate fix, offer to create MC task
- if HIGH found → list with recommended fixes
- if depth=deep → include code snippets for each finding
Report Format
SECURITY AUDIT REPORT
Target: [scope]
Depth: [quick|standard|deep]
Date: [timestamp]
CRITICAL (block deployment):
[C1] [finding] — [file:line] — [fix]
HIGH (fix before next release):
[H1] [finding] — [fix]
MEDIUM:
[M1] [finding]
LAWS: [PASS|FAIL — list failures]
OWASP: [X/10 categories clean]
Tools run: [list]
$ARGUMENTS
Business Skills
/invoice
Source: ~/.claude/skills/invoice/SKILL.md
Invoice — Kreiraj i Pošalji Fakturu
Description
Vodeni workflow za kreiranje, pregled, i slanje fakture. Od validacije klijenta do auto-remind schedulea. Koristi invoice-generator.js za kreiranje, drafts.js za email workflow, i opciono fiken.js za accounting sync.
Trigger
Koristi ovaj skill kad:
- Alem kaže "napravi fakturu", "pošalji račun", "fakturiši", "invoice"
- Milestone je dostignut i treba fakturisati
- Periodična faktura (mjesečna, kvartalna)
Alati
- Fakture:
~/system/tools/invoice-generator.js - Contacts:
~/system/tools/contacts.js - Drafts:
~/system/tools/drafts.js - Fiken:
~/system/tools/fiken.js - Email: MCP
mcp__email__email_send(account: "john") - Templates:
~/system/templates/invoices/standard-invoice.html - DB:
~/system/databases/invoices.db
Workflow
Korak 1: Validacija klijenta
Provjeri da klijent postoji u sistemu:
# Provjeri contacts.db
NODE_PATH=~/system/node_modules node ~/system/tools/contacts.js search "<klijent>"
# Provjeri da ima email
NODE_PATH=~/system/node_modules node ~/system/tools/contacts.js show <id>
- Ako klijent NE postoji → pitaj Alema za podatke, dodaj u contacts.js
- Ako nema email → pitaj Alema. NE izmišljaj.
- Provjeri: org_number, adresa (treba za fakturu)
Korak 2: Prikupi podatke za fakturu
Interaktivno od Alema (ili iz konteksta):
- Klijent (ime/firma)
- Iznos (bez MVA)
- Valuta (NOK, EUR, USD, BAM, RSD)
- Opis (šta se fakturiše)
- Line items (opciono — detaljnije stavke)
- Payment terms (default: 14 dana)
- Reference (project, contract, PO number)
Korak 3: Kreiraj fakturu
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js create "<klijent>" <iznos> <valuta> "<opis>"
Provjeri output:
- Invoice ID kreiran
- MVA 25% dodan (za NOK fakture)
- Total = iznos + MVA
- Due date = today + payment terms
- PDF generisan u
~/system/deliverables/invoices/
Korak 4: Review sa Alemom
OBAVEZNO: Pokaži Alemu fakturu prije slanja:
- Klijent: {ime}
- Iznos: {iznos} {valuta}
- MVA (25%): {mva}
- Total: {total} {valuta}
- Opis: {opis}
- Due date: {datum}
- Payment terms: {dani} dana
Čekaj eksplicitno odobrenje. NIKAD slati bez "OK" ili "SEND".
Korak 5: Kreiraj email draft
Fakturu NE slati direktno — koristi drafts workflow:
# Draft se kreira automatski pri invoice create
# Ili ručno:
NODE_PATH=~/system/node_modules node ~/system/tools/drafts.js list pending
Draft risk classification:
- Invoice = HIGH risk → zahtijeva manual approval
- Reminder = MEDIUM risk → auto-approve + notify
Korak 6: Pošalji fakturu
Nakon Alemovog odobrenja:
# Approve draft
NODE_PATH=~/system/node_modules node ~/system/tools/drafts.js approve <draft-id>
# Send
NODE_PATH=~/system/node_modules node ~/system/tools/drafts.js send <draft-id>
Ili direktno via MCP email:
mcp__email__email_send(
account: "john",
to: "<klijent-email>",
subject: "Invoice #<id> — ALAI Holding AS",
body: "<tekst>",
attachments: ["~/system/deliverables/invoices/INV-<id>.pdf"]
)
Korak 7: Fiken sync (opciono)
Ako klijent ima Fiken kontakt:
# Sync invoice to Fiken
NODE_PATH=~/system/node_modules node ~/system/tools/fiken.js invoices sync
Korak 8: Auto-remind schedule
Automatski reminder schedule (pipeline-watcher daemon):
- Day 7: Friendly reminder (auto-send, Norwegian)
- Day 14: Firm reminder (auto-send + Slack notify)
- Day 30+: MC task za Alema (eskalacija)
Provjeri remind status:
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js list overdue
Ručni reminder:
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js remind <invoice-id>
Pravila
- MVA 25% za NOK — invoice-generator.js automatski dodaje. Provjeri total.
- CEO odobrava — Faktura je HIGH risk. NIKAD slati bez Alemovog "OK".
- Klijent mora postojati — Nema fakture za nepostojeći kontakt u contacts.db.
- Referenca obavezna — Svaka faktura mora imati vezu na ugovor ili PO.
- ALAI branding — PDF koristi standard ALAI template.
- Valuta match — Koristi valutu iz ugovora. Ne miješaj NOK/EUR.
- Anti-hallucination — NE izmišljaj iznose. Ako Alem nije rekao cifru, pitaj.
- Due date realan — Default 14 dana. Provjeri ugovor za custom terms.
- Fiken sync — Sync nakon svake nove fakture ako je Fiken konfigurisan.
Primjer — Standardna Faktura
Alem: "Fakturiši Wizard 25000 NOK za februar maintenance"
John:
1. contacts.js search "Wizard" → Wizard NUF, anel@wizard.no
2. invoice-generator.js create "Wizard NUF" 25000 NOK "February 2026 maintenance and support"
3. Output: INV-0005, 25000 + 6250 MVA = 31250 NOK, due 2026-02-26
4. → Pokaži Alemu za review
5. Alem: "OK"
6. → drafts.js approve + send
7. → fiken.js invoices sync
8. → Auto-remind active (Day 7, 14, 30)
Primjer — Multi-line Faktura
Alem: "Fakturiši Ren Drøm za januar: 40h development @ 1200 NOK + 5000 NOK hosting"
John:
1. contacts.js search "Ren Drøm" → found
2. invoice-generator.js create "Ren Drøm AS" 53000 NOK "January 2026: Development (40h × 1200 NOK) + Hosting (5000 NOK)"
3. → Review → Approve → Send → Sync
Tracking
# Lista svih faktura
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js list all
# Neplaćene
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js list unpaid
# Overdue
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js list overdue
# Statistika
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js stats
# Fiken dashboard
NODE_PATH=~/system/node_modules node ~/system/tools/fiken.js dashboard
/pipeline-review
Source: ~/.claude/skills/pipeline-review/SKILL.md
name: pipeline-review version: "2.0" level: 3 trigger: "pipeline review, kako stoje leadovi, sales update, pregled prodaje, forecast" author: john updated: 2026-03-16 description: Strukturirani sedmični/mjesečni pregled sales pipeline-a. Query CRM, generiše follow-up drafte, ažurira forecast. IF stale leads > 5 THEN auto-draft follow-up emails.
Pipeline Review — Sales Pipeline Pregled
Description
Strukturirani pregled svih aktivnih leadova u sales pipeline-u. Za svaki lead: status, kontekst, preporuka (follow-up, advance, lose). Auto-generiše follow-up email drafte za stale leadove. Ažurira forecast.
Trigger
Koristi ovaj skill kad:
- Alem kaže "pipeline review", "kako stoje leadovi", "sales update", "pregled prodaje"
- Sedmični/mjesečni pregled poslovanja
- Prije sastanka sa klijentima ili partnerima
- Kad treba forecast za planning
Alati
- Pipeline:
~/system/tools/sales-pipeline.js - CRM:
~/system/tools/unified-crm.js - Contacts:
~/system/tools/contacts.js - Drafts:
~/system/tools/drafts.js - Invoices:
~/system/tools/invoice-generator.js - Tasks:
node ~/system/tools/mc.js - Email: MCP
mcp__email__emails_find(za zadnji kontakt)
Workflow
Korak 1: Snapshot pipeline-a
# Svi aktivni leadovi
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js list
# Statistika
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js stats
# Forecast
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js forecast
Korak 2: Per-lead analiza
Za SVAKI aktivni lead (ne-lost, ne-won), prikaži:
## <Lead Name> — Stage: <stage>
- **Dani u stage-u:** X dana (od zadnjeg advance-a)
- **Zadnji kontakt:** <datum> (<tip kontakta>)
- **Vrijednost:** <amount> <currency>
- **Izvor:** <source>
- **BANT:** Budget ✓/✗ | Authority ✓/✗ | Need ✓/✗ | Timeline ✓/✗
- **Notes:** <zadnje bilješke>
- **Preporuka:** FOLLOW-UP / ADVANCE / LOSE / HOLD
Provjeri kontekst za svaki lead:
# Lead detalji
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js show <lead-id>
# CRM presjek (invoices, tickets, tasks)
NODE_PATH=~/system/node_modules node ~/system/tools/unified-crm.js client "<name>"
# Zadnji email
# mcp__email__emails_find(account: "john", query: "<client email>", limit: 3)
Korak 3: Klasificiraj leadove
Sortiraj po prioritetu:
HOT (akcija odmah):
- Lead u negotiating > 7 dana bez kontakta
- Proposal sent > 14 dana bez odgovora
- Qualified lead > 21 dana bez advance-a
WARM (akcija ove sedmice):
- Lead u qualified, treba zakazati discovery
- Proposal treba napisati/poslati
- Follow-up email čeka
COLD (preispitaj):
- Lead u prospect > 30 dana
- Nema BANT kvalifikacije
- Ghosting (3+ pokušaja kontakta bez odgovora)
Korak 4: Generiši akcije
Za svaki lead predloži konkretnu akciju:
| Situacija | Akcija |
|---|---|
| Stale > 7 dana | Follow-up email draft |
| Qualified, nema discovery | Zakaži discovery call |
| Discovery done, nema proposal | Napiši proposal (CEO gate!) |
| Proposal sent, nema odgovora | Follow-up "gentle nudge" |
| Negotiating, nema progresa | Call/meeting za clarification |
| Ghost (3+ attempts) | Final follow-up → LOSE ako nema odgovora |
Korak 5: Auto-generate follow-up drafte
Za stale leadove, kreiraj email draft:
# Draft se kreira u drafts.db
# Tip: follow-up = MEDIUM risk (auto-approve + notify)
Follow-up template (Norwegian — standardni jezik za norveške klijente):
Subject: Oppfølging — [Projekt/Tema]
Hei [Navn],
Jeg ville bare følge opp samtalen vår om [tema].
[Specifikt neste steg eller spørsmål].
Har du mulighet til en rask prat denne uken?
Med vennlig hilsen,
ALAI Holding AS
Za internasjonale klijente — English template:
Subject: Following up — [Project/Topic]
Hi [Name],
Just following up on our conversation about [topic].
[Specific next step or question].
Would you have time for a quick call this week?
Best regards,
ALAI Holding AS
Korak 6: Ažuriraj forecast
Na osnovu pregleda, prikaži:
## Pipeline Forecast
| Stage | Leads | Total Value | Weighted (prob) |
|-------|-------|-------------|-----------------|
| Prospect | X | Y NOK | Y × 10% |
| Qualified | X | Y NOK | Y × 25% |
| Proposal Sent | X | Y NOK | Y × 50% |
| Negotiating | X | Y NOK | Y × 75% |
| **Total Pipeline** | **X** | **Y NOK** | **Z NOK** |
## Won (last 30/60/90 days)
| Period | Deals | Revenue |
|--------|-------|---------|
| Last 30d | X | Y NOK |
| Last 60d | X | Y NOK |
| Last 90d | X | Y NOK |
Korak 7: MC taskovi za high-priority
Za HOT leadove, kreiraj MC task:
node ~/system/tools/mc.js add "Follow up: <Lead> — <specifična akcija>" --priority H --route bizdev
Korak 8: Executive Summary
Prikaži Alemu sažetak:
## Pipeline Review — <datum>
**Active leads:** X
**Total pipeline value:** Y NOK
**Weighted forecast:** Z NOK
**HOT (akcija odmah):**
- <Lead 1>: <akcija>
- <Lead 2>: <akcija>
**WARM (ova sedmica):**
- <Lead 3>: <akcija>
**COLD (preispitaj):**
- <Lead 4>: razmotriti LOSE
**Won this month:** X deals, Y NOK
**Lost this month:** X deals, Y NOK
**Next steps:**
1. <konkretna akcija 1>
2. <konkretna akcija 2>
3. <konkretna akcija 3>
Pravila
- Svaki lead pregledan — NE preskakati leadove, čak ni "očigledno mrtve"
- Dani se broje — Stale = bez kontakta > 7 dana u aktivnom stage-u
- Follow-up ≠ spam — Max 3 follow-up pokušaja. Nakon 3. → LOSE ili HOLD
- Forecast realan — NE napuhavati brojke. Weighted probability po stage-u
- CEO vidi summary — Pipeline review UVIJEK završava executive summary-jem
- Anti-hallucination — NE izmišljaj lead podatke. Čitaj iz pipeline.js
- BANT provjera — Svaki qualified lead mora imati minimum 2/4 BANT
- Lost ≠ zauvijek — Lost leadovi mogu biti reactivated. Log razlog.
- Draft, ne send — Follow-up emaile UVIJEK kao draft. CEO/John odobri.
Primjer
Alem: "Kako stoji pipeline?"
John:
## Pipeline Review — 2026-02-12
**Active leads:** 4
**Total pipeline value:** 425,000 NOK
**Weighted forecast:** 168,750 NOK
**HOT:**
- Riad (negotiating, 12 dana): AI feature wishlist. Čeka prototip. → Zakazati demo call
- TechCorp (proposal_sent, 8 dana): AI chatbot. → Follow-up email draft kreiran
**WARM:**
- DataViz AS (qualified, 5 dana): Dashboard projekt. → Zakazati discovery
- NordTech (prospect, 3 dana): Svjež lead. → Kvalificiraj BANT
**COLD:**
- OldLead AS (prospect, 45 dana): Nema odgovora na 2 pokušaja. → Preporučujem LOSE
**Won this month:** 1 deal, 150,000 NOK (Wizard NUF)
**Lost this month:** 0
Next steps:
1. Demo call sa Riad ove sedmice
2. Follow-up email TechCorp (draft kreiran)
3. Discovery call DataViz AS
⏱ Operational Limits
- MAX TURNS: 20 (validate) | 10 (lookup)
- Report to John after pipeline snapshot. Do NOT loop on stale leads > 3 iterations.
/financial-overview
Source: ~/.claude/skills/financial-overview/SKILL.md
Financial Overview — Finansijski Pregled
Description
Kompletni finansijski pregled: bank balances, outstanding invoices, pipeline forecast, runway kalkulacija. Agregira podatke iz Fiken API, invoice DB, i sales pipeline-a u executive summary.
Trigger
Koristi ovaj skill kad:
- Alem kaže "financial overview", "koliko imamo para", "runway", "pregled finansija"
- "bank balance", "outstanding invoices", "koliko nam duguju"
- Mjesečni/kvartalni finansijski pregled
- Planning meeting — treba forecast
Alati
- Fiken:
~/system/tools/fiken.js - Invoices:
~/system/tools/invoice-generator.js - Pipeline:
~/system/tools/sales-pipeline.js - CRM:
~/system/tools/unified-crm.js - Dashboard: http://localhost:3030
Workflow
Korak 1: Bank Balances (Fiken)
NODE_PATH=~/system/node_modules node ~/system/tools/fiken.js balances
Prikaži per-company:
## Bank Balances
| Company | Account | Balance | Currency |
|---------|---------|---------|----------|
| ALAI Holding AS | Drift | XX,XXX | NOK |
| BasicConsulting | Drift | XX,XXX | NOK |
| ... | ... | ... | ... |
| **TOTAL** | | **XXX,XXX** | **NOK** |
Korak 2: Outstanding Invoices
# Neplaćene fakture
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js list unpaid
# Overdue fakture
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js list overdue
# Statistika
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js stats
Prikaži:
## Outstanding Invoices
| Invoice | Client | Amount | Due Date | Status | Days |
|---------|--------|--------|----------|--------|------|
| INV-001 | Client A | 50,000 NOK | 2026-02-20 | Open | 8 days left |
| INV-002 | Client B | 30,000 NOK | 2026-02-01 | OVERDUE | 11 days late |
| **Total Outstanding** | | **80,000 NOK** | | | |
| **Total Overdue** | | **30,000 NOK** | | | |
Korak 3: Recent Payments (last 30 days)
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js list paid
NODE_PATH=~/system/node_modules node ~/system/tools/fiken.js dashboard --json
## Recent Payments (last 30 days)
| Date | Client | Amount | Invoice |
|------|--------|--------|---------|
| 2026-02-05 | Client A | 75,000 NOK | INV-003 |
| **Total Received** | | **75,000 NOK** | |
Korak 4: Pipeline Forecast
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js forecast
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js stats
## Pipeline Forecast
| Period | Expected Revenue | Probability-Weighted |
|--------|-----------------|---------------------|
| Next 30 days | XXX,XXX NOK | XX,XXX NOK |
| Next 60 days | XXX,XXX NOK | XX,XXX NOK |
| Next 90 days | XXX,XXX NOK | XX,XXX NOK |
### By Stage
| Stage | Deals | Value | Weight |
|-------|-------|-------|--------|
| Qualified | X | Y NOK | Y x 25% |
| Proposal Sent | X | Y NOK | Y x 50% |
| Negotiating | X | Y NOK | Y x 75% |
Korak 5: Monthly Burn Rate
Estimiraj mjesečne troškove (iz poznatih podataka):
- Hosting & infrastructure (poznato iz subscriptions)
- Software licenses (poznato)
- Subcontractor costs (iz faktura)
- NE estimiraj troškove koje ne znaš — označi kao TBD
## Monthly Burn Rate (estimated)
| Category | Amount | Note |
|----------|--------|------|
| Hosting | X NOK | Vercel, Railway, etc. |
| Software | X NOK | GitHub, Fiken, etc. |
| Subcontractors | X NOK | Ako postoji |
| **Total Known** | **X NOK** | |
| Salaries, rent, etc. | TBD | Alem ima podatke |
Korak 6: Runway Kalkulacija
## Runway
- **Cash on hand:** XXX,XXX NOK (bank balances)
- **Outstanding receivable:** XX,XXX NOK (neplaćene fakture)
- **Monthly burn (known):** XX,XXX NOK
- **Runway (cash only):** X.X mjeseci
- **Runway (cash + receivable):** X.X mjeseci
- **Runway (cash + receivable + 30d forecast):** X.X mjeseci
**Status:** OK / WARNING (< 3 mjeseca) / CRITICAL (< 1 mjesec)
NAPOMENA: Ako ne znaš pune mjesečne troškove, runway je OPTIMISTIČKI. Označi to jasno.
Korak 7: Per-Company Breakdown
Ako ima više kompanija (ALAI Holding, BasicConsulting, itd.):
## Per-Company
| Company | Bank | Outstanding | Pipeline | Status |
|---------|------|-------------|----------|--------|
| ALAI Holding | XX,XXX | XX,XXX | XX,XXX | OK |
| BasicConsulting | XX,XXX | XX,XXX | XX,XXX | OK |
Korak 8: Executive Summary
Prikaži Alemu kompaktan pregled:
## Financial Overview — <datum>
**Cash:** XXX,XXX NOK (across all accounts)
**Outstanding:** XX,XXX NOK (X invoices, Y overdue)
**Pipeline (weighted):** XX,XXX NOK (next 90 days)
**Runway:** X.X months (cash + receivable)
**Action Items:**
1. <overdue invoice chase>
2. <pipeline advancement needed>
3. <any concerns>
**Trend:** Revenue up/down/stable vs last month
Pravila
- Stvarni podaci — Svaka cifra dolazi iz tool-a (fiken.js, invoice-generator.js, sales-pipeline.js). NE izmišljaj.
- Currency match — Prikazuj sve u NOK. Ako ima EUR/USD, konvertuj sa napomenom o kursu.
- Overdue highlight — Overdue fakture UVIJEK crveno/boldano. To su prioritet.
- TBD za nepoznato — Ako ne znaš neki trošak, piši TBD. NE estimiraj bez podataka.
- Forecast ≠ revenue — Pipeline forecast je probabilistički. Jasno označi.
- Runway je estimate — Ako nemaš pune troškove, piši "minimum runway" i napomeni.
- Privacy — Finansijski podaci su CONFIDENTIAL. Ne loguj u HiveMind detalje.
- Fiken = source of truth — Za bank balanse i fakture, Fiken API je autoritativan.
- Dashboard link — Uvijek ponudi http://localhost:3030 za live dashboard.
Primjer
Alem: "Koliko imamo para?"
John:
## Financial Overview — 2026-02-12
**Cash:** 82,450 NOK (Fiken — 2 accounts)
- ALAI Holding drift: 65,200 NOK
- BasicConsulting drift: 17,250 NOK
**Outstanding:** 31,250 NOK (1 invoice)
- INV-005 Wizard NUF: 31,250 NOK (due 2026-02-26) — on time
**Overdue:** 0 NOK
**Pipeline (weighted, 90d):** 168,750 NOK
- 4 active leads, total value 425,000 NOK
**Runway:** ~5.5 months (cash only, based on known burn ~15K NOK/mo)
Note: Full burn rate TBD — ovo je samo infrastructure + software.
**Action Items:**
1. No overdue invoices — good
2. Riad negotiation needs push (12 days stale)
3. Consider invoicing Ren Drøm for January work
Live dashboard: http://localhost:3030
Quick Commands
# Full financial snapshot
NODE_PATH=~/system/node_modules node ~/system/tools/fiken.js dashboard
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js stats
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js forecast
# Overdue chase
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js list overdue
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js remind <id>
/onboard-client
Source: ~/.claude/skills/onboard-client/SKILL.md
Onboard Client — Guided Client Onboarding Workflow
Description
Vodeni workflow za onboarding novog klijenta kroz 7 faza. Od prvog kontakta do početka developmenta. Svaka faza ima gate koji mora biti zadovoljen prije prelaska na sljedeću.
Trigger
Koristi ovaj skill kad:
- Alem kaže "novi klijent", "new client", "imamo novog klijenta"
- Novi lead treba biti pretvoren u klijenta
- Klijent postoji ali je zapeo u nekoj fazi (nastavak procesa)
Alati
- Onboarding:
~/system/tools/onboard-client.js - Pipeline:
~/system/tools/sales-pipeline.js - Contacts:
~/system/tools/contacts.js - Documents:
~/system/tools/docusign.js - Signing:
~/system/tools/send-signing-email.js - Drafts:
~/system/tools/drafts.js - CRM:
~/system/tools/unified-crm.js - Tasks:
node ~/system/tools/mc.js - Scaffold:
bash ~/system/template/scaffold.sh - Proces doc:
~/ALAI/processes/client-onboarding.md
Workflow
Korak 0: Detektuj stanje
Prije bilo čega — provjeri da li klijent već postoji:
# Provjeri contacts
NODE_PATH=~/system/node_modules node ~/system/tools/contacts.js search "<ime>"
# Provjeri pipeline
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js list
# Provjeri projekte
ls ~/projects/ | grep -i "<ime>"
- Ako klijent postoji → pitaj Alema u kojoj je fazi i nastavi od tamo
- Ako ne postoji → počni od Faze 1
Korak 1: First Contact (Faza 1)
Cilj: Zabilježi prvog kontakta, kvalificiraj lead
Prikupi podatke interaktivno od Alema:
- Ime klijenta (osoba ili firma)
- Firma (ako je osobni kontakt)
- Izvor (referral, inbound, linkedin, upwork, cold_email, website)
- Projekt tip (web app, mobile, AI, consulting, automation)
- Estimacija vrijednosti (NOK)
- Kratak opis projekta
Kreiraj lead i kontakt:
# Dodaj u pipeline
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js add "<ime>" "<email>" "<izvor>" "<opis>"
# Dodaj u contacts
NODE_PATH=~/system/node_modules node ~/system/tools/contacts.js add "<ime>" "<email>" --company "<firma>" --type client --notes "New lead: <opis>"
Gate: Lead kreiran, kontakt dodan, discovery poziv zakazan
Output: ~/ALAI/clients/<CLIENT>/intake/first-contact.md
Korak 2: Discovery (Faza 2)
Cilj: Razumij problem, ciljeve, budget, timeline
Generiši discovery pitanja za meeting:
- Koji problem rješavamo?
- Ko su korisnici?
- Koje platforme (web, mobile, desktop)?
- Koji budget range?
- Koji timeline?
- Koje integracije trebaju?
- Koji su success metrics?
Nakon discovery call-a:
- Kreiraj
project-brief.mdsa 10 sekcija (iz procesa) - Pošalji brief klijentu na potvrdu
- Advance lead u pipeline:
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js advance <lead-id> "Discovery complete, brief sent"
Gate: Brief napisan, klijent potvrdio
Output: ~/ALAI/clients/<CLIENT>/intake/discovery-notes.md, project-brief.md
Korak 3: NDA (Faza 3)
Cilj: Potpiši NDA prije dijeljenja detalja
# Kreiraj NDA od template-a
NODE_PATH=~/system/node_modules node ~/system/tools/docusign.js create "<CLIENT>" nda --field CLIENT_NAME="<ime>" --field CLIENT_EMAIL="<email>"
# Pošalji na potpis koristeći /send-for-signing workflow
# → Vidi skill: send-for-signing
OBAVEZNO: Koristi /send-for-signing skill za slanje. NIKAD ručno.
Gate: NDA potpisan od obje strane
Output: ~/ALAI/clients/<CLIENT>/legal/nda-signed.pdf
Korak 4: Proposal (Faza 4)
Cilj: Definiši scope, tech stack, faze, pricing
Proposal sadrži 10 sekcija:
- Executive Summary
- Scope of Work
- Tech Stack
- Project Phases
- Timeline
- Pricing (sa MVA 25% ako NOK)
- Payment Schedule
- Out of Scope
- Assumptions
- Validity Period
CEO GATE: Proposal MORA biti odobren od Alema prije slanja!
- Pokaži Alemu: scope, pricing, timeline
- Čekaj eksplicitno "GO" ili "SEND"
- NIKAD slati bez odobrenja (ZAKON #2)
# Advance pipeline
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js advance <lead-id> "Proposal sent, awaiting response"
Gate: Klijent prihvatio proposal (pisana potvrda)
Output: ~/ALAI/clients/<CLIENT>/intake/proposal.md
Korak 5: Contract (Faza 5)
Cilj: Potpiši ugovor, primi prvu uplatu
- Kreiraj ugovor od template-a:
NODE_PATH=~/system/node_modules node ~/system/tools/docusign.js create "<CLIENT>" contract --field CLIENT_NAME="<ime>"
- Pošalji na potpis:
/send-for-signingworkflow - Kreiraj prvu fakturu:
NODE_PATH=~/system/node_modules node ~/system/tools/invoice-generator.js create "<CLIENT>" <iznos> NOK "Project kickoff payment"
Gate: Ugovor potpisan, prva uplata primljena
Output: ~/ALAI/clients/<CLIENT>/legal/contract-signed.pdf
Korak 6: Project Setup (Faza 6)
Cilj: Scaffoldaj projekat, kreiraj backlog
# Full onboard (scaffold + lead + routing + MC task)
NODE_PATH=~/system/node_modules node ~/system/tools/onboard-client.js new "<slug>" "<email>" "<izvor>" "<vrijednost>" "<opis>"
# Ili samo scaffold
bash ~/system/template/scaffold.sh "<ProjectName>"
Kick-off agenda:
- Scope review
- Communication channels
- Access/credentials
- Sprint cadence
- Escalation path
Gate: Projekt scaffoldan, kick-off održan, backlog kreiran
Output: ~/projects/<slug>/, project.json, kick-off notes
Korak 7: Development Start (Faza 7)
Cilj: Počni sprint, delegiraj zadatke
# Advance pipeline to WON (requires --approved flag)
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js advance <lead-id> "Contract signed, project started" --approved
# Kreiraj MC task za projekat
node ~/system/tools/mc.js add "<CLIENT>: Sprint 1 planning" --priority H --route backend
Gate: First sprint isplaniran, taskovi dodijeljeni Output: Sprint backlog, MC taskovi
Pravila
- Faze se NE preskakaju — NDA mora biti potpisan prije Proposal-a
- CEO odobrava Proposal — ZAKON #2: NIKAD slati pricing bez Alemovog odobrenja
- Pipeline advance = gate pass — Advance lead SAMO kad je gate zadovoljen
- Test first za signing — Svaki dokument na potpis → test na post@alai.no prvo
- Kontakt podatke NE izmišljaj — Ako nemaš email, pitaj Alema
- WON stage = contract signed — sales-pipeline.js enforce-a --approved flag
- Sve NOK fakture sa MVA 25% — invoice-generator.js auto-dodaje
- ALAI branding — Svi dokumenti, emailovi, fakture sa ALAI brandingom
Primjer — Kompletni Onboarding
Alem: "Imamo novog klijenta — TechCorp, kontakt je Lars Olsen, lars@techcorp.no, došao preko LinkedIn. Žele AI chatbot za customer support."
John:
1. contacts.js add "Lars Olsen" "lars@techcorp.no" --company "TechCorp AS" --type client
2. sales-pipeline.js add "TechCorp AS" "lars@techcorp.no" "linkedin" "AI chatbot for customer support"
3. → Discovery call zakazan
4. → Brief napisan, klijent potvrdio
5. → NDA potpisan (send-for-signing flow)
6. → Proposal napisan, Alem odobrio, klijent prihvatio
7. → Contract potpisan, prva uplata stigla
8. → onboard-client.js new "techcorp" "lars@techcorp.no" "linkedin" "150000" "AI chatbot"
9. → Sprint 1 kreiran, agenti dodijeljeni
Status Tracking
U svakom momentu možeš provjeriti status:
# Pipeline pozicija
NODE_PATH=~/system/node_modules node ~/system/tools/sales-pipeline.js show <lead-id>
# Onboarding timeline
NODE_PATH=~/system/node_modules node ~/system/tools/onboard-client.js timeline "<client>"
# CRM overview
NODE_PATH=~/system/node_modules node ~/system/tools/unified-crm.js client "<client>"
/onboard-partner
Source: ~/.claude/skills/onboard-partner/SKILL.md
Onboard Partner — Guided Partner Onboarding Workflow
Description
Vodeni workflow za onboarding novog partnera. Od klasifikacije tipa partnera do potpisa ugovora i operativnog setup-a. Prati ~/ALAI/processes/partner-management.md proces.
Trigger
Koristi ovaj skill kad:
- Alem kaže "novi partner", "new partner", "partnerski ugovor"
- Nova kompanija želi saradnju (delivery, referral, tech, strategic)
- Postojeći kontakt prelazi u partnerski odnos
Alati
- Contacts:
~/system/tools/contacts.js - Signing:
~/system/tools/send-signing-email.js - Documents:
~/system/tools/docusign.js - Tasks:
node ~/system/tools/mc.js - Pipeline:
~/system/tools/sales-pipeline.js - Proces doc:
~/ALAI/processes/partner-management.md - Partner dir:
~/ALAI/partners/PARTNER-DIRECTORY.md
Workflow
Korak 1: Klasificiraj tip partnera
Pitaj Alema ili odredi iz konteksta:
| Tip | Opis | Revenue Model |
|---|---|---|
| Technology | Cloud, SaaS, AI provajderi | Discounts, co-marketing |
| Delivery | Dev shopovi, consulting firme | Margin 20-40% (mi invoiciramo klijenta) |
| Referral | Pojedinci/firme koji šalju klijente | Komisija 5-15% first-year |
| Strategic | Joint delivery/product partneri | Revenue split (50/50 default) |
Prikupi podatke:
- Ime partnera (firma)
- Kontakt osoba (ime, email, pozicija)
- Tip partnera (technology/delivery/referral/strategic)
- Šta nude (kratki opis)
- Revenue potencijal (godišnji estimate)
- Org. number (ako norveški)
- Zemlja
Korak 2: Due Diligence
Provjeri partnera (minimum za početak):
Financial:
- Company registry check (Proff.no za Norveška, ili lokalni registar)
- Financials: prihod, profit, stabilnost
- Bankruptcy history
Technical:
- Tech stack kompatibilnost
- Portfolio / case studies
- Security practices (GDPR, ISO 27001?)
Operational:
- Reference checks (2-3 ako moguće)
- Timezone / availability
- Team size
Kreiraj due diligence report:
~/ALAI/partners/<PARTNER>/intake/due-diligence-report.md
CEO GATE: Due diligence report → Alem + John approve → proceed Pokaži Alemu: risk assessment (H/M/L), Go/No-Go preporuku.
Korak 3: Kreiraj partner directory
mkdir -p ~/ALAI/partners/<PARTNER>/{intake,legal,comms/{meetings,check-ins,reviews},financials}
Kreiraj ~/ALAI/partners/<PARTNER>/partner-profile.md:
- Ime, tip, kontakt, revenue model
- Due diligence summary
- Strategic value
- Risk assessment
Korak 4: NDA
Pošalji NDA koristeći /send-for-signing workflow:
# Kreiraj NDA
NODE_PATH=~/system/node_modules node ~/system/tools/docusign.js create "<PARTNER>" nda \
--field CLIENT_NAME="<partner-ime>" \
--field CLIENT_EMAIL="<kontakt-email>" \
--field CLIENT_REPRESENTATIVE="<kontakt-osoba>"
# Test + Send via send-for-signing skill
Gate: NDA potpisan od obje strane
Korak 5: Partnership Agreement
Na osnovu tipa partnera, kreiraj agreement:
Delivery Partner:
- Subcontractor rate definition
- Quality standards
- IP ownership (client retains)
- SLA requirements
- Non-compete clause
Referral Partner:
- Commission structure (10% < 100K, 5% > 100K NOK)
- Lead qualification criteria
- Payment terms (after client's first payment)
- Non-solicitation
Strategic Partner:
- Revenue split (default 50/50)
- Joint delivery responsibilities
- IP ownership split
- Invoicing procedure (bi-weekly/monthly)
- Audit rights
- Exit clause
CEO GATE: Agreement → Alem pregleda i odobri → tek onda šalji na potpis.
Pošalji via /send-for-signing:
NODE_PATH=~/system/node_modules node ~/system/tools/send-signing-email.js send <template_id> \
'{"name":"Alem Basic","email":"alem@alai.no","role":"First Party"}' \
'{"name":"<Partner Contact>","email":"<email>","role":"Second Party"}' \
--subject "Partnership Agreement — ALAI x <Partner>" \
--doc-name "Partnership Agreement"
Gate: Agreement potpisan od obje strane
Korak 6: Dodaj u contacts.db
NODE_PATH=~/system/node_modules node ~/system/tools/contacts.js add "<kontakt-osoba>" "<email>" \
--company "<Partner Name>" \
--type partner \
--role "<pozicija>" \
--notes "Partner type: <tip>. Agreement signed <datum>."
Korak 7: Update Partner Directory
Dodaj entry u ~/ALAI/partners/PARTNER-DIRECTORY.md:
| <Partner Name> | <Tip> | Active | <datum> | — | <kontakt> | <revenue model> | <target> | <review date> | — | <notes> |
Korak 8: Operativni setup
- Kreiraj Slack kanal (ako potrebno):
node ~/system/tools/slack.js send general "New partner onboarded: <Partner Name> (<tip>)"
- Kreiraj MC task za praćenje:
node ~/system/tools/mc.js add "Partner onboarding: <Partner> — first joint project setup" --priority M --route bizdev
-
Zakaži monthly check-in (prvi u roku 30 dana)
-
Loguj u HiveMind:
node ~/system/agents/hivemind/hivemind.js post john event "New partner onboarded: <Partner Name> (<tip>). Agreement signed. Revenue model: <model>."
Korak 9: Revenue konfiguracija
Na osnovu tipa:
Delivery: Dogovori subcontractor rate. Dokumentuj u agreement. Referral: Definiši commission tiers. Dokumentuj u agreement. Strategic: Definiši split %. Setup dedicated payment tracking.
Pravila
- CEO odobrava — Alem mora odobriti onboarding PRIJE slanja agreement-a
- Due diligence obavezan — Minimum financial + operational check
- NDA prije Agreement-a — NDA mora biti potpisan prije partnership agreement-a
- Koristi /send-for-signing — NIKAD slati dokumente mimo standardnog flow-a
- Partner Directory update — SVAKI novi partner mora biti u PARTNER-DIRECTORY.md
- contacts.db update — SVAKI partner kontakt mora biti u contacts.db
- MC task — UVIJEK kreiraj tracking task za novog partnera
- Revenue model dokumentovan — Commission/margin/split MORA biti u agreement-u
- Review date — Postavi annual review datum (datum potpisa + 12 mjeseci)
- Exit clause — SVAKI agreement MORA imati exit clause (90 dana notice default)
Primjer — Delivery Partner
Alem: "Symphony.is želi biti delivery partner. Kontakt Adnan, adnan@symphony.is."
John:
1. Tip: Delivery (subcontracting)
2. Due diligence: 650+ engineers, Bosnia/Serbia, enterprise clients
→ Risk: M (large minimum, dependency risk)
→ Recommendation: GO with careful scope management
3. Alem approves → proceed
4. mkdir ~/ALAI/partners/symphony-is/{intake,legal,comms,financials}
5. NDA → send-for-signing → signed
6. Subcontractor Agreement → Alem reviews → send-for-signing → signed
7. contacts.js add "Adnan Cesko" "adnan@symphony.is" --company "Symphony.is" --type partner
8. PARTNER-DIRECTORY.md updated
9. MC task: "Symphony.is: first joint project identification"
10. HiveMind: "New partner: Symphony.is (Delivery). 650+ engineers. Revenue potential: $500K+/yr"
Primjer — Referral Partner
Alem: "Kerim će nam slati klijente. 10% komisija."
John:
1. Tip: Referral
2. Minimal DD (individual, known contact)
3. Simple referral agreement: 10% of first-year contract value
4. contacts.js add + PARTNER-DIRECTORY.md
5. Track referrals via sales-pipeline.js source="referral"
Status Tracking
# Partner directory
cat ~/ALAI/partners/PARTNER-DIRECTORY.md
# Partner kontakti
NODE_PATH=~/system/node_modules node ~/system/tools/contacts.js list --type partner
# Partner MC tasks
node ~/system/tools/mc.js list | grep -i partner
/send-for-signing
Source: ~/.claude/skills/send-for-signing/SKILL.md
Send Document — ALAI Branded Document Signing
Description
Generički workflow za slanje BILO KOJEG dokumenta na potpis. Pokriva NDA, DPA, ugovore, partnership agreemente, i custom dokumente. Koristi DocuSeal za signing + ALAI branded SMTP email sa embedded logom. NIKAD ne koristi DocuSeal-ov default email.
Trigger
Koristi ovaj skill kad:
- Alem kaže "pošalji na potpis", "send for signing", "treba potpis"
- "pošalji NDA", "pošalji DPA", "pošalji ugovor"
- Bilo koji dokument treba e-potpis
- Kreiraš novi ugovor/NDA/DPA i treba ga poslati
Alati
- Signing Tool:
~/system/tools/send-signing-email.js - Document Tool:
~/system/tools/docusign.js - Contacts:
~/system/tools/contacts.js - DocuSeal API:
~/system/config/docuseal.json - SMTP:
~/system/config/mail-credentials-alai.json(post@alai.no) - Logo:
~/system/context/branding/shared/alai-email-logo.png(96x96, CID inline) - Brand: primary=#308050, secondary=#0F172A, Inter font
Workflow
Korak 0: Detektuj tip dokumenta
Na osnovu konteksta odredi tip:
| Trigger | Tip | Template |
|---|---|---|
| "NDA", "non-disclosure" | NDA | docusign.js create <CLIENT> nda |
| "DPA", "data processing" | DPA | Custom HTML template |
| "ugovor", "contract" | Contract | docusign.js create <CLIENT> contract |
| "partnership", "partnerski" | Partnership | Custom HTML template |
| "proposal" | Proposal | docusign.js create <CLIENT> proposal |
| Custom | Custom | Kreiraj HTML od nule |
Korak 1: Auto-populate iz contacts.db
Ako klijent/partner postoji u sistemu, automatski popuni polja:
# Nađi kontakt
NODE_PATH=~/system/node_modules node ~/system/tools/contacts.js search "<ime>"
NODE_PATH=~/system/node_modules node ~/system/tools/contacts.js show <id>
Izvuci: ime, email, firma, org_number, adresa. Ako ne postoji → pitaj Alema za podatke.
Korak 2: Pripremi HTML dokument
- Za poznate tipove koristi
docusign.js:
NODE_PATH=~/system/node_modules node ~/system/tools/docusign.js create "<CLIENT>" nda \
--field CLIENT_NAME="<ime>" \
--field CLIENT_EMAIL="<email>" \
--field CLIENT_REPRESENTATIVE="<osoba>" \
--field PROJECT_DESCRIPTION="<opis>"
- Za custom dokumente: konvertuj MD → HTML sa čistim A4 stilom
OBAVEZNO: Dodaj DocuSeal field tagove u signature sekciju:
<signature-field name="Signer1 Signature" role="First Party"></signature-field>
<date-field name="Signer1 Date" role="First Party"></date-field>
<signature-field name="Signer2 Signature" role="Second Party"></signature-field>
<date-field name="Signer2 Date" role="Second Party"></date-field>
KLJUČNO: Koristi role= atribut (NE data-submitter=!)
Svaki potpisnik = zasebna role vrijednost ("First Party", "Second Party", itd.)
Korak 3: Kreiraj DocuSeal template
curl -s -X POST "https://docuseal.eu/api/templates/html" \
-H "X-Auth-Token: <token>" \
-H "Content-Type: application/json" \
-d @template.json
- Provjeri response:
submittersarray mora imati ONOLIKO submittera koliko imarolevrijednosti - Provjeri
fieldsarray: svaki field mora imati odgovarajućisubmitter_uuid
Korak 4: TEST PRVO (OBAVEZNO!)
NODE_PATH=~/system/node_modules node ~/system/tools/send-signing-email.js test post@alai.no
- Provjeri da je email stigao na post@alai.no
- Provjeri: ALAI logo (embedded), zeleni button, signing link, branding
- TEK nakon uspješnog testa → šalji pravi email
Korak 5: Pošalji na potpis
NODE_PATH=~/system/node_modules node ~/system/tools/send-signing-email.js send <template_id> \
'{"name":"Alem Basic","email":"alem@alai.no","role":"First Party"}' \
'{"name":"Signer Name","email":"signer@email.com","role":"Second Party"}' \
--subject "Document Name — ALAI x Partner" \
--doc-name "Document Ready for Signature" \
--changes "Key point 1|Key point 2|Key point 3"
Korak 6: Provjeri status
NODE_PATH=~/system/node_modules node ~/system/tools/send-signing-email.js check <submission_id>
Korak 7: Post-signing
Nakon potpisa:
- Download potpisan PDF
- Spremi u
~/ALAI/clients/<CLIENT>/legal/ili~/ALAI/partners/<PARTNER>/legal/ - Loguj u HiveMind:
node ~/system/agents/hivemind/hivemind.js post john event "Document signed: <type> with <party>"
- Advance pipeline ako je dio onboarding procesa
Pravila
- UVIJEK ALAI branding — NIKAD DocuSeal default email (send_email: false)
- UVIJEK test prvo — Pošalji na post@alai.no, verifikuj, pa šalji za pravo
- Alemov email = alem@alai.no — NE info@alai.no (info@ čita John)
- Logo = CID inline — NE eksterni URL (email klijenti blokiraju)
- From = post@alai.no — "ALAI Holding AS" sender name
- NE spamaj submissione — Jednom testiraj, jednom pošalji
- DocuSeal HTML tagovi —
role=atribut, NEdata-submitter= - Order = preserved — Prvi potpisnik potpiše, drugi dobije email nakon
- Auto-populate — Uvijek provjeri contacts.db prije ručnog unosa
- Storage obavezan — Potpisan dokument MORA biti sačuvan u legal/ direktoriju
Email Template Struktura
+-----------------------------+
| [ALAI Logo - CID inline] | <- #0F172A background
| ALAI Holding AS |
| Document Signing |
+-----------------------------+
| |
| Document Title | <- #1A1A1A, 18px
| |
| Dear {name}, |
| A document is ready... |
| |
| +- KEY DETAILS -----------+ | <- #F8FAFC box
| | * Change 1 | |
| | * Change 2 | |
| +---------------------------+ |
| |
| [ Review & Sign Document ] | <- #308050 button
| |
| Unique link warning |
| Contact: info@alai.no |
+-----------------------------+
| ALAI Holding AS | <- #F8FAFC footer
| Org.nr 932 516 136 |
| Ilemoen 4A, 2040 Klofta |
+-----------------------------+
Primjeri
NDA
# 1. Kreiraj NDA
NODE_PATH=~/system/node_modules node ~/system/tools/docusign.js create "TechCorp" nda \
--field CLIENT_NAME="TechCorp AS" --field CLIENT_EMAIL="lars@techcorp.no" \
--field CLIENT_REPRESENTATIVE="Lars Olsen" --field PROJECT_DESCRIPTION="AI Chatbot"
# 2. Upload HTML kao DocuSeal template (sa role= tagovima)
# 3. Test
NODE_PATH=~/system/node_modules node ~/system/tools/send-signing-email.js test post@alai.no
# 4. Pošalji
NODE_PATH=~/system/node_modules node ~/system/tools/send-signing-email.js send <template_id> \
'{"name":"Alem Basic","email":"alem@alai.no","role":"First Party"}' \
'{"name":"Lars Olsen","email":"lars@techcorp.no","role":"Second Party"}' \
--subject "NDA — ALAI x TechCorp" \
--doc-name "Non-Disclosure Agreement" \
--changes "Duration: 2 years|Jurisdiction: Norway"
Partnership Agreement
NODE_PATH=~/system/node_modules node ~/system/tools/send-signing-email.js send 406709 \
'{"name":"Alem Basic","email":"alem@alai.no","role":"First Party"}' \
'{"name":"Anel Pasic","email":"anelwizard@gmail.com","role":"Second Party"}' \
--subject "Partnership Agreement — ALAI x Wizard NUF" \
--doc-name "Partnership Agreement Ready for Signature" \
--changes "Invoicing: monthly to every 2 months|Split: 50/50 (unchanged)"
Troubleshooting
| Problem | Rješenje |
|---|---|
| "Template does not contain fields" | HTML nema DocuSeal tagove (<signature-field>, <date-field>) |
| Samo jedan submitter u template | Koristiš data-submitter= umjesto role= |
| Logo ne prikazuje | Provjeri da alai-email-logo.png postoji i da se koristi CID attachment |
| Email ne stiže | Provjeri SMTP credentials u mail-credentials-alai.json |
| Signing link ne radi | DocuSeal sandbox mode — link radi ali ima sandbox banner |
| Kontakt nema email | Provjeri contacts.js show — pitaj Alema ako nedostaje |
Design Skills
/canvas-design
Source: ~/.claude/skills/canvas-design/SKILL.md
name: canvas-design description: Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations. license: Complete terms in LICENSE.txt
These are instructions for creating design philosophies - aesthetic movements that are then EXPRESSED VISUALLY. Output only .md files, .pdf files, and .png files.
Complete this in two steps:
- Design Philosophy Creation (.md file)
- Express by creating it on a canvas (.pdf file or .png file)
First, undertake this task:
DESIGN PHILOSOPHY CREATION
To begin, create a VISUAL PHILOSOPHY (not layouts or templates) that will be interpreted through:
- Form, space, color, composition
- Images, graphics, shapes, patterns
- Minimal text as visual accent
THE CRITICAL UNDERSTANDING
- What is received: Some subtle input or instructions by the user that should be taken into account, but used as a foundation; it should not constrain creative freedom.
- What is created: A design philosophy/aesthetic movement.
- What happens next: Then, the same version receives the philosophy and EXPRESSES IT VISUALLY - creating artifacts that are 90% visual design, 10% essential text.
Consider this approach:
- Write a manifesto for an art movement
- The next phase involves making the artwork
The philosophy must emphasize: Visual expression. Spatial communication. Artistic interpretation. Minimal words.
HOW TO GENERATE A VISUAL PHILOSOPHY
Name the movement (1-2 words): "Brutalist Joy" / "Chromatic Silence" / "Metabolist Dreams"
Articulate the philosophy (4-6 paragraphs - concise but complete):
To capture the VISUAL essence, express how the philosophy manifests through:
- Space and form
- Color and material
- Scale and rhythm
- Composition and balance
- Visual hierarchy
CRITICAL GUIDELINES:
- Avoid redundancy: Each design aspect should be mentioned once. Avoid repeating points about color theory, spatial relationships, or typographic principles unless adding new depth.
- Emphasize craftsmanship REPEATEDLY: The philosophy MUST stress multiple times that the final work should appear as though it took countless hours to create, was labored over with care, and comes from someone at the absolute top of their field. This framing is essential - repeat phrases like "meticulously crafted," "the product of deep expertise," "painstaking attention," "master-level execution."
- Leave creative space: Remain specific about the aesthetic direction, but concise enough that the next Claude has room to make interpretive choices also at a extremely high level of craftmanship.
The philosophy must guide the next version to express ideas VISUALLY, not through text. Information lives in design, not paragraphs.
PHILOSOPHY EXAMPLES
"Concrete Poetry" Philosophy: Communication through monumental form and bold geometry. Visual expression: Massive color blocks, sculptural typography (huge single words, tiny labels), Brutalist spatial divisions, Polish poster energy meets Le Corbusier. Ideas expressed through visual weight and spatial tension, not explanation. Text as rare, powerful gesture - never paragraphs, only essential words integrated into the visual architecture. Every element placed with the precision of a master craftsman.
"Chromatic Language" Philosophy: Color as the primary information system. Visual expression: Geometric precision where color zones create meaning. Typography minimal - small sans-serif labels letting chromatic fields communicate. Think Josef Albers' interaction meets data visualization. Information encoded spatially and chromatically. Words only to anchor what color already shows. The result of painstaking chromatic calibration.
"Analog Meditation" Philosophy: Quiet visual contemplation through texture and breathing room. Visual expression: Paper grain, ink bleeds, vast negative space. Photography and illustration dominate. Typography whispered (small, restrained, serving the visual). Japanese photobook aesthetic. Images breathe across pages. Text appears sparingly - short phrases, never explanatory blocks. Each composition balanced with the care of a meditation practice.
"Organic Systems" Philosophy: Natural clustering and modular growth patterns. Visual expression: Rounded forms, organic arrangements, color from nature through architecture. Information shown through visual diagrams, spatial relationships, iconography. Text only for key labels floating in space. The composition tells the story through expert spatial orchestration.
"Geometric Silence" Philosophy: Pure order and restraint. Visual expression: Grid-based precision, bold photography or stark graphics, dramatic negative space. Typography precise but minimal - small essential text, large quiet zones. Swiss formalism meets Brutalist material honesty. Structure communicates, not words. Every alignment the work of countless refinements.
These are condensed examples. The actual design philosophy should be 4-6 substantial paragraphs.
ESSENTIAL PRINCIPLES
- VISUAL PHILOSOPHY: Create an aesthetic worldview to be expressed through design
- MINIMAL TEXT: Always emphasize that text is sparse, essential-only, integrated as visual element - never lengthy
- SPATIAL EXPRESSION: Ideas communicate through space, form, color, composition - not paragraphs
- ARTISTIC FREEDOM: The next Claude interprets the philosophy visually - provide creative room
- PURE DESIGN: This is about making ART OBJECTS, not documents with decoration
- EXPERT CRAFTSMANSHIP: Repeatedly emphasize the final work must look meticulously crafted, labored over with care, the product of countless hours by someone at the top of their field
The design philosophy should be 4-6 paragraphs long. Fill it with poetic design philosophy that brings together the core vision. Avoid repeating the same points. Keep the design philosophy generic without mentioning the intention of the art, as if it can be used wherever. Output the design philosophy as a .md file.
DEDUCING THE SUBTLE REFERENCE
CRITICAL STEP: Before creating the canvas, identify the subtle conceptual thread from the original request.
THE ESSENTIAL PRINCIPLE: The topic is a subtle, niche reference embedded within the art itself - not always literal, always sophisticated. Someone familiar with the subject should feel it intuitively, while others simply experience a masterful abstract composition. The design philosophy provides the aesthetic language. The deduced topic provides the soul - the quiet conceptual DNA woven invisibly into form, color, and composition.
This is VERY IMPORTANT: The reference must be refined so it enhances the work's depth without announcing itself. Think like a jazz musician quoting another song - only those who know will catch it, but everyone appreciates the music.
CANVAS CREATION
With both the philosophy and the conceptual framework established, express it on a canvas. Take a moment to gather thoughts and clear the mind. Use the design philosophy created and the instructions below to craft a masterpiece, embodying all aspects of the philosophy with expert craftsmanship.
IMPORTANT: For any type of content, even if the user requests something for a movie/game/book, the approach should still be sophisticated. Never lose sight of the idea that this should be art, not something that's cartoony or amateur.
To create museum or magazine quality work, use the design philosophy as the foundation. Create one single page, highly visual, design-forward PDF or PNG output (unless asked for more pages). Generally use repeating patterns and perfect shapes. Treat the abstract philosophical design as if it were a scientific bible, borrowing the visual language of systematic observation—dense accumulation of marks, repeated elements, or layered patterns that build meaning through patient repetition and reward sustained viewing. Add sparse, clinical typography and systematic reference markers that suggest this could be a diagram from an imaginary discipline, treating the invisible subject with the same reverence typically reserved for documenting observable phenomena. Anchor the piece with simple phrase(s) or details positioned subtly, using a limited color palette that feels intentional and cohesive. Embrace the paradox of using analytical visual language to express ideas about human experience: the result should feel like an artifact that proves something ephemeral can be studied, mapped, and understood through careful attention. This is true art.
Text as a contextual element: Text is always minimal and visual-first, but let context guide whether that means whisper-quiet labels or bold typographic gestures. A punk venue poster might have larger, more aggressive type than a minimalist ceramics studio identity. Most of the time, font should be thin. All use of fonts must be design-forward and prioritize visual communication. Regardless of text scale, nothing falls off the page and nothing overlaps. Every element must be contained within the canvas boundaries with proper margins. Check carefully that all text, graphics, and visual elements have breathing room and clear separation. This is non-negotiable for professional execution. IMPORTANT: Use different fonts if writing text. Search the ./canvas-fonts directory. Regardless of approach, sophistication is non-negotiable.
Download and use whatever fonts are needed to make this a reality. Get creative by making the typography actually part of the art itself -- if the art is abstract, bring the font onto the canvas, not typeset digitally.
To push boundaries, follow design instinct/intuition while using the philosophy as a guiding principle. Embrace ultimate design freedom and choice. Push aesthetics and design to the frontier.
CRITICAL: To achieve human-crafted quality (not AI-generated), create work that looks like it took countless hours. Make it appear as though someone at the absolute top of their field labored over every detail with painstaking care. Ensure the composition, spacing, color choices, typography - everything screams expert-level craftsmanship. Double-check that nothing overlaps, formatting is flawless, every detail perfect. Create something that could be shown to people to prove expertise and rank as undeniably impressive.
Output the final result as a single, downloadable .pdf or .png file, alongside the design philosophy used as a .md file.
FINAL STEP
IMPORTANT: The user ALREADY said "It isn't perfect enough. It must be pristine, a masterpiece if craftsmanship, as if it were about to be displayed in a museum."
CRITICAL: To refine the work, avoid adding more graphics; instead refine what has been created and make it extremely crisp, respecting the design philosophy and the principles of minimalism entirely. Rather than adding a fun filter or refactoring a font, consider how to make the existing composition more cohesive with the art. If the instinct is to call a new function or draw a new shape, STOP and instead ask: "How can I make what's already here more of a piece of art?"
Take a second pass. Go back to the code and refine/polish further to make this a philosophically designed masterpiece.
MULTI-PAGE OPTION
To create additional pages when requested, create more creative pages along the same lines as the design philosophy but distinctly different as well. Bundle those pages in the same .pdf or many .pngs. Treat the first page as just a single page in a whole coffee table book waiting to be filled. Make the next pages unique twists and memories of the original. Have them almost tell a story in a very tasteful way. Exercise full creative freedom.
/frontend-design
Source: ~/.claude/skills/frontend-design/SKILL.md
name: frontend-design description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics. license: Complete terms in LICENSE.txt
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction:
- Purpose: What problem does this interface solve? Who uses it?
- Tone: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- Constraints: Technical requirements (framework, performance, accessibility).
- Differentiation: What makes this UNFORGETTABLE? What's the one thing someone will remember?
CRITICAL: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail
Frontend Aesthetics Guidelines
Focus on:
- Typography: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- Color & Theme: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- Motion: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- Spatial Composition: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- Backgrounds & Visual Details: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
IMPORTANT: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
/figma-design
Source: ~/.claude/skills/figma-design/SKILL.md
name: figma-design description: "Figma specialist for EXISTING Figma files. Extracts tokens, generates React code, validates builds, exports assets. Use when working WITH a Figma file. For NEW designs from scratch, use /design-system." argument-hint: "[Figma file key or 'drop' shortcut] [what to do — extract, generate, validate, export]"
Figma Design Specialist
You work with EXISTING Figma files — extracting tokens, generating code, validating builds, and exporting assets.
Knowledge Base: ~/system/context/figma-knowledge-base.md — READ THIS FIRST for all Figma operations.
When This Skill Triggers
- "extract from Figma", "Figma tokens", "export from Figma"
- "generate code from Figma", "Figma to React"
- "compare with Figma", "validate against Figma"
- Working with an existing Figma file
- Design token extraction or sync
Tools Available
| Tool | Command | What It Does |
|---|---|---|
figma-extract.js |
extract-tokens, extract-components, frame-to-prompt, export-image, list-nodes |
Read Figma REST API |
figma-to-react.js |
<file-key> <node-id> |
Generate React + Tailwind from Figma frame |
figma-token-sync.js |
<file-key> --format all |
Variables → W3C tokens + Tailwind + CSS vars |
figma-validate.js |
compare <file-key> <node-id> <url> |
Visual diff: Figma vs built page |
design-to-code.js |
assemble --stitch-code X --assets-dir Y |
Stitch HTML → React TSX |
Common Workflows
Extract Design Tokens
# Get all tokens (colors, typography, spacing)
node ~/system/tools/figma-extract.js extract-tokens <file-key>
# Sync variables to code-ready formats
node ~/system/tools/figma-token-sync.js <file-key> --format all --output ./tokens/
Generate React from Figma Frame
# List nodes to find the right frame
node ~/system/tools/figma-extract.js list-nodes <file-key>
# Generate React + Tailwind component
node ~/system/tools/figma-to-react.js <file-key> <node-id> --output ./src/components/Screen.tsx
Export Assets
# Export frame as PNG (2x retina)
node ~/system/tools/figma-extract.js export-image <file-key> <node-id> --format png --scale 2 --output ./public/frame.png
# Export as SVG (icons, logos)
node ~/system/tools/figma-extract.js export-image <file-key> <node-id> --format svg --output ./public/icon.svg
Validate Build Against Design
# Compare Figma design vs built page — outputs diff report
node ~/system/tools/figma-validate.js compare <file-key> <node-id> http://localhost:3000/login
Drop App Shortcuts
File key: P535qC6nAREfoTsMWfOqqi
| Page | Node ID | Description |
|---|---|---|
| Page 1 | 0:1 |
Main page |
| Design System | 6:2 |
Colors, typography, components |
| Screen Test | 6:140 |
Component testing |
| Screens | 6:142 |
Full app screens |
| Login v2 UX | 6:175 |
Login flow variations |
Figma REST API Quick Reference
| Endpoint | What |
|---|---|
GET /v1/files/{key} |
Full file data |
GET /v1/files/{key}/nodes?ids=X |
Specific nodes |
GET /v1/images/{key}?ids=X&format=png&scale=2 |
Export as image |
GET /v1/files/{key}/variables/local |
Design variables |
GET /v1/files/{key}/components |
Components |
Auth: X-Figma-Token header from ~/system/config/figma.json
Rate limits: 6-20 req/min (Tier 1), use 200ms delay between requests
Export: PNG/JPG up to 4x scale (max 32MP), SVG/PDF at 1x only, URLs expire in 30 days
Figma Live Bridge (WebSocket — PROTOTYPE)
For direct manipulation of Figma Desktop. Reliability: ~40% — use REST API tools above instead when possible.
Setup: Figma Desktop → Plugins → Development → Claude MCP Plugin → get channel ID
Connection: WebSocket on port 3055 via bun socket
Commands: create_frame, create_text, create_rectangle, create_ellipse, set_fill_color, get_document_info, get_node_info
Timeouts: set_corner_radius, set_effects, set_auto_layout — use workarounds
Design Token Architecture (3-Tier)
PRIMITIVE → raw values (blue-500, spacing-16)
SEMANTIC → purpose (color-primary → blue-500)
COMPONENT → scoped (button-bg → color-primary)
Naming: {category}-{role}-{modifier}-{state} (kebab-case)
Modes: Light/Dark via Figma Variable modes
Visual Verification (ZAKON #0.1)
ALWAYS validate builds against Figma. Run figma-validate.js before claiming "done".
List DIFFERENCES, not similarities. If you can't find any differences, you're not looking carefully enough.
Quality Checklist
- Tokens extracted from Figma (not guessed)
- Colors match Figma exactly (verified with hex comparison)
- Typography matches (font family, size, weight, line-height)
- Spacing matches (padding, gaps, margins)
- Assets exported from Figma (not hand-drawn SVG)
- Visual validation run (
figma-validate.js) - Difference percentage below 10%
Reference
- Knowledge base:
~/system/context/figma-knowledge-base.md - REST API reference:
~/system/context/figma-rest-api-reference-2025-2026.md - Config:
~/system/config/figma.json - Manifest:
~/system/tools/manifest.md
/design-system
Source: ~/.claude/skills/design-system/SKILL.md
name: design-system description: "End-to-end design-to-build pipeline for NEW projects. Stitch generates designs → Figma import → extract tokens → generate code → validate. Use for new designs from scratch." argument-hint: "[project brief — app name, industry, brand colors, screens to design]"
Design-to-Build Pipeline
Create new designs from scratch and build them into production code. For working with EXISTING Figma files, use /figma-design instead.
Knowledge Base: ~/system/context/figma-knowledge-base.md — READ THIS FIRST.
The Pipeline (7 Steps)
BRIEF → STITCH → FIGMA → EXTRACT → BUILD → VALIDATE → DEPLOY
↓ ↓ ↓ ↓ ↓ ↓ ↓
Spec Generate Import Tokens React Compare Ship
(FREE) (manual) (auto) (auto) (visual)
Step 1: BRIEF — Parse Requirements
Extract from request:
- App name and industry (fintech, SaaS, music, consulting)
- Brand — primary color, accent, background
- Screens to design (login, dashboard, send-money, etc.)
- Elements — what must be on screen
- Audience — who uses this
- Vibe — 3 keywords (e.g., "trustworthy, effortless, Scandinavian")
Step 2: STITCH — Generate Design (FREE)
node ~/system/tools/stitch-generate.js \
--brief "[APP]" --screen "[SCREEN]" --industry "[INDUSTRY]" \
--primary "[HEX]" --secondary "[HEX]" \
--vibe "[KW1], [KW2], [KW3]" \
--elements "[EL1],[EL2],[EL3]" \
--model pro --options 3
Output: 3 style variants in ~/system/design-output/stitch-<timestamp>/
Present for approval:
node ~/system/tools/design-board.js create "[Project] [Screen]" "reviewer@email" \
--options '[...png paths...]' --recommend 2
WAIT for CEO/client approval before proceeding.
Step 3: FIGMA — Import Approved Design (Manual Step)
Method A: In Stitch, "Copy to Figma" → Cmd+V in Figma Desktop
Method B: Download HTML/CSS from Stitch → use html.to.design plugin in Figma (80-90% accuracy)
Method C: Use figma-populate.js WebSocket bridge (unreliable — last resort)
After import: Figma IS the source of truth. All subsequent work reads FROM Figma.
Step 4: EXTRACT — Tokens + Assets from Figma
# Design tokens → all formats
node ~/system/tools/figma-token-sync.js <file-key> --format all --output ./tokens/
# Individual assets (logos, icons)
node ~/system/tools/figma-extract.js export-image <file-key> <node-id> --format svg --output ./public/logo.svg
# Generate implementation prompt
node ~/system/tools/figma-extract.js frame-to-prompt <file-key> <node-id>
Step 5: BUILD — Code from Figma Data
Option A: Direct Figma → React (NEW)
node ~/system/tools/figma-to-react.js <file-key> <node-id> --output ./src/app/page.tsx
Option B: Stitch HTML → React (existing)
node ~/system/tools/design-to-code.js assemble \
--stitch-code code.html --assets-dir exports/ --target-page page.tsx --preserve-logic
Rules:
- Logo/icons → exported from Figma, NEVER hand-drawn SVG
- Colors → extracted token values, not guessed hex codes
- Typography → match extracted font/size/weight exactly
- Spacing → match extracted spacing values
- Layout → follow Auto Layout → Flexbox mapping
Step 6: VALIDATE — Compare Code to Design
node ~/system/tools/figma-validate.js compare <file-key> <node-id> http://localhost:3000/page
ZAKON #0.1: List DIFFERENCES, not similarities. If diff > 10%, fix before proceeding.
Step 7: DEPLOY
# Docker build for Fly.io
docker build -t app .
flyctl deploy
# Or Vercel
vercel --prod
Design System Architecture
Token Structure (3-Tier MANDATORY)
PRIMITIVE: blue-500, gray-900, spacing-16
↓
SEMANTIC: color-primary, text-primary, spacing-md
↓
COMPONENT: button-bg-primary, card-padding, input-border-focus
Typography Scale (Major Third 1.25)
| Size | Use | Tailwind |
|---|---|---|
| 12px | Caption | text-xs |
| 14px | Small body | text-sm |
| 16px | Body (BASE) | text-base |
| 20px | Subtitle | text-xl |
| 25px | H3 | text-2xl |
| 31px | H2 | text-3xl |
| 39px | H1 | text-4xl |
Spacing Grid (4px baseline + 8pt elements)
| Value | Token | Tailwind | Use |
|---|---|---|---|
| 4px | spacing-xs | p-1 |
Micro spacing |
| 8px | spacing-sm | p-2 |
Between label↔input |
| 12px | spacing-md-sm | p-3 |
Compact padding |
| 16px | spacing-md | p-4 |
Form fields |
| 24px | spacing-lg | p-6 |
Card padding |
| 32px | spacing-xl | p-8 |
Between sections |
| 48px | spacing-2xl | p-12 |
Major breaks |
| 64px | spacing-3xl | p-16 |
Hero spacing |
Color System
Semantic tokens (minimum set):
color-primary,color-secondary,color-accentcolor-success,color-warning,color-error,color-infotext-primary,text-secondary,text-disabledsurface-base,surface-raised,surface-overlayborder-default,border-focus,border-error
WCAG: AA minimum (4.5:1 normal text, 3:1 large text)
Elevation (5 levels)
| Level | Use | CSS |
|---|---|---|
| 0 | Flat | none |
| 1 | Cards | shadow-sm |
| 2 | Buttons | shadow-md |
| 3 | Dropdowns | shadow-lg |
| 4 | Modals | shadow-xl |
Industry Patterns
Fintech
- Trust signals: green, navy, white
- BankID/Vipps prominence, security cues
- Biometric-first auth, generous whitespace
- Clear number formatting, transaction lists
SaaS
- Clean layouts, data visualization
- System fonts, minimal palette
- Dashboard patterns, data tables
Music/Creative
- Dark themes, neon accents
- Bold typography, gradient meshes
- Dynamic visuals, high contrast
Professional Services
- Minimal, structured layouts
- Restrained palette, strong hierarchy
- Corporate, competent, reliable
Quality Gate (MANDATORY before delivery)
- Design approved by CEO/client (Step 3 gate)
- Tokens extracted from Figma (NOT guessed)
-
figma-validate.jsrun — diff < 10% - DIFFERENCES listed explicitly
- All assets exported from Figma
- WCAG AA contrast verified
- Mobile touch targets ≥ 44px
Reference
- Knowledge base:
~/system/context/figma-knowledge-base.md - REST API:
~/system/context/figma-rest-api-reference-2025-2026.md - Config:
~/system/config/figma.json - Tools:
~/system/tools/manifest.md
/brand-guidelines
Source: ~/.claude/skills/brand-guidelines/SKILL.md
name: brand-guidelines description: Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply. license: Complete terms in LICENSE.txt
Anthropic Brand Styling
Overview
To access Anthropic's official brand identity and style resources, use this skill.
Keywords: branding, corporate identity, visual identity, post-processing, styling, brand colors, typography, Anthropic brand, visual formatting, visual design
Brand Guidelines
Colors
Main Colors:
- Dark:
#141413- Primary text and dark backgrounds - Light:
#faf9f5- Light backgrounds and text on dark - Mid Gray:
#b0aea5- Secondary elements - Light Gray:
#e8e6dc- Subtle backgrounds
Accent Colors:
- Orange:
#d97757- Primary accent - Blue:
#6a9bcc- Secondary accent - Green:
#788c5d- Tertiary accent
Typography
- Headings: Poppins (with Arial fallback)
- Body Text: Lora (with Georgia fallback)
- Note: Fonts should be pre-installed in your environment for best results
Features
Smart Font Application
- Applies Poppins font to headings (24pt and larger)
- Applies Lora font to body text
- Automatically falls back to Arial/Georgia if custom fonts unavailable
- Preserves readability across all systems
Text Styling
- Headings (24pt+): Poppins font
- Body text: Lora font
- Smart color selection based on background
- Preserves text hierarchy and formatting
Shape and Accent Colors
- Non-text shapes use accent colors
- Cycles through orange, blue, and green accents
- Maintains visual interest while staying on-brand
Technical Details
Font Management
- Uses system-installed Poppins and Lora fonts when available
- Provides automatic fallback to Arial (headings) and Georgia (body)
- No font installation required - works with existing system fonts
- For best results, pre-install Poppins and Lora fonts in your environment
Color Application
- Uses RGB color values for precise brand matching
- Applied via python-pptx's RGBColor class
- Maintains color fidelity across different systems
Document Skills
Source: ~/.claude/skills/pdf/SKILL.md
name: pdf description: Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill. license: Proprietary. LICENSE.txt has complete terms
PDF Processing Guide
Overview
This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see REFERENCE.md. If you need to fill out a PDF form, read FORMS.md and follow its instructions.
Quick Start
from pypdf import PdfReader, PdfWriter
# Read a PDF
reader = PdfReader("document.pdf")
print(f"Pages: {len(reader.pages)}")
# Extract text
text = ""
for page in reader.pages:
text += page.extract_text()
Python Libraries
pypdf - Basic Operations
Merge PDFs
from pypdf import PdfWriter, PdfReader
writer = PdfWriter()
for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
reader = PdfReader(pdf_file)
for page in reader.pages:
writer.add_page(page)
with open("merged.pdf", "wb") as output:
writer.write(output)
Split PDF
reader = PdfReader("input.pdf")
for i, page in enumerate(reader.pages):
writer = PdfWriter()
writer.add_page(page)
with open(f"page_{i+1}.pdf", "wb") as output:
writer.write(output)
Extract Metadata
reader = PdfReader("document.pdf")
meta = reader.metadata
print(f"Title: {meta.title}")
print(f"Author: {meta.author}")
print(f"Subject: {meta.subject}")
print(f"Creator: {meta.creator}")
Rotate Pages
reader = PdfReader("input.pdf")
writer = PdfWriter()
page = reader.pages[0]
page.rotate(90) # Rotate 90 degrees clockwise
writer.add_page(page)
with open("rotated.pdf", "wb") as output:
writer.write(output)
pdfplumber - Text and Table Extraction
Extract Text with Layout
import pdfplumber
with pdfplumber.open("document.pdf") as pdf:
for page in pdf.pages:
text = page.extract_text()
print(text)
Extract Tables
with pdfplumber.open("document.pdf") as pdf:
for i, page in enumerate(pdf.pages):
tables = page.extract_tables()
for j, table in enumerate(tables):
print(f"Table {j+1} on page {i+1}:")
for row in table:
print(row)
Advanced Table Extraction
import pandas as pd
with pdfplumber.open("document.pdf") as pdf:
all_tables = []
for page in pdf.pages:
tables = page.extract_tables()
for table in tables:
if table: # Check if table is not empty
df = pd.DataFrame(table[1:], columns=table[0])
all_tables.append(df)
# Combine all tables
if all_tables:
combined_df = pd.concat(all_tables, ignore_index=True)
combined_df.to_excel("extracted_tables.xlsx", index=False)
reportlab - Create PDFs
Basic PDF Creation
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
c = canvas.Canvas("hello.pdf", pagesize=letter)
width, height = letter
# Add text
c.drawString(100, height - 100, "Hello World!")
c.drawString(100, height - 120, "This is a PDF created with reportlab")
# Add a line
c.line(100, height - 140, 400, height - 140)
# Save
c.save()
Create PDF with Multiple Pages
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet
doc = SimpleDocTemplate("report.pdf", pagesize=letter)
styles = getSampleStyleSheet()
story = []
# Add content
title = Paragraph("Report Title", styles['Title'])
story.append(title)
story.append(Spacer(1, 12))
body = Paragraph("This is the body of the report. " * 20, styles['Normal'])
story.append(body)
story.append(PageBreak())
# Page 2
story.append(Paragraph("Page 2", styles['Heading1']))
story.append(Paragraph("Content for page 2", styles['Normal']))
# Build PDF
doc.build(story)
Subscripts and Superscripts
IMPORTANT: Never use Unicode subscript/superscript characters (₀₁₂₃₄₅₆₇₈₉, ⁰¹²³⁴⁵⁶⁷⁸⁹) in ReportLab PDFs. The built-in fonts do not include these glyphs, causing them to render as solid black boxes.
Instead, use ReportLab's XML markup tags in Paragraph objects:
from reportlab.platypus import Paragraph
from reportlab.lib.styles import getSampleStyleSheet
styles = getSampleStyleSheet()
# Subscripts: use <sub> tag
chemical = Paragraph("H<sub>2</sub>O", styles['Normal'])
# Superscripts: use <super> tag
squared = Paragraph("x<super>2</super> + y<super>2</super>", styles['Normal'])
For canvas-drawn text (not Paragraph objects), manually adjust font the size and position rather than using Unicode subscripts/superscripts.
Command-Line Tools
pdftotext (poppler-utils)
# Extract text
pdftotext input.pdf output.txt
# Extract text preserving layout
pdftotext -layout input.pdf output.txt
# Extract specific pages
pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5
qpdf
# Merge PDFs
qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
# Split pages
qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
qpdf input.pdf --pages . 6-10 -- pages6-10.pdf
# Rotate pages
qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees
# Remove password
qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf
pdftk (if available)
# Merge
pdftk file1.pdf file2.pdf cat output merged.pdf
# Split
pdftk input.pdf burst
# Rotate
pdftk input.pdf rotate 1east output rotated.pdf
Common Tasks
Extract Text from Scanned PDFs
# Requires: pip install pytesseract pdf2image
import pytesseract
from pdf2image import convert_from_path
# Convert PDF to images
images = convert_from_path('scanned.pdf')
# OCR each page
text = ""
for i, image in enumerate(images):
text += f"Page {i+1}:\n"
text += pytesseract.image_to_string(image)
text += "\n\n"
print(text)
Add Watermark
from pypdf import PdfReader, PdfWriter
# Create watermark (or load existing)
watermark = PdfReader("watermark.pdf").pages[0]
# Apply to all pages
reader = PdfReader("document.pdf")
writer = PdfWriter()
for page in reader.pages:
page.merge_page(watermark)
writer.add_page(page)
with open("watermarked.pdf", "wb") as output:
writer.write(output)
Extract Images
# Using pdfimages (poppler-utils)
pdfimages -j input.pdf output_prefix
# This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc.
Password Protection
from pypdf import PdfReader, PdfWriter
reader = PdfReader("input.pdf")
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
# Add password
writer.encrypt("userpassword", "ownerpassword")
with open("encrypted.pdf", "wb") as output:
writer.write(output)
Quick Reference
| Task | Best Tool | Command/Code |
|---|---|---|
| Merge PDFs | pypdf | writer.add_page(page) |
| Split PDFs | pypdf | One page per file |
| Extract text | pdfplumber | page.extract_text() |
| Extract tables | pdfplumber | page.extract_tables() |
| Create PDFs | reportlab | Canvas or Platypus |
| Command line merge | qpdf | qpdf --empty --pages ... |
| OCR scanned PDFs | pytesseract | Convert to image first |
| Fill PDF forms | pdf-lib or pypdf (see FORMS.md) | See FORMS.md |
Next Steps
- For advanced pypdfium2 usage, see REFERENCE.md
- For JavaScript libraries (pdf-lib), see REFERENCE.md
- If you need to fill out a PDF form, follow the instructions in FORMS.md
- For troubleshooting guides, see REFERENCE.md
/docx
Source: ~/.claude/skills/docx/SKILL.md
name: docx description: "Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of "Word doc", "word document", ".docx", or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a "report", "memo", "letter", "template", or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation." license: Proprietary. LICENSE.txt has complete terms
DOCX creation, editing, and analysis
Overview
A .docx file is a ZIP archive containing XML files.
Quick Reference
| Task | Approach |
|---|---|
| Read/analyze content | pandoc or unpack for raw XML |
| Create new document | Use docx-js - see Creating New Documents below |
| Edit existing document | Unpack → edit XML → repack - see Editing Existing Documents below |
Converting .doc to .docx
Legacy .doc files must be converted before editing:
python scripts/office/soffice.py --headless --convert-to docx document.doc
Reading Content
# Text extraction with tracked changes
pandoc --track-changes=all document.docx -o output.md
# Raw XML access
python scripts/office/unpack.py document.docx unpacked/
Converting to Images
python scripts/office/soffice.py --headless --convert-to pdf document.docx
pdftoppm -jpeg -r 150 document.pdf page
Accepting Tracked Changes
To produce a clean document with all tracked changes accepted (requires LibreOffice):
python scripts/accept_changes.py input.docx output.docx
Creating New Documents
Generate .docx files with JavaScript, then validate. Install: npm install -g docx
Setup
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
VerticalAlign, PageNumber, PageBreak } = require('docx');
const doc = new Document({ sections: [{ children: [/* content */] }] });
Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer));
Validation
After creating the file, validate it. If validation fails, unpack, fix the XML, and repack.
python scripts/office/validate.py doc.docx
Page Size
// CRITICAL: docx-js defaults to A4, not US Letter
// Always set page size explicitly for consistent results
sections: [{
properties: {
page: {
size: {
width: 12240, // 8.5 inches in DXA
height: 15840 // 11 inches in DXA
},
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch margins
}
},
children: [/* content */]
}]
Common page sizes (DXA units, 1440 DXA = 1 inch):
| Paper | Width | Height | Content Width (1" margins) |
|---|---|---|---|
| US Letter | 12,240 | 15,840 | 9,360 |
| A4 (default) | 11,906 | 16,838 | 9,026 |
Landscape orientation: docx-js swaps width/height internally, so pass portrait dimensions and let it handle the swap:
size: {
width: 12240, // Pass SHORT edge as width
height: 15840, // Pass LONG edge as height
orientation: PageOrientation.LANDSCAPE // docx-js swaps them in the XML
},
// Content width = 15840 - left margin - right margin (uses the long edge)
Styles (Override Built-in Headings)
Use Arial as the default font (universally supported). Keep titles black for readability.
const doc = new Document({
styles: {
default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt default
paragraphStyles: [
// IMPORTANT: Use exact IDs to override built-in styles
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 32, bold: true, font: "Arial" },
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // outlineLevel required for TOC
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 28, bold: true, font: "Arial" },
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } },
]
},
sections: [{
children: [
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] }),
]
}]
});
Lists (NEVER use unicode bullets)
// ❌ WRONG - never manually insert bullet characters
new Paragraph({ children: [new TextRun("• Item")] }) // BAD
new Paragraph({ children: [new TextRun("\u2022 Item")] }) // BAD
// ✅ CORRECT - use numbering config with LevelFormat.BULLET
const doc = new Document({
numbering: {
config: [
{ reference: "bullets",
levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
{ reference: "numbers",
levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
]
},
sections: [{
children: [
new Paragraph({ numbering: { reference: "bullets", level: 0 },
children: [new TextRun("Bullet item")] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 },
children: [new TextRun("Numbered item")] }),
]
}]
});
// ⚠️ Each reference creates INDEPENDENT numbering
// Same reference = continues (1,2,3 then 4,5,6)
// Different reference = restarts (1,2,3 then 1,2,3)
Tables
CRITICAL: Tables need dual widths - set both columnWidths on the table AND width on each cell. Without both, tables render incorrectly on some platforms.
// CRITICAL: Always set table width for consistent rendering
// CRITICAL: Use ShadingType.CLEAR (not SOLID) to prevent black backgrounds
const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
const borders = { top: border, bottom: border, left: border, right: border };
new Table({
width: { size: 9360, type: WidthType.DXA }, // Always use DXA (percentages break in Google Docs)
columnWidths: [4680, 4680], // Must sum to table width (DXA: 1440 = 1 inch)
rows: [
new TableRow({
children: [
new TableCell({
borders,
width: { size: 4680, type: WidthType.DXA }, // Also set on each cell
shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // CLEAR not SOLID
margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Cell padding (internal, not added to width)
children: [new Paragraph({ children: [new TextRun("Cell")] })]
})
]
})
]
})
Table width calculation:
Always use WidthType.DXA — WidthType.PERCENTAGE breaks in Google Docs.
// Table width = sum of columnWidths = content width
// US Letter with 1" margins: 12240 - 2880 = 9360 DXA
width: { size: 9360, type: WidthType.DXA },
columnWidths: [7000, 2360] // Must sum to table width
Width rules:
- Always use
WidthType.DXA— neverWidthType.PERCENTAGE(incompatible with Google Docs) - Table width must equal the sum of
columnWidths - Cell
widthmust match correspondingcolumnWidth - Cell
marginsare internal padding - they reduce content area, not add to cell width - For full-width tables: use content width (page width minus left and right margins)
Images
// CRITICAL: type parameter is REQUIRED
new Paragraph({
children: [new ImageRun({
type: "png", // Required: png, jpg, jpeg, gif, bmp, svg
data: fs.readFileSync("image.png"),
transformation: { width: 200, height: 150 },
altText: { title: "Title", description: "Desc", name: "Name" } // All three required
})]
})
Page Breaks
// CRITICAL: PageBreak must be inside a Paragraph
new Paragraph({ children: [new PageBreak()] })
// Or use pageBreakBefore
new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] })
Table of Contents
// CRITICAL: Headings must use HeadingLevel ONLY - no custom styles
new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" })
Headers/Footers
sections: [{
properties: {
page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } // 1440 = 1 inch
},
headers: {
default: new Header({ children: [new Paragraph({ children: [new TextRun("Header")] })] })
},
footers: {
default: new Footer({ children: [new Paragraph({
children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] })]
})] })
},
children: [/* content */]
}]
Critical Rules for docx-js
- Set page size explicitly - docx-js defaults to A4; use US Letter (12240 x 15840 DXA) for US documents
- Landscape: pass portrait dimensions - docx-js swaps width/height internally; pass short edge as
width, long edge asheight, and setorientation: PageOrientation.LANDSCAPE - Never use
\n- use separate Paragraph elements - Never use unicode bullets - use
LevelFormat.BULLETwith numbering config - PageBreak must be in Paragraph - standalone creates invalid XML
- ImageRun requires
type- always specify png/jpg/etc - Always set table
widthwith DXA - never useWidthType.PERCENTAGE(breaks in Google Docs) - Tables need dual widths -
columnWidthsarray AND cellwidth, both must match - Table width = sum of columnWidths - for DXA, ensure they add up exactly
- Always add cell margins - use
margins: { top: 80, bottom: 80, left: 120, right: 120 }for readable padding - Use
ShadingType.CLEAR- never SOLID for table shading - TOC requires HeadingLevel only - no custom styles on heading paragraphs
- Override built-in styles - use exact IDs: "Heading1", "Heading2", etc.
- Include
outlineLevel- required for TOC (0 for H1, 1 for H2, etc.)
Editing Existing Documents
Follow all 3 steps in order.
Step 1: Unpack
python scripts/office/unpack.py document.docx unpacked/
Extracts XML, pretty-prints, merges adjacent runs, and converts smart quotes to XML entities (“ etc.) so they survive editing. Use --merge-runs false to skip run merging.
Step 2: Edit XML
Edit files in unpacked/word/. See XML Reference below for patterns.
Use "Claude" as the author for tracked changes and comments, unless the user explicitly requests use of a different name.
Use the Edit tool directly for string replacement. Do not write Python scripts. Scripts introduce unnecessary complexity. The Edit tool shows exactly what is being replaced.
CRITICAL: Use smart quotes for new content. When adding text with apostrophes or quotes, use XML entities to produce smart quotes:
<!-- Use these entities for professional typography -->
<w:t>Here’s a quote: “Hello”</w:t>
| Entity | Character |
|---|---|
‘ |
‘ (left single) |
’ |
’ (right single / apostrophe) |
“ |
“ (left double) |
” |
” (right double) |
Adding comments: Use comment.py to handle boilerplate across multiple XML files (text must be pre-escaped XML):
python scripts/comment.py unpacked/ 0 "Comment text with & and ’"
python scripts/comment.py unpacked/ 1 "Reply text" --parent 0 # reply to comment 0
python scripts/comment.py unpacked/ 0 "Text" --author "Custom Author" # custom author name
Then add markers to document.xml (see Comments in XML Reference).
Step 3: Pack
python scripts/office/pack.py unpacked/ output.docx --original document.docx
Validates with auto-repair, condenses XML, and creates DOCX. Use --validate false to skip.
Auto-repair will fix:
durableId>= 0x7FFFFFFF (regenerates valid ID)- Missing
xml:space="preserve"on<w:t>with whitespace
Auto-repair won't fix:
- Malformed XML, invalid element nesting, missing relationships, schema violations
Common Pitfalls
- Replace entire
<w:r>elements: When adding tracked changes, replace the whole<w:r>...</w:r>block with<w:del>...<w:ins>...as siblings. Don't inject tracked change tags inside a run. - Preserve
<w:rPr>formatting: Copy the original run's<w:rPr>block into your tracked change runs to maintain bold, font size, etc.
XML Reference
Schema Compliance
- Element order in
<w:pPr>:<w:pStyle>,<w:numPr>,<w:spacing>,<w:ind>,<w:jc>,<w:rPr>last - Whitespace: Add
xml:space="preserve"to<w:t>with leading/trailing spaces - RSIDs: Must be 8-digit hex (e.g.,
00AB1234)
Tracked Changes
Insertion:
<w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:t>inserted text</w:t></w:r>
</w:ins>
Deletion:
<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:delText>deleted text</w:delText></w:r>
</w:del>
Inside <w:del>: Use <w:delText> instead of <w:t>, and <w:delInstrText> instead of <w:instrText>.
Minimal edits - only mark what changes:
<!-- Change "30 days" to "60 days" -->
<w:r><w:t>The term is </w:t></w:r>
<w:del w:id="1" w:author="Claude" w:date="...">
<w:r><w:delText>30</w:delText></w:r>
</w:del>
<w:ins w:id="2" w:author="Claude" w:date="...">
<w:r><w:t>60</w:t></w:r>
</w:ins>
<w:r><w:t> days.</w:t></w:r>
Deleting entire paragraphs/list items - when removing ALL content from a paragraph, also mark the paragraph mark as deleted so it merges with the next paragraph. Add <w:del/> inside <w:pPr><w:rPr>:
<w:p>
<w:pPr>
<w:numPr>...</w:numPr> <!-- list numbering if present -->
<w:rPr>
<w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z"/>
</w:rPr>
</w:pPr>
<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:delText>Entire paragraph content being deleted...</w:delText></w:r>
</w:del>
</w:p>
Without the <w:del/> in <w:pPr><w:rPr>, accepting changes leaves an empty paragraph/list item.
Rejecting another author's insertion - nest deletion inside their insertion:
<w:ins w:author="Jane" w:id="5">
<w:del w:author="Claude" w:id="10">
<w:r><w:delText>their inserted text</w:delText></w:r>
</w:del>
</w:ins>
Restoring another author's deletion - add insertion after (don't modify their deletion):
<w:del w:author="Jane" w:id="5">
<w:r><w:delText>deleted text</w:delText></w:r>
</w:del>
<w:ins w:author="Claude" w:id="10">
<w:r><w:t>deleted text</w:t></w:r>
</w:ins>
Comments
After running comment.py (see Step 2), add markers to document.xml. For replies, use --parent flag and nest markers inside the parent's.
CRITICAL: <w:commentRangeStart> and <w:commentRangeEnd> are siblings of <w:r>, never inside <w:r>.
<!-- Comment markers are direct children of w:p, never inside w:r -->
<w:commentRangeStart w:id="0"/>
<w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:delText>deleted</w:delText></w:r>
</w:del>
<w:r><w:t> more text</w:t></w:r>
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
<!-- Comment 0 with reply 1 nested inside -->
<w:commentRangeStart w:id="0"/>
<w:commentRangeStart w:id="1"/>
<w:r><w:t>text</w:t></w:r>
<w:commentRangeEnd w:id="1"/>
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="1"/></w:r>
Images
- Add image file to
word/media/ - Add relationship to
word/_rels/document.xml.rels:
<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
- Add content type to
[Content_Types].xml:
<Default Extension="png" ContentType="image/png"/>
- Reference in document.xml:
<w:drawing>
<wp:inline>
<wp:extent cx="914400" cy="914400"/> <!-- EMUs: 914400 = 1 inch -->
<a:graphic>
<a:graphicData uri=".../picture">
<pic:pic>
<pic:blipFill><a:blip r:embed="rId5"/></pic:blipFill>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
Dependencies
- pandoc: Text extraction
- docx:
npm install -g docx(new documents) - LibreOffice: PDF conversion (auto-configured for sandboxed environments via
scripts/office/soffice.py) - Poppler:
pdftoppmfor images
/pptx
Source: ~/.claude/skills/pptx/SKILL.md
name: pptx description: "Use this skill any time a .pptx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions "deck," "slides," "presentation," or references a .pptx filename, regardless of what they plan to do with the content afterward. If a .pptx file needs to be opened, created, or touched, use this skill." license: Proprietary. LICENSE.txt has complete terms
PPTX Skill
Quick Reference
| Task | Guide |
|---|---|
| Read/analyze content | python -m markitdown presentation.pptx |
| Edit or create from template | Read editing.md |
| Create from scratch | Read pptxgenjs.md |
Reading Content
# Text extraction
python -m markitdown presentation.pptx
# Visual overview
python scripts/thumbnail.py presentation.pptx
# Raw XML
python scripts/office/unpack.py presentation.pptx unpacked/
Editing Workflow
Read editing.md for full details.
- Analyze template with
thumbnail.py - Unpack → manipulate slides → edit content → clean → pack
Creating from Scratch
Read pptxgenjs.md for full details.
Use when no template or reference presentation is available.
Design Ideas
Don't create boring slides. Plain bullets on a white background won't impress anyone. Consider ideas from this list for each slide.
Before Starting
- Pick a bold, content-informed color palette: The palette should feel designed for THIS topic. If swapping your colors into a completely different presentation would still "work," you haven't made specific enough choices.
- Dominance over equality: One color should dominate (60-70% visual weight), with 1-2 supporting tones and one sharp accent. Never give all colors equal weight.
- Dark/light contrast: Dark backgrounds for title + conclusion slides, light for content ("sandwich" structure). Or commit to dark throughout for a premium feel.
- Commit to a visual motif: Pick ONE distinctive element and repeat it — rounded image frames, icons in colored circles, thick single-side borders. Carry it across every slide.
Color Palettes
Choose colors that match your topic — don't default to generic blue. Use these palettes as inspiration:
| Theme | Primary | Secondary | Accent |
|---|---|---|---|
| Midnight Executive | 1E2761 (navy) |
CADCFC (ice blue) |
FFFFFF (white) |
| Forest & Moss | 2C5F2D (forest) |
97BC62 (moss) |
F5F5F5 (cream) |
| Coral Energy | F96167 (coral) |
F9E795 (gold) |
2F3C7E (navy) |
| Warm Terracotta | B85042 (terracotta) |
E7E8D1 (sand) |
A7BEAE (sage) |
| Ocean Gradient | 065A82 (deep blue) |
1C7293 (teal) |
21295C (midnight) |
| Charcoal Minimal | 36454F (charcoal) |
F2F2F2 (off-white) |
212121 (black) |
| Teal Trust | 028090 (teal) |
00A896 (seafoam) |
02C39A (mint) |
| Berry & Cream | 6D2E46 (berry) |
A26769 (dusty rose) |
ECE2D0 (cream) |
| Sage Calm | 84B59F (sage) |
69A297 (eucalyptus) |
50808E (slate) |
| Cherry Bold | 990011 (cherry) |
FCF6F5 (off-white) |
2F3C7E (navy) |
For Each Slide
Every slide needs a visual element — image, chart, icon, or shape. Text-only slides are forgettable.
Layout options:
- Two-column (text left, illustration on right)
- Icon + text rows (icon in colored circle, bold header, description below)
- 2x2 or 2x3 grid (image on one side, grid of content blocks on other)
- Half-bleed image (full left or right side) with content overlay
Data display:
- Large stat callouts (big numbers 60-72pt with small labels below)
- Comparison columns (before/after, pros/cons, side-by-side options)
- Timeline or process flow (numbered steps, arrows)
Visual polish:
- Icons in small colored circles next to section headers
- Italic accent text for key stats or taglines
Typography
Choose an interesting font pairing — don't default to Arial. Pick a header font with personality and pair it with a clean body font.
| Header Font | Body Font |
|---|---|
| Georgia | Calibri |
| Arial Black | Arial |
| Calibri | Calibri Light |
| Cambria | Calibri |
| Trebuchet MS | Calibri |
| Impact | Arial |
| Palatino | Garamond |
| Consolas | Calibri |
| Element | Size |
|---|---|
| Slide title | 36-44pt bold |
| Section header | 20-24pt bold |
| Body text | 14-16pt |
| Captions | 10-12pt muted |
Spacing
- 0.5" minimum margins
- 0.3-0.5" between content blocks
- Leave breathing room—don't fill every inch
Avoid (Common Mistakes)
- Don't repeat the same layout — vary columns, cards, and callouts across slides
- Don't center body text — left-align paragraphs and lists; center only titles
- Don't skimp on size contrast — titles need 36pt+ to stand out from 14-16pt body
- Don't default to blue — pick colors that reflect the specific topic
- Don't mix spacing randomly — choose 0.3" or 0.5" gaps and use consistently
- Don't style one slide and leave the rest plain — commit fully or keep it simple throughout
- Don't create text-only slides — add images, icons, charts, or visual elements; avoid plain title + bullets
- Don't forget text box padding — when aligning lines or shapes with text edges, set
margin: 0on the text box or offset the shape to account for padding - Don't use low-contrast elements — icons AND text need strong contrast against the background; avoid light text on light backgrounds or dark text on dark backgrounds
- NEVER use accent lines under titles — these are a hallmark of AI-generated slides; use whitespace or background color instead
QA (Required)
Assume there are problems. Your job is to find them.
Your first render is almost never correct. Approach QA as a bug hunt, not a confirmation step. If you found zero issues on first inspection, you weren't looking hard enough.
Content QA
python -m markitdown output.pptx
Check for missing content, typos, wrong order.
When using templates, check for leftover placeholder text:
python -m markitdown output.pptx | grep -iE "xxxx|lorem|ipsum|this.*(page|slide).*layout"
If grep returns results, fix them before declaring success.
Visual QA
⚠️ USE SUBAGENTS — even for 2-3 slides. You've been staring at the code and will see what you expect, not what's there. Subagents have fresh eyes.
Convert slides to images (see Converting to Images), then use this prompt:
Visually inspect these slides. Assume there are issues — find them.
Look for:
- Overlapping elements (text through shapes, lines through words, stacked elements)
- Text overflow or cut off at edges/box boundaries
- Decorative lines positioned for single-line text but title wrapped to two lines
- Source citations or footers colliding with content above
- Elements too close (< 0.3" gaps) or cards/sections nearly touching
- Uneven gaps (large empty area in one place, cramped in another)
- Insufficient margin from slide edges (< 0.5")
- Columns or similar elements not aligned consistently
- Low-contrast text (e.g., light gray text on cream-colored background)
- Low-contrast icons (e.g., dark icons on dark backgrounds without a contrasting circle)
- Text boxes too narrow causing excessive wrapping
- Leftover placeholder content
For each slide, list issues or areas of concern, even if minor.
Read and analyze these images:
1. /path/to/slide-01.jpg (Expected: [brief description])
2. /path/to/slide-02.jpg (Expected: [brief description])
Report ALL issues found, including minor ones.
Verification Loop
- Generate slides → Convert to images → Inspect
- List issues found (if none found, look again more critically)
- Fix issues
- Re-verify affected slides — one fix often creates another problem
- Repeat until a full pass reveals no new issues
Do not declare success until you've completed at least one fix-and-verify cycle.
Converting to Images
Convert presentations to individual slide images for visual inspection:
python scripts/office/soffice.py --headless --convert-to pdf output.pptx
pdftoppm -jpeg -r 150 output.pdf slide
This creates slide-01.jpg, slide-02.jpg, etc.
To re-render specific slides after fixes:
pdftoppm -jpeg -r 150 -f N -l N output.pdf slide-fixed
Dependencies
pip install "markitdown[pptx]"- text extractionpip install Pillow- thumbnail gridsnpm install -g pptxgenjs- creating from scratch- LibreOffice (
soffice) - PDF conversion (auto-configured for sandboxed environments viascripts/office/soffice.py) - Poppler (
pdftoppm) - PDF to images
/xlsx
Source: ~/.claude/skills/xlsx/SKILL.md
name: xlsx description: "Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like "the xlsx in my downloads") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved." license: Proprietary. LICENSE.txt has complete terms
Requirements for Outputs
All Excel files
Professional Font
- Use a consistent, professional font (e.g., Arial, Times New Roman) for all deliverables unless otherwise instructed by the user
Zero Formula Errors
- Every Excel model MUST be delivered with ZERO formula errors (#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?)
Preserve Existing Templates (when updating templates)
- Study and EXACTLY match existing format, style, and conventions when modifying files
- Never impose standardized formatting on files with established patterns
- Existing template conventions ALWAYS override these guidelines
Financial models
Color Coding Standards
Unless otherwise stated by the user or existing template
Industry-Standard Color Conventions
- Blue text (RGB: 0,0,255): Hardcoded inputs, and numbers users will change for scenarios
- Black text (RGB: 0,0,0): ALL formulas and calculations
- Green text (RGB: 0,128,0): Links pulling from other worksheets within same workbook
- Red text (RGB: 255,0,0): External links to other files
- Yellow background (RGB: 255,255,0): Key assumptions needing attention or cells that need to be updated
Number Formatting Standards
Required Format Rules
- Years: Format as text strings (e.g., "2024" not "2,024")
- Currency: Use $#,##0 format; ALWAYS specify units in headers ("Revenue ($mm)")
- Zeros: Use number formatting to make all zeros "-", including percentages (e.g., "$#,##0;($#,##0);-")
- Percentages: Default to 0.0% format (one decimal)
- Multiples: Format as 0.0x for valuation multiples (EV/EBITDA, P/E)
- Negative numbers: Use parentheses (123) not minus -123
Formula Construction Rules
Assumptions Placement
- Place ALL assumptions (growth rates, margins, multiples, etc.) in separate assumption cells
- Use cell references instead of hardcoded values in formulas
- Example: Use =B5*(1+$B$6) instead of =B5*1.05
Formula Error Prevention
- Verify all cell references are correct
- Check for off-by-one errors in ranges
- Ensure consistent formulas across all projection periods
- Test with edge cases (zero values, negative numbers)
- Verify no unintended circular references
Documentation Requirements for Hardcodes
- Comment or in cells beside (if end of table). Format: "Source: [System/Document], [Date], [Specific Reference], [URL if applicable]"
- Examples:
- "Source: Company 10-K, FY2024, Page 45, Revenue Note, [SEC EDGAR URL]"
- "Source: Company 10-Q, Q2 2025, Exhibit 99.1, [SEC EDGAR URL]"
- "Source: Bloomberg Terminal, 8/15/2025, AAPL US Equity"
- "Source: FactSet, 8/20/2025, Consensus Estimates Screen"
XLSX creation, editing, and analysis
Overview
A user may ask you to create, edit, or analyze the contents of an .xlsx file. You have different tools and workflows available for different tasks.
Important Requirements
LibreOffice Required for Formula Recalculation: You can assume LibreOffice is installed for recalculating formula values using the scripts/recalc.py script. The script automatically configures LibreOffice on first run, including in sandboxed environments where Unix sockets are restricted (handled by scripts/office/soffice.py)
Reading and analyzing data
Data analysis with pandas
For data analysis, visualization, and basic operations, use pandas which provides powerful data manipulation capabilities:
import pandas as pd
# Read Excel
df = pd.read_excel('file.xlsx') # Default: first sheet
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # All sheets as dict
# Analyze
df.head() # Preview data
df.info() # Column info
df.describe() # Statistics
# Write Excel
df.to_excel('output.xlsx', index=False)
Excel File Workflows
CRITICAL: Use Formulas, Not Hardcoded Values
Always use Excel formulas instead of calculating values in Python and hardcoding them. This ensures the spreadsheet remains dynamic and updateable.
❌ WRONG - Hardcoding Calculated Values
# Bad: Calculating in Python and hardcoding result
total = df['Sales'].sum()
sheet['B10'] = total # Hardcodes 5000
# Bad: Computing growth rate in Python
growth = (df.iloc[-1]['Revenue'] - df.iloc[0]['Revenue']) / df.iloc[0]['Revenue']
sheet['C5'] = growth # Hardcodes 0.15
# Bad: Python calculation for average
avg = sum(values) / len(values)
sheet['D20'] = avg # Hardcodes 42.5
✅ CORRECT - Using Excel Formulas
# Good: Let Excel calculate the sum
sheet['B10'] = '=SUM(B2:B9)'
# Good: Growth rate as Excel formula
sheet['C5'] = '=(C4-C2)/C2'
# Good: Average using Excel function
sheet['D20'] = '=AVERAGE(D2:D19)'
This applies to ALL calculations - totals, percentages, ratios, differences, etc. The spreadsheet should be able to recalculate when source data changes.
Common Workflow
- Choose tool: pandas for data, openpyxl for formulas/formatting
- Create/Load: Create new workbook or load existing file
- Modify: Add/edit data, formulas, and formatting
- Save: Write to file
- Recalculate formulas (MANDATORY IF USING FORMULAS): Use the scripts/recalc.py script
python scripts/recalc.py output.xlsx - Verify and fix any errors:
- The script returns JSON with error details
- If
statusiserrors_found, checkerror_summaryfor specific error types and locations - Fix the identified errors and recalculate again
- Common errors to fix:
#REF!: Invalid cell references#DIV/0!: Division by zero#VALUE!: Wrong data type in formula#NAME?: Unrecognized formula name
Creating new Excel files
# Using openpyxl for formulas and formatting
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
wb = Workbook()
sheet = wb.active
# Add data
sheet['A1'] = 'Hello'
sheet['B1'] = 'World'
sheet.append(['Row', 'of', 'data'])
# Add formula
sheet['B2'] = '=SUM(A1:A10)'
# Formatting
sheet['A1'].font = Font(bold=True, color='FF0000')
sheet['A1'].fill = PatternFill('solid', start_color='FFFF00')
sheet['A1'].alignment = Alignment(horizontal='center')
# Column width
sheet.column_dimensions['A'].width = 20
wb.save('output.xlsx')
Editing existing Excel files
# Using openpyxl to preserve formulas and formatting
from openpyxl import load_workbook
# Load existing file
wb = load_workbook('existing.xlsx')
sheet = wb.active # or wb['SheetName'] for specific sheet
# Working with multiple sheets
for sheet_name in wb.sheetnames:
sheet = wb[sheet_name]
print(f"Sheet: {sheet_name}")
# Modify cells
sheet['A1'] = 'New Value'
sheet.insert_rows(2) # Insert row at position 2
sheet.delete_cols(3) # Delete column 3
# Add new sheet
new_sheet = wb.create_sheet('NewSheet')
new_sheet['A1'] = 'Data'
wb.save('modified.xlsx')
Recalculating formulas
Excel files created or modified by openpyxl contain formulas as strings but not calculated values. Use the provided scripts/recalc.py script to recalculate formulas:
python scripts/recalc.py <excel_file> [timeout_seconds]
Example:
python scripts/recalc.py output.xlsx 30
The script:
- Automatically sets up LibreOffice macro on first run
- Recalculates all formulas in all sheets
- Scans ALL cells for Excel errors (#REF!, #DIV/0!, etc.)
- Returns JSON with detailed error locations and counts
- Works on both Linux and macOS
Formula Verification Checklist
Quick checks to ensure formulas work correctly:
Essential Verification
- Test 2-3 sample references: Verify they pull correct values before building full model
- Column mapping: Confirm Excel columns match (e.g., column 64 = BL, not BK)
- Row offset: Remember Excel rows are 1-indexed (DataFrame row 5 = Excel row 6)
Common Pitfalls
- NaN handling: Check for null values with
pd.notna() - Far-right columns: FY data often in columns 50+
- Multiple matches: Search all occurrences, not just first
- Division by zero: Check denominators before using
/in formulas (#DIV/0!) - Wrong references: Verify all cell references point to intended cells (#REF!)
- Cross-sheet references: Use correct format (Sheet1!A1) for linking sheets
Formula Testing Strategy
- Start small: Test formulas on 2-3 cells before applying broadly
- Verify dependencies: Check all cells referenced in formulas exist
- Test edge cases: Include zero, negative, and very large values
Interpreting scripts/recalc.py Output
The script returns JSON with error details:
{
"status": "success", // or "errors_found"
"total_errors": 0, // Total error count
"total_formulas": 42, // Number of formulas in file
"error_summary": { // Only present if errors found
"#REF!": {
"count": 2,
"locations": ["Sheet1!B5", "Sheet1!C10"]
}
}
}
Best Practices
Library Selection
- pandas: Best for data analysis, bulk operations, and simple data export
- openpyxl: Best for complex formatting, formulas, and Excel-specific features
Working with openpyxl
- Cell indices are 1-based (row=1, column=1 refers to cell A1)
- Use
data_only=Trueto read calculated values:load_workbook('file.xlsx', data_only=True) - Warning: If opened with
data_only=Trueand saved, formulas are replaced with values and permanently lost - For large files: Use
read_only=Truefor reading orwrite_only=Truefor writing - Formulas are preserved but not evaluated - use scripts/recalc.py to update values
Working with pandas
- Specify data types to avoid inference issues:
pd.read_excel('file.xlsx', dtype={'id': str}) - For large files, read specific columns:
pd.read_excel('file.xlsx', usecols=['A', 'C', 'E']) - Handle dates properly:
pd.read_excel('file.xlsx', parse_dates=['date_column'])
Code Style Guidelines
IMPORTANT: When generating Python code for Excel operations:
- Write minimal, concise Python code without unnecessary comments
- Avoid verbose variable names and redundant operations
- Avoid unnecessary print statements
For Excel files themselves:
- Add comments to cells with complex formulas or important assumptions
- Document data sources for hardcoded values
- Include notes for key calculations and model sections
/doc-coauthoring
Source: ~/.claude/skills/doc-coauthoring/SKILL.md
name: doc-coauthoring description: Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.
Doc Co-Authoring Workflow
This skill provides a structured workflow for guiding users through collaborative document creation. Act as an active guide, walking users through three stages: Context Gathering, Refinement & Structure, and Reader Testing.
When to Offer This Workflow
Trigger conditions:
- User mentions writing documentation: "write a doc", "draft a proposal", "create a spec", "write up"
- User mentions specific doc types: "PRD", "design doc", "decision doc", "RFC"
- User seems to be starting a substantial writing task
Initial offer: Offer the user a structured workflow for co-authoring the document. Explain the three stages:
- Context Gathering: User provides all relevant context while Claude asks clarifying questions
- Refinement & Structure: Iteratively build each section through brainstorming and editing
- Reader Testing: Test the doc with a fresh Claude (no context) to catch blind spots before others read it
Explain that this approach helps ensure the doc works well when others read it (including when they paste it into Claude). Ask if they want to try this workflow or prefer to work freeform.
If user declines, work freeform. If user accepts, proceed to Stage 1.
Stage 1: Context Gathering
Goal: Close the gap between what the user knows and what Claude knows, enabling smart guidance later.
Initial Questions
Start by asking the user for meta-context about the document:
- What type of document is this? (e.g., technical spec, decision doc, proposal)
- Who's the primary audience?
- What's the desired impact when someone reads this?
- Is there a template or specific format to follow?
- Any other constraints or context to know?
Inform them they can answer in shorthand or dump information however works best for them.
If user provides a template or mentions a doc type:
- Ask if they have a template document to share
- If they provide a link to a shared document, use the appropriate integration to fetch it
- If they provide a file, read it
If user mentions editing an existing shared document:
- Use the appropriate integration to read the current state
- Check for images without alt-text
- If images exist without alt-text, explain that when others use Claude to understand the doc, Claude won't be able to see them. Ask if they want alt-text generated. If so, request they paste each image into chat for descriptive alt-text generation.
Info Dumping
Once initial questions are answered, encourage the user to dump all the context they have. Request information such as:
- Background on the project/problem
- Related team discussions or shared documents
- Why alternative solutions aren't being used
- Organizational context (team dynamics, past incidents, politics)
- Timeline pressures or constraints
- Technical architecture or dependencies
- Stakeholder concerns
Advise them not to worry about organizing it - just get it all out. Offer multiple ways to provide context:
- Info dump stream-of-consciousness
- Point to team channels or threads to read
- Link to shared documents
If integrations are available (e.g., Slack, Teams, Google Drive, SharePoint, or other MCP servers), mention that these can be used to pull in context directly.
If no integrations are detected and in Claude.ai or Claude app: Suggest they can enable connectors in their Claude settings to allow pulling context from messaging apps and document storage directly.
Inform them clarifying questions will be asked once they've done their initial dump.
During context gathering:
-
If user mentions team channels or shared documents:
- If integrations available: Inform them the content will be read now, then use the appropriate integration
- If integrations not available: Explain lack of access. Suggest they enable connectors in Claude settings, or paste the relevant content directly.
-
If user mentions entities/projects that are unknown:
- Ask if connected tools should be searched to learn more
- Wait for user confirmation before searching
-
As user provides context, track what's being learned and what's still unclear
Asking clarifying questions:
When user signals they've done their initial dump (or after substantial context provided), ask clarifying questions to ensure understanding:
Generate 5-10 numbered questions based on gaps in the context.
Inform them they can use shorthand to answer (e.g., "1: yes, 2: see #channel, 3: no because backwards compat"), link to more docs, point to channels to read, or just keep info-dumping. Whatever's most efficient for them.
Exit condition: Sufficient context has been gathered when questions show understanding - when edge cases and trade-offs can be asked about without needing basics explained.
Transition: Ask if there's any more context they want to provide at this stage, or if it's time to move on to drafting the document.
If user wants to add more, let them. When ready, proceed to Stage 2.
Stage 2: Refinement & Structure
Goal: Build the document section by section through brainstorming, curation, and iterative refinement.
Instructions to user: Explain that the document will be built section by section. For each section:
- Clarifying questions will be asked about what to include
- 5-20 options will be brainstormed
- User will indicate what to keep/remove/combine
- The section will be drafted
- It will be refined through surgical edits
Start with whichever section has the most unknowns (usually the core decision/proposal), then work through the rest.
Section ordering:
If the document structure is clear: Ask which section they'd like to start with.
Suggest starting with whichever section has the most unknowns. For decision docs, that's usually the core proposal. For specs, it's typically the technical approach. Summary sections are best left for last.
If user doesn't know what sections they need: Based on the type of document and template, suggest 3-5 sections appropriate for the doc type.
Ask if this structure works, or if they want to adjust it.
Once structure is agreed:
Create the initial document structure with placeholder text for all sections.
If access to artifacts is available:
Use create_file to create an artifact. This gives both Claude and the user a scaffold to work from.
Inform them that the initial structure with placeholders for all sections will be created.
Create artifact with all section headers and brief placeholder text like "[To be written]" or "[Content here]".
Provide the scaffold link and indicate it's time to fill in each section.
If no access to artifacts:
Create a markdown file in the working directory. Name it appropriately (e.g., decision-doc.md, technical-spec.md).
Inform them that the initial structure with placeholders for all sections will be created.
Create file with all section headers and placeholder text.
Confirm the filename has been created and indicate it's time to fill in each section.
For each section:
Step 1: Clarifying Questions
Announce work will begin on the [SECTION NAME] section. Ask 5-10 clarifying questions about what should be included:
Generate 5-10 specific questions based on context and section purpose.
Inform them they can answer in shorthand or just indicate what's important to cover.
Step 2: Brainstorming
For the [SECTION NAME] section, brainstorm [5-20] things that might be included, depending on the section's complexity. Look for:
Generate 5-20 numbered options based on section complexity. At the end, offer to brainstorm more if they want additional options.
Step 3: Curation
Ask which points should be kept, removed, or combined. Request brief justifications to help learn priorities for the next sections.
Provide examples:
- "Keep 1,4,7,9"
- "Remove 3 (duplicates 1)"
- "Remove 6 (audience already knows this)"
- "Combine 11 and 12"
If user gives freeform feedback (e.g., "looks good" or "I like most of it but...") instead of numbered selections, extract their preferences and proceed. Parse what they want kept/removed/changed and apply it.
Step 4: Gap Check
Based on what they've selected, ask if there's anything important missing for the [SECTION NAME] section.
Step 5: Drafting
Use str_replace to replace the placeholder text for this section with the actual drafted content.
Announce the [SECTION NAME] section will be drafted now based on what they've selected.
If using artifacts: After drafting, provide a link to the artifact.
Ask them to read through it and indicate what to change. Note that being specific helps learning for the next sections.
If using a file (no artifacts): After drafting, confirm completion.
Inform them the [SECTION NAME] section has been drafted in [filename]. Ask them to read through it and indicate what to change. Note that being specific helps learning for the next sections.
Key instruction for user (include when drafting the first section): Provide a note: Instead of editing the doc directly, ask them to indicate what to change. This helps learning of their style for future sections. For example: "Remove the X bullet - already covered by Y" or "Make the third paragraph more concise".
Step 6: Iterative Refinement
As user provides feedback:
- Use
str_replaceto make edits (never reprint the whole doc) - If using artifacts: Provide link to artifact after each edit
- If using files: Just confirm edits are complete
- If user edits doc directly and asks to read it: mentally note the changes they made and keep them in mind for future sections (this shows their preferences)
Continue iterating until user is satisfied with the section.
Quality Checking
After 3 consecutive iterations with no substantial changes, ask if anything can be removed without losing important information.
When section is done, confirm [SECTION NAME] is complete. Ask if ready to move to the next section.
Repeat for all sections.
Near Completion
As approaching completion (80%+ of sections done), announce intention to re-read the entire document and check for:
- Flow and consistency across sections
- Redundancy or contradictions
- Anything that feels like "slop" or generic filler
- Whether every sentence carries weight
Read entire document and provide feedback.
When all sections are drafted and refined: Announce all sections are drafted. Indicate intention to review the complete document one more time.
Review for overall coherence, flow, completeness.
Provide any final suggestions.
Ask if ready to move to Reader Testing, or if they want to refine anything else.
Stage 3: Reader Testing
Goal: Test the document with a fresh Claude (no context bleed) to verify it works for readers.
Instructions to user: Explain that testing will now occur to see if the document actually works for readers. This catches blind spots - things that make sense to the authors but might confuse others.
Testing Approach
If access to sub-agents is available (e.g., in Claude Code):
Perform the testing directly without user involvement.
Step 1: Predict Reader Questions
Announce intention to predict what questions readers might ask when trying to discover this document.
Generate 5-10 questions that readers would realistically ask.
Step 2: Test with Sub-Agent
Announce that these questions will be tested with a fresh Claude instance (no context from this conversation).
For each question, invoke a sub-agent with just the document content and the question.
Summarize what Reader Claude got right/wrong for each question.
Step 3: Run Additional Checks
Announce additional checks will be performed.
Invoke sub-agent to check for ambiguity, false assumptions, contradictions.
Summarize any issues found.
Step 4: Report and Fix
If issues found: Report that Reader Claude struggled with specific issues.
List the specific issues.
Indicate intention to fix these gaps.
Loop back to refinement for problematic sections.
If no access to sub-agents (e.g., claude.ai web interface):
The user will need to do the testing manually.
Step 1: Predict Reader Questions
Ask what questions people might ask when trying to discover this document. What would they type into Claude.ai?
Generate 5-10 questions that readers would realistically ask.
Step 2: Setup Testing
Provide testing instructions:
- Open a fresh Claude conversation: https://claude.ai
- Paste or share the document content (if using a shared doc platform with connectors enabled, provide the link)
- Ask Reader Claude the generated questions
For each question, instruct Reader Claude to provide:
- The answer
- Whether anything was ambiguous or unclear
- What knowledge/context the doc assumes is already known
Check if Reader Claude gives correct answers or misinterprets anything.
Step 3: Additional Checks
Also ask Reader Claude:
- "What in this doc might be ambiguous or unclear to readers?"
- "What knowledge or context does this doc assume readers already have?"
- "Are there any internal contradictions or inconsistencies?"
Step 4: Iterate Based on Results
Ask what Reader Claude got wrong or struggled with. Indicate intention to fix those gaps.
Loop back to refinement for any problematic sections.
Exit Condition (Both Approaches)
When Reader Claude consistently answers questions correctly and doesn't surface new gaps or ambiguities, the doc is ready.
Final Review
When Reader Testing passes: Announce the doc has passed Reader Claude testing. Before completion:
- Recommend they do a final read-through themselves - they own this document and are responsible for its quality
- Suggest double-checking any facts, links, or technical details
- Ask them to verify it achieves the impact they wanted
Ask if they want one more review, or if the work is done.
If user wants final review, provide it. Otherwise: Announce document completion. Provide a few final tips:
- Consider linking this conversation in an appendix so readers can see how the doc was developed
- Use appendices to provide depth without bloating the main doc
- Update the doc as feedback is received from real readers
Tips for Effective Guidance
Tone:
- Be direct and procedural
- Explain rationale briefly when it affects user behavior
- Don't try to "sell" the approach - just execute it
Handling Deviations:
- If user wants to skip a stage: Ask if they want to skip this and write freeform
- If user seems frustrated: Acknowledge this is taking longer than expected. Suggest ways to move faster
- Always give user agency to adjust the process
Context Management:
- Throughout, if context is missing on something mentioned, proactively ask
- Don't let gaps accumulate - address them as they come up
Artifact Management:
- Use
create_filefor drafting full sections - Use
str_replacefor all edits - Provide artifact link after every change
- Never use artifacts for brainstorming lists - that's just conversation
Quality over Speed:
- Don't rush through stages
- Each iteration should make meaningful improvements
- The goal is a document that actually works for readers
Specialized Patterns (sp-*)
/sp-brainstorm
Source: ~/.claude/skills/sp-brainstorm/SKILL.md
description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores requirements and design before implementation." disable-model-invocation: true
Invoke the superpowers:brainstorming skill and follow it exactly as presented to you
/sp-brainstorming
Source: ~/.claude/skills/sp-brainstorming/SKILL.md
name: brainstorming description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation."
Brainstorming Ideas Into Designs
Overview
Help turn ideas into fully formed designs and specs through natural collaborative dialogue.
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design in small sections (200-300 words), checking after each section whether it looks right so far.
The Process
Understanding the idea:
- Check out the current project state first (files, docs, recent commits)
- Ask questions one at a time to refine the idea
- Prefer multiple choice questions when possible, but open-ended is fine too
- Only one question per message - if a topic needs more exploration, break it into multiple questions
- Focus on understanding: purpose, constraints, success criteria
Exploring approaches:
- Propose 2-3 different approaches with trade-offs
- Present options conversationally with your recommendation and reasoning
- Lead with your recommended option and explain why
Presenting the design:
- Once you believe you understand what you're building, present the design
- Break it into sections of 200-300 words
- Ask after each section whether it looks right so far
- Cover: architecture, components, data flow, error handling, testing
- Be ready to go back and clarify if something doesn't make sense
After the Design
Documentation:
- Write the validated design to
docs/plans/YYYY-MM-DD-<topic>-design.md - Use elements-of-style:writing-clearly-and-concisely skill if available
- Commit the design document to git
Implementation (if continuing):
- Ask: "Ready to set up for implementation?"
- Use superpowers:using-git-worktrees to create isolated workspace
- Use superpowers:writing-plans to create detailed implementation plan
Key Principles
- One question at a time - Don't overwhelm with multiple questions
- Multiple choice preferred - Easier to answer than open-ended when possible
- YAGNI ruthlessly - Remove unnecessary features from all designs
- Explore alternatives - Always propose 2-3 approaches before settling
- Incremental validation - Present design in sections, validate each
- Be flexible - Go back and clarify when something doesn't make sense
/sp-dispatching-parallel-agents
Source: ~/.claude/skills/sp-dispatching-parallel-agents/SKILL.md
name: dispatching-parallel-agents description: Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies
Dispatching Parallel Agents
Overview
When you have multiple unrelated failures (different test files, different subsystems, different bugs), investigating them sequentially wastes time. Each investigation is independent and can happen in parallel.
Core principle: Dispatch one agent per independent problem domain. Let them work concurrently.
When to Use
digraph when_to_use {
"Multiple failures?" [shape=diamond];
"Are they independent?" [shape=diamond];
"Single agent investigates all" [shape=box];
"One agent per problem domain" [shape=box];
"Can they work in parallel?" [shape=diamond];
"Sequential agents" [shape=box];
"Parallel dispatch" [shape=box];
"Multiple failures?" -> "Are they independent?" [label="yes"];
"Are they independent?" -> "Single agent investigates all" [label="no - related"];
"Are they independent?" -> "Can they work in parallel?" [label="yes"];
"Can they work in parallel?" -> "Parallel dispatch" [label="yes"];
"Can they work in parallel?" -> "Sequential agents" [label="no - shared state"];
}
Use when:
- 3+ test files failing with different root causes
- Multiple subsystems broken independently
- Each problem can be understood without context from others
- No shared state between investigations
Don't use when:
The Pattern
1. Identify Independent Domains
Group failures by what's broken:
- File A tests: Tool approval flow
- File B tests: Batch completion behavior
- File C tests: Abort functionality
Each domain is independent - fixing tool approval doesn't affect abort tests.
2. Create Focused Agent Tasks
Each agent gets:
- Specific scope: One test file or subsystem
- Clear goal: Make these tests pass
- Constraints: Don't change other code
- Expected output: Summary of what you found and fixed
3. Dispatch in Parallel
// In Claude Code / AI environment
Task("Fix agent-tool-abort.test.ts failures")
Task("Fix batch-completion-behavior.test.ts failures")
Task("Fix tool-approval-race-conditions.test.ts failures")
// All three run concurrently
4. Review and Integrate
When agents return:
- Read each summary
- Verify fixes don't conflict
- Run full test suite
- Integrate all changes
Agent Prompt Structure
Good agent prompts are:
- Focused - One clear problem domain
- Self-contained - All context needed to understand the problem
- Specific about output - What should the agent return?
Fix the 3 failing tests in src/agents/agent-tool-abort.test.ts:
1. "should abort tool with partial output capture" - expects 'interrupted at' in message
2. "should handle mixed completed and aborted tools" - fast tool aborted instead of completed
3. "should properly track pendingToolCount" - expects 3 results but gets 0
These are timing/race condition issues. Your task:
1. Read the test file and understand what each test verifies
2. Identify root cause - timing issues or actual bugs?
3. Fix by:
- Replacing arbitrary timeouts with event-based waiting
- Fixing bugs in abort implementation if found
- Adjusting test expectations if testing changed behavior
Do NOT just increase timeouts - find the real issue.
Return: Summary of what you found and what you fixed.
Common Mistakes
❌ Too broad: "Fix all the tests" - agent gets lost ✅ Specific: "Fix agent-tool-abort.test.ts" - focused scope
❌ No context: "Fix the race condition" - agent doesn't know where ✅ Context: Paste the error messages and test names
❌ No constraints: Agent might refactor everything ✅ Constraints: "Do NOT change production code" or "Fix tests only"
❌ Vague output: "Fix it" - you don't know what changed ✅ Specific: "Return summary of root cause and changes"
When NOT to Use
Real Example from Session
Scenario: 6 test failures across 3 files after major refactoring
Failures:
- agent-tool-abort.test.ts: 3 failures (timing issues)
- batch-completion-behavior.test.ts: 2 failures (tools not executing)
- tool-approval-race-conditions.test.ts: 1 failure (execution count = 0)
Decision: Independent domains - abort logic separate from batch completion separate from race conditions
Dispatch:
Agent 1 → Fix agent-tool-abort.test.ts
Agent 2 → Fix batch-completion-behavior.test.ts
Agent 3 → Fix tool-approval-race-conditions.test.ts
Results:
- Agent 1: Replaced timeouts with event-based waiting
- Agent 2: Fixed event structure bug (threadId in wrong place)
- Agent 3: Added wait for async tool execution to complete
Integration: All fixes independent, no conflicts, full suite green
Time saved: 3 problems solved in parallel vs sequentially
Key Benefits
- Parallelization - Multiple investigations happen simultaneously
- Focus - Each agent has narrow scope, less context to track
- Independence - Agents don't interfere with each other
- Speed - 3 problems solved in time of 1
Verification
After agents return:
- Review each summary - Understand what changed
- Check for conflicts - Did agents edit same code?
- Run full suite - Verify all fixes work together
- Spot check - Agents can make systematic errors
Real-World Impact
From debugging session (2025-10-03):
- 6 failures across 3 files
- 3 agents dispatched in parallel
- All investigations completed concurrently
- All fixes integrated successfully
- Zero conflicts between agent changes
/sp-execute-plan
Source: ~/.claude/skills/sp-execute-plan/SKILL.md
description: Execute plan in batches with review checkpoints disable-model-invocation: true
Invoke the superpowers:executing-plans skill and follow it exactly as presented to you
/sp-executing-plans
Source: ~/.claude/skills/sp-executing-plans/SKILL.md
name: executing-plans description: Use when you have a written implementation plan to execute in a separate session with review checkpoints
Executing Plans
Overview
Load plan, review critically, execute tasks in batches, report for review between batches.
Core principle: Batch execution with checkpoints for architect review.
Announce at start: "I'm using the executing-plans skill to implement this plan."
The Process
Step 1: Load and Review Plan
- Read plan file
- Review critically - identify any questions or concerns about the plan
- If concerns: Raise them with your human partner before starting
- If no concerns: Create TodoWrite and proceed
Step 2: Execute Batch
Default: First 3 tasks
For each task:
- Mark as in_progress
- Follow each step exactly (plan has bite-sized steps)
- Run verifications as specified
- Mark as completed
Step 3: Report
When batch complete:
- Show what was implemented
- Show verification output
- Say: "Ready for feedback."
Step 4: Continue
Based on feedback:
- Apply changes if needed
- Execute next batch
- Repeat until complete
Step 5: Complete Development
After all tasks complete and verified:
- Announce: "I'm using the finishing-a-development-branch skill to complete this work."
- REQUIRED SUB-SKILL: Use superpowers:finishing-a-development-branch
- Follow that skill to verify tests, present options, execute choice
When to Stop and Ask for Help
STOP executing immediately when:
- Hit a blocker mid-batch (missing dependency, test fails, instruction unclear)
- Plan has critical gaps preventing starting
- You don't understand an instruction
- Verification fails repeatedly
Ask for clarification rather than guessing.
When to Revisit Earlier Steps
Return to Review (Step 1) when:
- Partner updates the plan based on your feedback
- Fundamental approach needs rethinking
Don't force through blockers - stop and ask.
Remember
- Review plan critically first
- Follow plan steps exactly
- Don't skip verifications
- Reference skills when plan says to
- Between batches: just report and wait
- Stop when blocked, don't guess
- Never start implementation on main/master branch without explicit user consent
Integration
Required workflow skills:
- superpowers:using-git-worktrees - REQUIRED: Set up isolated workspace before starting
- superpowers:writing-plans - Creates the plan this skill executes
- superpowers:finishing-a-development-branch - Complete development after all tasks
/sp-finishing-a-development-branch
Source: ~/.claude/skills/sp-finishing-a-development-branch/SKILL.md
name: finishing-a-development-branch description: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Finishing a Development Branch
Overview
Guide completion of development work by presenting clear options and handling chosen workflow.
Core principle: Verify tests → Present options → Execute choice → Clean up.
Announce at start: "I'm using the finishing-a-development-branch skill to complete this work."
The Process
Step 1: Verify Tests
Before presenting options, verify tests pass:
# Run project's test suite
npm test / cargo test / pytest / go test ./...
If tests fail:
Tests failing (<N> failures). Must fix before completing:
[Show failures]
Cannot proceed with merge/PR until tests pass.
Stop. Don't proceed to Step 2.
If tests pass: Continue to Step 2.
Step 2: Determine Base Branch
# Try common base branches
git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null
Or ask: "This branch split from main - is that correct?"
Step 3: Present Options
Present exactly these 4 options:
Implementation complete. What would you like to do?
1. Merge back to <base-branch> locally
2. Push and create a Pull Request
3. Keep the branch as-is (I'll handle it later)
4. Discard this work
Which option?
Don't add explanation - keep options concise.
Step 4: Execute Choice
Option 1: Merge Locally
# Switch to base branch
git checkout <base-branch>
# Pull latest
git pull
# Merge feature branch
git merge <feature-branch>
# Verify tests on merged result
<test command>
# If tests pass
git branch -d <feature-branch>
Then: Cleanup worktree (Step 5)
Option 2: Push and Create PR
# Push branch
git push -u origin <feature-branch>
# Create PR
gh pr create --title "<title>" --body "$(cat <<'EOF'
## Summary
<2-3 bullets of what changed>
## Test Plan
- [ ] <verification steps>
EOF
)"
Then: Cleanup worktree (Step 5)
Option 3: Keep As-Is
Report: "Keeping branch . Worktree preserved at ."
Don't cleanup worktree.
Option 4: Discard
Confirm first:
This will permanently delete:
- Branch <name>
- All commits: <commit-list>
- Worktree at <path>
Type 'discard' to confirm.
Wait for exact confirmation.
If confirmed:
git checkout <base-branch>
git branch -D <feature-branch>
Then: Cleanup worktree (Step 5)
Step 5: Cleanup Worktree
For Options 1, 2, 4:
Check if in worktree:
git worktree list | grep $(git branch --show-current)
If yes:
git worktree remove <worktree-path>
For Option 3: Keep worktree.
Quick Reference
| Option | Merge | Push | Keep Worktree | Cleanup Branch |
|---|---|---|---|---|
| 1. Merge locally | ✓ | - | - | ✓ |
| 2. Create PR | - | ✓ | ✓ | - |
| 3. Keep as-is | - | - | ✓ | - |
| 4. Discard | - | - | - | ✓ (force) |
Common Mistakes
Skipping test verification
- Problem: Merge broken code, create failing PR
- Fix: Always verify tests before offering options
Open-ended questions
- Problem: "What should I do next?" → ambiguous
- Fix: Present exactly 4 structured options
Automatic worktree cleanup
- Problem: Remove worktree when might need it (Option 2, 3)
- Fix: Only cleanup for Options 1 and 4
No confirmation for discard
- Problem: Accidentally delete work
- Fix: Require typed "discard" confirmation
Red Flags
Never:
- Proceed with failing tests
- Merge without verifying tests on result
- Delete work without confirmation
- Force-push without explicit request
Always:
- Verify tests before offering options
- Present exactly 4 options
- Get typed confirmation for Option 4
- Clean up worktree for Options 1 & 4 only
Integration
Called by:
- subagent-driven-development (Step 7) - After all tasks complete
- executing-plans (Step 5) - After all batches complete
Pairs with:
- using-git-worktrees - Cleans up worktree created by that skill
/sp-receiving-code-review
Source: ~/.claude/skills/sp-receiving-code-review/SKILL.md
name: receiving-code-review description: Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation
Code Review Reception
Overview
Code review requires technical evaluation, not emotional performance.
Core principle: Verify before implementing. Ask before assuming. Technical correctness over social comfort.
The Response Pattern
WHEN receiving code review feedback:
1. READ: Complete feedback without reacting
2. UNDERSTAND: Restate requirement in own words (or ask)
3. VERIFY: Check against codebase reality
4. EVALUATE: Technically sound for THIS codebase?
5. RESPOND: Technical acknowledgment or reasoned pushback
6. IMPLEMENT: One item at a time, test each
Forbidden Responses
NEVER:
- "You're absolutely right!" (explicit CLAUDE.md violation)
- "Great point!" / "Excellent feedback!" (performative)
- "Let me implement that now" (before verification)
INSTEAD:
- Restate the technical requirement
- Ask clarifying questions
- Push back with technical reasoning if wrong
- Just start working (actions > words)
Handling Unclear Feedback
IF any item is unclear:
STOP - do not implement anything yet
ASK for clarification on unclear items
WHY: Items may be related. Partial understanding = wrong implementation.
Example:
your human partner: "Fix 1-6"
You understand 1,2,3,6. Unclear on 4,5.
❌ WRONG: Implement 1,2,3,6 now, ask about 4,5 later
✅ RIGHT: "I understand items 1,2,3,6. Need clarification on 4 and 5 before proceeding."
Source-Specific Handling
From your human partner
- Trusted - implement after understanding
- Still ask if scope unclear
- No performative agreement
- Skip to action or technical acknowledgment
From External Reviewers
BEFORE implementing:
1. Check: Technically correct for THIS codebase?
2. Check: Breaks existing functionality?
3. Check: Reason for current implementation?
4. Check: Works on all platforms/versions?
5. Check: Does reviewer understand full context?
IF suggestion seems wrong:
Push back with technical reasoning
IF can't easily verify:
Say so: "I can't verify this without [X]. Should I [investigate/ask/proceed]?"
IF conflicts with your human partner's prior decisions:
Stop and discuss with your human partner first
your human partner's rule: "External feedback - be skeptical, but check carefully"
YAGNI Check for "Professional" Features
IF reviewer suggests "implementing properly":
grep codebase for actual usage
IF unused: "This endpoint isn't called. Remove it (YAGNI)?"
IF used: Then implement properly
your human partner's rule: "You and reviewer both report to me. If we don't need this feature, don't add it."
Implementation Order
FOR multi-item feedback:
1. Clarify anything unclear FIRST
2. Then implement in this order:
- Blocking issues (breaks, security)
- Simple fixes (typos, imports)
- Complex fixes (refactoring, logic)
3. Test each fix individually
4. Verify no regressions
When To Push Back
Push back when:
- Suggestion breaks existing functionality
- Reviewer lacks full context
- Violates YAGNI (unused feature)
- Technically incorrect for this stack
- Legacy/compatibility reasons exist
- Conflicts with your human partner's architectural decisions
How to push back:
- Use technical reasoning, not defensiveness
- Ask specific questions
- Reference working tests/code
- Involve your human partner if architectural
Signal if uncomfortable pushing back out loud: "Strange things are afoot at the Circle K"
Acknowledging Correct Feedback
When feedback IS correct:
✅ "Fixed. [Brief description of what changed]"
✅ "Good catch - [specific issue]. Fixed in [location]."
✅ [Just fix it and show in the code]
❌ "You're absolutely right!"
❌ "Great point!"
❌ "Thanks for catching that!"
❌ "Thanks for [anything]"
❌ ANY gratitude expression
Why no thanks: Actions speak. Just fix it. The code itself shows you heard the feedback.
If you catch yourself about to write "Thanks": DELETE IT. State the fix instead.
Gracefully Correcting Your Pushback
If you pushed back and were wrong:
✅ "You were right - I checked [X] and it does [Y]. Implementing now."
✅ "Verified this and you're correct. My initial understanding was wrong because [reason]. Fixing."
❌ Long apology
❌ Defending why you pushed back
❌ Over-explaining
State the correction factually and move on.
Common Mistakes
| Mistake | Fix |
|---|---|
| Performative agreement | State requirement or just act |
| Blind implementation | Verify against codebase first |
| Batch without testing | One at a time, test each |
| Assuming reviewer is right | Check if breaks things |
| Avoiding pushback | Technical correctness > comfort |
| Partial implementation | Clarify all items first |
| Can't verify, proceed anyway | State limitation, ask for direction |
Real Examples
Performative Agreement (Bad):
Reviewer: "Remove legacy code"
❌ "You're absolutely right! Let me remove that..."
Technical Verification (Good):
Reviewer: "Remove legacy code"
✅ "Checking... build target is 10.15+, this API needs 13+. Need legacy for backward compat. Current impl has wrong bundle ID - fix it or drop pre-13 support?"
YAGNI (Good):
Reviewer: "Implement proper metrics tracking with database, date filters, CSV export"
✅ "Grepped codebase - nothing calls this endpoint. Remove it (YAGNI)? Or is there usage I'm missing?"
Unclear Item (Good):
your human partner: "Fix items 1-6"
You understand 1,2,3,6. Unclear on 4,5.
✅ "Understand 1,2,3,6. Need clarification on 4 and 5 before implementing."
GitHub Thread Replies
When replying to inline review comments on GitHub, reply in the comment thread (gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies), not as a top-level PR comment.
The Bottom Line
External feedback = suggestions to evaluate, not orders to follow.
Verify. Question. Then implement.
No performative agreement. Technical rigor always.
/sp-requesting-code-review
Source: ~/.claude/skills/sp-requesting-code-review/SKILL.md
name: requesting-code-review description: Use when completing tasks, implementing major features, or before merging to verify work meets requirements
Requesting Code Review
Dispatch superpowers:code-reviewer subagent to catch issues before they cascade.
Core principle: Review early, review often.
When to Request Review
Mandatory:
- After each task in subagent-driven development
- After completing major feature
- Before merge to main
Optional but valuable:
- When stuck (fresh perspective)
- Before refactoring (baseline check)
- After fixing complex bug
How to Request
1. Get git SHAs:
BASE_SHA=$(git rev-parse HEAD~1) # or origin/main
HEAD_SHA=$(git rev-parse HEAD)
2. Dispatch code-reviewer subagent:
Use Task tool with superpowers:code-reviewer type, fill template at code-reviewer.md
Placeholders:
{WHAT_WAS_IMPLEMENTED}- What you just built{PLAN_OR_REQUIREMENTS}- What it should do{BASE_SHA}- Starting commit{HEAD_SHA}- Ending commit{DESCRIPTION}- Brief summary
3. Act on feedback:
- Fix Critical issues immediately
- Fix Important issues before proceeding
- Note Minor issues for later
- Push back if reviewer is wrong (with reasoning)
Example
[Just completed Task 2: Add verification function]
You: Let me request code review before proceeding.
BASE_SHA=$(git log --oneline | grep "Task 1" | head -1 | awk '{print $1}')
HEAD_SHA=$(git rev-parse HEAD)
[Dispatch superpowers:code-reviewer subagent]
WHAT_WAS_IMPLEMENTED: Verification and repair functions for conversation index
PLAN_OR_REQUIREMENTS: Task 2 from docs/plans/deployment-plan.md
BASE_SHA: a7981ec
HEAD_SHA: 3df7661
DESCRIPTION: Added verifyIndex() and repairIndex() with 4 issue types
[Subagent returns]:
Strengths: Clean architecture, real tests
Issues:
Important: Missing progress indicators
Minor: Magic number (100) for reporting interval
Assessment: Ready to proceed
You: [Fix progress indicators]
[Continue to Task 3]
Integration with Workflows
Subagent-Driven Development:
- Review after EACH task
- Catch issues before they compound
- Fix before moving to next task
Executing Plans:
- Review after each batch (3 tasks)
- Get feedback, apply, continue
Ad-Hoc Development:
- Review before merge
- Review when stuck
Red Flags
Never:
- Skip review because "it's simple"
- Ignore Critical issues
- Proceed with unfixed Important issues
- Argue with valid technical feedback
If reviewer wrong:
- Push back with technical reasoning
- Show code/tests that prove it works
- Request clarification
See template at: requesting-code-review/code-reviewer.md
/sp-subagent-driven-development
Source: ~/.claude/skills/sp-subagent-driven-development/SKILL.md
name: subagent-driven-development description: Use when executing implementation plans with independent tasks in the current session
Subagent-Driven Development
Execute plan by dispatching fresh subagent per task, with two-stage review after each: spec compliance review first, then code quality review.
Core principle: Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration
When to Use
digraph when_to_use {
"Have implementation plan?" [shape=diamond];
"Tasks mostly independent?" [shape=diamond];
"Stay in this session?" [shape=diamond];
"subagent-driven-development" [shape=box];
"executing-plans" [shape=box];
"Manual execution or brainstorm first" [shape=box];
"Have implementation plan?" -> "Tasks mostly independent?" [label="yes"];
"Have implementation plan?" -> "Manual execution or brainstorm first" [label="no"];
"Tasks mostly independent?" -> "Stay in this session?" [label="yes"];
"Tasks mostly independent?" -> "Manual execution or brainstorm first" [label="no - tightly coupled"];
"Stay in this session?" -> "subagent-driven-development" [label="yes"];
"Stay in this session?" -> "executing-plans" [label="no - parallel session"];
}
vs. Executing Plans (parallel session):
- Same session (no context switch)
- Fresh subagent per task (no context pollution)
- Two-stage review after each task: spec compliance first, then code quality
- Faster iteration (no human-in-loop between tasks)
The Process
digraph process {
rankdir=TB;
subgraph cluster_per_task {
label="Per Task";
"Dispatch implementer subagent (./implementer-prompt.md)" [shape=box];
"Implementer subagent asks questions?" [shape=diamond];
"Answer questions, provide context" [shape=box];
"Implementer subagent implements, tests, commits, self-reviews" [shape=box];
"Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" [shape=box];
"Spec reviewer subagent confirms code matches spec?" [shape=diamond];
"Implementer subagent fixes spec gaps" [shape=box];
"Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [shape=box];
"Code quality reviewer subagent approves?" [shape=diamond];
"Implementer subagent fixes quality issues" [shape=box];
"Mark task complete in TodoWrite" [shape=box];
}
"Read plan, extract all tasks with full text, note context, create TodoWrite" [shape=box];
"More tasks remain?" [shape=diamond];
"Dispatch final code reviewer subagent for entire implementation" [shape=box];
"Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen];
"Read plan, extract all tasks with full text, note context, create TodoWrite" -> "Dispatch implementer subagent (./implementer-prompt.md)";
"Dispatch implementer subagent (./implementer-prompt.md)" -> "Implementer subagent asks questions?";
"Implementer subagent asks questions?" -> "Answer questions, provide context" [label="yes"];
"Answer questions, provide context" -> "Dispatch implementer subagent (./implementer-prompt.md)";
"Implementer subagent asks questions?" -> "Implementer subagent implements, tests, commits, self-reviews" [label="no"];
"Implementer subagent implements, tests, commits, self-reviews" -> "Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)";
"Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" -> "Spec reviewer subagent confirms code matches spec?";
"Spec reviewer subagent confirms code matches spec?" -> "Implementer subagent fixes spec gaps" [label="no"];
"Implementer subagent fixes spec gaps" -> "Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" [label="re-review"];
"Spec reviewer subagent confirms code matches spec?" -> "Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [label="yes"];
"Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" -> "Code quality reviewer subagent approves?";
"Code quality reviewer subagent approves?" -> "Implementer subagent fixes quality issues" [label="no"];
"Implementer subagent fixes quality issues" -> "Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [label="re-review"];
"Code quality reviewer subagent approves?" -> "Mark task complete in TodoWrite" [label="yes"];
"Mark task complete in TodoWrite" -> "More tasks remain?";
"More tasks remain?" -> "Dispatch implementer subagent (./implementer-prompt.md)" [label="yes"];
"More tasks remain?" -> "Dispatch final code reviewer subagent for entire implementation" [label="no"];
"Dispatch final code reviewer subagent for entire implementation" -> "Use superpowers:finishing-a-development-branch";
}
Prompt Templates
./implementer-prompt.md- Dispatch implementer subagent./spec-reviewer-prompt.md- Dispatch spec compliance reviewer subagent./code-quality-reviewer-prompt.md- Dispatch code quality reviewer subagent
Example Workflow
You: I'm using Subagent-Driven Development to execute this plan.
[Read plan file once: docs/plans/feature-plan.md]
[Extract all 5 tasks with full text and context]
[Create TodoWrite with all tasks]
Task 1: Hook installation script
[Get Task 1 text and context (already extracted)]
[Dispatch implementation subagent with full task text + context]
Implementer: "Before I begin - should the hook be installed at user or system level?"
You: "User level (~/.config/superpowers/hooks/)"
Implementer: "Got it. Implementing now..."
[Later] Implementer:
- Implemented install-hook command
- Added tests, 5/5 passing
- Self-review: Found I missed --force flag, added it
- Committed
[Dispatch spec compliance reviewer]
Spec reviewer: ✅ Spec compliant - all requirements met, nothing extra
[Get git SHAs, dispatch code quality reviewer]
Code reviewer: Strengths: Good test coverage, clean. Issues: None. Approved.
[Mark Task 1 complete]
Task 2: Recovery modes
[Get Task 2 text and context (already extracted)]
[Dispatch implementation subagent with full task text + context]
Implementer: [No questions, proceeds]
Implementer:
- Added verify/repair modes
- 8/8 tests passing
- Self-review: All good
- Committed
[Dispatch spec compliance reviewer]
Spec reviewer: ❌ Issues:
- Missing: Progress reporting (spec says "report every 100 items")
- Extra: Added --json flag (not requested)
[Implementer fixes issues]
Implementer: Removed --json flag, added progress reporting
[Spec reviewer reviews again]
Spec reviewer: ✅ Spec compliant now
[Dispatch code quality reviewer]
Code reviewer: Strengths: Solid. Issues (Important): Magic number (100)
[Implementer fixes]
Implementer: Extracted PROGRESS_INTERVAL constant
[Code reviewer reviews again]
Code reviewer: ✅ Approved
[Mark Task 2 complete]
...
[After all tasks]
[Dispatch final code-reviewer]
Final reviewer: All requirements met, ready to merge
Done!
Advantages
vs. Manual execution:
- Subagents follow TDD naturally
- Fresh context per task (no confusion)
- Parallel-safe (subagents don't interfere)
- Subagent can ask questions (before AND during work)
vs. Executing Plans:
- Same session (no handoff)
- Continuous progress (no waiting)
- Review checkpoints automatic
Efficiency gains:
- No file reading overhead (controller provides full text)
- Controller curates exactly what context is needed
- Subagent gets complete information upfront
- Questions surfaced before work begins (not after)
Quality gates:
- Self-review catches issues before handoff
- Two-stage review: spec compliance, then code quality
- Review loops ensure fixes actually work
- Spec compliance prevents over/under-building
- Code quality ensures implementation is well-built
Cost:
- More subagent invocations (implementer + 2 reviewers per task)
- Controller does more prep work (extracting all tasks upfront)
- Review loops add iterations
- But catches issues early (cheaper than debugging later)
Red Flags
Never:
- Start implementation on main/master branch without explicit user consent
- Skip reviews (spec compliance OR code quality)
- Proceed with unfixed issues
- Dispatch multiple implementation subagents in parallel (conflicts)
- Make subagent read plan file (provide full text instead)
- Skip scene-setting context (subagent needs to understand where task fits)
- Ignore subagent questions (answer before letting them proceed)
- Accept "close enough" on spec compliance (spec reviewer found issues = not done)
- Skip review loops (reviewer found issues = implementer fixes = review again)
- Let implementer self-review replace actual review (both are needed)
- Start code quality review before spec compliance is ✅ (wrong order)
- Move to next task while either review has open issues
If subagent asks questions:
- Answer clearly and completely
- Provide additional context if needed
- Don't rush them into implementation
If reviewer finds issues:
- Implementer (same subagent) fixes them
- Reviewer reviews again
- Repeat until approved
- Don't skip the re-review
If subagent fails task:
- Dispatch fix subagent with specific instructions
- Don't try to fix manually (context pollution)
Integration
Required workflow skills:
- superpowers:using-git-worktrees - REQUIRED: Set up isolated workspace before starting
- superpowers:writing-plans - Creates the plan this skill executes
- superpowers:requesting-code-review - Code review template for reviewer subagents
- superpowers:finishing-a-development-branch - Complete development after all tasks
Subagents should use:
- superpowers:test-driven-development - Subagents follow TDD for each task
Alternative workflow:
- superpowers:executing-plans - Use for parallel session instead of same-session execution
/sp-systematic-debugging
Source: ~/.claude/skills/sp-systematic-debugging/SKILL.md
name: systematic-debugging description: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
Systematic Debugging
Overview
Random fixes waste time and create new bugs. Quick patches mask underlying issues.
Core principle: ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
Violating the letter of this process is violating the spirit of debugging.
The Iron Law
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
If you haven't completed Phase 1, you cannot propose fixes.
When to Use
Use for ANY technical issue:
- Test failures
- Bugs in production
- Unexpected behavior
- Performance problems
- Build failures
- Integration issues
Use this ESPECIALLY when:
- Under time pressure (emergencies make guessing tempting)
- "Just one quick fix" seems obvious
- You've already tried multiple fixes
- Previous fix didn't work
- You don't fully understand the issue
Don't skip when:
- Issue seems simple (simple bugs have root causes too)
- You're in a hurry (rushing guarantees rework)
- Manager wants it fixed NOW (systematic is faster than thrashing)
The Four Phases
You MUST complete each phase before proceeding to the next.
Phase 1: Root Cause Investigation
BEFORE attempting ANY fix:
-
Read Error Messages Carefully
- Don't skip past errors or warnings
- They often contain the exact solution
- Read stack traces completely
- Note line numbers, file paths, error codes
-
Reproduce Consistently
- Can you trigger it reliably?
- What are the exact steps?
- Does it happen every time?
- If not reproducible → gather more data, don't guess
-
Check Recent Changes
- What changed that could cause this?
- Git diff, recent commits
- New dependencies, config changes
- Environmental differences
-
Gather Evidence in Multi-Component Systems
WHEN system has multiple components (CI → build → signing, API → service → database):
BEFORE proposing fixes, add diagnostic instrumentation:
For EACH component boundary: - Log what data enters component - Log what data exits component - Verify environment/config propagation - Check state at each layer Run once to gather evidence showing WHERE it breaks THEN analyze evidence to identify failing component THEN investigate that specific componentExample (multi-layer system):
# Layer 1: Workflow echo "=== Secrets available in workflow: ===" echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}" # Layer 2: Build script echo "=== Env vars in build script: ===" env | grep IDENTITY || echo "IDENTITY not in environment" # Layer 3: Signing script echo "=== Keychain state: ===" security list-keychains security find-identity -v # Layer 4: Actual signing codesign --sign "$IDENTITY" --verbose=4 "$APP"This reveals: Which layer fails (secrets → workflow ✓, workflow → build ✗)
-
Trace Data Flow
WHEN error is deep in call stack:
See
root-cause-tracing.mdin this directory for the complete backward tracing technique.Quick version:
- Where does bad value originate?
- What called this with bad value?
- Keep tracing up until you find the source
- Fix at source, not at symptom
Phase 2: Pattern Analysis
Find the pattern before fixing:
-
Find Working Examples
- Locate similar working code in same codebase
- What works that's similar to what's broken?
-
Compare Against References
- If implementing pattern, read reference implementation COMPLETELY
- Don't skim - read every line
- Understand the pattern fully before applying
-
Identify Differences
- What's different between working and broken?
- List every difference, however small
- Don't assume "that can't matter"
-
Understand Dependencies
- What other components does this need?
- What settings, config, environment?
- What assumptions does it make?
Phase 3: Hypothesis and Testing
Scientific method:
-
Form Single Hypothesis
- State clearly: "I think X is the root cause because Y"
- Write it down
- Be specific, not vague
-
Test Minimally
- Make the SMALLEST possible change to test hypothesis
- One variable at a time
- Don't fix multiple things at once
-
Verify Before Continuing
- Did it work? Yes → Phase 4
- Didn't work? Form NEW hypothesis
- DON'T add more fixes on top
-
When You Don't Know
- Say "I don't understand X"
- Don't pretend to know
- Ask for help
- Research more
Phase 4: Implementation
Fix the root cause, not the symptom:
-
Create Failing Test Case
- Simplest possible reproduction
- Automated test if possible
- One-off test script if no framework
- MUST have before fixing
- Use the
superpowers:test-driven-developmentskill for writing proper failing tests
-
Implement Single Fix
- Address the root cause identified
- ONE change at a time
- No "while I'm here" improvements
- No bundled refactoring
-
Verify Fix
- Test passes now?
- No other tests broken?
- Issue actually resolved?
-
If Fix Doesn't Work
- STOP
- Count: How many fixes have you tried?
- If < 3: Return to Phase 1, re-analyze with new information
- If ≥ 3: STOP and question the architecture (step 5 below)
- DON'T attempt Fix #4 without architectural discussion
-
If 3+ Fixes Failed: Question Architecture
Pattern indicating architectural problem:
- Each fix reveals new shared state/coupling/problem in different place
- Fixes require "massive refactoring" to implement
- Each fix creates new symptoms elsewhere
STOP and question fundamentals:
- Is this pattern fundamentally sound?
- Are we "sticking with it through sheer inertia"?
- Should we refactor architecture vs. continue fixing symptoms?
Discuss with your human partner before attempting more fixes
This is NOT a failed hypothesis - this is a wrong architecture.
Red Flags - STOP and Follow Process
If you catch yourself thinking:
- "Quick fix for now, investigate later"
- "Just try changing X and see if it works"
- "Add multiple changes, run tests"
- "Skip the test, I'll manually verify"
- "It's probably X, let me fix that"
- "I don't fully understand but this might work"
- "Pattern says X but I'll adapt it differently"
- "Here are the main problems: [lists fixes without investigation]"
- Proposing solutions before tracing data flow
- "One more fix attempt" (when already tried 2+)
- Each fix reveals new problem in different place
ALL of these mean: STOP. Return to Phase 1.
If 3+ fixes failed: Question the architecture (see Phase 4.5)
your human partner's Signals You're Doing It Wrong
Watch for these redirections:
- "Is that not happening?" - You assumed without verifying
- "Will it show us...?" - You should have added evidence gathering
- "Stop guessing" - You're proposing fixes without understanding
- "Ultrathink this" - Question fundamentals, not just symptoms
- "We're stuck?" (frustrated) - Your approach isn't working
When you see these: STOP. Return to Phase 1.
Common Rationalizations
| Excuse | Reality |
|---|---|
| "Issue is simple, don't need process" | Simple issues have root causes too. Process is fast for simple bugs. |
| "Emergency, no time for process" | Systematic debugging is FASTER than guess-and-check thrashing. |
| "Just try this first, then investigate" | First fix sets the pattern. Do it right from the start. |
| "I'll write test after confirming fix works" | Untested fixes don't stick. Test first proves it. |
| "Multiple fixes at once saves time" | Can't isolate what worked. Causes new bugs. |
| "Reference too long, I'll adapt the pattern" | Partial understanding guarantees bugs. Read it completely. |
| "I see the problem, let me fix it" | Seeing symptoms ≠ understanding root cause. |
| "One more fix attempt" (after 2+ failures) | 3+ failures = architectural problem. Question pattern, don't fix again. |
Quick Reference
| Phase | Key Activities | Success Criteria |
|---|---|---|
| 1. Root Cause | Read errors, reproduce, check changes, gather evidence | Understand WHAT and WHY |
| 2. Pattern | Find working examples, compare | Identify differences |
| 3. Hypothesis | Form theory, test minimally | Confirmed or new hypothesis |
| 4. Implementation | Create test, fix, verify | Bug resolved, tests pass |
When Process Reveals "No Root Cause"
If systematic investigation reveals issue is truly environmental, timing-dependent, or external:
- You've completed the process
- Document what you investigated
- Implement appropriate handling (retry, timeout, error message)
- Add monitoring/logging for future investigation
But: 95% of "no root cause" cases are incomplete investigation.
Supporting Techniques
These techniques are part of systematic debugging and available in this directory:
root-cause-tracing.md- Trace bugs backward through call stack to find original triggerdefense-in-depth.md- Add validation at multiple layers after finding root causecondition-based-waiting.md- Replace arbitrary timeouts with condition polling
- superpowers:test-driven-development - For creating failing test case (Phase 4, Step 1)
- superpowers:verification-before-completion - Verify fix worked before claiming success
Real-World Impact
From debugging sessions:
- Systematic approach: 15-30 minutes to fix
- Random fixes approach: 2-3 hours of thrashing
- First-time fix rate: 95% vs 40%
- New bugs introduced: Near zero vs common
/sp-test-driven-development
Source: ~/.claude/skills/sp-test-driven-development/SKILL.md
name: test-driven-development description: Use when implementing any feature or bugfix, before writing implementation code
Test-Driven Development (TDD)
Overview
Write the test first. Watch it fail. Write minimal code to pass.
Core principle: If you didn't watch the test fail, you don't know if it tests the right thing.
Violating the letter of the rules is violating the spirit of the rules.
When to Use
Always:
- New features
- Bug fixes
- Refactoring
- Behavior changes
Exceptions (ask your human partner):
- Throwaway prototypes
- Generated code
- Configuration files
Thinking "skip TDD just this once"? Stop. That's rationalization.
The Iron Law
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write code before the test? Delete it. Start over.
No exceptions:
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete
Implement fresh from tests. Period.
Red-Green-Refactor
digraph tdd_cycle {
rankdir=LR;
red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="Verify fails\ncorrectly", shape=diamond];
green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="Verify passes\nAll green", shape=diamond];
refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
next [label="Next", shape=ellipse];
red -> verify_red;
verify_red -> green [label="yes"];
verify_red -> red [label="wrong\nfailure"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no"];
refactor -> verify_green [label="stay\ngreen"];
verify_green -> next;
next -> red;
}
RED - Write Failing Test
Write one minimal test showing what should happen.
```typescript test('retries failed operations 3 times', async () => { let attempts = 0; const operation = () => { attempts++; if (attempts < 3) throw new Error('fail'); return 'success'; };const result = await retryOperation(operation);
expect(result).toBe('success'); expect(attempts).toBe(3); });
Clear name, tests real behavior, one thing
</Good>
<Bad>
```typescript
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
Vague name, tests mock not code </Bad>
Requirements:
- One behavior
- Clear name
- Real code (no mocks unless unavoidable)
Verify RED - Watch It Fail
MANDATORY. Never skip.
npm test path/to/test.test.ts
Confirm:
- Test fails (not errors)
- Failure message is expected
- Fails because feature missing (not typos)
Test passes? You're testing existing behavior. Fix test.
Test errors? Fix error, re-run until it fails correctly.
GREEN - Minimal Code
Write simplest code to pass the test.
```typescript async function retryOperation(fn: () => Promise): Promise { for (let i = 0; i < 3; i++) { try { return await fn(); } catch (e) { if (i === 2) throw e; } } throw new Error('unreachable'); } ``` Just enough to pass ```typescript async function retryOperation( fn: () => Promise, options?: { maxRetries?: number; backoff?: 'linear' | 'exponential'; onRetry?: (attempt: number) => void; } ): Promise { // YAGNI } ``` Over-engineeredDon't add features, refactor other code, or "improve" beyond the test.
Verify GREEN - Watch It Pass
MANDATORY.
npm test path/to/test.test.ts
Confirm:
- Test passes
- Other tests still pass
- Output pristine (no errors, warnings)
Test fails? Fix code, not test.
Other tests fail? Fix now.
REFACTOR - Clean Up
After green only:
- Remove duplication
- Improve names
- Extract helpers
Keep tests green. Don't add behavior.
Repeat
Next failing test for next feature.
Good Tests
| Quality | Good | Bad |
|---|---|---|
| Minimal | One thing. "and" in name? Split it. | test('validates email and domain and whitespace') |
| Clear | Name describes behavior | test('test1') |
| Shows intent | Demonstrates desired API | Obscures what code should do |
Why Order Matters
"I'll write tests after to verify it works"
Tests written after code pass immediately. Passing immediately proves nothing:
- Might test wrong thing
- Might test implementation, not behavior
- Might miss edge cases you forgot
- You never saw it catch the bug
Test-first forces you to see the test fail, proving it actually tests something.
"I already manually tested all the edge cases"
Manual testing is ad-hoc. You think you tested everything but:
- No record of what you tested
- Can't re-run when code changes
- Easy to forget cases under pressure
- "It worked when I tried it" ≠ comprehensive
Automated tests are systematic. They run the same way every time.
"Deleting X hours of work is wasteful"
Sunk cost fallacy. The time is already gone. Your choice now:
- Delete and rewrite with TDD (X more hours, high confidence)
- Keep it and add tests after (30 min, low confidence, likely bugs)
The "waste" is keeping code you can't trust. Working code without real tests is technical debt.
"TDD is dogmatic, being pragmatic means adapting"
TDD IS pragmatic:
- Finds bugs before commit (faster than debugging after)
- Prevents regressions (tests catch breaks immediately)
- Documents behavior (tests show how to use code)
- Enables refactoring (change freely, tests catch breaks)
"Pragmatic" shortcuts = debugging in production = slower.
"Tests after achieve the same goals - it's spirit not ritual"
No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"
Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones.
Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't).
30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work.
Common Rationalizations
| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for existing code. |
Red Flags - STOP and Start Over
- Code before test
- Test after implementation
- Test passes immediately
- Can't explain why test failed
- Tests added "later"
- Rationalizing "just this once"
- "I already manually tested it"
- "Tests after achieve the same purpose"
- "It's about spirit not ritual"
- "Keep as reference" or "adapt existing code"
- "Already spent X hours, deleting is wasteful"
- "TDD is dogmatic, I'm being pragmatic"
- "This is different because..."
All of these mean: Delete code. Start over with TDD.
Example: Bug Fix
Bug: Empty email accepted
RED
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
Verify RED
$ npm test
FAIL: expected 'Email required', got undefined
GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
Verify GREEN
$ npm test
PASS
REFACTOR Extract validation for multiple fields if needed.
Verification Checklist
Before marking work complete:
- Every new function/method has a test
- Watched each test fail before implementing
- Each test failed for expected reason (feature missing, not typo)
- Wrote minimal code to pass each test
- All tests pass
- Output pristine (no errors, warnings)
- Tests use real code (mocks only if unavoidable)
- Edge cases and errors covered
Can't check all boxes? You skipped TDD. Start over.
When Stuck
| Problem | Solution |
|---|---|
| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. |
| Test too complicated | Design too complicated. Simplify interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify design. |
Debugging Integration
Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.
Never fix bugs without a test.
Testing Anti-Patterns
When adding mocks or test utilities, read @testing-anti-patterns.md to avoid common pitfalls:
- Testing mock behavior instead of real behavior
- Adding test-only methods to production classes
- Mocking without understanding dependencies
Final Rule
Production code → test exists and failed first
Otherwise → not TDD
No exceptions without your human partner's permission.
/sp-using-git-worktrees
Source: ~/.claude/skills/sp-using-git-worktrees/SKILL.md
name: using-git-worktrees description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Using Git Worktrees
Overview
Git worktrees create isolated workspaces sharing the same repository, allowing work on multiple branches simultaneously without switching.
Core principle: Systematic directory selection + safety verification = reliable isolation.
Announce at start: "I'm using the using-git-worktrees skill to set up an isolated workspace."
Directory Selection Process
Follow this priority order:
1. Check Existing Directories
# Check in priority order
ls -d .worktrees 2>/dev/null # Preferred (hidden)
ls -d worktrees 2>/dev/null # Alternative
If found: Use that directory. If both exist, .worktrees wins.
2. Check CLAUDE.md
grep -i "worktree.*director" CLAUDE.md 2>/dev/null
If preference specified: Use it without asking.
3. Ask User
If no directory exists and no CLAUDE.md preference:
No worktree directory found. Where should I create worktrees?
1. .worktrees/ (project-local, hidden)
2. ~/.config/superpowers/worktrees/<project-name>/ (global location)
Which would you prefer?
Safety Verification
For Project-Local Directories (.worktrees or worktrees)
MUST verify directory is ignored before creating worktree:
# Check if directory is ignored (respects local, global, and system gitignore)
git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null
If NOT ignored:
Per Jesse's rule "Fix broken things immediately":
- Add appropriate line to .gitignore
- Commit the change
- Proceed with worktree creation
Why critical: Prevents accidentally committing worktree contents to repository.
For Global Directory (~/.config/superpowers/worktrees)
No .gitignore verification needed - outside project entirely.
Creation Steps
1. Detect Project Name
project=$(basename "$(git rev-parse --show-toplevel)")
2. Create Worktree
# Determine full path
case $LOCATION in
.worktrees|worktrees)
path="$LOCATION/$BRANCH_NAME"
;;
~/.config/superpowers/worktrees/*)
path="~/.config/superpowers/worktrees/$project/$BRANCH_NAME"
;;
esac
# Create worktree with new branch
git worktree add "$path" -b "$BRANCH_NAME"
cd "$path"
3. Run Project Setup
Auto-detect and run appropriate setup:
# Node.js
if [ -f package.json ]; then npm install; fi
# Rust
if [ -f Cargo.toml ]; then cargo build; fi
# Python
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f pyproject.toml ]; then poetry install; fi
# Go
if [ -f go.mod ]; then go mod download; fi
4. Verify Clean Baseline
Run tests to ensure worktree starts clean:
# Examples - use project-appropriate command
npm test
cargo test
pytest
go test ./...
If tests fail: Report failures, ask whether to proceed or investigate.
If tests pass: Report ready.
5. Report Location
Worktree ready at <full-path>
Tests passing (<N> tests, 0 failures)
Ready to implement <feature-name>
Quick Reference
| Situation | Action |
|---|---|
.worktrees/ exists |
Use it (verify ignored) |
worktrees/ exists |
Use it (verify ignored) |
| Both exist | Use .worktrees/ |
| Neither exists | Check CLAUDE.md → Ask user |
| Directory not ignored | Add to .gitignore + commit |
| Tests fail during baseline | Report failures + ask |
| No package.json/Cargo.toml | Skip dependency install |
Common Mistakes
Skipping ignore verification
- Problem: Worktree contents get tracked, pollute git status
- Fix: Always use
git check-ignorebefore creating project-local worktree
Assuming directory location
- Problem: Creates inconsistency, violates project conventions
- Fix: Follow priority: existing > CLAUDE.md > ask
Proceeding with failing tests
- Problem: Can't distinguish new bugs from pre-existing issues
- Fix: Report failures, get explicit permission to proceed
Hardcoding setup commands
- Problem: Breaks on projects using different tools
- Fix: Auto-detect from project files (package.json, etc.)
Example Workflow
You: I'm using the using-git-worktrees skill to set up an isolated workspace.
[Check .worktrees/ - exists]
[Verify ignored - git check-ignore confirms .worktrees/ is ignored]
[Create worktree: git worktree add .worktrees/auth -b feature/auth]
[Run npm install]
[Run npm test - 47 passing]
Worktree ready at /Users/jesse/myproject/.worktrees/auth
Tests passing (47 tests, 0 failures)
Ready to implement auth feature
Red Flags
Never:
- Create worktree without verifying it's ignored (project-local)
- Skip baseline test verification
- Proceed with failing tests without asking
- Assume directory location when ambiguous
- Skip CLAUDE.md check
Always:
- Follow directory priority: existing > CLAUDE.md > ask
- Verify directory is ignored for project-local
- Auto-detect and run project setup
- Verify clean test baseline
Integration
Called by:
- brainstorming (Phase 4) - REQUIRED when design is approved and implementation follows
- subagent-driven-development - REQUIRED before executing any tasks
- executing-plans - REQUIRED before executing any tasks
- Any skill needing isolated workspace
Pairs with:
- finishing-a-development-branch - REQUIRED for cleanup after work complete
/sp-using-superpowers
Source: ~/.claude/skills/sp-using-superpowers/SKILL.md
name: using-superpowers description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
This is not negotiable. This is not optional. You cannot rationalize your way out of this. </EXTREMELY-IMPORTANT>
How to Access Skills
In Claude Code: Use the Skill tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
In other environments: Check your platform's documentation for how skills are loaded.
Using Skills
The Rule
Invoke relevant or requested skills BEFORE any response or action. Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
digraph skill_flow {
"User message received" [shape=doublecircle];
"Might any skill apply?" [shape=diamond];
"Invoke Skill tool" [shape=box];
"Announce: 'Using [skill] to [purpose]'" [shape=box];
"Has checklist?" [shape=diamond];
"Create TodoWrite todo per item" [shape=box];
"Follow skill exactly" [shape=box];
"Respond (including clarifications)" [shape=doublecircle];
"User message received" -> "Might any skill apply?";
"Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
"Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
"Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
"Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
"Has checklist?" -> "Follow skill exactly" [label="no"];
"Create TodoWrite todo per item" -> "Follow skill exactly";
}
Red Flags
These thoughts mean STOP—you're rationalizing:
| Thought | Reality |
|---|---|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
Skill Priority
When multiple skills could apply, use this order:
- Process skills first (brainstorming, debugging) - these determine HOW to approach the task
- Implementation skills second (frontend-design, mcp-builder) - these guide execution
"Let's build X" → brainstorming first, then implementation skills. "Fix this bug" → debugging first, then domain-specific skills.
Skill Types
Rigid (TDD, debugging): Follow exactly. Don't adapt away discipline.
Flexible (patterns): Adapt principles to context.
The skill itself tells you which.
User Instructions
Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.
/sp-verification-before-completion
Source: ~/.claude/skills/sp-verification-before-completion/SKILL.md
name: verification-before-completion description: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
Verification Before Completion
Overview
Claiming work is complete without verification is dishonesty, not efficiency.
Core principle: Evidence before claims, always.
Violating the letter of this rule is violating the spirit of this rule.
The Iron Law
NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE
If you haven't run the verification command in this message, you cannot claim it passes.
The Gate Function
BEFORE claiming any status or expressing satisfaction:
1. IDENTIFY: What command proves this claim?
2. RUN: Execute the FULL command (fresh, complete)
3. READ: Full output, check exit code, count failures
4. VERIFY: Does output confirm the claim?
- If NO: State actual status with evidence
- If YES: State claim WITH evidence
5. ONLY THEN: Make the claim
Skip any step = lying, not verifying
Common Failures
| Claim | Requires | Not Sufficient |
|---|---|---|
| Tests pass | Test command output: 0 failures | Previous run, "should pass" |
| Linter clean | Linter output: 0 errors | Partial check, extrapolation |
| Build succeeds | Build command: exit 0 | Linter passing, logs look good |
| Bug fixed | Test original symptom: passes | Code changed, assumed fixed |
| Regression test works | Red-green cycle verified | Test passes once |
| Agent completed | VCS diff shows changes | Agent reports "success" |
| Requirements met | Line-by-line checklist | Tests passing |
Red Flags - STOP
- Using "should", "probably", "seems to"
- Expressing satisfaction before verification ("Great!", "Perfect!", "Done!", etc.)
- About to commit/push/PR without verification
- Trusting agent success reports
- Relying on partial verification
- Thinking "just this once"
- Tired and wanting work over
- ANY wording implying success without having run verification
Rationalization Prevention
| Excuse | Reality |
|---|---|
| "Should work now" | RUN the verification |
| "I'm confident" | Confidence ≠ evidence |
| "Just this once" | No exceptions |
| "Linter passed" | Linter ≠ compiler |
| "Agent said success" | Verify independently |
| "I'm tired" | Exhaustion ≠ excuse |
| "Partial check is enough" | Partial proves nothing |
| "Different words so rule doesn't apply" | Spirit over letter |
Key Patterns
Tests:
✅ [Run test command] [See: 34/34 pass] "All tests pass"
❌ "Should pass now" / "Looks correct"
Regression tests (TDD Red-Green):
✅ Write → Run (pass) → Revert fix → Run (MUST FAIL) → Restore → Run (pass)
❌ "I've written a regression test" (without red-green verification)
Build:
✅ [Run build] [See: exit 0] "Build passes"
❌ "Linter passed" (linter doesn't check compilation)
Requirements:
✅ Re-read plan → Create checklist → Verify each → Report gaps or completion
❌ "Tests pass, phase complete"
Agent delegation:
✅ Agent reports success → Check VCS diff → Verify changes → Report actual state
❌ Trust agent report
Why This Matters
From 24 failure memories:
- your human partner said "I don't believe you" - trust broken
- Undefined functions shipped - would crash
- Missing requirements shipped - incomplete features
- Time wasted on false completion → redirect → rework
- Violates: "Honesty is a core value. If you lie, you'll be replaced."
When To Apply
ALWAYS before:
- ANY variation of success/completion claims
- ANY expression of satisfaction
- ANY positive statement about work state
- Committing, PR creation, task completion
- Moving to next task
- Delegating to agents
Rule applies to:
- Exact phrases
- Paraphrases and synonyms
- Implications of success
- ANY communication suggesting completion/correctness
The Bottom Line
No shortcuts for verification.
Run the command. Read the output. THEN claim the result.
This is non-negotiable.
/sp-write-plan
Source: ~/.claude/skills/sp-write-plan/SKILL.md
description: Create detailed implementation plan with bite-sized tasks disable-model-invocation: true
Invoke the superpowers:writing-plans skill and follow it exactly as presented to you
/sp-writing-plans
Source: ~/.claude/skills/sp-writing-plans/SKILL.md
name: writing-plans description: Use when you have a spec or requirements for a multi-step task, before touching code
Writing Plans
Overview
Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits.
Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well.
Announce at start: "I'm using the writing-plans skill to create the implementation plan."
Context: This should be run in a dedicated worktree (created by brainstorming skill).
Save plans to: docs/plans/YYYY-MM-DD-<feature-name>.md
Bite-Sized Task Granularity
Each step is one action (2-5 minutes):
- "Write the failing test" - step
- "Run it to make sure it fails" - step
- "Implement the minimal code to make the test pass" - step
- "Run the tests and make sure they pass" - step
- "Commit" - step
Plan Document Header
Every plan MUST start with this header:
# [Feature Name] Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** [One sentence describing what this builds]
**Architecture:** [2-3 sentences about approach]
**Tech Stack:** [Key technologies/libraries]
---
Task Structure
### Task N: [Component Name]
**Files:**
- Create: `exact/path/to/file.py`
- Modify: `exact/path/to/existing.py:123-145`
- Test: `tests/exact/path/to/test.py`
**Step 1: Write the failing test**
```python
def test_specific_behavior():
result = function(input)
assert result == expected
Step 2: Run test to verify it fails
Run: pytest tests/path/test.py::test_name -v
Expected: FAIL with "function not defined"
Step 3: Write minimal implementation
def function(input):
return expected
Step 4: Run test to verify it passes
Run: pytest tests/path/test.py::test_name -v
Expected: PASS
Step 5: Commit
git add tests/path/test.py src/path/file.py
git commit -m "feat: add specific feature"
## Remember
- Exact file paths always
- Complete code in plan (not "add validation")
- Exact commands with expected output
- Reference relevant skills with @ syntax
- DRY, YAGNI, TDD, frequent commits
## Execution Handoff
After saving the plan, offer execution choice:
**"Plan complete and saved to `docs/plans/<filename>.md`. Two execution options:**
**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
**Which approach?"**
**If Subagent-Driven chosen:**
- **REQUIRED SUB-SKILL:** Use superpowers:subagent-driven-development
- Stay in this session
- Fresh subagent per task + code review
**If Parallel Session chosen:**
- Guide them to open new session in worktree
- **REQUIRED SUB-SKILL:** New session uses superpowers:executing-plans
/sp-writing-skills
Source: ~/.claude/skills/sp-writing-skills/SKILL.md
name: writing-skills description: Use when creating new skills, editing existing skills, or verifying skills work before deployment
Writing Skills
Overview
Writing skills IS Test-Driven Development applied to process documentation.
Personal skills live in agent-specific directories (~/.claude/skills for Claude Code, ~/.agents/skills/ for Codex)
You write test cases (pressure scenarios with subagents), watch them fail (baseline behavior), write the skill (documentation), watch tests pass (agents comply), and refactor (close loopholes).
Core principle: If you didn't watch an agent fail without the skill, you don't know if the skill teaches the right thing.
REQUIRED BACKGROUND: You MUST understand superpowers:test-driven-development before using this skill. That skill defines the fundamental RED-GREEN-REFACTOR cycle. This skill adapts TDD to documentation.
Official guidance: For Anthropic's official skill authoring best practices, see anthropic-best-practices.md. This document provides additional patterns and guidelines that complement the TDD-focused approach in this skill.
What is a Skill?
A skill is a reference guide for proven techniques, patterns, or tools. Skills help future Claude instances find and apply effective approaches.
Skills are: Reusable techniques, patterns, tools, reference guides
Skills are NOT: Narratives about how you solved a problem once
TDD Mapping for Skills
| TDD Concept | Skill Creation |
|---|---|
| Test case | Pressure scenario with subagent |
| Production code | Skill document (SKILL.md) |
| Test fails (RED) | Agent violates rule without skill (baseline) |
| Test passes (GREEN) | Agent complies with skill present |
| Refactor | Close loopholes while maintaining compliance |
| Write test first | Run baseline scenario BEFORE writing skill |
| Watch it fail | Document exact rationalizations agent uses |
| Minimal code | Write skill addressing those specific violations |
| Watch it pass | Verify agent now complies |
| Refactor cycle | Find new rationalizations → plug → re-verify |
The entire skill creation process follows RED-GREEN-REFACTOR.
When to Create a Skill
Create when:
- Technique wasn't intuitively obvious to you
- You'd reference this again across projects
- Pattern applies broadly (not project-specific)
- Others would benefit
Don't create for:
- One-off solutions
- Standard practices well-documented elsewhere
- Project-specific conventions (put in CLAUDE.md)
- Mechanical constraints (if it's enforceable with regex/validation, automate it—save documentation for judgment calls)
Skill Types
Technique
Concrete method with steps to follow (condition-based-waiting, root-cause-tracing)
Pattern
Way of thinking about problems (flatten-with-flags, test-invariants)
Reference
API docs, syntax guides, tool documentation (office docs)
Directory Structure
skills/
skill-name/
SKILL.md # Main reference (required)
supporting-file.* # Only if needed
Flat namespace - all skills in one searchable namespace
Separate files for:
- Heavy reference (100+ lines) - API docs, comprehensive syntax
- Reusable tools - Scripts, utilities, templates
Keep inline:
- Principles and concepts
- Code patterns (< 50 lines)
- Everything else
SKILL.md Structure
Frontmatter (YAML):
- Only two fields supported:
nameanddescription - Max 1024 characters total
name: Use letters, numbers, and hyphens only (no parentheses, special chars)description: Third-person, describes ONLY when to use (NOT what it does)- Start with "Use when..." to focus on triggering conditions
- Include specific symptoms, situations, and contexts
- NEVER summarize the skill's process or workflow (see CSO section for why)
- Keep under 500 characters if possible
---
name: Skill-Name-With-Hyphens
description: Use when [specific triggering conditions and symptoms]
---
# Skill Name
## Overview
What is this? Core principle in 1-2 sentences.
## When to Use
[Small inline flowchart IF decision non-obvious]
Bullet list with SYMPTOMS and use cases
When NOT to use
## Core Pattern (for techniques/patterns)
Before/after code comparison
## Quick Reference
Table or bullets for scanning common operations
## Implementation
Inline code for simple patterns
Link to file for heavy reference or reusable tools
## Common Mistakes
What goes wrong + fixes
## Real-World Impact (optional)
Concrete results
Claude Search Optimization (CSO)
Critical for discovery: Future Claude needs to FIND your skill
1. Rich Description Field
Purpose: Claude reads description to decide which skills to load for a given task. Make it answer: "Should I read this skill right now?"
Format: Start with "Use when..." to focus on triggering conditions
CRITICAL: Description = When to Use, NOT What the Skill Does
The description should ONLY describe triggering conditions. Do NOT summarize the skill's process or workflow in the description.
Why this matters: Testing revealed that when a description summarizes the skill's workflow, Claude may follow the description instead of reading the full skill content. A description saying "code review between tasks" caused Claude to do ONE review, even though the skill's flowchart clearly showed TWO reviews (spec compliance then code quality).
When the description was changed to just "Use when executing implementation plans with independent tasks" (no workflow summary), Claude correctly read the flowchart and followed the two-stage review process.
The trap: Descriptions that summarize workflow create a shortcut Claude will take. The skill body becomes documentation Claude skips.
# ❌ BAD: Summarizes workflow - Claude may follow this instead of reading skill
description: Use when executing plans - dispatches subagent per task with code review between tasks
# ❌ BAD: Too much process detail
description: Use for TDD - write test first, watch it fail, write minimal code, refactor
# ✅ GOOD: Just triggering conditions, no workflow summary
description: Use when executing implementation plans with independent tasks in the current session
# ✅ GOOD: Triggering conditions only
description: Use when implementing any feature or bugfix, before writing implementation code
Content:
- Use concrete triggers, symptoms, and situations that signal this skill applies
- Describe the problem (race conditions, inconsistent behavior) not language-specific symptoms (setTimeout, sleep)
- Keep triggers technology-agnostic unless the skill itself is technology-specific
- If skill is technology-specific, make that explicit in the trigger
- Write in third person (injected into system prompt)
- NEVER summarize the skill's process or workflow
# ❌ BAD: Too abstract, vague, doesn't include when to use
description: For async testing
# ❌ BAD: First person
description: I can help you with async tests when they're flaky
# ❌ BAD: Mentions technology but skill isn't specific to it
description: Use when tests use setTimeout/sleep and are flaky
# ✅ GOOD: Starts with "Use when", describes problem, no workflow
description: Use when tests have race conditions, timing dependencies, or pass/fail inconsistently
# ✅ GOOD: Technology-specific skill with explicit trigger
description: Use when using React Router and handling authentication redirects
2. Keyword Coverage
Use words Claude would search for:
- Error messages: "Hook timed out", "ENOTEMPTY", "race condition"
- Symptoms: "flaky", "hanging", "zombie", "pollution"
- Synonyms: "timeout/hang/freeze", "cleanup/teardown/afterEach"
- Tools: Actual commands, library names, file types
3. Descriptive Naming
Use active voice, verb-first:
- ✅
creating-skillsnotskill-creation - ✅
condition-based-waitingnotasync-test-helpers
4. Token Efficiency (Critical)
Problem: getting-started and frequently-referenced skills load into EVERY conversation. Every token counts.
Target word counts:
- getting-started workflows: <150 words each
- Frequently-loaded skills: <200 words total
- Other skills: <500 words (still be concise)
Techniques:
Move details to tool help:
# ❌ BAD: Document all flags in SKILL.md
search-conversations supports --text, --both, --after DATE, --before DATE, --limit N
# ✅ GOOD: Reference --help
search-conversations supports multiple modes and filters. Run --help for details.
Use cross-references:
# ❌ BAD: Repeat workflow details
When searching, dispatch subagent with template...
[20 lines of repeated instructions]
# ✅ GOOD: Reference other skill
Always use subagents (50-100x context savings). REQUIRED: Use [other-skill-name] for workflow.
Compress examples:
# ❌ BAD: Verbose example (42 words)
your human partner: "How did we handle authentication errors in React Router before?"
You: I'll search past conversations for React Router authentication patterns.
[Dispatch subagent with search query: "React Router authentication error handling 401"]
# ✅ GOOD: Minimal example (20 words)
Partner: "How did we handle auth errors in React Router?"
You: Searching...
[Dispatch subagent → synthesis]
Eliminate redundancy:
- Don't repeat what's in cross-referenced skills
- Don't explain what's obvious from command
- Don't include multiple examples of same pattern
Verification:
wc -w skills/path/SKILL.md
# getting-started workflows: aim for <150 each
# Other frequently-loaded: aim for <200 total
Name by what you DO or core insight:
- ✅
condition-based-waiting>async-test-helpers - ✅
using-skillsnotskill-usage - ✅
flatten-with-flags>data-structure-refactoring - ✅
root-cause-tracing>debugging-techniques
Gerunds (-ing) work well for processes:
creating-skills,testing-skills,debugging-with-logs- Active, describes the action you're taking
4. Cross-Referencing Other Skills
When writing documentation that references other skills:
Use skill name only, with explicit requirement markers:
- ✅ Good:
**REQUIRED SUB-SKILL:** Use superpowers:test-driven-development - ✅ Good:
**REQUIRED BACKGROUND:** You MUST understand superpowers:systematic-debugging - ❌ Bad:
See skills/testing/test-driven-development(unclear if required) - ❌ Bad:
@skills/testing/test-driven-development/SKILL.md(force-loads, burns context)
Why no @ links: @ syntax force-loads files immediately, consuming 200k+ context before you need them.
Flowchart Usage
digraph when_flowchart {
"Need to show information?" [shape=diamond];
"Decision where I might go wrong?" [shape=diamond];
"Use markdown" [shape=box];
"Small inline flowchart" [shape=box];
"Need to show information?" -> "Decision where I might go wrong?" [label="yes"];
"Decision where I might go wrong?" -> "Small inline flowchart" [label="yes"];
"Decision where I might go wrong?" -> "Use markdown" [label="no"];
}
Use flowcharts ONLY for:
- Non-obvious decision points
- Process loops where you might stop too early
- "When to use A vs B" decisions
Never use flowcharts for:
- Reference material → Tables, lists
- Code examples → Markdown blocks
- Linear instructions → Numbered lists
- Labels without semantic meaning (step1, helper2)
See @graphviz-conventions.dot for graphviz style rules.
Visualizing for your human partner: Use render-graphs.js in this directory to render a skill's flowcharts to SVG:
./render-graphs.js ../some-skill # Each diagram separately
./render-graphs.js ../some-skill --combine # All diagrams in one SVG
Code Examples
One excellent example beats many mediocre ones
Choose most relevant language:
- Testing techniques → TypeScript/JavaScript
- System debugging → Shell/Python
- Data processing → Python
Good example:
- Complete and runnable
- Well-commented explaining WHY
- From real scenario
- Shows pattern clearly
- Ready to adapt (not generic template)
Don't:
- Implement in 5+ languages
- Create fill-in-the-blank templates
- Write contrived examples
You're good at porting - one great example is enough.
File Organization
Self-Contained Skill
defense-in-depth/
SKILL.md # Everything inline
When: All content fits, no heavy reference needed
Skill with Reusable Tool
condition-based-waiting/
SKILL.md # Overview + patterns
example.ts # Working helpers to adapt
When: Tool is reusable code, not just narrative
Skill with Heavy Reference
pptx/
SKILL.md # Overview + workflows
pptxgenjs.md # 600 lines API reference
ooxml.md # 500 lines XML structure
scripts/ # Executable tools
When: Reference material too large for inline
The Iron Law (Same as TDD)
NO SKILL WITHOUT A FAILING TEST FIRST
This applies to NEW skills AND EDITS to existing skills.
Write skill before testing? Delete it. Start over. Edit skill without testing? Same violation.
No exceptions:
- Not for "simple additions"
- Not for "just adding a section"
- Not for "documentation updates"
- Don't keep untested changes as "reference"
- Don't "adapt" while running tests
- Delete means delete
REQUIRED BACKGROUND: The superpowers:test-driven-development skill explains why this matters. Same principles apply to documentation.
Testing All Skill Types
Different skill types need different test approaches:
Discipline-Enforcing Skills (rules/requirements)
Examples: TDD, verification-before-completion, designing-before-coding
Test with:
- Academic questions: Do they understand the rules?
- Pressure scenarios: Do they comply under stress?
- Multiple pressures combined: time + sunk cost + exhaustion
- Identify rationalizations and add explicit counters
Success criteria: Agent follows rule under maximum pressure
Technique Skills (how-to guides)
Examples: condition-based-waiting, root-cause-tracing, defensive-programming
Test with:
- Application scenarios: Can they apply the technique correctly?
- Variation scenarios: Do they handle edge cases?
- Missing information tests: Do instructions have gaps?
Success criteria: Agent successfully applies technique to new scenario
Pattern Skills (mental models)
Examples: reducing-complexity, information-hiding concepts
Test with:
- Recognition scenarios: Do they recognize when pattern applies?
- Application scenarios: Can they use the mental model?
- Counter-examples: Do they know when NOT to apply?
Success criteria: Agent correctly identifies when/how to apply pattern
Reference Skills (documentation/APIs)
Examples: API documentation, command references, library guides
Test with:
- Retrieval scenarios: Can they find the right information?
- Application scenarios: Can they use what they found correctly?
- Gap testing: Are common use cases covered?
Success criteria: Agent finds and correctly applies reference information
Common Rationalizations for Skipping Testing
| Excuse | Reality |
|---|---|
| "Skill is obviously clear" | Clear to you ≠ clear to other agents. Test it. |
| "It's just a reference" | References can have gaps, unclear sections. Test retrieval. |
| "Testing is overkill" | Untested skills have issues. Always. 15 min testing saves hours. |
| "I'll test if problems emerge" | Problems = agents can't use skill. Test BEFORE deploying. |
| "Too tedious to test" | Testing is less tedious than debugging bad skill in production. |
| "I'm confident it's good" | Overconfidence guarantees issues. Test anyway. |
| "Academic review is enough" | Reading ≠ using. Test application scenarios. |
| "No time to test" | Deploying untested skill wastes more time fixing it later. |
All of these mean: Test before deploying. No exceptions.
Bulletproofing Skills Against Rationalization
Skills that enforce discipline (like TDD) need to resist rationalization. Agents are smart and will find loopholes when under pressure.
Psychology note: Understanding WHY persuasion techniques work helps you apply them systematically. See persuasion-principles.md for research foundation (Cialdini, 2021; Meincke et al., 2025) on authority, commitment, scarcity, social proof, and unity principles.
Close Every Loophole Explicitly
Don't just state the rule - forbid specific workarounds:
```markdown Write code before test? Delete it. ``` ```markdown Write code before test? Delete it. Start over.No exceptions:
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete
</Good>
### Address "Spirit vs Letter" Arguments
Add foundational principle early:
```markdown
**Violating the letter of the rules is violating the spirit of the rules.**
This cuts off entire class of "I'm following the spirit" rationalizations.
Build Rationalization Table
Capture rationalizations from baseline testing (see Testing section below). Every excuse agents make goes in the table:
| Excuse | Reality |
|--------|---------|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
Create Red Flags List
Make it easy for agents to self-check when rationalizing:
## Red Flags - STOP and Start Over
- Code before test
- "I already manually tested it"
- "Tests after achieve the same purpose"
- "It's about spirit not ritual"
- "This is different because..."
**All of these mean: Delete code. Start over with TDD.**
Update CSO for Violation Symptoms
Add to description: symptoms of when you're ABOUT to violate the rule:
description: use when implementing any feature or bugfix, before writing implementation code
RED-GREEN-REFACTOR for Skills
Follow the TDD cycle:
RED: Write Failing Test (Baseline)
Run pressure scenario with subagent WITHOUT the skill. Document exact behavior:
- What choices did they make?
- What rationalizations did they use (verbatim)?
- Which pressures triggered violations?
This is "watch the test fail" - you must see what agents naturally do before writing the skill.
GREEN: Write Minimal Skill
Write skill that addresses those specific rationalizations. Don't add extra content for hypothetical cases.
Run same scenarios WITH skill. Agent should now comply.
REFACTOR: Close Loopholes
Agent found new rationalization? Add explicit counter. Re-test until bulletproof.
Testing methodology: See @testing-skills-with-subagents.md for the complete testing methodology:
- How to write pressure scenarios
- Pressure types (time, sunk cost, authority, exhaustion)
- Plugging holes systematically
- Meta-testing techniques
Anti-Patterns
❌ Narrative Example
"In session 2025-10-03, we found empty projectDir caused..." Why bad: Too specific, not reusable
❌ Multi-Language Dilution
example-js.js, example-py.py, example-go.go Why bad: Mediocre quality, maintenance burden
❌ Code in Flowcharts
step1 [label="import fs"];
step2 [label="read file"];
Why bad: Can't copy-paste, hard to read
❌ Generic Labels
helper1, helper2, step3, pattern4 Why bad: Labels should have semantic meaning
STOP: Before Moving to Next Skill
After writing ANY skill, you MUST STOP and complete the deployment process.
Do NOT:
- Create multiple skills in batch without testing each
- Move to next skill before current one is verified
- Skip testing because "batching is more efficient"
The deployment checklist below is MANDATORY for EACH skill.
Deploying untested skills = deploying untested code. It's a violation of quality standards.
Skill Creation Checklist (TDD Adapted)
IMPORTANT: Use TodoWrite to create todos for EACH checklist item below.
RED Phase - Write Failing Test:
- Create pressure scenarios (3+ combined pressures for discipline skills)
- Run scenarios WITHOUT skill - document baseline behavior verbatim
- Identify patterns in rationalizations/failures
GREEN Phase - Write Minimal Skill:
- Name uses only letters, numbers, hyphens (no parentheses/special chars)
- YAML frontmatter with only name and description (max 1024 chars)
- Description starts with "Use when..." and includes specific triggers/symptoms
- Description written in third person
- Keywords throughout for search (errors, symptoms, tools)
- Clear overview with core principle
- Address specific baseline failures identified in RED
- Code inline OR link to separate file
- One excellent example (not multi-language)
- Run scenarios WITH skill - verify agents now comply
REFACTOR Phase - Close Loopholes:
- Identify NEW rationalizations from testing
- Add explicit counters (if discipline skill)
- Build rationalization table from all test iterations
- Create red flags list
- Re-test until bulletproof
Quality Checks:
- Small flowchart only if decision non-obvious
- Quick reference table
- Common mistakes section
- No narrative storytelling
- Supporting files only for tools or heavy reference
Deployment:
- Commit skill to git and push to your fork (if configured)
- Consider contributing back via PR (if broadly useful)
Discovery Workflow
How future Claude finds your skill:
- Encounters problem ("tests are flaky")
- Finds SKILL (description matches)
- Scans overview (is this relevant?)
- Reads patterns (quick reference table)
- Loads example (only when implementing)
Optimize for this flow - put searchable terms early and often.
The Bottom Line
Creating skills IS TDD for process documentation.
Same Iron Law: No skill without failing test first. Same cycle: RED (baseline) → GREEN (write skill) → REFACTOR (close loopholes). Same benefits: Better quality, fewer surprises, bulletproof results.
If you follow TDD for code, follow it for skills. It's the same discipline applied to documentation.
Sentry Skills
/sentry-agents-md
Source: ~/.claude/skills/sentry-agents-md/SKILL.md
name: agents-md description: This skill should be used when the user asks to "create AGENTS.md", "update AGENTS.md", "maintain agent docs", "set up CLAUDE.md", or needs to keep agent instructions concise. Guides discovery of local skills and enforces minimal documentation style.
Maintaining AGENTS.md
AGENTS.md is the canonical agent-facing documentation. Keep it minimal—agents are capable and don't need hand-holding.
File Setup
- Create
AGENTS.mdat project root - Create symlink:
ln -s AGENTS.md CLAUDE.md
Before Writing
Discover local skills to reference:
find .claude/skills -name "SKILL.md" 2>/dev/null
ls plugins/*/skills/*/SKILL.md 2>/dev/null
Read each skill's frontmatter to understand when to reference it.
Writing Rules
- Headers + bullets - No paragraphs
- Code blocks - For commands and templates
- Reference, don't duplicate - Point to skills: "Use
db-migrateskill. See.claude/skills/db-migrate/SKILL.md" - No filler - No intros, conclusions, or pleasantries
- Trust capabilities - Omit obvious context
Required Sections
Package Manager
Which tool and key commands only:
## Package Manager
Use **pnpm**: `pnpm install`, `pnpm dev`, `pnpm test`
Commit Attribution
Always include this section. Agents should use their own identity:
## Commit Attribution
AI commits MUST include:
Example: `Co-Authored-By: Claude Sonnet 4 <noreply@example.com>`
Key Conventions
Project-specific patterns agents must follow. Keep brief.
Local Skills
Reference each discovered skill:
## Database
Use `db-migrate` skill for schema changes. See `.claude/skills/db-migrate/SKILL.md`
## Testing
Use `write-tests` skill. See `.claude/skills/write-tests/SKILL.md`
Optional Sections
Add only if truly needed:
- API route patterns (show template, not explanation)
- CLI commands (table format)
- File naming conventions
Anti-Patterns
Omit these:
- "Welcome to..." or "This document explains..."
- "You should..." or "Remember to..."
- Content duplicated from skills (reference instead)
- Obvious instructions ("run tests", "write clean code")
- Explanations of why (just say what)
- Long prose paragraphs
Example Structure
# Agent Instructions
## Package Manager
Use **pnpm**: `pnpm install`, `pnpm dev`
## Commit Attribution
AI commits MUST include:
## API Routes
[Template code block]
## Database
Use `db-migrate` skill. See `.claude/skills/db-migrate/SKILL.md`
## Testing
Use `write-tests` skill. See `.claude/skills/write-tests/SKILL.md`
## CLI
| Command | Description |
|---------|-------------|
| `pnpm cli sync` | Sync data |
/sentry-brand-guidelines
Source: ~/.claude/skills/sentry-brand-guidelines/SKILL.md
name: brand-guidelines description: Write copy following Sentry brand guidelines. Use when writing UI text, error messages, empty states, onboarding flows, 404 pages, documentation, marketing copy, or any user-facing content. Covers both Plain Speech (default) and Sentry Voice tones.
Brand Guidelines
Write user-facing copy following Sentry's brand guidelines.
Tone Selection
Choose the appropriate tone based on context:
| Use Plain Speech | Use Sentry Voice |
|---|---|
| Product UI (buttons, labels, forms) | 404 pages |
| Documentation | Empty states |
| Error messages | Onboarding flows |
| Settings pages | Loading states |
| Transactional emails | "What's New" announcements |
| Help text | Marketing copy |
Default to Plain Speech unless the context specifically calls for personality.
Plain Speech (Default)
Plain Speech is clear, direct, and functional. Use it for most UI elements.
Rules
- Be concise - Use the fewest words needed
- Be direct - Tell users what to do, not what they can do
- Use active voice - "Save your changes" not "Your changes will be saved"
- Avoid jargon - Use simple words users understand
- Be specific - "3 errors found" not "Some errors found"
Examples
| Instead of | Write |
|---|---|
| "Click here to save your changes" | "Save" |
| "You can filter results by date" | "Filter by date" |
| "An error has occurred" | "Something went wrong" |
| "Please enter a valid email address" | "Enter a valid email" |
| "Are you sure you want to delete?" | "Delete this item?" |
Sentry Voice
Sentry Voice adds personality in appropriate moments. It's empathetic, self-aware, and occasionally snarky.
Principles
- Empathetic snark - Direct frustration at the situation, never the user
- Self-aware - Acknowledge the absurdity of software
- Fun but functional - Personality should enhance, not obscure meaning
- Earned moments - Only use when users have time to appreciate it
Examples
404 Pages:
"This page doesn't exist. Maybe it never did. Maybe it was a dream. Either way, let's get you back on track."
Empty States:
"No errors yet. Enjoy this moment of peace while it lasts."
Onboarding:
"Let's get your first error. Don't worry, it's not as scary as it sounds."
Loading States:
"Crunching the numbers..." "Fetching your data..."
When NOT to Use Sentry Voice
- Error messages (users are frustrated)
- Settings pages (users are focused)
- Documentation (users need information)
- Billing/payment flows (users need trust)
General Rules
Spelling and Grammar
- Use American English spelling (color, not colour)
- Use Title Case for headings and page titles
- Use Sentence case for body text, buttons, and labels
Punctuation
- No exclamation marks in UI text (exception: celebratory moments)
- No periods in short UI labels or button text
- Use periods in complete sentences and help text
- No ALL CAPS except for acronyms (API, SDK, URL)
Word Choices
| Avoid | Prefer |
|---|---|
| Please | (omit) |
| Sorry | (be specific about the problem) |
| Error occurred | Something went wrong |
| Invalid | (explain what's wrong) |
| Success! | (describe what happened) |
| Oops | (be specific) |
Dash Usage
| Type | Use | Example |
|---|---|---|
| Hyphen (-) | Compound words, ranges | "real-time", "1-10" |
| En-dash (--) | Ranges, relationships | "2023--2024", "parent--child" |
| Em-dash (---) | Interruption, emphasis | "Errors---even small ones---matter" |
In most UI contexts, use hyphens. Reserve en-dashes for date ranges and em-dashes for longer prose.
UI Element Guidelines
Buttons
- Use action verbs: "Save", "Delete", "Create"
- Be specific: "Create Project" not just "Create"
- Max 2-3 words when possible
- No periods or exclamation marks
Error Messages
- Say what happened
- Say why (if helpful)
- Say what to do next
Good: "Could not save changes. Check your connection and try again." Bad: "Error: Save failed."
Empty States
- Explain what would normally be here
- Provide a clear action to populate the state
- Sentry Voice is appropriate here
Good: "No projects yet. Create your first project to start tracking errors."
Confirmation Dialogs
- Make the action clear in the title
- Explain consequences if destructive
- Use specific button labels ("Delete Project", not "OK")
Tooltips and Help Text
- Keep under 2 sentences
- Explain the "why", not just the "what"
- Link to docs for complex topics
Anti-Patterns
Avoid these common mistakes:
- Robot speak: "Item has been successfully deleted" -> "Deleted"
- Passive voice: "Changes were saved" -> "Changes saved"
- Unnecessary words: "In order to" -> "To"
- Hedging: "This might cause..." -> "This will cause..."
- Double negatives: "Not unlike..." -> "Similar to..."
- Marketing speak in UI: "Supercharge your workflow" -> "Speed up your workflow"
References
/sentry-claude-settings-audit
Source: ~/.claude/skills/sentry-claude-settings-audit/SKILL.md
name: claude-settings-audit description: Analyze a repository to generate recommended Claude Code settings.json permissions. Use when setting up a new project, auditing existing settings, or determining which read-only bash commands to allow. Detects tech stack, build tools, and monorepo structure.
Claude Settings Audit
Analyze this repository and generate recommended Claude Code settings.json permissions for read-only commands.
Phase 1: Detect Tech Stack
Run these commands to detect the repository structure:
ls -la
find . -maxdepth 2 \( -name "*.toml" -o -name "*.json" -o -name "*.lock" -o -name "*.yaml" -o -name "*.yml" -o -name "Makefile" -o -name "Dockerfile" -o -name "*.tf" \) 2>/dev/null | head -50
Check for these indicator files:
| Category | Files to Check |
|---|---|
| Python | pyproject.toml, setup.py, requirements.txt, Pipfile, poetry.lock, uv.lock |
| Node.js | package.json, package-lock.json, yarn.lock, pnpm-lock.yaml |
| Go | go.mod, go.sum |
| Rust | Cargo.toml, Cargo.lock |
| Ruby | Gemfile, Gemfile.lock |
| Java | pom.xml, build.gradle, build.gradle.kts |
| Build | Makefile, Dockerfile, docker-compose.yml |
| Infra | *.tf files, kubernetes/, helm/ |
| Monorepo | lerna.json, nx.json, turbo.json, pnpm-workspace.yaml |
Phase 2: Detect Services
Check for service integrations:
| Service | Detection |
|---|---|
| Sentry | sentry-sdk in deps, @sentry/* packages, .sentryclirc, sentry.properties |
| Linear | Linear config files, .linear/ directory |
Read dependency files to identify frameworks:
package.json→ checkdependenciesanddevDependenciespyproject.toml→ check[project.dependencies]or[tool.poetry.dependencies]Gemfile→ check gem namesCargo.toml→ check[dependencies]
Phase 3: Check Existing Settings
cat .claude/settings.json 2>/dev/null || echo "No existing settings"
Phase 4: Generate Recommendations
Build the allow list by combining:
Baseline Commands (Always Include)
[
"Bash(ls:*)",
"Bash(pwd:*)",
"Bash(find:*)",
"Bash(file:*)",
"Bash(stat:*)",
"Bash(wc:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(cat:*)",
"Bash(tree:*)",
"Bash(git status:*)",
"Bash(git log:*)",
"Bash(git diff:*)",
"Bash(git show:*)",
"Bash(git branch:*)",
"Bash(git remote:*)",
"Bash(git tag:*)",
"Bash(git stash list:*)",
"Bash(git rev-parse:*)",
"Bash(gh pr view:*)",
"Bash(gh pr list:*)",
"Bash(gh pr checks:*)",
"Bash(gh pr diff:*)",
"Bash(gh issue view:*)",
"Bash(gh issue list:*)",
"Bash(gh run view:*)",
"Bash(gh run list:*)",
"Bash(gh run logs:*)",
"Bash(gh repo view:*)",
"Bash(gh api:*)"
]
Stack-Specific Commands
Only include commands for tools actually detected in the project.
Python (if any Python files or config detected)
| If Detected | Add These Commands |
|---|---|
| Any Python | python --version, python3 --version |
poetry.lock |
poetry show, poetry env info |
uv.lock |
uv pip list, uv tree |
Pipfile.lock |
pipenv graph |
requirements.txt (no other lock) |
pip list, pip show, pip freeze |
Node.js (if package.json detected)
| If Detected | Add These Commands |
|---|---|
| Any Node.js | node --version |
pnpm-lock.yaml |
pnpm list, pnpm why |
yarn.lock |
yarn list, yarn info, yarn why |
package-lock.json |
npm list, npm view, npm outdated |
TypeScript (tsconfig.json) |
tsc --version |
Other Languages
| If Detected | Add These Commands |
|---|---|
go.mod |
go version, go list, go mod graph, go env |
Cargo.toml |
rustc --version, cargo --version, cargo tree, cargo metadata |
Gemfile |
ruby --version, bundle list, bundle show |
pom.xml |
java --version, mvn --version, mvn dependency:tree |
build.gradle |
java --version, gradle --version, gradle dependencies |
Build Tools
| If Detected | Add These Commands |
|---|---|
Dockerfile |
docker --version, docker ps, docker images |
docker-compose.yml |
docker-compose ps, docker-compose config |
*.tf files |
terraform --version, terraform providers, terraform state list |
Makefile |
make --version, make -n |
Skills (for Sentry Projects)
If this is a Sentry project (or sentry-skills plugin is installed), include:
[
"Skill(sentry-skills:commit)",
"Skill(sentry-skills:create-pr)",
"Skill(sentry-skills:code-review)",
"Skill(sentry-skills:find-bugs)",
"Skill(sentry-skills:iterate-pr)",
"Skill(sentry-skills:claude-settings-audit)",
"Skill(sentry-skills:agents-md)",
"Skill(sentry-skills:brand-guidelines)",
"Skill(sentry-skills:doc-coauthoring)",
"Skill(sentry-skills:security-review)",
"Skill(sentry-skills:django-perf-review)",
"Skill(sentry-skills:code-simplifier)",
"Skill(sentry-skills:skill-creator)",
"Skill(sentry-skills:skill-scanner)"
]
WebFetch Domains
Always Include (Sentry Projects)
[
"WebFetch(domain:docs.sentry.io)",
"WebFetch(domain:develop.sentry.dev)",
"WebFetch(domain:docs.github.com)",
"WebFetch(domain:cli.github.com)"
]
Framework-Specific
| If Detected | Add Domains |
|---|---|
| Django | docs.djangoproject.com |
| Flask | flask.palletsprojects.com |
| FastAPI | fastapi.tiangolo.com |
| React | react.dev |
| Next.js | nextjs.org |
| Vue | vuejs.org |
| Express | expressjs.com |
| Rails | guides.rubyonrails.org, api.rubyonrails.org |
| Go | pkg.go.dev |
| Rust | docs.rs, doc.rust-lang.org |
| Docker | docs.docker.com |
| Kubernetes | kubernetes.io |
| Terraform | registry.terraform.io |
MCP Server Suggestions
MCP servers are configured in .mcp.json (not settings.json). Check for existing config:
cat .mcp.json 2>/dev/null || echo "No existing .mcp.json"
Sentry MCP (if Sentry SDK detected)
Add to .mcp.json (replace {org-slug} and {project-slug} with your Sentry organization and project slugs):
{
"mcpServers": {
"sentry": {
"type": "http",
"url": "https://mcp.sentry.dev/mcp/{org-slug}/{project-slug}"
}
}
}
Linear MCP (if Linear usage detected)
Add to .mcp.json:
{
"mcpServers": {
"linear": {
"command": "npx",
"args": ["-y", "@linear/mcp-server"],
"env": {
"LINEAR_API_KEY": "${LINEAR_API_KEY}"
}
}
}
}
Note: Never suggest GitHub MCP. Always use gh CLI commands for GitHub.
Output Format
Present your findings as:
- Summary Table - What was detected
- Recommended settings.json - Complete JSON ready to copy
- MCP Suggestions - If applicable
- Merge Instructions - If existing settings found
Example output structure:
## Detected Tech Stack
| Category | Found |
| --------------- | -------------- |
| Languages | Python 3.x |
| Package Manager | poetry |
| Frameworks | Django, Celery |
| Services | Sentry |
| Build Tools | Docker, Make |
## Recommended .claude/settings.json
\`\`\`json
{
"permissions": {
"allow": [
// ... grouped by category with comments
],
"deny": []
}
}
\`\`\`
## Recommended .mcp.json (if applicable)
If you use Sentry or Linear, add the MCP config to `.mcp.json`...
Important Rules
What to Include
- Only READ-ONLY commands that cannot modify state
- Only tools that are actually used by the project (detected via lock files)
- Standard system commands (ls, cat, find, etc.)
- The
:*suffix allows any arguments to the base command
What to NEVER Include
- Absolute paths - Never include user-specific paths like
/home/user/scripts/fooor/Users/name/bin/bar - Custom scripts - Never include project scripts that may have side effects (e.g.,
./scripts/deploy.sh) - Alternative package managers - If the project uses pnpm, do NOT include npm/yarn commands
- Commands that modify state - No install, build, run, write, or delete commands
Package Manager Rules
Only include the package manager actually used by the project:
| If Detected | Include | Do NOT Include |
|---|---|---|
pnpm-lock.yaml |
pnpm commands | npm, yarn |
yarn.lock |
yarn commands | npm, pnpm |
package-lock.json |
npm commands | yarn, pnpm |
poetry.lock |
poetry commands | pip (unless also has requirements.txt) |
uv.lock |
uv commands | pip, poetry |
Pipfile.lock |
pipenv commands | pip, poetry |
If multiple lock files exist, include only the commands for each detected manager.
/sentry-code-review
Source: ~/.claude/skills/sentry-code-review/SKILL.md
name: code-review description: Perform code reviews following Sentry engineering practices. Use when reviewing pull requests, examining code changes, or providing feedback on code quality. Covers security, performance, testing, and design review.
Sentry Code Review
Follow these guidelines when reviewing code for Sentry projects.
Review Checklist
Identifying Problems
Look for these issues in code changes:
- Runtime errors: Potential exceptions, null pointer issues, out-of-bounds access
- Performance: Unbounded O(n²) operations, N+1 queries, unnecessary allocations
- Side effects: Unintended behavioral changes affecting other components
- Backwards compatibility: Breaking API changes without migration path
- ORM queries: Complex Django ORM with unexpected query performance
- Security vulnerabilities: Injection, XSS, access control gaps, secrets exposure
Design Assessment
- Do component interactions make logical sense?
- Does the change align with existing project architecture?
- Are there conflicts with current requirements or goals?
Test Coverage
Every PR should have appropriate test coverage:
- Functional tests for business logic
- Integration tests for component interactions
- End-to-end tests for critical user paths
Verify tests cover actual requirements and edge cases. Avoid excessive branching or looping in test code.
Long-Term Impact
Flag for senior engineer review when changes involve:
- Database schema modifications
- API contract changes
- New framework or library adoption
- Performance-critical code paths
- Security-sensitive functionality
Feedback Guidelines
Tone
- Be polite and empathetic
- Provide actionable suggestions, not vague criticism
- Phrase as questions when uncertain: "Have you considered...?"
Approval
- Approve when only minor issues remain
- Don't block PRs for stylistic preferences
- Remember: the goal is risk reduction, not perfect code
Common Patterns to Flag
Python/Django
# Bad: N+1 query
for user in users:
print(user.profile.name) # Separate query per user
# Good: Prefetch related
users = User.objects.prefetch_related('profile')
TypeScript/React
// Bad: Missing dependency in useEffect
useEffect(() => {
fetchData(userId);
}, []); // userId not in deps
// Good: Include all dependencies
useEffect(() => {
fetchData(userId);
}, [userId]);
Security
# Bad: SQL injection risk
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# Good: Parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", [user_id])
References
/sentry-code-simplifier
Source: ~/.claude/skills/sentry-code-simplifier/SKILL.md
name: code-simplifier description: Simplifies and refines code for clarity, consistency, and maintainability while preserving all functionality. Use when asked to "simplify code", "clean up code", "refactor for clarity", "improve readability", or review recently modified code for elegance. Focuses on project-specific best practices.
Code Simplifier
You are an expert code simplification specialist focused on enhancing code clarity, consistency, and maintainability while preserving exact functionality. Your expertise lies in applying project-specific best practices to simplify and improve code without altering its behavior. You prioritize readable, explicit code over overly compact solutions.
Refinement Principles
1. Preserve Functionality
Never change what the code does - only how it does it. All original features, outputs, and behaviors must remain intact.
2. Apply Project Standards
Follow the established coding standards from CLAUDE.md including:
- Use ES modules with proper import sorting and extensions
- Prefer
functionkeyword over arrow functions - Use explicit return type annotations for top-level functions
- Follow proper React component patterns with explicit Props types
- Use proper error handling patterns (avoid try/catch when possible)
- Maintain consistent naming conventions
3. Enhance Clarity
Simplify code structure by:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- Avoiding nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choosing clarity over brevity - explicit code is often better than overly compact code
4. Maintain Balance
Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. Focus Scope
Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
Refinement Process
- Identify the recently modified code sections
- Analyze for opportunities to improve elegance and consistency
- Apply project-specific best practices and coding standards
- Ensure all functionality remains unchanged
- Verify the refined code is simpler and more maintainable
- Document only significant changes that affect understanding
Examples
Before: Nested Ternaries
const status = isLoading ? 'loading' : hasError ? 'error' : isComplete ? 'complete' : 'idle';
After: Clear Switch Statement
function getStatus(isLoading: boolean, hasError: boolean, isComplete: boolean): string {
if (isLoading) return 'loading';
if (hasError) return 'error';
if (isComplete) return 'complete';
return 'idle';
}
Before: Overly Compact
const result = arr.filter(x => x > 0).map(x => x * 2).reduce((a, b) => a + b, 0);
After: Clear Steps
const positiveNumbers = arr.filter(x => x > 0);
const doubled = positiveNumbers.map(x => x * 2);
const sum = doubled.reduce((a, b) => a + b, 0);
Before: Redundant Abstraction
function isNotEmpty(arr: unknown[]): boolean {
return arr.length > 0;
}
if (isNotEmpty(items)) {
// ...
}
After: Direct Check
if (items.length > 0) {
// ...
}
/sentry-commit
Source: ~/.claude/skills/sentry-commit/SKILL.md
name: commit description: Create commit messages following Sentry conventions. Use when committing code changes, writing commit messages, or formatting git history. Follows conventional commits with Sentry-specific issue references.
Sentry Commit Messages
Follow these conventions when creating commits for Sentry projects.
Prerequisites
Before committing, ensure you're working on a feature branch, not the main branch.
# Check current branch
git branch --show-current
If you're on main or master, create a new branch first:
# Create and switch to a new branch
git checkout -b <type>/<short-description>
Branch naming should follow the pattern: <type>/<short-description> where type matches the commit type (e.g., feat/add-user-auth, fix/null-pointer-error, ref/extract-validation).
Format
<type>(<scope>): <subject>
<body>
<footer>
The header is required. Scope is optional. All lines must stay under 100 characters.
Commit Types
| Type | Purpose |
|---|---|
feat |
New feature |
fix |
Bug fix |
ref |
Refactoring (no behavior change) |
perf |
Performance improvement |
docs |
Documentation only |
test |
Test additions or corrections |
build |
Build system or dependencies |
ci |
CI configuration |
chore |
Maintenance tasks |
style |
Code formatting (no logic change) |
meta |
Repository metadata |
license |
License changes |
Subject Line Rules
- Use imperative, present tense: "Add feature" not "Added feature"
- Capitalize the first letter
- No period at the end
- Maximum 70 characters
Body Guidelines
- Explain what and why, not how
- Use imperative mood and present tense
- Include motivation for the change
- Contrast with previous behavior when relevant
Footer: Issue References
Reference issues in the footer using these patterns:
Fixes GH-1234
Fixes #1234
Fixes SENTRY-1234
Refs LINEAR-ABC-123
Fixescloses the issue when mergedRefslinks without closing
AI-Generated Changes
When changes were primarily generated by a coding agent (like Claude Code), include the Co-Authored-By attribution in the commit footer:
Co-Authored-By: Claude <noreply@anthropic.com>
This is the only indicator of AI involvement that should appear in commits. Do not add phrases like "Generated by AI", "Written with Claude", or similar markers in the subject, body, or anywhere else in the commit message.
Examples
Simple fix
fix(api): Handle null response in user endpoint
The user API could return null for deleted accounts, causing a crash
in the dashboard. Add null check before accessing user properties.
Fixes SENTRY-5678
Co-Authored-By: Claude <noreply@anthropic.com>
Feature with scope
feat(alerts): Add Slack thread replies for alert updates
When an alert is updated or resolved, post a reply to the original
Slack thread instead of creating a new message. This keeps related
notifications grouped together.
Refs GH-1234
Refactor
ref: Extract common validation logic to shared module
Move duplicate validation code from three endpoints into a shared
validator class. No behavior change.
Breaking change
feat(api)!: Remove deprecated v1 endpoints
Remove all v1 API endpoints that were deprecated in version 23.1.
Clients should migrate to v2 endpoints.
BREAKING CHANGE: v1 endpoints no longer available
Fixes SENTRY-9999
Revert Format
revert: feat(api): Add new endpoint
This reverts commit abc123def456.
Reason: Caused performance regression in production.
Principles
- Each commit should be a single, stable change
- Commits should be independently reviewable
- The repository should be in a working state after each commit
References
/sentry-create-pr
Source: ~/.claude/skills/sentry-create-pr/SKILL.md
name: create-pr description: Create pull requests following Sentry conventions. Use when opening PRs, writing PR descriptions, or preparing changes for review. Follows Sentry's code review guidelines.
Create Pull Request
Create pull requests following Sentry's engineering practices.
Requires: GitHub CLI (gh) authenticated and available.
Prerequisites
Before creating a PR, ensure all changes are committed. If there are uncommitted changes, run the sentry-skills:commit skill first to commit them properly.
# Check for uncommitted changes
git status --porcelain
If the output shows any uncommitted changes (modified, added, or untracked files that should be included), invoke the sentry-skills:commit skill before proceeding.
Process
Step 1: Verify Branch State
# Detect the default branch
BASE=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
# Check current branch and status
git status
git log $BASE..HEAD --oneline
Ensure:
- All changes are committed
- Branch is up to date with remote
- Changes are rebased on the base branch if needed
Step 2: Analyze Changes
Review what will be included in the PR:
# See all commits that will be in the PR
git log $BASE..HEAD
# See the full diff
git diff $BASE...HEAD
Understand the scope and purpose of all changes before writing the description.
Step 3: Write the PR Description
Use this structure for PR descriptions (ignoring any repository PR templates):
<brief description of what the PR does>
<why these changes are being made - the motivation>
<alternative approaches considered, if any>
<any additional context reviewers need>
Do NOT include:
- "Test plan" sections
- Checkbox lists of testing steps
- Redundant summaries of the diff
Do include:
- Clear explanation of what and why
- Links to relevant issues or tickets
- Context that isn't obvious from the code
- Notes on specific areas that need careful review
Step 4: Create the PR
gh pr create --draft --title "<type>(<scope>): <description>" --body "$(cat <<'EOF'
<description body here>
EOF
)"
Title format follows commit conventions:
feat(scope): Add new featurefix(scope): Fix the bugref: Refactor something
PR Description Examples
Feature PR
Add Slack thread replies for alert notifications
When an alert is updated or resolved, we now post a reply to the original
Slack thread instead of creating a new message. This keeps related
notifications grouped and reduces channel noise.
Previously considered posting edits to the original message, but threading
better preserves the timeline of events and works when the original message
is older than Slack's edit window.
Refs SENTRY-1234
Bug Fix PR
Handle null response in user API endpoint
The user endpoint could return null for soft-deleted accounts, causing
dashboard crashes when accessing user properties. This adds a null check
and returns a proper 404 response.
Found while investigating SENTRY-5678.
Fixes SENTRY-5678
Refactor PR
Extract validation logic to shared module
Moves duplicate validation code from the alerts, issues, and projects
endpoints into a shared validator class. No behavior change.
This prepares for adding new validation rules in SENTRY-9999 without
duplicating logic across endpoints.
Issue References
Reference issues in the PR body:
| Syntax | Effect |
|---|---|
Fixes #1234 |
Closes GitHub issue on merge |
Fixes SENTRY-1234 |
Closes Sentry issue |
Refs GH-1234 |
Links without closing |
Refs LINEAR-ABC-123 |
Links Linear issue |
Guidelines
- One PR per feature/fix - Don't bundle unrelated changes
- Keep PRs reviewable - Smaller PRs get faster, better reviews
- Explain the why - Code shows what; description explains why
- Mark WIP early - Use draft PRs for early feedback
Editing Existing PRs
If you need to update a PR after creation, use gh api instead of gh pr edit:
# Update PR description
gh api -X PATCH repos/{owner}/{repo}/pulls/PR_NUMBER -f body="$(cat <<'EOF'
Updated description here
EOF
)"
# Update PR title
gh api -X PATCH repos/{owner}/{repo}/pulls/PR_NUMBER -f title='new: Title here'
# Update both
gh api -X PATCH repos/{owner}/{repo}/pulls/PR_NUMBER \
-f title='new: Title' \
-f body='New description'
Note: gh pr edit is currently broken due to GitHub's Projects (classic) deprecation.
References
/sentry-django-access-review
Source: ~/.claude/skills/sentry-django-access-review/SKILL.md
name: django-access-review description: 'Django access control and IDOR security review. Use when reviewing Django views, DRF viewsets, ORM queries, or any Python/Django code handling user authorization. Trigger keywords: "IDOR", "access control", "authorization", "Django permissions", "object permissions", "tenant isolation", "broken access".' allowed-tools: Read Grep Glob Bash Task license: LICENSE
Django Access Control & IDOR Review
Find access control vulnerabilities by investigating how the codebase answers one question:
Can User A access, modify, or delete User B's data?
Philosophy: Investigation Over Pattern Matching
Do NOT scan for predefined vulnerable patterns. Instead:
- Understand how authorization works in THIS codebase
- Ask questions about specific data flows
- Trace code to find where (or if) access checks happen
- Report only what you've confirmed through investigation
Every codebase implements authorization differently. Your job is to understand this specific implementation, then find gaps.
Phase 1: Understand the Authorization Model
Before looking for bugs, answer these questions about the codebase:
How is authorization enforced?
Research the codebase to find:
□ Where are permission checks implemented?
- Decorators? (@login_required, @permission_required, custom?)
- Middleware? (TenantMiddleware, AuthorizationMiddleware?)
- Base classes? (BaseAPIView, TenantScopedViewSet?)
- Permission classes? (DRF permission_classes?)
- Custom mixins? (OwnershipMixin, TenantMixin?)
□ How are queries scoped?
- Custom managers? (TenantManager, UserScopedManager?)
- get_queryset() overrides?
- Middleware that sets query context?
□ What's the ownership model?
- Single user ownership? (document.owner_id)
- Organization/tenant ownership? (document.organization_id)
- Hierarchical? (org -> team -> user -> resource)
- Role-based within context? (org admin vs member)
Investigation commands
# Find how auth is typically done
grep -rn "permission_classes\|@login_required\|@permission_required" --include="*.py" | head -20
# Find base classes that views inherit from
grep -rn "class Base.*View\|class.*Mixin.*:" --include="*.py" | head -20
# Find custom managers
grep -rn "class.*Manager\|def get_queryset" --include="*.py" | head -20
# Find ownership fields on models
grep -rn "owner\|user_id\|organization\|tenant" --include="models.py" | head -30
Do not proceed until you understand the authorization model.
Phase 2: Map the Attack Surface
Identify endpoints that handle user-specific data:
What resources exist?
□ What models contain user data?
□ Which have ownership fields (owner_id, user_id, organization_id)?
□ Which are accessed via ID in URLs or request bodies?
What operations are exposed?
For each resource, map:
- List endpoints - what data is returned?
- Detail/retrieve endpoints - how is the object fetched?
- Create endpoints - who sets the owner?
- Update endpoints - can users modify others' data?
- Delete endpoints - can users delete others' data?
- Custom actions - what do they access?
Phase 3: Ask Questions and Investigate
For each endpoint that handles user data, ask:
The Core Question
"If I'm User A and I know the ID of User B's resource, can I access it?"
Trace the code to answer this:
1. Where does the resource ID enter the system?
- URL path: /api/documents/{id}/
- Query param: ?document_id=123
- Request body: {"document_id": 123}
2. Where is that ID used to fetch data?
- Find the ORM query or database call
3. Between (1) and (2), what checks exist?
- Is the query scoped to current user?
- Is there an explicit ownership check?
- Is there a permission check on the object?
- Does a base class or mixin enforce access?
4. If you can't find a check, is there one you missed?
- Check parent classes
- Check middleware
- Check managers
- Check decorators at URL level
Follow-Up Questions
□ For list endpoints: Does the query filter to user's data, or return everything?
□ For create endpoints: Who sets the owner - the server or the request?
□ For bulk operations: Are they scoped to user's data?
□ For related resources: If I can access a document, can I access its comments?
What if the document belongs to someone else?
□ For tenant/org resources: Can User in Org A access Org B's data by changing
the org_id in the URL?
Phase 4: Trace Specific Flows
Pick a concrete endpoint and trace it completely.
Example Investigation
Endpoint: GET /api/documents/{pk}/
1. Find the view handling this URL
→ DocumentViewSet.retrieve() in api/views.py
2. Check what DocumentViewSet inherits from
→ class DocumentViewSet(viewsets.ModelViewSet)
→ No custom base class with authorization
3. Check permission_classes
→ permission_classes = [IsAuthenticated]
→ Only checks login, not ownership
4. Check get_queryset()
→ def get_queryset(self):
→ return Document.objects.all()
→ Returns ALL documents!
5. Check for has_object_permission()
→ Not implemented
6. Check retrieve() method
→ Uses default, which calls get_object()
→ get_object() uses get_queryset(), which returns all
7. Conclusion: IDOR - Any authenticated user can access any document
What to look for when tracing
Potential gap indicators (investigate further, don't auto-flag):
- get_queryset() returns .all() or filters without user
- Direct Model.objects.get(pk=pk) without ownership in query
- ID comes from request body for sensitive operations
- Permission class checks auth but not ownership
- No has_object_permission() and queryset isn't scoped
Likely safe patterns (but verify the implementation):
- get_queryset() filters by request.user or user's org
- Custom permission class with has_object_permission()
- Base class that enforces scoping
- Manager that auto-filters
Phase 5: Report Findings
Only report issues you've confirmed through investigation.
Confidence Levels
| Level | Meaning | Action |
|---|---|---|
| HIGH | Traced the flow, confirmed no check exists | Report with evidence |
| MEDIUM | Check may exist but couldn't confirm | Note for manual verification |
| LOW | Theoretical, likely mitigated | Do not report |
Suggested Fixes Must Enforce, Not Document
Bad fix: Adding a comment saying "caller must validate permissions" Good fix: Adding code that actually validates permissions
A comment or docstring does not enforce authorization. Your suggested fix must include actual code that:
- Validates the user has permission before proceeding
- Raises an exception or returns an error if unauthorized
- Makes unauthorized access impossible, not just discouraged
Example of a BAD fix suggestion:
def get_resource(resource_id):
# IMPORTANT: Caller must ensure user has access to this resource
return Resource.objects.get(pk=resource_id)
Example of a GOOD fix suggestion:
def get_resource(resource_id, user):
resource = Resource.objects.get(pk=resource_id)
if resource.owner_id != user.id:
raise PermissionDenied("Access denied")
return resource
If you can't determine the right enforcement mechanism, say so - but never suggest documentation as the fix.
Report Format
## Access Control Review: [Component]
### Authorization Model
[Brief description of how this codebase handles authorization]
### Findings
#### [IDOR-001] [Title] (Severity: High/Medium)
- **Location**: `path/to/file.py:123`
- **Confidence**: High - confirmed through code tracing
- **The Question**: Can User A access User B's documents?
- **Investigation**:
1. Traced GET /api/documents/{pk}/ to DocumentViewSet
2. Checked get_queryset() - returns Document.objects.all()
3. Checked permission_classes - only IsAuthenticated
4. Checked for has_object_permission() - not implemented
5. Verified no relevant middleware or base class checks
- **Evidence**: [Code snippet showing the gap]
- **Impact**: Any authenticated user can read any document by ID
- **Suggested Fix**: [Code that enforces authorization - NOT a comment]
### Needs Manual Verification
[Issues where authorization exists but couldn't confirm effectiveness]
### Areas Not Reviewed
[Endpoints or flows not covered in this review]
Common Django Authorization Patterns
These are patterns you might find - not a checklist to match against.
Query Scoping
# Scoped to user
Document.objects.filter(owner=request.user)
# Scoped to organization
Document.objects.filter(organization=request.user.organization)
# Using a custom manager
Document.objects.for_user(request.user) # Investigate what this does
Permission Enforcement
# DRF permission classes
permission_classes = [IsAuthenticated, IsOwner]
# Custom has_object_permission
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
# Django decorators
@permission_required('app.view_document')
# Manual checks
if document.owner != request.user:
raise PermissionDenied()
Ownership Assignment
# Server-side (safe)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# From request (investigate)
serializer.save(**request.data) # Does request.data include owner?
Investigation Checklist
Use this to guide your review, not as a pass/fail checklist:
□ I understand how authorization is typically implemented in this codebase
□ I've identified the ownership model (user, org, tenant, etc.)
□ I've mapped the key endpoints that handle user data
□ For each sensitive endpoint, I've traced the flow and asked:
- Where does the ID come from?
- Where is data fetched?
- What checks exist between input and data access?
□ I've verified my findings by checking parent classes and middleware
□ I've only reported issues I've confirmed through investigation
/sentry-django-perf-review
Source: ~/.claude/skills/sentry-django-perf-review/SKILL.md
name: django-perf-review description: Django performance code review. Use when asked to "review Django performance", "find N+1 queries", "optimize Django", "check queryset performance", "database performance", "Django ORM issues", or audit Django code for performance problems. allowed-tools: Read Grep Glob Bash Task license: LICENSE
Django Performance Review
Review Django code for validated performance issues. Research the codebase to confirm issues before reporting. Report only what you can prove.
Review Approach
- Research first - Trace data flow, check for existing optimizations, verify data volume
- Validate before reporting - Pattern matching is not validation
- Zero findings is acceptable - Don't manufacture issues to appear thorough
- Severity must match impact - If you catch yourself writing "minor" in a CRITICAL finding, it's not critical. Downgrade or skip it.
Impact Categories
Issues are organized by impact. Focus on CRITICAL and HIGH - these cause real problems at scale.
| Priority | Category | Impact |
|---|---|---|
| 1 | N+1 Queries | CRITICAL - Multiplies with data, causes timeouts |
| 2 | Unbounded Querysets | CRITICAL - Memory exhaustion, OOM kills |
| 3 | Missing Indexes | HIGH - Full table scans on large tables |
| 4 | Write Loops | HIGH - Lock contention, slow requests |
| 5 | Inefficient Patterns | LOW - Rarely worth reporting |
Priority 1: N+1 Queries (CRITICAL)
Impact: Each N+1 adds O(n) database round trips. 100 rows = 100 extra queries. 10,000 rows = timeout.
Rule: Prefetch related data accessed in loops
Validate by tracing: View → Queryset → Template/Serializer → Loop access
# PROBLEM: N+1 - each iteration queries profile
def user_list(request):
users = User.objects.all()
return render(request, 'users.html', {'users': users})
# Template:
# {% for user in users %}
# {{ user.profile.bio }} ← triggers query per user
# {% endfor %}
# SOLUTION: Prefetch in view
def user_list(request):
users = User.objects.select_related('profile')
return render(request, 'users.html', {'users': users})
Rule: Prefetch in serializers, not just views
DRF serializers accessing related fields cause N+1 if queryset isn't optimized.
# PROBLEM: SerializerMethodField queries per object
class UserSerializer(serializers.ModelSerializer):
order_count = serializers.SerializerMethodField()
def get_order_count(self, obj):
return obj.orders.count() # ← query per user
# SOLUTION: Annotate in viewset, access in serializer
class UserViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return User.objects.annotate(order_count=Count('orders'))
class UserSerializer(serializers.ModelSerializer):
order_count = serializers.IntegerField(read_only=True)
Rule: Model properties that query are dangerous in loops
# PROBLEM: Property triggers query when accessed
class User(models.Model):
@property
def recent_orders(self):
return self.orders.filter(created__gte=last_week)[:5]
# Used in template loop = N+1
# SOLUTION: Use Prefetch with custom queryset, or annotate
Validation Checklist for N+1
- Traced data flow from view to template/serializer
- Confirmed related field is accessed inside a loop
- Searched codebase for existing select_related/prefetch_related
- Verified table has significant row count (1000+)
- Confirmed this is a hot path (not admin, not rare action)
Priority 2: Unbounded Querysets (CRITICAL)
Impact: Loading entire tables exhausts memory. Large tables cause OOM kills and worker restarts.
Rule: Always paginate list endpoints
# PROBLEM: No pagination - loads all rows
class UserListView(ListView):
model = User
template_name = 'users.html'
# SOLUTION: Add pagination
class UserListView(ListView):
model = User
template_name = 'users.html'
paginate_by = 25
Rule: Use iterator() for large batch processing
# PROBLEM: Loads all objects into memory at once
for user in User.objects.all():
process(user)
# SOLUTION: Stream with iterator()
for user in User.objects.iterator(chunk_size=1000):
process(user)
Rule: Never call list() on unbounded querysets
# PROBLEM: Forces full evaluation into memory
all_users = list(User.objects.all())
# SOLUTION: Keep as queryset, slice if needed
users = User.objects.all()[:100]
Validation Checklist for Unbounded Querysets
- Table is large (10k+ rows) or will grow unbounded
- No pagination class, paginate_by, or slicing
- This runs on user-facing request (not background job with chunking)
Priority 3: Missing Indexes (HIGH)
Impact: Full table scans. Negligible on small tables, catastrophic on large ones.
Rule: Index fields used in WHERE clauses on large tables
# PROBLEM: Filtering on unindexed field
# User.objects.filter(email=email) # full scan if no index
class User(models.Model):
email = models.EmailField() # ← no db_index
# SOLUTION: Add index
class User(models.Model):
email = models.EmailField(db_index=True)
Rule: Index fields used in ORDER BY on large tables
# PROBLEM: Sorting requires full scan without index
Order.objects.order_by('-created')
# SOLUTION: Index the sort field
class Order(models.Model):
created = models.DateTimeField(db_index=True)
Rule: Use composite indexes for common query patterns
class Order(models.Model):
user = models.ForeignKey(User)
status = models.CharField(max_length=20)
created = models.DateTimeField()
class Meta:
indexes = [
models.Index(fields=['user', 'status']), # for filter(user=x, status=y)
models.Index(fields=['status', '-created']), # for filter(status=x).order_by('-created')
]
Validation Checklist for Missing Indexes
- Table has 10k+ rows
- Field is used in filter() or order_by() on hot path
- Checked model - no db_index=True or Meta.indexes entry
- Not a foreign key (already indexed automatically)
Priority 4: Write Loops (HIGH)
Impact: N database writes instead of 1. Lock contention. Slow requests.
Rule: Use bulk_create instead of create() in loops
# PROBLEM: N inserts, N round trips
for item in items:
Model.objects.create(name=item['name'])
# SOLUTION: Single bulk insert
Model.objects.bulk_create([
Model(name=item['name']) for item in items
])
Rule: Use update() or bulk_update instead of save() in loops
# PROBLEM: N updates
for obj in queryset:
obj.status = 'done'
obj.save()
# SOLUTION A: Single UPDATE statement (same value for all)
queryset.update(status='done')
# SOLUTION B: bulk_update (different values)
for obj in objects:
obj.status = compute_status(obj)
Model.objects.bulk_update(objects, ['status'], batch_size=500)
Rule: Use delete() on queryset, not in loops
# PROBLEM: N deletes
for obj in queryset:
obj.delete()
# SOLUTION: Single DELETE
queryset.delete()
Validation Checklist for Write Loops
- Loop iterates over 100+ items (or unbounded)
- Each iteration calls create(), save(), or delete()
- This runs on user-facing request (not one-time migration script)
Priority 5: Inefficient Patterns (LOW)
Rarely worth reporting. Include only as minor notes if you're already reporting real issues.
Pattern: count() vs exists()
# Slightly suboptimal
if queryset.count() > 0:
do_thing()
# Marginally better
if queryset.exists():
do_thing()
Usually skip - difference is <1ms in most cases.
Pattern: len(queryset) vs count()
# Fetches all rows to count
if len(queryset) > 0: # bad if queryset not yet evaluated
# Single COUNT query
if queryset.count() > 0:
Only flag if queryset is large and not already evaluated.
Pattern: get() in small loops
# N queries, but if N is small (< 20), often fine
for id in ids:
obj = Model.objects.get(id=id)
Only flag if loop is large or this is in a very hot path.
Validation Requirements
Before reporting ANY issue:
- Trace the data flow - Follow queryset from creation to consumption
- Search for existing optimizations - Grep for select_related, prefetch_related, pagination
- Verify data volume - Check if table is actually large
- Confirm hot path - Trace call sites, verify this runs frequently
- Rule out mitigations - Check for caching, rate limiting
If you cannot validate all steps, do not report.
Output Format
## Django Performance Review: [File/Component Name]
### Summary
Validated issues: X (Y Critical, Z High)
### Findings
#### [PERF-001] N+1 Query in UserListView (CRITICAL)
**Location:** `views.py:45`
**Issue:** Related field `profile` accessed in template loop without prefetch.
**Validation:**
- Traced: UserListView → users queryset → user_list.html → `{{ user.profile.bio }}` in loop
- Searched codebase: no select_related('profile') found
- User table: 50k+ rows (verified in admin)
- Hot path: linked from homepage navigation
**Evidence:**
```python
def get_queryset(self):
return User.objects.filter(active=True) # no select_related
Fix:
def get_queryset(self):
return User.objects.filter(active=True).select_related('profile')
If no issues found: "No performance issues identified after reviewing [files] and validating [what you checked]."
**Before submitting, sanity check each finding:**
- Does the severity match the actual impact? ("Minor inefficiency" ≠ CRITICAL)
- Is this a real performance issue or just a style preference?
- Would fixing this measurably improve performance?
If the answer to any is "no" - remove the finding.
---
## What NOT to Report
- Test files
- Admin-only views
- Management commands
- Migration files
- One-time scripts
- Code behind disabled feature flags
- Tables with <1000 rows that won't grow
- Patterns in cold paths (rarely executed code)
- Micro-optimizations (exists vs count, only/defer without evidence)
### False Positives to Avoid
**Queryset variable assignment is not an issue:**
```python
# This is FINE - no performance difference
projects_qs = Project.objects.filter(org=org)
projects = list(projects_qs)
# vs this - identical performance
projects = list(Project.objects.filter(org=org))
Querysets are lazy. Assigning to a variable doesn't execute anything.
Single query patterns are not N+1:
# This is ONE query, not N+1
projects = list(Project.objects.filter(org=org))
N+1 requires a loop that triggers additional queries. A single list() call is fine.
Missing select_related on single object fetch is not N+1:
# This is 2 queries, not N+1 - report as LOW at most
state = AutofixState.objects.filter(pr_id=pr_id).first()
project_id = state.request.project_id # second query
N+1 requires a loop. A single object doing 2 queries instead of 1 can be reported as LOW if relevant, but never as CRITICAL/HIGH.
Style preferences are not performance issues: If your only suggestion is "combine these two lines" or "rename this variable" - that's style, not performance. Don't report it.
/sentry-doc-coauthoring
Source: ~/.claude/skills/sentry-doc-coauthoring/SKILL.md
name: doc-coauthoring description: Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.
Doc Co-Authoring Workflow
This skill provides a structured workflow for guiding users through collaborative document creation. Act as an active guide, walking users through three stages: Context Gathering, Refinement & Structure, and Reader Testing.
When to Offer This Workflow
Trigger conditions:
- User mentions writing documentation: "write a doc", "draft a proposal", "create a spec", "write up"
- User mentions specific doc types: "PRD", "design doc", "decision doc", "RFC"
- User seems to be starting a substantial writing task
Initial offer: Offer the user a structured workflow for co-authoring the document. Explain the three stages:
- Context Gathering: User provides all relevant context while Claude asks clarifying questions
- Refinement & Structure: Iteratively build each section through brainstorming and editing
- Reader Testing: Test the doc with a fresh Claude (no context) to catch blind spots before others read it
Explain that this approach helps ensure the doc works well when others read it (including when they paste it into Claude). Ask if they want to try this workflow or prefer to work freeform.
If user declines, work freeform. If user accepts, proceed to Stage 1.
Stage 1: Context Gathering
Goal: Close the gap between what the user knows and what Claude knows, enabling smart guidance later.
Initial Questions
Start by asking the user for meta-context about the document:
- What type of document is this? (e.g., technical spec, decision doc, proposal)
- Who's the primary audience?
- What's the desired impact when someone reads this?
- Is there a template or specific format to follow?
- Any other constraints or context to know?
Inform them they can answer in shorthand or dump information however works best for them.
If user provides a template or mentions a doc type:
- Ask if they have a template document to share
- If they provide a link to a shared document, use the appropriate integration to fetch it
- If they provide a file, read it
If user mentions editing an existing shared document:
- Use the appropriate integration to read the current state
- Check for images without alt-text
- If images exist without alt-text, explain that when others use Claude to understand the doc, Claude won't be able to see them. Ask if they want alt-text generated. If so, request they paste each image into chat for descriptive alt-text generation.
Info Dumping
Once initial questions are answered, encourage the user to dump all the context they have. Request information such as:
- Background on the project/problem
- Related team discussions or shared documents
- Why alternative solutions aren't being used
- Organizational context (team dynamics, past incidents, politics)
- Timeline pressures or constraints
- Technical architecture or dependencies
- Stakeholder concerns
Advise them not to worry about organizing it - just get it all out. Offer multiple ways to provide context:
- Info dump stream-of-consciousness
- Point to team channels or threads to read
- Link to shared documents
If integrations are available (e.g., Slack, Teams, Google Drive, SharePoint, or other MCP servers), mention that these can be used to pull in context directly.
If no integrations are detected and in Claude.ai or Claude app: Suggest they can enable connectors in their Claude settings to allow pulling context from messaging apps and document storage directly.
Inform them clarifying questions will be asked once they've done their initial dump.
During context gathering:
-
If user mentions team channels or shared documents:
- If integrations available: Inform them the content will be read now, then use the appropriate integration
- If integrations not available: Explain lack of access. Suggest they enable connectors in Claude settings, or paste the relevant content directly.
-
If user mentions entities/projects that are unknown:
- Ask if connected tools should be searched to learn more
- Wait for user confirmation before searching
-
As user provides context, track what's being learned and what's still unclear
Asking clarifying questions:
When user signals they've done their initial dump (or after substantial context provided), ask clarifying questions to ensure understanding:
Generate 5-10 numbered questions based on gaps in the context.
Inform them they can use shorthand to answer (e.g., "1: yes, 2: see #channel, 3: no because backwards compat"), link to more docs, point to channels to read, or just keep info-dumping. Whatever's most efficient for them.
Exit condition: Sufficient context has been gathered when questions show understanding - when edge cases and trade-offs can be asked about without needing basics explained.
Transition: Ask if there's any more context they want to provide at this stage, or if it's time to move on to drafting the document.
If user wants to add more, let them. When ready, proceed to Stage 2.
Stage 2: Refinement & Structure
Goal: Build the document section by section through brainstorming, curation, and iterative refinement.
Instructions to user: Explain that the document will be built section by section. For each section:
- Clarifying questions will be asked about what to include
- 5-20 options will be brainstormed
- User will indicate what to keep/remove/combine
- The section will be drafted
- It will be refined through surgical edits
Start with whichever section has the most unknowns (usually the core decision/proposal), then work through the rest.
Section ordering:
If the document structure is clear: Ask which section they'd like to start with.
Suggest starting with whichever section has the most unknowns. For decision docs, that's usually the core proposal. For specs, it's typically the technical approach. Summary sections are best left for last.
If user doesn't know what sections they need: Based on the type of document and template, suggest 3-5 sections appropriate for the doc type.
Ask if this structure works, or if they want to adjust it.
Once structure is agreed:
Create the initial document structure with placeholder text for all sections.
If access to artifacts is available:
Use create_file to create an artifact. This gives both Claude and the user a scaffold to work from.
Inform them that the initial structure with placeholders for all sections will be created.
Create artifact with all section headers and brief placeholder text like "[To be written]" or "[Content here]".
Provide the scaffold link and indicate it's time to fill in each section.
If no access to artifacts:
Create a markdown file in the working directory. Name it appropriately (e.g., decision-doc.md, technical-spec.md).
Inform them that the initial structure with placeholders for all sections will be created.
Create file with all section headers and placeholder text.
Confirm the filename has been created and indicate it's time to fill in each section.
For each section:
Step 1: Clarifying Questions
Announce work will begin on the [SECTION NAME] section. Ask 5-10 clarifying questions about what should be included:
Generate 5-10 specific questions based on context and section purpose.
Inform them they can answer in shorthand or just indicate what's important to cover.
Step 2: Brainstorming
For the [SECTION NAME] section, brainstorm [5-20] things that might be included, depending on the section's complexity. Look for:
Generate 5-20 numbered options based on section complexity. At the end, offer to brainstorm more if they want additional options.
Step 3: Curation
Ask which points should be kept, removed, or combined. Request brief justifications to help learn priorities for the next sections.
Provide examples:
- "Keep 1,4,7,9"
- "Remove 3 (duplicates 1)"
- "Remove 6 (audience already knows this)"
- "Combine 11 and 12"
If user gives freeform feedback (e.g., "looks good" or "I like most of it but...") instead of numbered selections, extract their preferences and proceed. Parse what they want kept/removed/changed and apply it.
Step 4: Gap Check
Based on what they've selected, ask if there's anything important missing for the [SECTION NAME] section.
Step 5: Drafting
Use str_replace to replace the placeholder text for this section with the actual drafted content.
Announce the [SECTION NAME] section will be drafted now based on what they've selected.
If using artifacts: After drafting, provide a link to the artifact.
Ask them to read through it and indicate what to change. Note that being specific helps learning for the next sections.
If using a file (no artifacts): After drafting, confirm completion.
Inform them the [SECTION NAME] section has been drafted in [filename]. Ask them to read through it and indicate what to change. Note that being specific helps learning for the next sections.
Key instruction for user (include when drafting the first section): Provide a note: Instead of editing the doc directly, ask them to indicate what to change. This helps learning of their style for future sections. For example: "Remove the X bullet - already covered by Y" or "Make the third paragraph more concise".
Step 6: Iterative Refinement
As user provides feedback:
- Use
str_replaceto make edits (never reprint the whole doc) - If using artifacts: Provide link to artifact after each edit
- If using files: Just confirm edits are complete
- If user edits doc directly and asks to read it: mentally note the changes they made and keep them in mind for future sections (this shows their preferences)
Continue iterating until user is satisfied with the section.
Quality Checking
After 3 consecutive iterations with no substantial changes, ask if anything can be removed without losing important information.
When section is done, confirm [SECTION NAME] is complete. Ask if ready to move to the next section.
Repeat for all sections.
Near Completion
As approaching completion (80%+ of sections done), announce intention to re-read the entire document and check for:
- Flow and consistency across sections
- Redundancy or contradictions
- Anything that feels like "slop" or generic filler
- Whether every sentence carries weight
Read entire document and provide feedback.
When all sections are drafted and refined: Announce all sections are drafted. Indicate intention to review the complete document one more time.
Review for overall coherence, flow, completeness.
Provide any final suggestions.
Ask if ready to move to Reader Testing, or if they want to refine anything else.
Stage 3: Reader Testing
Goal: Test the document with a fresh Claude (no context bleed) to verify it works for readers.
Instructions to user: Explain that testing will now occur to see if the document actually works for readers. This catches blind spots - things that make sense to the authors but might confuse others.
Testing Approach
If access to sub-agents is available (e.g., in Claude Code):
Perform the testing directly without user involvement.
Step 1: Predict Reader Questions
Announce intention to predict what questions readers might ask when trying to discover this document.
Generate 5-10 questions that readers would realistically ask.
Step 2: Test with Sub-Agent
Announce that these questions will be tested with a fresh Claude instance (no context from this conversation).
For each question, invoke a sub-agent with just the document content and the question.
Summarize what Reader Claude got right/wrong for each question.
Step 3: Run Additional Checks
Announce additional checks will be performed.
Invoke sub-agent to check for ambiguity, false assumptions, contradictions.
Summarize any issues found.
Step 4: Report and Fix
If issues found: Report that Reader Claude struggled with specific issues.
List the specific issues.
Indicate intention to fix these gaps.
Loop back to refinement for problematic sections.
If no access to sub-agents (e.g., claude.ai web interface):
The user will need to do the testing manually.
Step 1: Predict Reader Questions
Ask what questions people might ask when trying to discover this document. What would they type into Claude.ai?
Generate 5-10 questions that readers would realistically ask.
Step 2: Setup Testing
Provide testing instructions:
- Open a fresh Claude conversation: https://claude.ai
- Paste or share the document content (if using a shared doc platform with connectors enabled, provide the link)
- Ask Reader Claude the generated questions
For each question, instruct Reader Claude to provide:
- The answer
- Whether anything was ambiguous or unclear
- What knowledge/context the doc assumes is already known
Check if Reader Claude gives correct answers or misinterprets anything.
Step 3: Additional Checks
Also ask Reader Claude:
- "What in this doc might be ambiguous or unclear to readers?"
- "What knowledge or context does this doc assume readers already have?"
- "Are there any internal contradictions or inconsistencies?"
Step 4: Iterate Based on Results
Ask what Reader Claude got wrong or struggled with. Indicate intention to fix those gaps.
Loop back to refinement for any problematic sections.
Exit Condition (Both Approaches)
When Reader Claude consistently answers questions correctly and doesn't surface new gaps or ambiguities, the doc is ready.
Final Review
When Reader Testing passes: Announce the doc has passed Reader Claude testing. Before completion:
- Recommend they do a final read-through themselves - they own this document and are responsible for its quality
- Suggest double-checking any facts, links, or technical details
- Ask them to verify it achieves the impact they wanted
Ask if they want one more review, or if the work is done.
If user wants final review, provide it. Otherwise: Announce document completion. Provide a few final tips:
- Consider linking this conversation in an appendix so readers can see how the doc was developed
- Use appendices to provide depth without bloating the main doc
- Update the doc as feedback is received from real readers
Tips for Effective Guidance
Tone:
- Be direct and procedural
- Explain rationale briefly when it affects user behavior
- Don't try to "sell" the approach - just execute it
Handling Deviations:
- If user wants to skip a stage: Ask if they want to skip this and write freeform
- If user seems frustrated: Acknowledge this is taking longer than expected. Suggest ways to move faster
- Always give user agency to adjust the process
Context Management:
- Throughout, if context is missing on something mentioned, proactively ask
- Don't let gaps accumulate - address them as they come up
Artifact Management:
- Use
create_filefor drafting full sections - Use
str_replacefor all edits - Provide artifact link after every change
- Never use artifacts for brainstorming lists - that's just conversation
Quality over Speed:
- Don't rush through stages
- Each iteration should make meaningful improvements
- The goal is a document that actually works for readers
Attribution
This skill was adapted from anthropics/skills.
/sentry-find-bugs
Source: ~/.claude/skills/sentry-find-bugs/SKILL.md
name: find-bugs description: Find bugs, security vulnerabilities, and code quality issues in local branch changes. Use when asked to review changes, find bugs, security review, or audit code on the current branch.
Find Bugs
Review changes on this branch for bugs, security vulnerabilities, and code quality issues.
Phase 1: Complete Input Gathering
- Get the FULL diff:
git diff $(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')...HEAD - If output is truncated, read each changed file individually until you have seen every changed line
- List all files modified in this branch before proceeding
Phase 2: Attack Surface Mapping
For each changed file, identify and list:
- All user inputs (request params, headers, body, URL components)
- All database queries
- All authentication/authorization checks
- All session/state operations
- All external calls
- All cryptographic operations
Phase 3: Security Checklist (check EVERY item for EVERY file)
- Injection: SQL, command, template, header injection
- XSS: All outputs in templates properly escaped?
- Authentication: Auth checks on all protected operations?
- Authorization/IDOR: Access control verified, not just auth?
- CSRF: State-changing operations protected?
- Race conditions: TOCTOU in any read-then-write patterns?
- Session: Fixation, expiration, secure flags?
- Cryptography: Secure random, proper algorithms, no secrets in logs?
- Information disclosure: Error messages, logs, timing attacks?
- DoS: Unbounded operations, missing rate limits, resource exhaustion?
- Business logic: Edge cases, state machine violations, numeric overflow?
Phase 4: Verification
For each potential issue:
- Check if it's already handled elsewhere in the changed code
- Search for existing tests covering the scenario
- Read surrounding context to verify the issue is real
Phase 5: Pre-Conclusion Audit
Before finalizing, you MUST:
- List every file you reviewed and confirm you read it completely
- List every checklist item and note whether you found issues or confirmed it's clean
- List any areas you could NOT fully verify and why
- Only then provide your final findings
Output Format
Prioritize: security vulnerabilities > bugs > code quality
Skip: stylistic/formatting issues
For each issue:
- File:Line - Brief description
- Severity: Critical/High/Medium/Low
- Problem: What's wrong
- Evidence: Why this is real (not already fixed, no existing test, etc.)
- Fix: Concrete suggestion
- References: OWASP, RFCs, or other standards if applicable
If you find nothing significant, say so - don't invent issues.
Do not make changes - just report findings. I'll decide what to address.
/sentry-iterate-pr
Source: ~/.claude/skills/sentry-iterate-pr/SKILL.md
name: iterate-pr description: Iterate on a PR until CI passes. Use when you need to fix CI failures, address review feedback, or continuously push fixes until all checks are green. Automates the feedback-fix-push-wait cycle.
Iterate on PR Until CI Passes
Continuously iterate on the current branch until all CI checks pass and review feedback is addressed.
Requires: GitHub CLI (gh) authenticated.
Important: All scripts must be run from the repository root directory (where .git is located), not from the skill directory. Use the full path to the script via ${CLAUDE_SKILL_ROOT}.
Bundled Scripts
scripts/fetch_pr_checks.py
Fetches CI check status and extracts failure snippets from logs.
uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.py [--pr NUMBER]
Returns JSON:
{
"pr": {"number": 123, "branch": "feat/foo"},
"summary": {"total": 5, "passed": 3, "failed": 2, "pending": 0},
"checks": [
{"name": "tests", "status": "fail", "log_snippet": "...", "run_id": 123},
{"name": "lint", "status": "pass"}
]
}
scripts/fetch_pr_feedback.py
Fetches and categorizes PR review feedback using the LOGAF scale.
uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py [--pr NUMBER]
Returns JSON with feedback categorized as:
high- Must address before merge (h:, blocker, changes requested)medium- Should address (m:, standard feedback)low- Optional (l:, nit, style, suggestion)bot- Automated comments (Codecov, Sentry, etc.)resolved- Already resolved threads
Workflow
1. Identify PR
gh pr view --json number,url,headRefName
Stop if no PR exists for the current branch.
2. Check CI Status
Run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.py to get structured failure data.
Wait if pending: If bot-related checks (sentry, codecov, cursor, bugbot, seer) are still running, wait before proceeding—they may post additional feedback.
3. Fix CI Failures
For each failure in the script output:
- Read the
log_snippetto understand the failure - Read the relevant code before making changes
- Fix the issue with minimal, targeted changes
Do NOT assume what failed based on check name alone—always read the logs.
4. Gather Review Feedback
Run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py to get categorized feedback.
5. Handle Feedback by LOGAF Priority
Auto-fix (no prompt):
high- must address (blockers, security, changes requested)medium- should address (standard feedback)
Prompt user for selection:
low- present numbered list and ask which to address:
Found 3 low-priority suggestions:
1. [l] "Consider renaming this variable" - @reviewer in api.py:42
2. [nit] "Could use a list comprehension" - @reviewer in utils.py:18
3. [style] "Add a docstring" - @reviewer in models.py:55
Which would you like to address? (e.g., "1,3" or "all" or "none")
Skip silently:
resolvedthreadsbotcomments (informational only)
6. Commit and Push
git add <files>
git commit -m "fix: <descriptive message>"
git push
7. Wait for CI
gh pr checks --watch --interval 30
8. Repeat
Return to step 2 if CI failed or new feedback appeared.
Exit Conditions
Success: All checks pass, no unaddressed high/medium feedback, user has decided on low-priority items.
Ask for help: Same failure after 3 attempts, feedback needs clarification, infrastructure issues.
Stop: No PR exists, branch needs rebase.
Fallback
If scripts fail, use gh CLI directly:
gh pr checks --json name,state,bucket,linkgh run view <run-id> --log-failedgh api repos/{owner}/{repo}/pulls/{number}/comments
/sentry-security-review
Source: ~/.claude/skills/sentry-security-review/SKILL.md
name: security-review description: Security code review for vulnerabilities. Use when asked to "security review", "find vulnerabilities", "check for security issues", "audit security", "OWASP review", or review code for injection, XSS, authentication, authorization, cryptography issues. Provides systematic review with confidence-based reporting. allowed-tools: Read Grep Glob Bash Task license: LICENSE
Security Review Skill
Identify exploitable security vulnerabilities in code. Report only HIGH CONFIDENCE findings—clear vulnerable patterns with attacker-controlled input.
Scope: Research vs. Reporting
CRITICAL DISTINCTION:
- Report on: Only the specific file, diff, or code provided by the user
- Research: The ENTIRE codebase to build confidence before reporting
Before flagging any issue, you MUST research the codebase to understand:
- Where does this input actually come from? (Trace data flow)
- Is there validation/sanitization elsewhere?
- How is this configured? (Check settings, config files, middleware)
- What framework protections exist?
Do NOT report issues based solely on pattern matching. Investigate first, then report only what you're confident is exploitable.
Confidence Levels
| Level | Criteria | Action |
|---|---|---|
| HIGH | Vulnerable pattern + attacker-controlled input confirmed | Report with severity |
| MEDIUM | Vulnerable pattern, input source unclear | Note as "Needs verification" |
| LOW | Theoretical, best practice, defense-in-depth | Do not report |
Do Not Flag
General Rules
- Test files (unless explicitly reviewing test security)
- Dead code, commented code, documentation strings
- Patterns using constants or server-controlled configuration
- Code paths that require prior authentication to reach (note the auth requirement instead)
Server-Controlled Values (NOT Attacker-Controlled)
These are configured by operators, not controlled by attackers:
| Source | Example | Why It's Safe |
|---|---|---|
| Django settings | settings.API_URL, settings.ALLOWED_HOSTS |
Set via config/env at deployment |
| Environment variables | os.environ.get('DATABASE_URL') |
Deployment configuration |
| Config files | config.yaml, app.config['KEY'] |
Server-side files |
| Framework constants | django.conf.settings.* |
Not user-modifiable |
| Hardcoded values | BASE_URL = "https://api.internal" |
Compile-time constants |
SSRF Example - NOT a vulnerability:
# SAFE: URL comes from Django settings (server-controlled)
response = requests.get(f"{settings.SEER_AUTOFIX_URL}{path}")
SSRF Example - IS a vulnerability:
# VULNERABLE: URL comes from request (attacker-controlled)
response = requests.get(request.GET.get('url'))
Framework-Mitigated Patterns
Check language guides before flagging. Common false positives:
| Pattern | Why It's Usually Safe |
|---|---|
Django {{ variable }} |
Auto-escaped by default |
React {variable} |
Auto-escaped by default |
Vue {{ variable }} |
Auto-escaped by default |
User.objects.filter(id=input) |
ORM parameterizes queries |
cursor.execute("...%s", (input,)) |
Parameterized query |
innerHTML = "<b>Loading...</b>" |
Constant string, no user input |
Only flag these when:
- Django:
{{ var|safe }},{% autoescape off %},mark_safe(user_input) - React:
dangerouslySetInnerHTML={{__html: userInput}} - Vue:
v-html="userInput" - ORM:
.raw(),.extra(),RawSQL()with string interpolation
Review Process
1. Detect Context
What type of code am I reviewing?
| Code Type | Load These References |
|---|---|
| API endpoints, routes | authorization.md, authentication.md, injection.md |
| Frontend, templates | xss.md, csrf.md |
| File handling, uploads | file-security.md |
| Crypto, secrets, tokens | cryptography.md, data-protection.md |
| Data serialization | deserialization.md |
| External requests | ssrf.md |
| Business workflows | business-logic.md |
| GraphQL, REST design | api-security.md |
| Config, headers, CORS | misconfiguration.md |
| CI/CD, dependencies | supply-chain.md |
| Error handling | error-handling.md |
| Audit, logging | logging.md |
2. Load Language Guide
Based on file extension or imports:
| Indicators | Guide |
|---|---|
.py, django, flask, fastapi |
languages/python.md |
.js, .ts, express, react, vue, next |
languages/javascript.md |
.go, go.mod |
languages/go.md |
.rs, Cargo.toml |
languages/rust.md |
.java, spring, @Controller |
languages/java.md |
3. Load Infrastructure Guide (if applicable)
| File Type | Guide |
|---|---|
Dockerfile, .dockerignore |
infrastructure/docker.md |
| K8s manifests, Helm charts | infrastructure/kubernetes.md |
.tf, Terraform |
infrastructure/terraform.md |
GitHub Actions, .gitlab-ci.yml |
infrastructure/ci-cd.md |
| AWS/GCP/Azure configs, IAM | infrastructure/cloud.md |
4. Research Before Flagging
For each potential issue, research the codebase to build confidence:
- Where does this value actually come from? Trace the data flow.
- Is it configured at deployment (settings, env vars) or from user input?
- Is there validation, sanitization, or allowlisting elsewhere?
- What framework protections apply?
Only report issues where you have HIGH confidence after understanding the broader context.
5. Verify Exploitability
For each potential finding, confirm:
Is the input attacker-controlled?
| Attacker-Controlled (Investigate) | Server-Controlled (Usually Safe) |
|---|---|
request.GET, request.POST, request.args |
settings.X, app.config['X'] |
request.json, request.data, request.body |
os.environ.get('X') |
request.headers (most headers) |
Hardcoded constants |
request.cookies (unsigned) |
Internal service URLs from config |
URL path segments: /users/<id>/ |
Database content from admin/system |
| File uploads (content and names) | Signed session data |
| Database content from other users | Framework settings |
| WebSocket messages |
Does the framework mitigate this?
- Check language guide for auto-escaping, parameterization
- Check for middleware/decorators that sanitize
Is there validation upstream?
- Input validation before this code
- Sanitization libraries (DOMPurify, bleach, etc.)
6. Report HIGH Confidence Only
Skip theoretical issues. Report only what you've confirmed is exploitable after research.
Severity Classification
| Severity | Impact | Examples |
|---|---|---|
| Critical | Direct exploit, severe impact, no auth required | RCE, SQL injection to data, auth bypass, hardcoded secrets |
| High | Exploitable with conditions, significant impact | Stored XSS, SSRF to metadata, IDOR to sensitive data |
| Medium | Specific conditions required, moderate impact | Reflected XSS, CSRF on state-changing actions, path traversal |
| Low | Defense-in-depth, minimal direct impact | Missing headers, verbose errors, weak algorithms in non-critical context |
Quick Patterns Reference
Always Flag (Critical)
eval(user_input) # Any language
exec(user_input) # Any language
pickle.loads(user_data) # Python
yaml.load(user_data) # Python (not safe_load)
unserialize($user_data) # PHP
deserialize(user_data) # Java ObjectInputStream
shell=True + user_input # Python subprocess
child_process.exec(user) # Node.js
Always Flag (High)
innerHTML = userInput # DOM XSS
dangerouslySetInnerHTML={user} # React XSS
v-html="userInput" # Vue XSS
f"SELECT * FROM x WHERE {user}" # SQL injection
`SELECT * FROM x WHERE ${user}` # SQL injection
os.system(f"cmd {user_input}") # Command injection
Always Flag (Secrets)
password = "hardcoded"
api_key = "sk-..."
AWS_SECRET_ACCESS_KEY = "..."
private_key = "-----BEGIN"
Check Context First (MUST Investigate Before Flagging)
# SSRF - ONLY if URL is from user input, NOT from settings/config
requests.get(request.GET['url']) # FLAG: User-controlled URL
requests.get(settings.API_URL) # SAFE: Server-controlled config
requests.get(f"{settings.BASE}/{x}") # CHECK: Is 'x' user input?
# Path traversal - ONLY if path is from user input
open(request.GET['file']) # FLAG: User-controlled path
open(settings.LOG_PATH) # SAFE: Server-controlled config
open(f"{BASE_DIR}/{filename}") # CHECK: Is 'filename' user input?
# Open redirect - ONLY if URL is from user input
redirect(request.GET['next']) # FLAG: User-controlled redirect
redirect(settings.LOGIN_URL) # SAFE: Server-controlled config
# Weak crypto - ONLY if used for security purposes
hashlib.md5(file_content) # SAFE: File checksums, caching
hashlib.md5(password) # FLAG: Password hashing
random.random() # SAFE: Non-security uses (UI, sampling)
random.random() for token # FLAG: Security tokens need secrets module
Output Format
## Security Review: [File/Component Name]
### Summary
- **Findings**: X (Y Critical, Z High, ...)
- **Risk Level**: Critical/High/Medium/Low
- **Confidence**: High/Mixed
### Findings
#### [VULN-001] [Vulnerability Type] (Severity)
- **Location**: `file.py:123`
- **Confidence**: High
- **Issue**: [What the vulnerability is]
- **Impact**: [What an attacker could do]
- **Evidence**:
```python
[Vulnerable code snippet]
- Fix: [How to remediate]
Needs Verification
[VERIFY-001] [Potential Issue]
- Location:
file.py:456 - Question: [What needs to be verified]
If no vulnerabilities found, state: "No high-confidence vulnerabilities identified."
---
## Reference Files
### Core Vulnerabilities (`references/`)
| File | Covers |
|------|--------|
| `injection.md` | SQL, NoSQL, OS command, LDAP, template injection |
| `xss.md` | Reflected, stored, DOM-based XSS |
| `authorization.md` | Authorization, IDOR, privilege escalation |
| `authentication.md` | Sessions, credentials, password storage |
| `cryptography.md` | Algorithms, key management, randomness |
| `deserialization.md` | Pickle, YAML, Java, PHP deserialization |
| `file-security.md` | Path traversal, uploads, XXE |
| `ssrf.md` | Server-side request forgery |
| `csrf.md` | Cross-site request forgery |
| `data-protection.md` | Secrets exposure, PII, logging |
| `api-security.md` | REST, GraphQL, mass assignment |
| `business-logic.md` | Race conditions, workflow bypass |
| `modern-threats.md` | Prototype pollution, LLM injection, WebSocket |
| `misconfiguration.md` | Headers, CORS, debug mode, defaults |
| `error-handling.md` | Fail-open, information disclosure |
| `supply-chain.md` | Dependencies, build security |
| `logging.md` | Audit failures, log injection |
### Language Guides (`languages/`)
- `python.md` - Django, Flask, FastAPI patterns
- `javascript.md` - Node, Express, React, Vue, Next.js
- `go.md` - Go-specific security patterns
- `rust.md` - Rust unsafe blocks, FFI security
- `java.md` - Spring, Java EE patterns
### Infrastructure (`infrastructure/`)
- `docker.md` - Container security
- `kubernetes.md` - K8s RBAC, secrets, policies
- `terraform.md` - IaC security
- `ci-cd.md` - Pipeline security
- `cloud.md` - AWS/GCP/Azure security
/sentry-skill-creator
Source: ~/.claude/skills/sentry-skill-creator/SKILL.md
name: skill-creator description: Create new agent skills following the Agent Skills specification. Use when asked to "create a skill", "add a new skill", "write a skill", "make a skill", "build a skill", or scaffold a new skill with SKILL.md. Guides through requirements, writing, registration, and verification.
Create a New Skill
Guide the user through creating a new agent skill following the Agent Skills specification. Follow each step in order.
Step 1: Understand the Skill
Gather requirements before writing anything.
Ask the user:
- What should this skill do? (one sentence)
- When should an agent use it? (trigger phrases)
- What tools does the skill need? (Read, Grep, Glob, Bash, Task, WebFetch, etc.)
- Where should the skill live? (which plugin or directory)
Determine the skill name:
- Lowercase alphanumeric with hyphens, 1-64 characters
- Descriptive and unique among existing skills
- Check the target skills directory to avoid name collisions
Choose a complexity tier:
| Tier | Structure | Use When |
|---|---|---|
| Simple | SKILL.md only |
Self-contained instructions under ~200 lines |
| With references | SKILL.md + references/ |
Domain knowledge that agents load conditionally |
| With scripts | SKILL.md + scripts/ |
Workflow automation needing Python scripts |
| Full | All of the above | Complex skills with automation and domain knowledge |
Read ${CLAUDE_SKILL_ROOT}/references/design-principles.md for guidance on keeping skills focused and concise.
Step 2: Study Existing Skills
Before writing, study 1-2 existing skills that match the chosen tier. Look for skills in the target repository or plugin to understand local conventions.
Read ${CLAUDE_SKILL_ROOT}/references/skill-patterns.md for concrete examples of each tier.
Also read CLAUDE.md (or AGENTS.md) at the repository root for repo-specific conventions that the skill should follow.
Step 3: Write the SKILL.md
Create <skill-directory>/<name>/SKILL.md.
Frontmatter
The YAML frontmatter must be the first thing in the file. No comments or blank lines before ---.
---
name: <skill-name>
description: <what it does>. Use when <trigger phrases>. <key capabilities>.
---
Required fields:
name— must match the directory name exactlydescription— up to 1024 chars; include trigger keywords that help agents match user intent
Optional fields:
model— override model (sonnet,opus,haiku); omit to use the user's defaultallowed-tools— space-delimited list (e.g.,Read Grep Glob Bash Task); omit to allow all toolslicense— license name or path (add when vendoring external content)
Body Guidelines
Write the body in imperative voice — these are instructions, not documentation.
| Do | Don't |
|---|---|
| "Read the file and extract..." | "This skill reads the file and extracts..." |
| "Report only HIGH confidence findings" | "The agent should report only HIGH confidence findings" |
| "Ask the user which option to use" | "You may want to ask the user..." |
Structure:
- Start with a one-line summary of what the skill does
- Organize steps with
## Step N: Titleheadings - Use tables for decision logic and mappings
- Include concrete examples of expected output
- End with validation criteria or exit conditions
Size limits:
- Keep SKILL.md under 500 lines
- If approaching the limit, move reference material to
references/files - Load reference files conditionally based on context (not all at once)
Attribution
If the skill is based on or adapted from external sources, add an HTML comment after the frontmatter closing ---:
---
name: example
description: ...
---
<!--
Based on [Original Name] by [Author/Org]:
https://github.com/example/original-source
-->
Step 4: Create Supporting Files
References (references/)
Use for domain knowledge the agent loads conditionally.
<name>/
├── SKILL.md
└── references/
├── topic-a.md
└── topic-b.md
Reference from SKILL.md with:
Read `${CLAUDE_SKILL_ROOT}/references/topic-a.md` for details on [topic].
Keep each reference file focused on one topic. Use markdown with tables and code blocks.
Scripts (scripts/)
Use for workflow automation that benefits from structured Python.
<name>/
├── SKILL.md
└── scripts/
└── do_thing.py
Script requirements:
- Always use
uv runto execute:uv run ${CLAUDE_SKILL_ROOT}/scripts/do_thing.py - Add PEP 723 inline metadata for dependencies:
# /// script
# requires-python = ">=3.12"
# dependencies = ["requests"]
# ///
- Output structured JSON for agent consumption
- Run from the repository root, not the skill directory
- Document the script's interface in SKILL.md (arguments, output format)
Assets (assets/)
Use for static files the skill references (templates, configs, etc.).
LICENSE
Include a LICENSE file in the skill directory when vendoring content with specific licensing requirements.
Step 5: Register the Skill
Registration steps vary by repository. Check the repository's CLAUDE.md or README.md for specific instructions.
- Verify directory-name match — confirm the directory name matches the
namefield in SKILL.md frontmatter exactly - Update documentation — add the skill to any skills index or table in README.md
- Update permissions — if the repo has
.claude/settings.json, addSkill(<plugin>:<name>)to thepermissions.allowarray - Check CLAUDE.md — read the repository's
CLAUDE.mdfor any additional registration steps specific to that project
Step 6: Verify
Run through this checklist before finishing:
Frontmatter
-
namematches directory name -
descriptionis under 1024 characters -
descriptionincludes trigger keywords - No content before the opening
---
Content
- SKILL.md is under 500 lines
- Written in imperative voice
- Steps are numbered and clear
- Examples of expected output included
- Reference files loaded conditionally (not unconditionally)
Registration
- Directory name matches frontmatter
name - Skill added to repo documentation (README or equivalent)
- Permissions updated (if applicable)
- Any repo-specific registration steps completed (check CLAUDE.md)
Scripts (if applicable)
- Uses
uv run ${CLAUDE_SKILL_ROOT}/scripts/... - Has PEP 723 inline metadata
- Outputs structured JSON
- Documented in SKILL.md
Report any issues found and fix them before completing.
/sentry-skill-scanner
Source: ~/.claude/skills/sentry-skill-scanner/SKILL.md
name: skill-scanner description: Scan agent skills for security issues. Use when asked to "scan a skill", "audit a skill", "review skill security", "check skill for injection", "validate SKILL.md", or assess whether an agent skill is safe to install. Checks for prompt injection, malicious scripts, excessive permissions, secret exposure, and supply chain risks. allowed-tools: Read Grep Glob Bash
Skill Security Scanner
Scan agent skills for security issues before adoption. Detects prompt injection, malicious code, excessive permissions, secret exposure, and supply chain risks.
Important: Run all scripts from the repository root using the full path via ${CLAUDE_SKILL_ROOT}.
Bundled Script
scripts/scan_skill.py
Static analysis scanner that detects deterministic patterns. Outputs structured JSON.
uv run ${CLAUDE_SKILL_ROOT}/scripts/scan_skill.py <skill-directory>
Returns JSON with findings, URLs, structure info, and severity counts. The script catches patterns mechanically — your job is to evaluate intent and filter false positives.
Workflow
Phase 1: Input & Discovery
Determine the scan target:
- If the user provides a skill directory path, use it directly
- If the user names a skill, look for it under
plugins/*/skills/<name>/or.claude/skills/<name>/ - If the user says "scan all skills", discover all
*/SKILL.mdfiles and scan each
Validate the target contains a SKILL.md file. List the skill structure:
ls -la <skill-directory>/
ls <skill-directory>/references/ 2>/dev/null
ls <skill-directory>/scripts/ 2>/dev/null
Phase 2: Automated Static Scan
Run the bundled scanner:
uv run ${CLAUDE_SKILL_ROOT}/scripts/scan_skill.py <skill-directory>
Parse the JSON output. The script produces findings with severity levels, URL analysis, and structure information. Use these as leads for deeper analysis.
Fallback: If the script fails, proceed with manual analysis using Grep patterns from the reference files.
Phase 3: Frontmatter Validation
Read the SKILL.md and check:
- Required fields:
nameanddescriptionmust be present - Name consistency:
namefield should match the directory name - Tool assessment: Review
allowed-tools— is Bash justified? Are tools unrestricted (*)? - Model override: Is a specific model forced? Why?
- Description quality: Does the description accurately represent what the skill does?
Phase 4: Prompt Injection Analysis
Load ${CLAUDE_SKILL_ROOT}/references/prompt-injection-patterns.md for context.
Review scanner findings in the "Prompt Injection" category. For each finding:
- Read the surrounding context in the file
- Determine if the pattern is performing injection (malicious) or discussing/detecting injection (legitimate)
- Skills about security, testing, or education commonly reference injection patterns — this is expected
Critical distinction: A security review skill that lists injection patterns in its references is documenting threats, not attacking. Only flag patterns that would execute against the agent running the skill.
Phase 5: Behavioral Analysis
This phase is agent-only — no pattern matching. Read the full SKILL.md instructions and evaluate:
Description vs. instructions alignment:
- Does the description match what the instructions actually tell the agent to do?
- A skill described as "code formatter" that instructs the agent to read ~/.ssh is misaligned
Config/memory poisoning:
- Instructions to modify
CLAUDE.md,MEMORY.md,settings.json,.mcp.json, or hook configurations - Instructions to add itself to allowlists or auto-approve permissions
- Writing to
~/.claude/or any agent configuration directory
Scope creep:
- Instructions that exceed the skill's stated purpose
- Unnecessary data gathering (reading files unrelated to the skill's function)
- Instructions to install other skills, plugins, or dependencies not mentioned in the description
Information gathering:
- Reading environment variables beyond what's needed
- Listing directory contents outside the skill's scope
- Accessing git history, credentials, or user data unnecessarily
Phase 6: Script Analysis
If the skill has a scripts/ directory:
- Load
${CLAUDE_SKILL_ROOT}/references/dangerous-code-patterns.mdfor context - Read each script file fully (do not skip any)
- Check scanner findings in the "Malicious Code" category
- For each finding, evaluate:
- Data exfiltration: Does the script send data to external URLs? What data?
- Reverse shells: Socket connections with redirected I/O
- Credential theft: Reading SSH keys, .env files, tokens from environment
- Dangerous execution: eval/exec with dynamic input, shell=True with interpolation
- Config modification: Writing to agent settings, shell configs, git hooks
- Check PEP 723
dependencies— are they legitimate, well-known packages? - Verify the script's behavior matches the SKILL.md description of what it does
Legitimate patterns: gh CLI calls, git commands, reading project files, JSON output to stdout are normal for skill scripts.
Phase 7: Supply Chain Assessment
Review URLs from the scanner output and any additional URLs found in scripts:
- Trusted domains: GitHub, PyPI, official docs — normal
- Untrusted domains: Unknown domains, personal sites, URL shorteners — flag for review
- Remote instruction loading: Any URL that fetches content to be executed or interpreted as instructions is high risk
- Dependency downloads: Scripts that download and execute binaries or code at runtime
- Unverifiable sources: References to packages or tools not on standard registries
Phase 8: Permission Analysis
Load ${CLAUDE_SKILL_ROOT}/references/permission-analysis.md for the tool risk matrix.
Evaluate:
- Least privilege: Are all granted tools actually used in the skill instructions?
- Tool justification: Does the skill body reference operations that require each tool?
- Risk level: Rate the overall permission profile using the tier system from the reference
Example assessments:
Read Grep Glob— Low risk, read-only analysis skillRead Grep Glob Bash— Medium risk, needs Bash justification (e.g., running bundled scripts)Read Grep Glob Bash Write Edit WebFetch Task— High risk, near-full access
Confidence Levels
| Level | Criteria | Action |
|---|---|---|
| HIGH | Pattern confirmed + malicious intent evident | Report with severity |
| MEDIUM | Suspicious pattern, intent unclear | Note as "Needs verification" |
| LOW | Theoretical, best practice only | Do not report |
False positive awareness is critical. The biggest risk is flagging legitimate security skills as malicious because they reference attack patterns. Always evaluate intent before reporting.
Output Format
## Skill Security Scan: [Skill Name]
### Summary
- **Findings**: X (Y Critical, Z High, ...)
- **Risk Level**: Critical / High / Medium / Low / Clean
- **Skill Structure**: SKILL.md only / +references / +scripts / full
### Findings
#### [SKILL-SEC-001] [Finding Type] (Severity)
- **Location**: `SKILL.md:42` or `scripts/tool.py:15`
- **Confidence**: High
- **Category**: Prompt Injection / Malicious Code / Excessive Permissions / Secret Exposure / Supply Chain / Validation
- **Issue**: [What was found]
- **Evidence**: [code snippet]
- **Risk**: [What could happen]
- **Remediation**: [How to fix]
### Needs Verification
[Medium-confidence items needing human review]
### Assessment
[Safe to install / Install with caution / Do not install]
[Brief justification for the assessment]
Risk level determination:
- Critical: Any high-confidence critical finding (prompt injection, credential theft, data exfiltration)
- High: High-confidence high-severity findings or multiple medium findings
- Medium: Medium-confidence findings or minor permission concerns
- Low: Only best-practice suggestions
- Clean: No findings after thorough analysis
Reference Files
| File | Purpose |
|---|---|
references/prompt-injection-patterns.md |
Injection patterns, jailbreaks, obfuscation techniques, false positive guide |
references/dangerous-code-patterns.md |
Script security patterns: exfiltration, shells, credential theft, eval/exec |
references/permission-analysis.md |
Tool risk tiers, least privilege methodology, common skill permission profiles |
Trail of Bits Skills
/ask-questions-if-underspecified
Source: ~/.claude/skills/tob-ask-questions-if-underspecified/skills/ask-questions-if-underspecified/SKILL.md
name: ask-questions-if-underspecified description: Clarify requirements before implementing. Use when serious doubts arise.
Ask Questions If Underspecified
When to Use
Use this skill when a request has multiple plausible interpretations or key details (objective, scope, constraints, environment, or safety) are unclear.
When NOT to Use
Do not use this skill when the request is already clear, or when a quick, low-risk discovery read can answer the missing details.
Goal
Ask the minimum set of clarifying questions needed to avoid wrong work; do not start implementing until the must-have questions are answered (or the user explicitly approves proceeding with stated assumptions).
Workflow
1) Decide whether the request is underspecified
Treat a request as underspecified if after exploring how to perform the work, some or all of the following are not clear:
- Define the objective (what should change vs stay the same)
- Define "done" (acceptance criteria, examples, edge cases)
- Define scope (which files/components/users are in/out)
- Define constraints (compatibility, performance, style, deps, time)
- Identify environment (language/runtime versions, OS, build/test runner)
- Clarify safety/reversibility (data migration, rollout/rollback, risk)
If multiple plausible interpretations exist, assume it is underspecified.
2) Ask must-have questions first (keep it small)
Ask 1-5 questions in the first pass. Prefer questions that eliminate whole branches of work.
Make questions easy to answer:
- Optimize for scannability (short, numbered questions; avoid paragraphs)
- Offer multiple-choice options when possible
- Suggest reasonable defaults when appropriate (mark them clearly as the default/recommended choice; bold the recommended choice in the list, or if you present options in a code block, put a bold "Recommended" line immediately above the block and also tag defaults inside the block)
- Include a fast-path response (e.g., reply
defaultsto accept all recommended/default choices) - Include a low-friction "not sure" option when helpful (e.g., "Not sure - use default")
- Separate "Need to know" from "Nice to know" if that reduces friction
- Structure options so the user can respond with compact decisions (e.g.,
1b 2a 3c); restate the chosen options in plain language to confirm
3) Pause before acting
Until must-have answers arrive:
- Do not run commands, edit files, or produce a detailed plan that depends on unknowns
- Do perform a clearly labeled, low-risk discovery step only if it does not commit you to a direction (e.g., inspect repo structure, read relevant config files)
If the user explicitly asks you to proceed without answers:
- State your assumptions as a short numbered list
- Ask for confirmation; proceed only after they confirm or correct them
4) Confirm interpretation, then proceed
Once you have answers, restate the requirements in 1-3 sentences (including key constraints and what success looks like), then start work.
Question templates
- "Before I start, I need: (1) ..., (2) ..., (3) .... If you don't care about (2), I will assume ...."
- "Which of these should it be? A) ... B) ... C) ... (pick one)"
- "What would you consider 'done'? For example: ..."
- "Any constraints I must follow (versions, performance, style, deps)? If none, I will target the existing project defaults."
- Use numbered questions with lettered options and a clear reply format
1) Scope?
a) Minimal change (default)
b) Refactor while touching the area
c) Not sure - use default
2) Compatibility target?
a) Current project defaults (default)
b) Also support older versions: <specify>
c) Not sure - use default
Reply with: defaults (or 1a 2a)
Anti-patterns
- Don't ask questions you can answer with a quick, low-risk discovery read (e.g., configs, existing patterns, docs).
- Don't ask open-ended questions if a tight multiple-choice or yes/no would eliminate ambiguity faster.
/audit-context-building
Source: ~/.claude/skills/tob-audit-context-building/skills/audit-context-building/SKILL.md
name: audit-context-building description: Enables ultra-granular, line-by-line code analysis to build deep architectural context before vulnerability or bug finding.
Deep Context Builder Skill (Ultra-Granular Pure Context Mode)
1. Purpose
This skill governs how Claude thinks during the context-building phase of an audit.
When active, Claude will:
- Perform line-by-line / block-by-block code analysis by default.
- Apply First Principles, 5 Whys, and 5 Hows at micro scale.
- Continuously link insights → functions → modules → entire system.
- Maintain a stable, explicit mental model that evolves with new evidence.
- Identify invariants, assumptions, flows, and reasoning hazards.
This skill defines a structured analysis format (see Example: Function Micro-Analysis below) and runs before the vulnerability-hunting phase.
2. When to Use This Skill
Use when:
- Deep comprehension is needed before bug or vulnerability discovery.
- You want bottom-up understanding instead of high-level guessing.
- Reducing hallucinations, contradictions, and context loss is critical.
- Preparing for security auditing, architecture review, or threat modeling.
Do not use for:
- Vulnerability findings
- Fix recommendations
- Exploit reasoning
- Severity/impact rating
3. How This Skill Behaves
When active, Claude will:
- Default to ultra-granular analysis of each block and line.
- Apply micro-level First Principles, 5 Whys, and 5 Hows.
- Build and refine a persistent global mental model.
- Update earlier assumptions when contradicted ("Earlier I thought X; now Y.").
- Periodically anchor summaries to maintain stable context.
- Avoid speculation; express uncertainty explicitly when needed.
Goal: deep, accurate understanding, not conclusions.
Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "I get the gist" | Gist-level understanding misses edge cases | Line-by-line analysis required |
| "This function is simple" | Simple functions compose into complex bugs | Apply 5 Whys anyway |
| "I'll remember this invariant" | You won't. Context degrades. | Write it down explicitly |
| "External call is probably fine" | External = adversarial until proven otherwise | Jump into code or model as hostile |
| "I can skip this helper" | Helpers contain assumptions that propagate | Trace the full call chain |
| "This is taking too long" | Rushed context = hallucinated vulnerabilities later | Slow is fast |
4. Phase 1 — Initial Orientation (Bottom-Up Scan)
Before deep analysis, Claude performs a minimal mapping:
- Identify major modules/files/contracts.
- Note obvious public/external entrypoints.
- Identify likely actors (users, owners, relayers, oracles, other contracts).
- Identify important storage variables, dicts, state structs, or cells.
- Build a preliminary structure without assuming behavior.
This establishes anchors for detailed analysis.
5. Phase 2 — Ultra-Granular Function Analysis (Default Mode)
Every non-trivial function receives full micro analysis.
5.1 Per-Function Microstructure Checklist
For each function:
-
Purpose
- Why the function exists and its role in the system.
-
Inputs & Assumptions
- Parameters and implicit inputs (state, sender, env).
- Preconditions and constraints.
-
Outputs & Effects
- Return values.
- State/storage writes.
- Events/messages.
- External interactions.
-
Block-by-Block / Line-by-Line Analysis For each logical block:
- What it does.
- Why it appears here (ordering logic).
- What assumptions it relies on.
- What invariants it establishes or maintains.
- What later logic depends on it.
Apply per-block:
- First Principles
- 5 Whys
- 5 Hows
5.2 Cross-Function & External Flow Analysis
(Full Integration of Jump-Into-External-Code Rule)
When encountering calls, continue the same micro-first analysis across boundaries.
Internal Calls
- Jump into the callee immediately.
- Perform block-by-block analysis of relevant code.
- Track flow of data, assumptions, and invariants: caller → callee → return → caller.
- Note if callee logic behaves differently in this specific call context.
External Calls — Two Cases
Case A — External Call to a Contract Whose Code Exists in the Codebase Treat as an internal call:
- Jump into the target contract/function.
- Continue block-by-block micro-analysis.
- Propagate invariants and assumptions seamlessly.
- Consider edge cases based on the actual code, not a black-box guess.
Case B — External Call Without Available Code (True External / Black Box) Analyze as adversarial:
- Describe payload/value/gas or parameters sent.
- Identify assumptions about the target.
- Consider all outcomes:
- revert
- incorrect/strange return values
- unexpected state changes
- misbehavior
- reentrancy (if applicable)
Continuity Rule
Treat the entire call chain as one continuous execution flow. Never reset context. All invariants, assumptions, and data dependencies must propagate across calls.
5.3 Complete Analysis Example
See FUNCTION_MICRO_ANALYSIS_EXAMPLE.md for a complete walkthrough demonstrating:
- Full micro-analysis of a DEX swap function
- Application of First Principles, 5 Whys, and 5 Hows
- Block-by-block analysis with invariants and assumptions
- Cross-function dependency mapping
- Risk analysis for external interactions
This example demonstrates the level of depth and structure required for all analyzed functions.
5.4 Output Requirements
When performing ultra-granular analysis, Claude MUST structure output following the format defined in OUTPUT_REQUIREMENTS.md.
Key requirements:
- Purpose (2-3 sentences minimum)
- Inputs & Assumptions (all parameters, preconditions, trust assumptions)
- Outputs & Effects (returns, state writes, external calls, events, postconditions)
- Block-by-Block Analysis (What, Why here, Assumptions, First Principles/5 Whys/5 Hows)
- Cross-Function Dependencies (internal calls, external calls with risk analysis, shared state)
Quality thresholds:
- Minimum 3 invariants per function
- Minimum 5 assumptions documented
- Minimum 3 risk considerations for external interactions
- At least 1 First Principles application
- At least 3 combined 5 Whys/5 Hows applications
5.5 Completeness Checklist
Before concluding micro-analysis of a function, verify against the COMPLETENESS_CHECKLIST.md:
- Structural Completeness: All required sections present (Purpose, Inputs, Outputs, Block-by-Block, Dependencies)
- Content Depth: Minimum thresholds met (invariants, assumptions, risk analysis, First Principles)
- Continuity & Integration: Cross-references, propagated assumptions, invariant couplings
- Anti-Hallucination: Line number citations, no vague statements, evidence-based claims
Analysis is complete when all checklist items are satisfied and no unresolved "unclear" items remain.
6. Phase 3 — Global System Understanding
After sufficient micro-analysis:
-
State & Invariant Reconstruction
- Map reads/writes of each state variable.
- Derive multi-function and multi-module invariants.
-
Workflow Reconstruction
- Identify end-to-end flows (deposit, withdraw, lifecycle, upgrades).
- Track how state transforms across these flows.
- Record assumptions that persist across steps.
-
Trust Boundary Mapping
- Actor → entrypoint → behavior.
- Identify untrusted input paths.
- Privilege changes and implicit role expectations.
-
Complexity & Fragility Clustering
- Functions with many assumptions.
- High branching logic.
- Multi-step dependencies.
- Coupled state changes across modules.
These clusters help guide the vulnerability-hunting phase.
7. Stability & Consistency Rules
(Anti-Hallucination, Anti-Contradiction)
Claude must:
-
Never reshape evidence to fit earlier assumptions. When contradicted:
- Update the model.
- State the correction explicitly.
-
Periodically anchor key facts Summarize core:
- invariants
- state relationships
- actor roles
- workflows
-
Avoid vague guesses Use:
- "Unclear; need to inspect X." instead of:
- "It probably…"
-
Cross-reference constantly Connect new insights to previous state, flows, and invariants to maintain global coherence.
8. Subagent Usage
Claude may spawn subagents for:
- Dense or complex functions.
- Long data-flow or control-flow chains.
- Cryptographic / mathematical logic.
- Complex state machines.
- Multi-module workflow reconstruction.
Subagents must:
- Follow the same micro-first rules.
- Return summaries that Claude integrates into its global model.
9. Relationship to Other Phases
This skill runs before:
- Vulnerability discovery
- Classification / triage
- Report writing
- Impact modeling
- Exploit reasoning
It exists solely to build:
- Deep understanding
- Stable context
- System-level clarity
10. Non-Goals
While active, Claude should NOT:
- Identify vulnerabilities
- Propose fixes
- Generate proofs-of-concept
- Model exploits
- Assign severity or impact
This is pure context building only.
/algorand-vulnerability-scanner
Source: ~/.claude/skills/tob-building-secure-contracts/skills/algorand-vulnerability-scanner/SKILL.md
name: algorand-vulnerability-scanner description: Scans Algorand smart contracts for 11 common vulnerabilities including rekeying attacks, unchecked transaction fees, missing field validations, and access control issues. Use when auditing Algorand projects (TEAL/PyTeal).
Algorand Vulnerability Scanner
1. Purpose
Systematically scan Algorand smart contracts (TEAL and PyTeal) for platform-specific security vulnerabilities documented in Trail of Bits' "Not So Smart Contracts" database. This skill encodes 11 critical vulnerability patterns unique to Algorand's transaction model.
2. When to Use This Skill
- Auditing Algorand smart contracts (stateful applications or smart signatures)
- Reviewing TEAL assembly or PyTeal code
- Pre-audit security assessment of Algorand projects
- Validating fixes for reported Algorand vulnerabilities
- Training team on Algorand-specific security patterns
3. Platform Detection
File Extensions & Indicators
- TEAL files:
.teal - PyTeal files:
.pywith PyTeal imports
Language/Framework Markers
# PyTeal indicators
from pyteal import *
from algosdk import *
# Common patterns
Txn, Gtxn, Global, InnerTxnBuilder
OnComplete, ApplicationCall, TxnType
@router.method, @Subroutine
Project Structure
approval_program.py/clear_program.pycontract.teal/signature.teal- References to Algorand SDK or Beaker framework
Tool Support
- Tealer: Trail of Bits static analyzer for Algorand
- Installation:
pip3 install tealer - Usage:
tealer contract.teal --detect all
4. How This Skill Works
When invoked, I will:
- Search your codebase for TEAL/PyTeal files
- Analyze each file for the 11 vulnerability patterns
- Report findings with file references and severity
- Provide fixes for each identified issue
- Run Tealer (if installed) for automated detection
5. Example Output
When vulnerabilities are found, you'll get a report like this:
=== ALGORAND VULNERABILITY SCAN RESULTS ===
Project: my-algorand-dapp
Files Scanned: 3 (.teal, .py)
Vulnerabilities Found: 2
---
[CRITICAL] Rekeying Attack
File: contracts/approval.py:45
Pattern: Missing RekeyTo validation
Code:
If(Txn.type_enum() == TxnType.Payment,
Seq([
# Missing: Assert(Txn.rekey_to() == Global.zero_address())
App.globalPut(Bytes("balance"), balance + Txn.amount()),
Approve()
])
)
Issue: The contract doesn't validate the RekeyTo field, allowing attackers
to change account authorization and bypass restrictions.
---
## 5. Vulnerability Patterns (11 Patterns)
I check for 11 critical vulnerability patterns unique to Algorand. For detailed detection patterns, code examples, mitigations, and testing strategies, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
### Pattern Summary:
1. **Rekeying Vulnerability** ⚠️ CRITICAL - Unchecked RekeyTo field
2. **Missing Transaction Verification** ⚠️ CRITICAL - No GroupSize/GroupIndex checks
3. **Group Transaction Manipulation** ⚠️ HIGH - Unsafe group transaction handling
4. **Asset Clawback Risk** ⚠️ HIGH - Missing clawback address checks
5. **Application State Manipulation** ⚠️ MEDIUM - Unsafe global/local state updates
6. **Asset Opt-In Missing** ⚠️ HIGH - No asset opt-in validation
7. **Minimum Balance Violation** ⚠️ MEDIUM - Account below minimum balance
8. **Close Remainder To Check** ⚠️ HIGH - Unchecked CloseRemainderTo field
9. **Application Clear State** ⚠️ MEDIUM - Unsafe clear state program
10. **Atomic Transaction Ordering** ⚠️ HIGH - Assuming transaction order
11. **Logic Signature Reuse** ⚠️ HIGH - Logic sigs without uniqueness constraints
For complete vulnerability patterns with code examples, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
## 5. Scanning Workflow
### Step 1: Platform Identification
1. Confirm file extensions (`.teal`, `.py`)
2. Identify framework (PyTeal, Beaker, pure TEAL)
3. Determine contract type (stateful application vs smart signature)
4. Locate approval and clear state programs
### Step 2: Static Analysis with Tealer
```bash
# Run Tealer on contract
tealer contract.teal --detect all
# Or specific detectors
tealer contract.teal --detect unprotected-rekey,group-size-check,update-application-check
Step 3: Manual Vulnerability Sweep
For each of the 11 vulnerabilities above:
- Search for relevant transaction field usage
- Verify validation logic exists
- Check for bypass conditions
- Validate inner transaction handling
Step 4: Transaction Field Validation Matrix
Create checklist for all transaction types used:
Payment Transactions:
- RekeyTo validated
- CloseRemainderTo validated
- Fee validated (if smart signature)
Asset Transfers:
- Asset ID validated
- AssetCloseTo validated
- RekeyTo validated
Application Calls:
- OnComplete validated
- Access controls enforced
- Group size validated
Inner Transactions:
- Fee explicitly set to 0
- RekeyTo not user-controlled (Teal v6+)
- All fields validated
Step 5: Group Transaction Analysis
For atomic transaction groups:
- Validate
Global.group_size()checks - Review absolute vs relative indexing
- Check for replay protection (Lease field)
- Verify OnComplete fields for ApplicationCalls in group
Step 6: Access Control Review
- Creator/admin privileges properly enforced
- Update/delete operations protected
- Sensitive functions have authorization checks
6. Reporting Format
Finding Template
## [SEVERITY] Vulnerability Name (e.g., Missing RekeyTo Validation)
**Location**: `contract.teal:45-50` or `approval_program.py:withdraw()`
**Description**:
The contract approves payment transactions without validating the RekeyTo field, allowing an attacker to rekey the account and bypass future authorization checks.
**Vulnerable Code**:
```python
# approval_program.py, line 45
If(Txn.type_enum() == TxnType.Payment,
Approve() # Missing RekeyTo check
)
Attack Scenario:
- Attacker submits payment transaction with RekeyTo set to attacker's address
- Contract approves transaction without checking RekeyTo
- Account authorization is rekeyed to attacker
- Attacker gains full control of account
Recommendation: Add explicit validation of the RekeyTo field:
If(And(
Txn.type_enum() == TxnType.Payment,
Txn.rekey_to() == Global.zero_address()
), Approve(), Reject())
References:
- building-secure-contracts/not-so-smart-contracts/algorand/rekeying
- Tealer detector:
unprotected-rekey
---
## 7. Priority Guidelines
### Critical (Immediate Fix Required)
- Rekeying attacks
- CloseRemainderTo / AssetCloseTo issues
- Access control bypasses
### High (Fix Before Deployment)
- Unchecked transaction fees
- Asset ID validation issues
- Group size validation
- Clear state transaction checks
### Medium (Address in Audit)
- Inner transaction fee issues
- Time-based replay attacks
- DoS via asset opt-in
---
## 8. Testing Recommendations
### Unit Tests Required
- Test each vulnerability scenario with PoC exploit
- Verify fixes prevent exploitation
- Test edge cases (group size = 0, empty addresses, etc.)
### Tealer Integration
```bash
# Add to CI/CD pipeline
tealer approval.teal --detect all --json > tealer-report.json
# Fail build on critical findings
tealer approval.teal --detect all --fail-on critical,high
Scenario Testing
- Submit transactions with all critical fields manipulated
- Test atomic groups with unexpected sizes
- Attempt access control bypasses
- Verify inner transaction fee handling
9. Additional Resources
- Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/algorand/ - Tealer Documentation: https://github.com/crytic/tealer
- Algorand Developer Docs: https://developer.algorand.org/docs/
- PyTeal Documentation: https://pyteal.readthedocs.io/
10. Quick Reference Checklist
Before completing Algorand audit, verify ALL items checked:
- RekeyTo validated in all transaction types
- CloseRemainderTo validated in payment transactions
- AssetCloseTo validated in asset transfers
- Transaction fees validated (smart signatures)
- Group size validated for atomic transactions
- Lease field used for replay protection (where applicable)
- Access controls on Update/Delete operations
- Asset ID validated in all asset operations
- Asset transfers use pull pattern to avoid DoS
- Inner transaction fees explicitly set to 0
- OnComplete field validated for ApplicationCall transactions
- Tealer scan completed with no critical/high findings
- Unit tests cover all vulnerability scenarios
/audit-prep-assistant
Source: ~/.claude/skills/tob-building-secure-contracts/skills/audit-prep-assistant/SKILL.md
name: audit-prep-assistant description: Prepares codebases for security review using Trail of Bits' checklist. Helps set review goals, runs static analysis tools, increases test coverage, removes dead code, ensures accessibility, and generates documentation (flowcharts, user stories, inline comments).
Audit Prep Assistant
Purpose
Helps prepare for a security review using Trail of Bits' checklist. A well-prepared codebase makes the review process smoother and more effective.
Use this: 1-2 weeks before your security audit
The Preparation Process
Step 1: Set Review Goals
Helps define what you want from the review:
Key Questions:
- What's the overall security level you're aiming for?
- What areas concern you most?
- Previous audit issues?
- Complex components?
- Fragile parts?
- What's the worst-case scenario for your project?
Documents goals to share with the assessment team.
Step 2: Resolve Easy Issues
Runs static analysis and helps fix low-hanging fruit:
Run Static Analysis:
For Solidity:
slither . --exclude-dependencies
For Rust:
dylint --all
For Go:
golangci-lint run
For Go/Rust/C++:
# CodeQL and Semgrep checks
Then I'll:
- Triage all findings
- Help fix easy issues
- Document accepted risks
Increase Test Coverage:
- Analyze current coverage
- Identify untested code
- Suggest new tests
- Run full test suite
Remove Dead Code:
- Find unused functions/variables
- Identify unused libraries
- Locate stale features
- Suggest cleanup
Goal: Clean static analysis report, high test coverage, minimal dead code
Step 3: Ensure Code Accessibility
Helps make code clear and accessible:
Provide Detailed File List:
- List all files in scope
- Mark out-of-scope files
- Explain folder structure
- Document dependencies
Create Build Instructions:
- Write step-by-step setup guide
- Test on fresh environment
- Document dependencies and versions
- Verify build succeeds
Freeze Stable Version:
- Identify commit hash for review
- Create dedicated branch
- Tag release version
- Lock dependencies
Identify Boilerplate:
- Mark copied/forked code
- Highlight your modifications
- Document third-party code
- Focus review on your code
Step 4: Generate Documentation
Helps create documentation:
Flowcharts and Sequence Diagrams:
- Map primary workflows
- Show component relationships
- Visualize data flow
- Identify critical paths
User Stories:
- Define user roles
- Document use cases
- Explain interactions
- Clarify expectations
On-chain/Off-chain Assumptions:
- Data validation procedures
- Oracle information
- Bridge assumptions
- Trust boundaries
Actors and Privileges:
- List all actors
- Document roles
- Define privileges
- Map access controls
External Developer Docs:
- Link docs to code
- Keep synchronized
- Explain architecture
- Document APIs
Function Documentation:
- System and function invariants
- Parameter ranges (min/max values)
- Arithmetic formulas and precision loss
- Complex logic explanations
- NatSpec for Solidity
Glossary:
- Define domain terms
- Explain acronyms
- Consistent terminology
- Business logic concepts
Video Walkthroughs (optional):
- Complex workflows
- Areas of concern
- Architecture overview
How I Work
When invoked, I will:
- Help set review goals - Ask about concerns and document them
- Run static analysis - Execute appropriate tools for your platform
- Analyze test coverage - Identify gaps and suggest improvements
- Find dead code - Search for unused code and libraries
- Review accessibility - Check build instructions and scope clarity
- Generate documentation - Create flowcharts, user stories, glossaries
- Create prep checklist - Track what's done and what's remaining
Adapts based on:
- Your platform (Solidity, Rust, Go, etc.)
- Available tools
- Existing documentation
- Review timeline
Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "README covers setup, no need for detailed build instructions" | READMEs assume context auditors don't have | Test build on fresh environment, document every dependency version |
| "Static analysis already ran, no need to run again" | Codebase changed since last run | Execute static analysis tools, generate fresh report |
| "Test coverage looks decent" | "Looks decent" isn't measured coverage | Run coverage tools, identify specific untested code paths |
| "Not much dead code to worry about" | Dead code hides during manual review | Use automated detection tools to find unused functions/variables |
| "Architecture is straightforward, no diagrams needed" | Text descriptions miss visual patterns | Generate actual flowcharts and sequence diagrams |
| "Can freeze version right before audit" | Last-minute freezing creates rushed handoff | Identify and document commit hash now, create dedicated branch |
| "Terms are self-explanatory" | Domain knowledge isn't universal | Create comprehensive glossary with all domain-specific terms |
| "I'll do this step later" | Steps build on each other - skipping creates gaps | Complete all 4 steps sequentially, track progress with checklist |
Example Output
When I finish helping you prepare, you'll have concrete deliverables like:
=== AUDIT PREP PACKAGE ===
Project: DeFi DEX Protocol
Audit Date: March 15, 2024
Preparation Status: Complete
---
## REVIEW GOALS DOCUMENT
Security Objectives:
- Verify economic security of liquidity pool swaps
- Validate oracle manipulation resistance
- Assess flash loan attack vectors
Areas of Concern:
1. Complex AMM pricing calculation (src/SwapRouter.sol:89-156)
2. Multi-hop swap routing logic (src/Router.sol)
3. Oracle price aggregation (src/PriceOracle.sol:45-78)
Worst-Case Scenario:
- Flash loan attack drains liquidity pools via oracle manipulation
Questions for Auditors:
- Can the AMM pricing model produce negative slippage under edge cases?
- Is the slippage protection sufficient to prevent sandwich attacks?
- How resilient is the system to temporary oracle failures?
---
## STATIC ANALYSIS REPORT
Slither Scan Results:
✓ High: 0 issues
✓ Medium: 0 issues
⚠ Low: 2 issues (triaged - documented in TRIAGE.md)
ℹ Info: 5 issues (code style, acceptable)
Tool: slither . --exclude-dependencies
Date: March 1, 2024
Status: CLEAN (all critical issues resolved)
---
## TEST COVERAGE REPORT
Overall Coverage: 94%
- Statements: 1,245 / 1,321 (94%)
- Branches: 456 / 498 (92%)
- Functions: 89 / 92 (97%)
Uncovered Areas:
- Emergency pause admin functions (tested manually)
- Governance migration path (one-time use)
Command: forge coverage
Status: EXCELLENT
---
## CODE SCOPE
In-Scope Files (8):
✓ src/SwapRouter.sol (456 lines)
✓ src/LiquidityPool.sol (234 lines)
✓ src/PairFactory.sol (389 lines)
✓ src/PriceOracle.sol (167 lines)
✓ src/LiquidityManager.sol (298 lines)
✓ src/Governance.sol (201 lines)
✓ src/FlashLoan.sol (145 lines)
✓ src/RewardsDistributor.sol (178 lines)
Out-of-Scope:
- lib/ (OpenZeppelin, external dependencies)
- test/ (test contracts)
- scripts/ (deployment scripts)
Total In-Scope: 2,068 lines of Solidity
---
## BUILD INSTRUCTIONS
Prerequisites:
- Foundry 0.2.0+
- Node.js 18+
- Git
Setup:
```bash
git clone https://github.com/project/repo.git
cd repo
git checkout audit-march-2024 # Frozen branch
forge install
forge build
forge test
Verification: ✓ Build succeeds without errors ✓ All 127 tests pass ✓ No warnings from compiler
DOCUMENTATION
Generated Artifacts: ✓ ARCHITECTURE.md - System overview with diagrams ✓ USER_STORIES.md - 12 user interaction flows ✓ GLOSSARY.md - 34 domain terms defined ✓ docs/diagrams/contract-interactions.png ✓ docs/diagrams/swap-flow.png ✓ docs/diagrams/state-machine.png
NatSpec Coverage: 100% of public functions
DEPLOYMENT INFO
Network: Ethereum Mainnet Commit: abc123def456 (audit-march-2024 branch) Deployed Contracts:
- SwapRouter: 0x1234...
- PriceOracle: 0x5678... [... etc]
PACKAGE READY FOR AUDIT ✓ Next Step: Share with Trail of Bits assessment team
---
## What You'll Get
**Review Goals Document**:
- Security objectives
- Areas of concern
- Worst-case scenarios
- Questions for auditors
**Clean Codebase**:
- Triaged static analysis (or clean report)
- High test coverage
- No dead code
- Clear scope
**Accessibility Package**:
- File list with scope
- Build instructions
- Frozen commit/branch
- Boilerplate identified
**Documentation Suite**:
- Flowcharts and diagrams
- User stories
- Architecture docs
- Actor/privilege map
- Inline code comments
- Glossary
- Video walkthroughs (if created)
**Audit Prep Checklist**:
- [ ] Review goals documented
- [ ] Static analysis clean/triaged
- [ ] Test coverage >80%
- [ ] Dead code removed
- [ ] Build instructions verified
- [ ] Stable version frozen
- [ ] Flowcharts created
- [ ] User stories documented
- [ ] Assumptions documented
- [ ] Actors/privileges listed
- [ ] Function docs complete
- [ ] Glossary created
---
## Timeline
**2 weeks before audit**:
- Set review goals
- Run static analysis
- Start fixing issues
**1 week before audit**:
- Increase test coverage
- Remove dead code
- Freeze stable version
- Start documentation
**Few days before audit**:
- Complete documentation
- Verify build instructions
- Create final checklist
- Send package to auditors
---
## Ready to Prep
Let me know when you're ready and I'll help you prepare for your security review!
/cairo-vulnerability-scanner
Source: ~/.claude/skills/tob-building-secure-contracts/skills/cairo-vulnerability-scanner/SKILL.md
name: cairo-vulnerability-scanner description: Scans Cairo/StarkNet smart contracts for 6 critical vulnerabilities including felt252 arithmetic overflow, L1-L2 messaging issues, address conversion problems, and signature replay. Use when auditing StarkNet projects.
Cairo/StarkNet Vulnerability Scanner
1. Purpose
Systematically scan Cairo smart contracts on StarkNet for platform-specific security vulnerabilities related to arithmetic, cross-layer messaging, and cryptographic operations. This skill encodes 6 critical vulnerability patterns unique to Cairo/StarkNet ecosystem.
2. When to Use This Skill
- Auditing StarkNet smart contracts (Cairo)
- Reviewing L1-L2 bridge implementations
- Pre-launch security assessment of StarkNet applications
- Validating cross-layer message handling
- Reviewing signature verification logic
- Assessing L1 handler functions
3. Platform Detection
File Extensions & Indicators
- Cairo files:
.cairo
Language/Framework Markers
// Cairo contract indicators
#[contract]
mod MyContract {
use starknet::ContractAddress;
#[storage]
struct Storage {
balance: LegacyMap<ContractAddress, felt252>,
}
#[external(v0)]
fn transfer(ref self: ContractState, to: ContractAddress, amount: felt252) {
// Contract logic
}
#[l1_handler]
fn handle_deposit(ref self: ContractState, from_address: felt252, amount: u256) {
// L1 message handler
}
}
// Common patterns
felt252, u128, u256
ContractAddress, EthAddress
#[external(v0)], #[l1_handler], #[constructor]
get_caller_address(), get_contract_address()
send_message_to_l1_syscall
Project Structure
src/contract.cairo- Main contract implementationsrc/lib.cairo- Library modulestests/- Contract testsScarb.toml- Cairo project configuration
Tool Support
- Caracal: Trail of Bits static analyzer for Cairo
- Installation:
pip install caracal - Usage:
caracal detect src/ - cairo-test: Built-in testing framework
- Starknet Foundry: Testing and development toolkit
4. How This Skill Works
When invoked, I will:
- Search your codebase for Cairo files
- Analyze each contract for the 6 vulnerability patterns
- Report findings with file references and severity
- Provide fixes for each identified issue
- Check L1-L2 interactions for messaging vulnerabilities
5. Example Output
When vulnerabilities are found, you'll get a report like this:
=== CAIRO/STARKNET VULNERABILITY SCAN RESULTS ===
---
## 5. Vulnerability Patterns (6 Patterns)
I check for 6 critical vulnerability patterns unique to Cairo/Starknet. For detailed detection patterns, code examples, mitigations, and testing strategies, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
### Pattern Summary:
1. **Unchecked Arithmetic** ⚠️ CRITICAL - Integer overflow/underflow in felt252
2. **Storage Collision** ⚠️ CRITICAL - Conflicting storage variable hashes
3. **Missing Access Control** ⚠️ CRITICAL - No caller validation on sensitive functions
4. **Improper Felt252 Boundaries** ⚠️ HIGH - Not validating felt252 range
5. **Unvalidated Contract Address** ⚠️ HIGH - Using untrusted contract addresses
6. **Missing Caller Validation** ⚠️ CRITICAL - No get_caller_address() checks
For complete vulnerability patterns with code examples, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
## 5. Scanning Workflow
### Step 1: Platform Identification
1. Verify Cairo language and StarkNet framework
2. Check Cairo version (Cairo 1.0+ vs legacy Cairo 0)
3. Locate contract files (`src/*.cairo`)
4. Identify L1-L2 bridge contracts (if applicable)
### Step 2: Arithmetic Safety Sweep
```bash
# Find felt252 usage in arithmetic
rg "felt252" src/ | rg "[-+*/]"
# Find balance/amount storage using felt252
rg "felt252" src/ | rg "balance|amount|total|supply"
# Should prefer u128, u256 instead
Step 3: L1 Handler Analysis
For each #[l1_handler] function:
- Validates
from_addressparameter - Checks address != zero
- Has proper access control
- Emits events for monitoring
Step 4: Signature Verification Review
For signature-based functions:
- Includes nonce tracking
- Nonce incremented after use
- Domain separator includes chain ID and contract address
- Cannot replay signatures
Step 5: L1-L2 Bridge Audit
If contract includes bridge functionality:
- L1 validates address < STARKNET_FIELD_PRIME
- L1 implements message cancellation
- L2 validates from_address in handlers
- Symmetric access controls L1 ↔ L2
- Test full roundtrip flows
Step 6: Static Analysis with Caracal
# Run Caracal detectors
caracal detect src/
# Specific detectors
caracal detect src/ --detectors unchecked-felt252-arithmetic
caracal detect src/ --detectors unchecked-l1-handler-from
caracal detect src/ --detectors missing-nonce-validation
6. Reporting Format
Finding Template
## [CRITICAL] Unchecked from_address in L1 Handler
**Location**: `src/bridge.cairo:145-155` (handle_deposit function)
**Description**:
The `handle_deposit` L1 handler function does not validate the `from_address` parameter. Any L1 contract can send messages to this function and mint tokens for arbitrary users, bypassing the intended L1 bridge access controls.
**Vulnerable Code**:
```rust
// bridge.cairo, line 145
#[l1_handler]
fn handle_deposit(
ref self: ContractState,
from_address: felt252, // Not validated!
user: ContractAddress,
amount: u256
) {
let current_balance = self.balances.read(user);
self.balances.write(user, current_balance + amount);
}
Attack Scenario:
- Attacker deploys malicious L1 contract
- Malicious contract calls
starknetCore.sendMessageToL2(l2Contract, selector, [attacker_address, 1000000]) - L2 handler processes message without checking sender
- Attacker receives 1,000,000 tokens without depositing any funds
- Protocol suffers infinite mint vulnerability
Recommendation:
Validate from_address against authorized L1 bridge:
#[l1_handler]
fn handle_deposit(
ref self: ContractState,
from_address: felt252,
user: ContractAddress,
amount: u256
) {
// Validate L1 sender
let authorized_l1_bridge = self.l1_bridge_address.read();
assert(from_address == authorized_l1_bridge, 'Unauthorized L1 sender');
let current_balance = self.balances.read(user);
self.balances.write(user, current_balance + amount);
}
References:
- building-secure-contracts/not-so-smart-contracts/cairo/unchecked_l1_handler_from
- Caracal detector:
unchecked-l1-handler-from
---
## 7. Priority Guidelines
### Critical (Immediate Fix Required)
- Unchecked from_address in L1 handlers (infinite mint)
- L1-L2 address conversion issues (funds to zero address)
### High (Fix Before Deployment)
- Felt252 arithmetic overflow/underflow (balance manipulation)
- Missing signature replay protection (replay attacks)
- L1-L2 message failure without cancellation (locked funds)
### Medium (Address in Audit)
- Overconstrained L1-L2 interactions (trapped funds)
---
## 8. Testing Recommendations
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_felt252_overflow() {
// Test arithmetic edge cases
}
#[test]
#[should_panic]
fn test_unauthorized_l1_handler() {
// Wrong from_address should fail
}
#[test]
fn test_signature_replay_protection() {
// Same signature twice should fail
}
}
Integration Tests (with L1)
// Test full L1-L2 flow
#[test]
fn test_deposit_withdraw_roundtrip() {
// 1. Deposit on L1
// 2. Wait for L2 processing
// 3. Verify L2 balance
// 4. Withdraw to L1
// 5. Verify L1 balance restored
}
Caracal CI Integration
# .github/workflows/security.yml
- name: Run Caracal
run: |
pip install caracal
caracal detect src/ --fail-on high,critical
9. Additional Resources
- Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/cairo/ - Caracal: https://github.com/crytic/caracal
- Cairo Documentation: https://book.cairo-lang.org/
- StarkNet Documentation: https://docs.starknet.io/
- OpenZeppelin Cairo Contracts: https://github.com/OpenZeppelin/cairo-contracts
10. Quick Reference Checklist
Before completing Cairo/StarkNet audit:
Arithmetic Safety (HIGH):
- No felt252 used for balances/amounts (use u128/u256)
- OR felt252 arithmetic has explicit bounds checking
- Overflow/underflow scenarios tested
L1 Handler Security (CRITICAL):
- ALL
#[l1_handler]functions validatefrom_address - from_address compared against stored L1 contract address
- Cannot bypass by deploying alternate L1 contract
L1-L2 Messaging (HIGH):
- L1 bridge validates addresses < STARKNET_FIELD_PRIME
- L1 bridge implements message cancellation
- L2 handlers check from_address
- Symmetric validation rules L1 ↔ L2
- Full roundtrip flows tested
Signature Security (HIGH):
- Signatures include nonce tracking
- Nonce incremented after each use
- Domain separator includes chain ID and contract address
- Signature replay tested and prevented
- Cross-chain replay prevented
Tool Usage:
- Caracal scan completed with no critical findings
- Unit tests cover all vulnerability scenarios
- Integration tests verify L1-L2 flows
- Testnet deployment tested before mainnet
/code-maturity-assessor
Source: ~/.claude/skills/tob-building-secure-contracts/skills/code-maturity-assessor/SKILL.md
name: code-maturity-assessor description: Systematic code maturity assessment using Trail of Bits' 9-category framework. Analyzes codebase for arithmetic safety, auditing practices, access controls, complexity, decentralization, documentation, MEV risks, low-level code, and testing. Produces professional scorecard with evidence-based ratings and actionable recommendations.
Code Maturity Assessor
Purpose
Systematically assesses codebase maturity using Trail of Bits' 9-category framework. Provides evidence-based ratings and actionable recommendations.
Framework: Building Secure Contracts - Code Maturity Evaluation v0.1.0
How This Works
Phase 1: Discovery
Explores the codebase to understand:
- Project structure and platform
- Contract/module files
- Test coverage
- Documentation availability
Phase 2: Analysis
For each of 9 categories, I'll:
- Search the code for relevant patterns
- Read key files to assess implementation
- Present findings with file references
- Ask clarifying questions about processes I can't see in code
- Determine rating based on criteria
Phase 3: Report
Generates:
- Executive summary
- Maturity scorecard (ratings for all 9 categories)
- Detailed analysis with evidence
- Priority-ordered improvement roadmap
Rating System
- Missing (0): Not present/not implemented
- Weak (1): Several significant improvements needed
- Moderate (2): Adequate, can be improved
- Satisfactory (3): Above average, minor improvements
- Strong (4): Exceptional, only small improvements possible
Rating Logic:
- ANY "Weak" criteria → Weak
- NO "Weak" + SOME "Moderate" unmet → Moderate
- ALL "Moderate" + SOME "Satisfactory" met → Satisfactory
- ALL "Satisfactory" + exceptional practices → Strong
The 9 Categories
I assess 9 comprehensive categories covering all aspects of code maturity. For detailed criteria, analysis approaches, and rating thresholds, see ASSESSMENT_CRITERIA.md.
Quick Reference:
1. ARITHMETIC
- Overflow protection mechanisms
- Precision handling and rounding
- Formula specifications
- Edge case testing
2. AUDITING
- Event definitions and coverage
- Monitoring infrastructure
- Incident response planning
3. AUTHENTICATION / ACCESS CONTROLS
- Privilege management
- Role separation
- Access control testing
- Key compromise scenarios
4. COMPLEXITY MANAGEMENT
- Function scope and clarity
- Cyclomatic complexity
- Inheritance hierarchies
- Code duplication
5. DECENTRALIZATION
- Centralization risks
- Upgrade control mechanisms
- User opt-out paths
- Timelock/multisig patterns
6. DOCUMENTATION
- Specifications and architecture
- Inline code documentation
- User stories
- Domain glossaries
7. TRANSACTION ORDERING RISKS
- MEV vulnerabilities
- Front-running protections
- Slippage controls
- Oracle security
8. LOW-LEVEL MANIPULATION
- Assembly usage
- Unsafe code sections
- Low-level calls
- Justification and testing
9. TESTING & VERIFICATION
- Test coverage
- Fuzzing and formal verification
- CI/CD integration
- Test quality
For complete assessment criteria including what I'll analyze, what I'll ask you, and detailed rating thresholds (WEAK/MODERATE/SATISFACTORY/STRONG), see ASSESSMENT_CRITERIA.md.
Example Output
When the assessment is complete, you'll receive a comprehensive maturity report including:
- Executive Summary: Overall score, top 3 strengths, top 3 gaps, priority recommendations
- Maturity Scorecard: Table with all 9 categories rated with scores and notes
- Detailed Analysis: Category-by-category breakdown with evidence (file:line references)
- Improvement Roadmap: Priority-ordered recommendations (CRITICAL/HIGH/MEDIUM) with effort estimates
For a complete example assessment report, see EXAMPLE_REPORT.md.
Assessment Process
When invoked, I will:
-
Explore codebase
- Find contract/module files
- Identify test files
- Locate documentation
-
Analyze each category
- Search for relevant code patterns
- Read key implementations
- Assess against criteria
- Collect evidence
-
Interactive assessment
- Present my findings with file references
- Ask about processes I can't see in code
- Discuss borderline cases
- Determine ratings together
-
Generate report
- Executive summary
- Maturity scorecard table
- Detailed category analysis with evidence
- Priority-ordered improvement roadmap
Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "Found some findings, assessment complete" | Assessment requires evaluating ALL 9 categories | Complete assessment of all 9 categories with evidence for each |
| "I see events, auditing category looks good" | Events alone don't equal auditing maturity | Check logging comprehensiveness, testing, incident response processes |
| "Code looks simple, complexity is low" | Visual simplicity masks composition complexity | Analyze cyclomatic complexity, dependency depth, state machine transitions |
| "Not a DeFi protocol, MEV category doesn't apply" | MEV extends beyond DeFi (governance, NFTs, games) | Verify with transaction ordering analysis before declaring N/A |
| "No assembly found, low-level category is N/A" | Low-level risks include external calls, delegatecall, inline assembly | Search for all low-level patterns before skipping category |
| "This is taking too long" | Thorough assessment requires time per category | Complete all 9 categories, ask clarifying questions about off-chain processes |
| "I can rate this without evidence" | Ratings without file:line references = unsubstantiated claims | Collect concrete code evidence for every category assessment |
| "User will know what to improve" | Vague guidance = no action | Provide priority-ordered roadmap with specific improvements and effort estimates |
Report Format
For detailed report structure and templates, see REPORT_FORMAT.md.
Structure:
-
Executive Summary
- Project name and platform
- Overall maturity (average rating)
- Top 3 strengths
- Top 3 critical gaps
- Priority recommendations
-
Maturity Scorecard
- Table with all 9 categories
- Ratings and scores
- Key findings notes
-
Detailed Analysis
- Per-category breakdown
- Evidence with file:line references
- Gaps and improvement actions
-
Improvement Roadmap
- CRITICAL (immediate)
- HIGH (1-2 months)
- MEDIUM (2-4 months)
- Effort estimates and impact
Ready to Begin
Estimated Time: 30-40 minutes
I'll need:
- Access to full codebase
- Your knowledge of processes (monitoring, incident response, team practices)
- Context about the project (DeFi, NFT, infrastructure, etc.)
Let's assess this codebase!
/cosmos-vulnerability-scanner
Source: ~/.claude/skills/tob-building-secure-contracts/skills/cosmos-vulnerability-scanner/SKILL.md
name: cosmos-vulnerability-scanner description: Scans Cosmos SDK blockchains for 9 consensus-critical vulnerabilities including non-determinism, incorrect signers, ABCI panics, and rounding errors. Use when auditing Cosmos chains or CosmWasm contracts.
Cosmos Vulnerability Scanner
1. Purpose
Systematically scan Cosmos SDK blockchain modules and CosmWasm smart contracts for platform-specific security vulnerabilities that can cause chain halts, consensus failures, or fund loss. This skill encodes 9 critical vulnerability patterns unique to Cosmos-based chains.
2. When to Use This Skill
- Auditing Cosmos SDK modules (custom x/ modules)
- Reviewing CosmWasm smart contracts (Rust)
- Pre-launch security assessment of Cosmos chains
- Investigating chain halt incidents
- Validating consensus-critical code changes
- Reviewing ABCI method implementations
3. Platform Detection
File Extensions & Indicators
- Go files:
.go,.proto - CosmWasm:
.rs(Rust with cosmwasm imports)
Language/Framework Markers
// Cosmos SDK indicators
import (
"github.com/cosmos/cosmos-sdk/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/..."
)
// Common patterns
keeper.Keeper
sdk.Msg, GetSigners()
BeginBlocker, EndBlocker
CheckTx, DeliverTx
protobuf service definitions
// CosmWasm indicators
use cosmwasm_std::*;
#[entry_point]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg)
Project Structure
x/modulename/- Custom moduleskeeper/keeper.go- State managementtypes/msgs.go- Message definitionsabci.go- BeginBlocker/EndBlockerhandler.go- Message handlers (legacy)
Tool Support
- CodeQL: Custom rules for non-determinism and panics
- go vet, golangci-lint: Basic Go static analysis
- Manual review: Critical for consensus issues
4. How This Skill Works
When invoked, I will:
- Search your codebase for Cosmos SDK modules
- Analyze each module for the 9 vulnerability patterns
- Report findings with file references and severity
- Provide fixes for each identified issue
- Check message handlers for validation issues
5. Example Output
When vulnerabilities are found, you'll get a report like this:
=== COSMOS SDK VULNERABILITY SCAN RESULTS ===
Project: my-cosmos-chain
Files Scanned: 6 (.go)
Vulnerabilities Found: 2
---
[CRITICAL] Incorrect GetSigners()
---
## 5. Vulnerability Patterns (9 Patterns)
I check for 9 critical vulnerability patterns unique to CosmWasm. For detailed detection patterns, code examples, mitigations, and testing strategies, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
### Pattern Summary:
1. **Missing Denom Validation** ⚠️ CRITICAL - Accepting arbitrary token denoms
2. **Insufficient Authorization** ⚠️ CRITICAL - Missing sender/admin validation
3. **Missing Balance Check** ⚠️ HIGH - Not verifying sufficient balances
4. **Improper Reply Handling** ⚠️ HIGH - Unsafe submessage reply processing
5. **Missing Reply ID Check** ⚠️ MEDIUM - Not validating reply IDs
6. **Improper IBC Packet Validation** ⚠️ CRITICAL - Unvalidated IBC packets
7. **Unvalidated Execute Message** ⚠️ HIGH - Missing message validation
8. **Integer Overflow** ⚠️ HIGH - Unchecked arithmetic operations
9. **Reentrancy via Submessages** ⚠️ MEDIUM - State changes before submessages
For complete vulnerability patterns with code examples, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
## 5. Scanning Workflow
### Step 1: Platform Identification
1. Identify Cosmos SDK version (`go.mod`)
2. Locate custom modules (`x/*/`)
3. Find ABCI methods (`abci.go`, BeginBlocker, EndBlocker)
4. Identify message types (`types/msgs.go`, `.proto`)
### Step 2: Critical Path Analysis
Focus on consensus-critical code:
- BeginBlocker / EndBlocker implementations
- Message handlers (execute, DeliverTx)
- Keeper methods that modify state
- CheckTx priority logic
### Step 3: Non-Determinism Sweep
**This is the highest priority check for Cosmos chains.**
```bash
# Search for non-deterministic patterns
grep -r "range.*map\[" x/
grep -r "\bint\b\|\buint\b" x/ | grep -v "int32\|int64\|uint32\|uint64"
grep -r "float32\|float64" x/
grep -r "go func\|go routine" x/
grep -r "select {" x/
grep -r "time.Now()" x/
grep -r "rand\." x/
For each finding:
- Verify it's in consensus-critical path
- Confirm it causes non-determinism
- Assess severity (chain halt vs data inconsistency)
Step 4: ABCI Method Analysis
Review BeginBlocker and EndBlocker:
- Computational complexity bounded?
- No unbounded iterations?
- No nested loops over large collections?
- Panic-prone operations validated?
- Benchmarked with maximum state?
Step 5: Message Validation
For each message type:
- GetSigners() address matches handler usage?
- All error returns checked?
- Priority set in CheckTx if critical?
- Handler registered (or using v0.47+ auto-registration)?
Step 6: Arithmetic & Bookkeeping
- sdk.Dec operations use multiply-before-divide?
- Rounding favors protocol over users?
- Custom bookkeeping synchronized with x/bank?
- Invariant checks in place?
6. Reporting Format
Finding Template
## [CRITICAL] Non-Deterministic Map Iteration in EndBlocker
**Location**: `x/dex/abci.go:45-52`
**Description**:
The EndBlocker iterates over an unordered map to distribute rewards, causing different validators to process users in different orders and produce different state roots. This will halt the chain when validators fail to reach consensus.
**Vulnerable Code**:
```go
// abci.go, line 45
func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
rewards := k.GetPendingRewards(ctx) // Returns map[string]sdk.Coins
for user, amount := range rewards { // NON-DETERMINISTIC ORDER
k.bankKeeper.SendCoins(ctx, moduleAcc, user, amount)
}
}
Attack Scenario:
- Multiple users have pending rewards
- Different validators iterate in different orders due to map randomization
- If any reward distribution fails mid-iteration, state diverges
- Validators produce different app hashes
- Chain halts - cannot reach consensus
Recommendation: Sort map keys before iteration:
func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
rewards := k.GetPendingRewards(ctx)
// Collect and sort keys for deterministic iteration
users := make([]string, 0, len(rewards))
for user := range rewards {
users = append(users, user)
}
sort.Strings(users) // Deterministic order
// Process in sorted order
for _, user := range users {
k.bankKeeper.SendCoins(ctx, moduleAcc, user, rewards[user])
}
}
References:
- building-secure-contracts/not-so-smart-contracts/cosmos/non_determinism
- Cosmos SDK docs: Determinism
---
## 7. Priority Guidelines
### Critical - CHAIN HALT Risk
- Non-determinism (any form)
- ABCI method panics
- Slow ABCI methods
- Incorrect GetSigners (allows unauthorized actions)
### High - Fund Loss Risk
- Missing error handling (bankKeeper.SendCoins)
- Broken bookkeeping (accounting mismatch)
- Missing message priority (oracle/emergency messages)
### Medium - Logic/DoS Risk
- Rounding errors (protocol value leakage)
- Unregistered message handlers (functionality broken)
---
## 8. Testing Recommendations
### Non-Determinism Testing
```bash
# Build for different architectures
GOARCH=amd64 go build
GOARCH=arm64 go build
# Run same operations, compare state roots
# Must be identical across architectures
# Fuzz test with concurrent operations
go test -fuzz=FuzzEndBlocker -parallel=10
ABCI Benchmarking
func BenchmarkBeginBlocker(b *testing.B) {
ctx := setupMaximalState() // Worst-case state
b.ResetTimer()
for i := 0; i < b.N; i++ {
BeginBlocker(ctx, keeper)
}
// Must complete in < 1 second
require.Less(b, b.Elapsed()/time.Duration(b.N), time.Second)
}
Invariant Testing
// Run invariants in integration tests
func TestInvariants(t *testing.T) {
app := setupApp()
// Execute operations
app.DeliverTx(...)
// Check invariants
_, broken := keeper.AllInvariants()(app.Ctx)
require.False(t, broken, "invariant violation detected")
}
9. Additional Resources
- Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/cosmos/ - Cosmos SDK Docs: https://docs.cosmos.network/
- CodeQL for Go: https://codeql.github.com/docs/codeql-language-guides/codeql-for-go/
- Cosmos Security Best Practices: https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/learn/advanced/17-determinism.md
10. Quick Reference Checklist
Before completing Cosmos chain audit:
Non-Determinism (CRITICAL):
- No map iteration in consensus code
- No platform-dependent types (int, uint, float)
- No goroutines in message handlers/ABCI
- No select statements with multiple channels
- No rand, time.Now(), memory addresses
- All serialization is deterministic
ABCI Methods (CRITICAL):
- BeginBlocker/EndBlocker computationally bounded
- No unbounded iterations
- No nested loops over large collections
- All panic-prone operations validated
- Benchmarked with maximum state
Message Handling (HIGH):
- GetSigners() matches handler address usage
- All error returns checked
- Critical messages prioritized in CheckTx
- All message types registered
Arithmetic & Accounting (MEDIUM):
- Multiply before divide pattern used
- Rounding favors protocol
- Custom bookkeeping synced with x/bank
- Invariant checks implemented
Testing:
- Cross-architecture builds tested
- ABCI methods benchmarked
- Invariants checked in CI
- Integration tests cover all messages
/guidelines-advisor
Source: ~/.claude/skills/tob-building-secure-contracts/skills/guidelines-advisor/SKILL.md
name: guidelines-advisor description: Smart contract development advisor based on Trail of Bits' best practices. Analyzes codebase to generate documentation/specifications, review architecture, check upgradeability patterns, assess implementation quality, identify pitfalls, review dependencies, and evaluate testing. Provides actionable recommendations.
Guidelines Advisor
Purpose
Systematically analyzes the codebase and provides guidance based on Trail of Bits' development guidelines:
- Generate documentation and specifications (plain English descriptions, architectural diagrams, code documentation)
- Optimize on-chain/off-chain architecture (only if applicable)
- Review upgradeability patterns (if your project has upgrades)
- Check delegatecall/proxy implementations (if present)
- Assess implementation quality (functions, inheritance, events)
- Identify common pitfalls
- Review dependencies
- Evaluate test suite and suggest improvements
Framework: Building Secure Contracts - Development Guidelines
How This Works
Phase 1: Discovery & Context
Explores the codebase to understand:
- Project structure and platform
- Contract/module files and their purposes
- Existing documentation
- Architecture patterns (proxies, upgrades, etc.)
- Testing setup
- Dependencies
Phase 2: Documentation Generation
Helps create:
- Plain English system description
- Architectural diagrams (using Slither printers for Solidity)
- Code documentation recommendations (NatSpec for Solidity)
Phase 3: Architecture Analysis
Analyzes:
- On-chain vs off-chain component distribution (if applicable)
- Upgradeability approach (if applicable)
- Delegatecall proxy patterns (if present)
Phase 4: Implementation Review
Assesses:
- Function composition and clarity
- Inheritance structure
- Event logging practices
- Common pitfalls presence
- Dependencies quality
- Testing coverage and techniques
Phase 5: Recommendations
Provides:
- Prioritized improvement suggestions
- Best practice guidance
- Actionable next steps
Assessment Areas
I analyze 11 comprehensive areas covering all aspects of smart contract development. For detailed criteria, best practices, and specific checks, see ASSESSMENT_AREAS.md.
Quick Reference:
-
Documentation & Specifications
- Plain English system descriptions
- Architectural diagrams
- NatSpec completeness (Solidity)
- Documentation gaps identification
-
On-Chain vs Off-Chain Computation
- Complexity analysis
- Gas optimization opportunities
- Verification vs computation patterns
-
Upgradeability
- Migration vs upgradeability trade-offs
- Data separation patterns
- Upgrade procedure documentation
-
Delegatecall Proxy Pattern
- Storage layout consistency
- Initialization patterns
- Function shadowing risks
- Slither upgradeability checks
-
Function Composition
- Function size and clarity
- Logical grouping
- Modularity assessment
-
Inheritance
- Hierarchy depth/width
- Diamond problem risks
- Inheritance visualization
-
Events
- Critical operation coverage
- Event naming consistency
- Indexed parameters
-
Common Pitfalls
- Reentrancy patterns
- Integer overflow/underflow
- Access control issues
- Platform-specific vulnerabilities
-
Dependencies
- Library quality assessment
- Version management
- Dependency manager usage
- Copied code detection
-
Testing & Verification
- Coverage analysis
- Fuzzing techniques
- Formal verification
- CI/CD integration
-
Platform-Specific Guidance
- Solidity version recommendations
- Compiler warning checks
- Inline assembly warnings
- Platform-specific tools
For complete details on each area including what I'll check, analyze, and recommend, see ASSESSMENT_AREAS.md.
Example Output
When the analysis is complete, you'll receive comprehensive guidance covering:
- System documentation with plain English descriptions
- Architectural diagrams and documentation gaps
- Architecture analysis (on-chain/off-chain, upgradeability, proxies)
- Implementation review (functions, inheritance, events, pitfalls)
- Dependencies and testing evaluation
- Prioritized recommendations (CRITICAL, HIGH, MEDIUM, LOW)
- Overall assessment and path to production
For a complete example analysis report, see EXAMPLE_REPORT.md.
Deliverables
I provide four comprehensive deliverable categories:
1. System Documentation
- Plain English descriptions
- Architectural diagrams
- Documentation gaps analysis
2. Architecture Analysis
- On-chain/off-chain assessment
- Upgradeability review
- Proxy pattern security review
3. Implementation Review
- Function composition analysis
- Inheritance assessment
- Events coverage
- Pitfall identification
- Dependencies evaluation
- Testing analysis
4. Prioritized Recommendations
- CRITICAL (address immediately)
- HIGH (address before deployment)
- MEDIUM (address for production quality)
- LOW (nice to have)
For detailed templates and examples of each deliverable, see DELIVERABLES.md.
Assessment Process
When invoked, I will:
-
Explore the codebase
- Identify all contract/module files
- Find existing documentation
- Locate test files
- Check for proxies/upgrades
- Identify dependencies
-
Generate documentation
- Create plain English system description
- Generate architectural diagrams (if tools available)
- Identify documentation gaps
-
Analyze architecture
- Assess on-chain/off-chain distribution (if applicable)
- Review upgradeability approach (if applicable)
- Audit proxy patterns (if present)
-
Review implementation
- Analyze functions, inheritance, events
- Check for common pitfalls
- Assess dependencies
- Evaluate testing
-
Provide recommendations
- Present findings with file references
- Ask clarifying questions about design decisions
- Suggest prioritized improvements
- Offer actionable next steps
Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "System is simple, description covers everything" | Plain English descriptions miss security-critical details | Complete all 5 phases: documentation, architecture, implementation, dependencies, recommendations |
| "No upgrades detected, skip upgradeability section" | Upgradeability can be implicit (ownable patterns, delegatecall) | Search for proxy patterns, delegatecall, storage collisions before declaring N/A |
| "Not applicable" without verification | Premature scope reduction misses vulnerabilities | Verify with explicit codebase search before skipping any guideline section |
| "Architecture is straightforward, no analysis needed" | Obvious architectures have subtle trust boundaries | Analyze on-chain/off-chain distribution, access control flow, external dependencies |
| "Common pitfalls don't apply to this codebase" | Every codebase has common pitfalls | Systematically check all guideline pitfalls with grep/code search |
| "Tests exist, testing guideline is satisfied" | Test existence ≠ test quality | Check coverage, property-based tests, integration tests, failure cases |
| "I can provide generic best practices" | Generic advice isn't actionable | Provide project-specific findings with file:line references |
| "User knows what to improve from findings" | Findings without prioritization = no action plan | Generate prioritized improvement roadmap with specific next steps |
Notes
- I'll only analyze relevant sections (won't hallucinate about upgrades if not present)
- I'll adapt to your platform (Solidity, Rust, Cairo, etc.)
- I'll use available tools (Slither, etc.) but work without them if unavailable
- I'll provide file references and line numbers for all findings
- I'll ask questions about design decisions I can't infer from code
Ready to Begin
What I'll need:
- Access to your codebase
- Context about your project goals
- Any existing documentation or specifications
- Information about deployment plans
Let's analyze your codebase and improve it using Trail of Bits' best practices!
/secure-workflow-guide
Source: ~/.claude/skills/tob-building-secure-contracts/skills/secure-workflow-guide/SKILL.md
name: secure-workflow-guide description: Guides through Trail of Bits' 5-step secure development workflow. Runs Slither scans, checks special features (upgradeability/ERC conformance/token integration), generates visual security diagrams, helps document security properties for fuzzing/verification, and reviews manual security areas.
Secure Workflow Guide
Purpose
Guides through Trail of Bits' secure development workflow - a 5-step process to enhance smart contract security throughout development.
Use this: On every check-in, before deployment, or when you want a security review
The 5-Step Workflow
Covers a security workflow including:
Step 1: Check for Known Security Issues
Run Slither with 70+ built-in detectors to find common vulnerabilities:
- Parse findings by severity
- Explain each issue with file references
- Recommend fixes
- Help triage false positives
Goal: Clean Slither report or documented triages
Step 2: Check Special Features
Detect and validate applicable features:
- Upgradeability: slither-check-upgradeability (17 upgrade risks)
- ERC conformance: slither-check-erc (6 common specs)
- Token integration: Recommend token-integration-analyzer skill
- Security properties: slither-prop for ERC20
Note: Only runs checks that apply to your codebase
Step 3: Visual Security Inspection
Generate 3 security diagrams:
- Inheritance graph: Identify shadowing and C3 linearization issues
- Function summary: Show visibility and access controls
- Variables and authorization: Map who can write to state variables
Review each diagram for security concerns
Step 4: Document Security Properties
Help document critical security properties:
- State machine transitions and invariants
- Access control requirements
- Arithmetic constraints and precision
- External interaction safety
- Standards conformance
Then set up testing:
- Echidna: Property-based fuzzing with invariants
- Manticore: Formal verification with symbolic execution
- Custom Slither checks: Project-specific business logic
Note: Most important activity for security
Step 5: Manual Review Areas
Analyze areas automated tools miss:
- Privacy: On-chain secrets, commit-reveal needs
- Front-running: Slippage protection, ordering risks, MEV
- Cryptography: Weak randomness, signature issues, hash collisions
- DeFi interactions: Oracle manipulation, flash loans, protocol assumptions
Search codebase for these patterns and flag risks
For detailed instructions, commands, and explanations for each step, see WORKFLOW_STEPS.md.
How I Work
When invoked, I will:
- Explore your codebase to understand structure
- Run Step 1: Slither security scan
- Detect and run Step 2: Special feature checks (only what applies)
- Generate Step 3: Visual security diagrams
- Guide Step 4: Security property documentation
- Analyze Step 5: Manual review areas
- Provide action plan: Prioritized fixes and next steps
Adapts based on:
- What tools you have installed
- What's applicable to your project
- Where you are in development
Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "Slither not available, I'll check manually" | Manual checking misses 70+ detector patterns | Install and run Slither, or document why it's blocked |
| "Can't generate diagrams, I'll describe the architecture" | Descriptions aren't visual - diagrams reveal patterns text misses | Execute slither --print commands, generate actual visual outputs |
| "No upgrades detected, skip upgradeability checks" | Proxies and upgrades are often implicit or planned | Verify with codebase search before skipping Step 2 checks |
| "Not a token, skip ERC checks" | Tokens can be integrated without obvious ERC inheritance | Check for token interactions, transfers, balances before skipping |
| "Can't set up Echidna now, suggesting it for later" | Property-based testing is Step 4, not optional | Document properties now, set up fuzzing infrastructure |
| "No DeFi interactions, skip oracle/flash loan checks" | DeFi patterns appear in unexpected places (price feeds, external calls) | Complete Step 5 manual review, search codebase for patterns |
| "This step doesn't apply to my project" | "Not applicable" without verification = missed vulnerabilities | Verify with explicit codebase search before declaring N/A |
| "I'll provide generic security advice instead of running workflow" | Generic advice isn't actionable, workflow finds specific issues | Execute all 5 steps, generate project-specific findings with file:line references |
Example Output
When I complete the workflow, you'll get a comprehensive security report covering:
- Step 1: Slither findings with severity, file references, and fix recommendations
- Step 2: Special feature validation results (upgradeability, ERC conformance, etc.)
- Step 3: Visual diagrams analyzing inheritance, functions, and state variable authorization
- Step 4: Documented security properties and testing setup (Echidna/Manticore)
- Step 5: Manual review findings (privacy, front-running, cryptography, DeFi risks)
- Action plan: Critical/high/medium priority tasks with effort estimates
- Workflow checklist: Progress on all 5 steps
For a complete example workflow report, see EXAMPLE_REPORT.md.
What You'll Get
Security Report:
- Slither findings with severity and fixes
- Special feature validation results
- Visual diagrams (PNG/PDF)
- Manual review findings
Action Plan:
- Critical issues to fix immediately
- Security properties to document
- Testing to set up (Echidna/Manticore)
- Manual areas to review
Workflow Checklist:
- Clean Slither report
- Special features validated
- Visual inspection complete
- Properties documented
- Manual review done
Getting Help
Trail of Bits Resources:
- Office Hours: Every Tuesday (schedule)
- Empire Hacking Slack: #crytic and #ethereum channels
Other Security:
- Remember: Security is about more than smart contracts
- Off-chain security (owner keys, infrastructure) equally critical
Ready to Start
Let me know when you're ready and I'll run through the workflow with your codebase!
/solana-vulnerability-scanner
Source: ~/.claude/skills/tob-building-secure-contracts/skills/solana-vulnerability-scanner/SKILL.md
name: solana-vulnerability-scanner description: Scans Solana programs for 6 critical vulnerabilities including arbitrary CPI, improper PDA validation, missing signer/ownership checks, and sysvar spoofing. Use when auditing Solana/Anchor programs.
Solana Vulnerability Scanner
1. Purpose
Systematically scan Solana programs (native and Anchor framework) for platform-specific security vulnerabilities related to cross-program invocations, account validation, and program-derived addresses. This skill encodes 6 critical vulnerability patterns unique to Solana's account model.
2. When to Use This Skill
- Auditing Solana programs (native Rust or Anchor)
- Reviewing cross-program invocation (CPI) logic
- Validating program-derived address (PDA) implementations
- Pre-launch security assessment of Solana protocols
- Reviewing account validation patterns
- Assessing instruction introspection logic
3. Platform Detection
File Extensions & Indicators
- Rust files:
.rs
Language/Framework Markers
// Native Solana program indicators
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
program::invoke,
program::invoke_signed,
};
entrypoint!(process_instruction);
// Anchor framework indicators
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// Program logic
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub authority: Signer<'info>,
}
// Common patterns
AccountInfo, Pubkey
invoke(), invoke_signed()
Signer<'info>, Account<'info>
#[account(...)] with constraints
seeds, bump
Project Structure
programs/*/src/lib.rs- Program implementationAnchor.toml- Anchor configurationCargo.tomlwithsolana-programoranchor-langtests/- Program tests
Tool Support
- Trail of Bits Solana Lints: Rust linters for Solana
- Installation: Add to Cargo.toml
- anchor test: Built-in testing framework
- Solana Test Validator: Local testing environment
4. How This Skill Works
When invoked, I will:
- Search your codebase for Solana/Anchor programs
- Analyze each program for the 6 vulnerability patterns
- Report findings with file references and severity
- Provide fixes for each identified issue
- Check account validation and CPI security
5. Example Output
5. Vulnerability Patterns (6 Patterns)
I check for 6 critical vulnerability patterns unique to Solana. For detailed detection patterns, code examples, mitigations, and testing strategies, see VULNERABILITY_PATTERNS.md.
Pattern Summary:
- Arbitrary CPI ⚠️ CRITICAL - User-controlled program IDs in CPI calls
- Improper PDA Validation ⚠️ CRITICAL - Using create_program_address without canonical bump
- Missing Ownership Check ⚠️ HIGH - Deserializing accounts without owner validation
- Missing Signer Check ⚠️ CRITICAL - Authority operations without is_signer check
- Sysvar Account Check ⚠️ HIGH - Spoofed sysvar accounts (pre-Solana 1.8.1)
- Improper Instruction Introspection ⚠️ MEDIUM - Absolute indexes allowing reuse
For complete vulnerability patterns with code examples, see VULNERABILITY_PATTERNS.md.
5. Scanning Workflow
Step 1: Platform Identification
- Verify Solana program (native or Anchor)
- Check Solana version (1.8.1+ for sysvar security)
- Locate program source (
programs/*/src/lib.rs) - Identify framework (native vs Anchor)
Step 2: CPI Security Review
# Find all CPI calls
rg "invoke\(|invoke_signed\(" programs/
# Check for program ID validation before each
# Should see program ID checks immediately before invoke
For each CPI:
- Program ID validated before invocation
- Cannot pass user-controlled program accounts
- Anchor: Uses
Program<'info, T>type
Step 3: PDA Validation Check
# Find PDA usage
rg "find_program_address|create_program_address" programs/
rg "seeds.*bump" programs/
# Anchor: Check for seeds constraints
rg "#\[account.*seeds" programs/
For each PDA:
- Uses
find_program_address()or Anchorseedsconstraint - Bump seed stored and reused
- Not using user-provided bump
Step 4: Account Validation Sweep
# Find account deserialization
rg "try_from_slice|try_deserialize" programs/
# Should see owner checks before deserialization
rg "\.owner\s*==|\.owner\s*!=" programs/
For each account used:
- Owner validated before deserialization
- Signer check for authority accounts
- Anchor: Uses
Account<'info, T>andSigner<'info>
Step 5: Instruction Introspection Review
# Find instruction introspection usage
rg "load_instruction_at|load_current_index|get_instruction_relative" programs/
# Check for checked versions
rg "load_instruction_at_checked|load_current_index_checked" programs/
- Using checked functions (Solana 1.8.1+)
- Using relative indexing
- Proper correlation validation
Step 6: Trail of Bits Solana Lints
# Add to Cargo.toml
[dependencies]
solana-program = "1.17" # Use latest version
[lints.clippy]
# Enable Solana-specific lints
# (Trail of Bits solana-lints if available)
6. Reporting Format
Finding Template
## [CRITICAL] Arbitrary CPI - Unchecked Program ID
**Location**: `programs/vault/src/lib.rs:145-160` (withdraw function)
**Description**:
The `withdraw` function performs a CPI to transfer SPL tokens without validating that the provided `token_program` account is actually the SPL Token program. An attacker can provide a malicious program that appears to perform a transfer but actually steals tokens or performs unauthorized actions.
**Vulnerable Code**:
```rust
// lib.rs, line 145
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let token_program = &ctx.accounts.token_program;
// WRONG: No validation of token_program.key()!
invoke(
&spl_token::instruction::transfer(...),
&[
ctx.accounts.vault.to_account_info(),
ctx.accounts.destination.to_account_info(),
ctx.accounts.authority.to_account_info(),
token_program.to_account_info(), // UNVALIDATED
],
)?;
Ok(())
}
Attack Scenario:
- Attacker deploys malicious "token program" that logs transfer instruction but doesn't execute it
- Attacker calls withdraw() providing malicious program as token_program
- Vault's authority signs the transaction
- Malicious program receives CPI with vault's signature
- Malicious program can now impersonate vault and drain real tokens
Recommendation:
Use Anchor's Program<'info, Token> type:
use anchor_spl::token::{Token, Transfer};
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub vault: Account<'info, TokenAccount>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>, // Validates program ID automatically
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.vault.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
};
let cpi_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
);
anchor_spl::token::transfer(cpi_ctx, amount)?;
Ok(())
}
References:
- building-secure-contracts/not-so-smart-contracts/solana/arbitrary_cpi
- Trail of Bits lint:
unchecked-cpi-program-id
---
## 7. Priority Guidelines
### Critical (Immediate Fix Required)
- Arbitrary CPI (attacker-controlled program execution)
- Improper PDA validation (account spoofing)
- Missing signer check (unauthorized access)
### High (Fix Before Launch)
- Missing ownership check (fake account data)
- Sysvar account check (authentication bypass, pre-1.8.1)
### Medium (Address in Audit)
- Improper instruction introspection (logic bypass)
---
## 8. Testing Recommendations
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_rejects_wrong_program_id() {
// Provide wrong program ID, should fail
}
#[test]
#[should_panic]
fn test_rejects_non_canonical_pda() {
// Provide non-canonical bump, should fail
}
#[test]
#[should_panic]
fn test_requires_signer() {
// Call without signature, should fail
}
}
Integration Tests (Anchor)
import * as anchor from "@coral-xyz/anchor";
describe("security tests", () => {
it("rejects arbitrary CPI", async () => {
const fakeTokenProgram = anchor.web3.Keypair.generate();
try {
await program.methods
.withdraw(amount)
.accounts({
tokenProgram: fakeTokenProgram.publicKey, // Wrong program
})
.rpc();
assert.fail("Should have rejected fake program");
} catch (err) {
// Expected to fail
}
});
});
Solana Test Validator
# Run local validator for testing
solana-test-validator
# Deploy and test program
anchor test
9. Additional Resources
- Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/solana/ - Trail of Bits Solana Lints: https://github.com/trailofbits/solana-lints
- Anchor Documentation: https://www.anchor-lang.com/
- Solana Program Library: https://github.com/solana-labs/solana-program-library
- Solana Cookbook: https://solanacookbook.com/
10. Quick Reference Checklist
Before completing Solana program audit:
CPI Security (CRITICAL):
- ALL CPI calls validate program ID before
invoke() - Cannot use user-provided program accounts
- Anchor: Uses
Program<'info, T>type
PDA Security (CRITICAL):
- PDAs use
find_program_address()or Anchorseedsconstraint - Bump seed stored and reused (not user-provided)
- PDA accounts validated against canonical address
Account Validation (HIGH):
- ALL accounts check owner before deserialization
- Native: Validates
account.owner == expected_program_id - Anchor: Uses
Account<'info, T>type
Signer Validation (CRITICAL):
Sysvar Security (HIGH):
- Using Solana 1.8.1+
- Using checked functions:
load_instruction_at_checked() - Sysvar addresses validated
Instruction Introspection (MEDIUM):
- Using relative indexes for correlation
- Proper validation between related instructions
- Cannot reuse same instruction across multiple calls
Testing:
- Unit tests cover all account validation
- Integration tests with malicious inputs
- Local validator testing completed
- Trail of Bits lints enabled and passing
/substrate-vulnerability-scanner
Source: ~/.claude/skills/tob-building-secure-contracts/skills/substrate-vulnerability-scanner/SKILL.md
name: substrate-vulnerability-scanner description: Scans Substrate/Polkadot pallets for 7 critical vulnerabilities including arithmetic overflow, panic DoS, incorrect weights, and bad origin checks. Use when auditing Substrate runtimes or FRAME pallets.
Substrate Vulnerability Scanner
1. Purpose
Systematically scan Substrate runtime modules (pallets) for platform-specific security vulnerabilities that can cause node crashes, DoS attacks, or unauthorized access. This skill encodes 7 critical vulnerability patterns unique to Substrate/FRAME-based chains.
2. When to Use This Skill
- Auditing custom Substrate pallets
- Reviewing FRAME runtime code
- Pre-launch security assessment of Substrate chains (Polkadot parachains, standalone chains)
- Validating dispatchable extrinsic functions
- Reviewing weight calculation functions
- Assessing unsigned transaction validation logic
3. Platform Detection
File Extensions & Indicators
- Rust files:
.rs
Language/Framework Markers
// Substrate/FRAME indicators
#[pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config { }
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(10_000)]
pub fn example_function(origin: OriginFor<T>) -> DispatchResult { }
}
}
// Common patterns
DispatchResult, DispatchError
ensure!, ensure_signed, ensure_root
StorageValue, StorageMap, StorageDoubleMap
#[pallet::storage]
#[pallet::call]
#[pallet::weight]
#[pallet::validate_unsigned]
Project Structure
pallets/*/lib.rs- Pallet implementationsruntime/lib.rs- Runtime configurationbenchmarking.rs- Weight benchmarksCargo.tomlwithframe-*dependencies
Tool Support
- cargo-fuzz: Fuzz testing for Rust
- test-fuzz: Property-based testing framework
- benchmarking framework: Built-in weight calculation
- try-runtime: Runtime migration testing
4. How This Skill Works
When invoked, I will:
- Search your codebase for Substrate pallets
- Analyze each pallet for the 7 vulnerability patterns
- Report findings with file references and severity
- Provide fixes for each identified issue
- Check weight calculations and origin validation
5. Vulnerability Patterns (7 Critical Patterns)
I check for 7 critical vulnerability patterns unique to Substrate/FRAME. For detailed detection patterns, code examples, mitigations, and testing strategies, see VULNERABILITY_PATTERNS.md.
Pattern Summary:
-
Arithmetic Overflow ⚠️ CRITICAL
- Direct
+,-,*,/operators wrap in release mode - Must use
checked_*orsaturating_*methods - Affects balance/token calculations, reward/fee math
- Direct
-
Don't Panic ⚠️ CRITICAL - DoS
- Panics cause node to stop processing blocks
- No
unwrap(),expect(), array indexing without bounds check - All user input must be validated with
ensure!
-
Weights and Fees ⚠️ CRITICAL - DoS
- Incorrect weights allow spam attacks
- Fixed weights for variable-cost operations enable DoS
- Must use benchmarking framework, bound all input parameters
-
Verify First, Write Last ⚠️ HIGH (Pre-v0.9.25)
- Storage writes before validation persist on error (pre-v0.9.25)
- Pattern: validate → write → emit event
- Upgrade to v0.9.25+ or use manual
#[transactional]
-
Unsigned Transaction Validation ⚠️ HIGH
- Insufficient validation allows spam/replay attacks
- Prefer signed transactions
- If unsigned: validate parameters, replay protection, authenticate source
-
Bad Randomness ⚠️ MEDIUM
pallet_randomness_collective_flipvulnerable to collusion- Must use BABE randomness (
pallet_babe::RandomnessFromOneEpochAgo) - Use
random(subject)notrandom_seed()
-
Bad Origin ⚠️ CRITICAL
ensure_signedallows any user for privileged operations- Must use
ensure_rootor custom origins (ForceOrigin, AdminOrigin) - Origin types must be properly configured in runtime
For complete vulnerability patterns with code examples, see VULNERABILITY_PATTERNS.md.
6. Scanning Workflow
Step 1: Platform Identification
- Verify Substrate/FRAME framework usage
- Check Substrate version (v0.9.25+ has transactional storage)
- Locate pallet implementations (
pallets/*/lib.rs) - Identify runtime configuration (
runtime/lib.rs)
Step 2: Dispatchable Analysis
For each #[pallet::call] function:
- Arithmetic: Uses checked/saturating operations?
- Panics: No unwrap/expect/indexing?
- Weights: Proportional to cost, bounded inputs?
- Origin: Appropriate validation level?
- Validation: All checks before storage writes?
Step 3: Panic Sweep
# Search for panic-prone patterns
rg "unwrap\(\)" pallets/
rg "expect\(" pallets/
rg "\[.*\]" pallets/ # Array indexing
rg " as u\d+" pallets/ # Type casts
rg "\.unwrap_or" pallets/
Step 4: Arithmetic Safety Check
# Find direct arithmetic
rg " \+ |\+=| - |-=| \* |\*=| / |/=" pallets/
# Should find checked/saturating alternatives instead
rg "checked_add|checked_sub|checked_mul|checked_div" pallets/
rg "saturating_add|saturating_sub|saturating_mul" pallets/
Step 5: Weight Analysis
- Run benchmarking:
cargo test --features runtime-benchmarks - Verify weights match computational cost
- Check for bounded input parameters
- Review weight calculation functions
Step 6: Origin & Privilege Review
# Find privileged operations
rg "ensure_signed" pallets/ | grep -E "pause|emergency|admin|force|sudo"
# Should use ensure_root or custom origins
rg "ensure_root|ForceOrigin|AdminOrigin" pallets/
Step 7: Testing Review
- Unit tests cover all dispatchables
- Fuzz tests for panic conditions
- Benchmarks for weight calculation
- try-runtime tests for migrations
7. Priority Guidelines
Critical (Immediate Fix Required)
- Arithmetic overflow (token creation, balance manipulation)
- Panic DoS (node crash risk)
- Bad origin (unauthorized privileged operations)
High (Fix Before Launch)
- Incorrect weights (DoS via spam)
- Verify-first violations (state corruption, pre-v0.9.25)
- Unsigned validation issues (spam, replay attacks)
Medium (Address in Audit)
- Bad randomness (manipulation possible but limited impact)
8. Testing Recommendations
Fuzz Testing
// Use test-fuzz for property-based testing
#[cfg(test)]
mod tests {
use test_fuzz::test_fuzz;
#[test_fuzz]
fn fuzz_transfer(from: AccountId, to: AccountId, amount: u128) {
// Should never panic
let _ = Pallet::transfer(from, to, amount);
}
#[test_fuzz]
fn fuzz_no_panics(call: Call) {
// No dispatchable should panic
let _ = call.dispatch(origin);
}
}
Benchmarking
# Run benchmarks to generate weights
cargo build --release --features runtime-benchmarks
./target/release/node benchmark pallet \
--chain dev \
--pallet pallet_example \
--extrinsic "*" \
--steps 50 \
--repeat 20
try-runtime
# Test runtime upgrades
cargo build --release --features try-runtime
try-runtime --runtime ./target/release/wbuild/runtime.wasm \
on-runtime-upgrade live --uri wss://rpc.polkadot.io
9. Additional Resources
- Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/substrate/ - Substrate Documentation: https://docs.substrate.io/
- FRAME Documentation: https://paritytech.github.io/substrate/master/frame_support/
- test-fuzz: https://github.com/trailofbits/test-fuzz
- Substrate StackExchange: https://substrate.stackexchange.com/
10. Quick Reference Checklist
Before completing Substrate pallet audit:
Arithmetic Safety (CRITICAL):
- No direct
+,-,*,/operators in dispatchables - All arithmetic uses
checked_*orsaturating_* - Type conversions use
try_into()with error handling
Panic Prevention (CRITICAL):
- No
unwrap()orexpect()in dispatchables - No direct array/slice indexing without bounds check
- All user inputs validated with
ensure! - Division operations check for zero divisor
Weights & DoS (CRITICAL):
- Weights proportional to computational cost
- Input parameters have maximum bounds
- Benchmarking used to determine weights
- No free (zero-weight) expensive operations
Access Control (CRITICAL):
- Privileged operations use
ensure_rootor custom origins -
ensure_signedonly for user-level operations - Origin types properly configured in runtime
- Sudo pallet removed before production
Storage Safety (HIGH):
- Using Substrate v0.9.25+ OR manual
#[transactional] - Validation before storage writes
- Events emitted after successful operations
Other (MEDIUM):
- Unsigned transactions use signed alternative if possible
- If unsigned: proper validation, replay protection, authentication
- BABE randomness used (not RandomnessCollectiveFlip)
- Randomness uses
random(subject)notrandom_seed()
Testing:
- Unit tests for all dispatchables
- Fuzz tests to find panics
- Benchmarks generated and verified
- try-runtime tests for migrations
/token-integration-analyzer
Source: ~/.claude/skills/tob-building-secure-contracts/skills/token-integration-analyzer/SKILL.md
name: token-integration-analyzer description: Token integration and implementation analyzer based on Trail of Bits' token integration checklist. Analyzes token implementations for ERC20/ERC721 conformity, checks for 20+ weird token patterns, assesses contract composition and owner privileges, performs on-chain scarcity analysis, and evaluates how protocols handle non-standard tokens. Context-aware for both token implementations and token integrations.
Token Integration Analyzer
Purpose
Systematically analyzes the codebase for token-related security concerns using Trail of Bits' token integration checklist:
- Token Implementations: Analyze if your token follows ERC20/ERC721 standards or has non-standard behavior
- Token Integrations: Analyze how your protocol handles arbitrary tokens, including weird/non-standard tokens
- On-chain Analysis: Query deployed contracts for scarcity, distribution, and configuration
- Security Assessment: Identify risks from 20+ known weird token patterns
Framework: Building Secure Contracts - Token Integration Checklist + Weird ERC20 Database
How This Works
Phase 1: Context Discovery
Determines analysis context:
- Token implementation: Are you building a token contract?
- Token integration: Does your protocol interact with external tokens?
- Platform: Ethereum, other EVM chains, or different platform?
- Token types: ERC20, ERC721, or both?
Phase 2: Slither Analysis (if Solidity)
For Solidity projects, I'll help run:
slither-check-erc- ERC conformity checksslither --print human-summary- Complexity and upgrade analysisslither --print contract-summary- Function analysisslither-prop- Property generation for testing
Phase 3: Code Analysis
Analyzes:
- Contract composition and complexity
- Owner privileges and centralization risks
- ERC20/ERC721 conformity
- Known weird token patterns
- Integration safety patterns
Phase 4: On-chain Analysis (if deployed)
If you provide a contract address, I'll query:
- Token scarcity and distribution
- Total supply and holder concentration
- Exchange listings
- On-chain configuration
Phase 5: Risk Assessment
Provides:
- Identified vulnerabilities
- Non-standard behaviors
- Integration risks
- Prioritized recommendations
Assessment Categories
I check 10 comprehensive categories covering all aspects of token security. For detailed criteria, patterns, and checklists, see ASSESSMENT_CATEGORIES.md.
Quick Reference:
- General Considerations - Security reviews, team transparency, security contacts
- Contract Composition - Complexity analysis, SafeMath usage, function count, entry points
- Owner Privileges - Upgradeability, minting, pausability, blacklisting, team accountability
- ERC20 Conformity - Return values, metadata, decimals, race conditions, Slither checks
- ERC20 Extension Risks - External calls/hooks, transfer fees, rebasing/yield-bearing tokens
- Token Scarcity Analysis - Supply distribution, holder concentration, exchange distribution, flash loan/mint risks
- Weird ERC20 Patterns (24 patterns including):
- Reentrant calls (ERC777 hooks)
- Missing return values (USDT, BNB, OMG)
- Fee on transfer (STA, PAXG)
- Balance modifications outside transfers (Ampleforth, Compound)
- Upgradable tokens (USDC, USDT)
- Flash mintable (DAI)
- Blocklists (USDC, USDT)
- Pausable tokens (BNB, ZIL)
- Approval race protections (USDT, KNC)
- Revert on approval/transfer to zero address
- Revert on zero value approvals/transfers
- Multiple token addresses
- Low decimals (USDC: 6, Gemini: 2)
- High decimals (YAM-V2: 24)
- transferFrom with src == msg.sender
- Non-string metadata (MKR)
- No revert on failure (ZRX, EURS)
- Revert on large approvals (UNI, COMP)
- Code injection via token name
- Unusual permit function (DAI, RAI, GLM)
- Transfer less than amount (cUSDCv3)
- ERC-20 native currency representation (Celo, Polygon, zkSync)
- And more...
- Token Integration Safety - Safe transfer patterns, balance verification, allowlists, wrappers, defensive patterns
- ERC721 Conformity - Transfer to 0x0, safeTransferFrom, metadata, ownerOf, approval clearing, token ID immutability
- ERC721 Common Risks - onERC721Received reentrancy, safe minting, burning approval clearing
Example Output
When analysis is complete, you'll receive a comprehensive report structured as follows:
=== TOKEN INTEGRATION ANALYSIS REPORT ===
Project: MultiToken DEX
Token Analyzed: Custom Reward Token + Integration Safety
Platform: Solidity 0.8.20
Analysis Date: March 15, 2024
---
## EXECUTIVE SUMMARY
Token Type: ERC20 Implementation + Protocol Integrating External Tokens
Overall Risk Level: MEDIUM
Critical Issues: 2
High Issues: 3
Medium Issues: 4
**Top Concerns:**
⚠ Fee-on-transfer tokens not handled correctly
⚠ No validation for missing return values (USDT compatibility)
⚠ Owner can mint unlimited tokens without cap
**Recommendation:** Address critical/high issues before mainnet launch.
---
## 1. GENERAL CONSIDERATIONS
✓ Contract audited by CertiK (June 2023)
✓ Team contactable via security@project.com
✗ No security mailing list for critical announcements
**Risk:** Users won't be notified of critical issues
**Action:** Set up security@project.com mailing list
---
## 2. CONTRACT COMPOSITION
### Complexity Analysis
**Slither human-summary Results:**
- 456 lines of code
- Cyclomatic complexity: Average 6, Max 14 (transferWithFee())
- 12 functions, 8 state variables
- Inheritance depth: 3 (moderate)
✓ Contract complexity is reasonable
⚠ transferWithFee() complexity high (14) - consider splitting
### SafeMath Usage
✓ Using Solidity 0.8.20 (built-in overflow protection)
✓ No unchecked blocks found
✓ All arithmetic operations protected
### Non-Token Functions
**Functions Beyond ERC20:**
- setFeeCollector() - Admin function ✓
- setTransferFee() - Admin function ✓
- withdrawFees() - Admin function ✓
- pause()/unpause() - Emergency functions ✓
⚠ 4 non-token functions (acceptable but adds complexity)
### Address Entry Points
✓ Single contract address
✓ No proxy with multiple entry points
✓ No token migration creating address confusion
**Status:** PASS
---
## 3. OWNER PRIVILEGES
### Upgradeability
⚠ Contract uses TransparentUpgradeableProxy
**Risk:** Owner can change contract logic at any time
**Current Implementation:**
- ProxyAdmin: 0x1234... (2/3 multisig) ✓
- Timelock: None ✗
**Recommendation:** Add 48-hour timelock to all upgrades
### Minting Capabilities
❌ CRITICAL: Unlimited minting
File: contracts/RewardToken.sol:89
```solidity
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount); // No cap!
}
Risk: Owner can inflate supply arbitrarily Fix: Add maximum supply cap or rate-limited minting
Pausability
✓ Pausable pattern implemented (OpenZeppelin) ✓ Only owner can pause ⚠ Paused state affects all transfers (including existing holders)
Risk: Owner can trap all user funds Mitigation: Use multi-sig for pause function (already implemented ✓)
Blacklisting
✗ No blacklist functionality Assessment: Good - no centralized censorship risk
Team Transparency
✓ Team members public (team.md) ✓ Company registered in Switzerland ✓ Accountable and contactable
Status: ACCEPTABLE
4. ERC20 CONFORMITY
Slither-check-erc Results
Command: slither-check-erc . RewardToken --erc erc20
✓ transfer returns bool ✓ transferFrom returns bool ✓ name, decimals, symbol present ✓ decimals returns uint8 (value: 18) ✓ Race condition mitigated (increaseAllowance/decreaseAllowance)
Status: FULLY COMPLIANT
slither-prop Test Results
Command: slither-prop . --contract RewardToken
Generated 12 properties, all passed: ✓ Transfer doesn't change total supply ✓ Allowance correctly updates ✓ Balance updates match transfer amounts ✓ No balance manipulation possible [... 8 more properties ...]
Echidna fuzzing: 50,000 runs, no violations ✓
Status: EXCELLENT
5. WEIRD TOKEN PATTERN ANALYSIS
Integration Safety Check
Your Protocol Integrates 5 External Tokens:
- USDT (0xdac17f9...)
- USDC (0xa0b86991...)
- DAI (0x6b175474...)
- WETH (0xc02aaa39...)
- UNI (0x1f9840a8...)
Critical Issues Found
❌ Pattern 7.2: Missing Return Values Found in: USDT integration File: contracts/Vault.sol:156
IERC20(usdt).transferFrom(msg.sender, address(this), amount);
// No return value check! USDT doesn't return bool
Risk: Silent failures on USDT transfers Exploit: User appears to deposit, but no tokens moved Fix: Use OpenZeppelin SafeERC20 wrapper
❌ Pattern 7.3: Fee on Transfer Risk for: Any token with transfer fees File: contracts/Vault.sol:170
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
token.transferFrom(msg.sender, address(this), amount);
shares = amount * exchangeRate; // WRONG! Should use actual received amount
Risk: Accounting mismatch if token takes fees
Exploit: User credited more shares than tokens deposited
Fix: Calculate shares from balanceAfter - balanceBefore
Known Non-Standard Token Handling
✓ USDC: Properly handled (SafeERC20, 6 decimals accounted for) ⚠ DAI: permit() function not used (opportunity for gas savings) ✗ USDT: Missing return value not handled (CRITICAL) ✓ WETH: Standard wrapper, properly handled ⚠ UNI: Large approval handling not checked (reverts >= 2^96)
[... Additional sections for remaining analysis categories ...]
For complete report template and deliverables format, see [REPORT_TEMPLATES.md](resources/REPORT_TEMPLATES.md).
---
## Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|-----------------|----------------|-----------------|
| "Token looks standard, ERC20 checks pass" | 20+ weird token patterns exist beyond ERC20 compliance | Check ALL weird token patterns from database (missing return, revert on zero, hooks, etc.) |
| "Slither shows no issues, integration is safe" | Slither detects some patterns, misses integration logic | Complete manual analysis of all 5 token integration criteria |
| "No fee-on-transfer detected, skip that check" | Fee-on-transfer can be owner-controlled or conditional | Test all transfer scenarios, check for conditional fee logic |
| "Balance checks exist, handling is safe" | Balance checks alone don't protect against all weird tokens | Verify safe transfer wrappers, revert handling, approval patterns |
| "Token is deployed by reputable team, assume standard" | Reputation doesn't guarantee standard behavior | Analyze actual code and on-chain behavior, don't trust assumptions |
| "Integration uses OpenZeppelin, must be safe" | OpenZeppelin libraries don't protect against weird external tokens | Verify defensive patterns around all external token calls |
| "Can't run Slither, skipping automated analysis" | Slither provides critical ERC conformance checks | Manually verify all slither-check-erc criteria or document why blocked |
| "This pattern seems fine" | Intuition misses subtle token integration bugs | Systematically check all 20+ weird token patterns with code evidence |
---
## Deliverables
When analysis is complete, I'll provide:
1. **Compliance Checklist** - Checkboxes for all assessment categories
2. **Weird Token Pattern Analysis** - Presence/absence of all 24 patterns with risk levels and evidence
3. **On-chain Analysis Report** (if applicable) - Holder distribution, exchange listings, configuration
4. **Integration Safety Assessment** (if applicable) - Safe transfer usage, defensive patterns, weird token handling
5. **Prioritized Recommendations** - CRITICAL/HIGH/MEDIUM/LOW issues with specific fixes
Complete deliverable templates available in [REPORT_TEMPLATES.md](resources/REPORT_TEMPLATES.md).
---
## Ready to Begin
**What I'll need**:
- Your codebase
- Context: Token implementation or integration?
- Token type: ERC20, ERC721, or both?
- Contract address (if deployed and want on-chain analysis)
- RPC endpoint (if querying on-chain)
Let's analyze your token implementation or integration for security risks!
/ton-vulnerability-scanner
Source: ~/.claude/skills/tob-building-secure-contracts/skills/ton-vulnerability-scanner/SKILL.md
name: ton-vulnerability-scanner description: Scans TON (The Open Network) smart contracts for 3 critical vulnerabilities including integer-as-boolean misuse, fake Jetton contracts, and forward TON without gas checks. Use when auditing FunC contracts.
TON Vulnerability Scanner
1. Purpose
Systematically scan TON blockchain smart contracts written in FunC for platform-specific security vulnerabilities related to boolean logic, Jetton token handling, and gas management. This skill encodes 3 critical vulnerability patterns unique to TON's architecture.
2. When to Use This Skill
- Auditing TON smart contracts (FunC language)
- Reviewing Jetton token implementations
- Validating token transfer notification handlers
- Pre-launch security assessment of TON dApps
- Reviewing gas forwarding logic
- Assessing boolean condition handling
3. Platform Detection
File Extensions & Indicators
- FunC files:
.fc,.func
Language/Framework Markers
;; FunC contract indicators
#include "imports/stdlib.fc";
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
;; Contract logic
}
() recv_external(slice in_msg) impure {
;; External message handler
}
;; Common patterns
send_raw_message()
load_uint(), load_msg_addr(), load_coins()
begin_cell(), end_cell(), store_*()
transfer_notification operation
op::transfer, op::transfer_notification
.store_uint().store_slice().store_coins()
Project Structure
contracts/*.fc- FunC contract sourcewrappers/*.ts- TypeScript wrapperstests/*.spec.ts- Contract testston.config.tsorwasm.config.ts- TON project config
Tool Support
- TON Blueprint: Development framework for TON
- toncli: CLI tool for TON contracts
- ton-compiler: FunC compiler
- Manual review primarily (limited automated tools)
4. How This Skill Works
When invoked, I will:
- Search your codebase for FunC/Tact contracts
- Analyze each contract for the 3 vulnerability patterns
- Report findings with file references and severity
- Provide fixes for each identified issue
- Check replay protection and sender validation
5. Example Output
When vulnerabilities are found, you'll get a report like this:
=== TON VULNERABILITY SCAN RESULTS ===
Project: my-ton-contract
Files Scanned: 3 (.fc, .tact)
Vulnerabilities Found: 2
---
[CRITICAL] Missing Replay Protection
File: contracts/wallet.fc:45
Pattern: No sequence number or nonce validation
---
## 5. Vulnerability Patterns (3 Patterns)
I check for 3 critical vulnerability patterns unique to TON. For detailed detection patterns, code examples, mitigations, and testing strategies, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
### Pattern Summary:
1. **Missing Sender Check** ⚠️ CRITICAL - No sender validation on privileged operations
2. **Integer Overflow** ⚠️ CRITICAL - Unchecked arithmetic in FunC
3. **Improper Gas Handling** ⚠️ HIGH - Insufficient gas reservations
For complete vulnerability patterns with code examples, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
## 5. Scanning Workflow
### Step 1: Platform Identification
1. Verify FunC language (`.fc` or `.func` files)
2. Check for TON Blueprint or toncli project structure
3. Locate contract source files
4. Identify Jetton-related contracts
### Step 2: Boolean Logic Review
```bash
# Find boolean-like variables
rg "int.*is_|int.*has_|int.*flag|int.*enabled" contracts/
# Check for positive integers used as booleans
rg "= 1;|return 1;" contracts/ | grep -E "is_|has_|flag|enabled|valid"
# Look for NOT operations on boolean-like values
rg "~.*\(|~ " contracts/
For each boolean:
- Uses -1 for true, 0 for false
- NOT using 1 or other positive integers
- Logic operations work correctly
Step 3: Jetton Handler Analysis
# Find transfer_notification handlers
rg "transfer_notification|op::transfer_notification" contracts/
For each Jetton handler:
- Validates sender address
- Sender checked against stored Jetton wallet address
- Cannot trust forward_payload without sender validation
- Has admin function to set Jetton wallet address
Step 4: Gas/Forward Amount Review
# Find forward amount usage
rg "forward_ton_amount|forward_amount" contracts/
rg "load_coins\(\)" contracts/
# Find send_raw_message calls
rg "send_raw_message" contracts/
For each outgoing message:
- Forward amounts are fixed/bounded
- OR user-provided amounts validated against msg_value
- Cannot drain contract balance
- Appropriate send_raw_message flags used
Step 5: Manual Review
TON contracts require thorough manual review:
- Boolean logic with
~,&,|operators - Message parsing and validation
- Gas economics and fee calculations
- Storage operations and data serialization
6. Reporting Format
Finding Template
## [CRITICAL] Fake Jetton Contract - Missing Sender Validation
**Location**: `contracts/staking.fc:85-95` (recv_internal, transfer_notification handler)
**Description**:
The `transfer_notification` operation handler does not validate that the sender is the expected Jetton wallet contract. Any attacker can send a fake `transfer_notification` message claiming to have transferred tokens, crediting themselves without actually depositing any Jettons.
**Vulnerable Code**:
```func
// staking.fc, line 85
if (op == op::transfer_notification) {
int jetton_amount = in_msg_body~load_coins();
slice from_user = in_msg_body~load_msg_addr();
;; WRONG: No validation of sender_address!
;; Attacker can claim any jetton_amount
credit_user(from_user, jetton_amount);
}
Attack Scenario:
- Attacker deploys malicious contract
- Malicious contract sends
transfer_notificationmessage to staking contract - Message claims attacker transferred 1,000,000 Jettons
- Staking contract credits attacker without checking sender
- Attacker can now withdraw from contract or gain benefits without depositing
Proof of Concept:
// Attacker sends fake transfer_notification
const attackerContract = await blockchain.treasury("attacker");
await stakingContract.sendInternalMessage(attackerContract.getSender(), {
op: OP_CODES.TRANSFER_NOTIFICATION,
jettonAmount: toNano("1000000"), // Fake amount
fromUser: attackerContract.address,
});
// Attacker successfully credited without sending real Jettons
const balance = await stakingContract.getUserBalance(attackerContract.address);
expect(balance).toEqual(toNano("1000000")); // Attack succeeded
Recommendation: Store expected Jetton wallet address and validate sender:
global slice jetton_wallet_address;
() recv_internal(...) impure {
load_data(); ;; Load jetton_wallet_address from storage
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();
int op = in_msg_body~load_uint(32);
if (op == op::transfer_notification) {
;; CRITICAL: Validate sender
throw_unless(error::wrong_jetton_wallet,
equal_slices(sender_address, jetton_wallet_address));
int jetton_amount = in_msg_body~load_coins();
slice from_user = in_msg_body~load_msg_addr();
;; Safe to credit user
credit_user(from_user, jetton_amount);
}
}
References:
- building-secure-contracts/not-so-smart-contracts/ton/fake_jetton_contract
---
## 7. Priority Guidelines
### Critical (Immediate Fix Required)
- Fake Jetton contract (unauthorized minting/crediting)
### High (Fix Before Launch)
- Integer as boolean (logic errors, broken conditions)
- Forward TON without gas check (balance drainage)
---
## 8. Testing Recommendations
### Unit Tests
```typescript
import { Blockchain } from "@ton/sandbox";
import { toNano } from "ton-core";
describe("Security tests", () => {
let blockchain: Blockchain;
let contract: Contract;
beforeEach(async () => {
blockchain = await Blockchain.create();
contract = blockchain.openContract(await Contract.fromInit());
});
it("should use correct boolean values", async () => {
// Test that TRUE = -1, FALSE = 0
const result = await contract.getFlag();
expect(result).toEqual(-1n); // True
expect(result).not.toEqual(1n); // Not 1!
});
it("should reject fake jetton transfer", async () => {
const attacker = await blockchain.treasury("attacker");
const result = await contract.send(
attacker.getSender(),
{ value: toNano("0.05") },
{
$$type: "TransferNotification",
query_id: 0n,
amount: toNano("1000"),
from: attacker.address,
}
);
expect(result.transactions).toHaveTransaction({
success: false, // Should reject
});
});
it("should validate gas for forward amount", async () => {
const result = await contract.send(
user.getSender(),
{ value: toNano("0.01") }, // Insufficient gas
{
$$type: "Transfer",
to: recipient.address,
forward_ton_amount: toNano("1"), // Trying to forward 1 TON
}
);
expect(result.transactions).toHaveTransaction({
success: false,
});
});
});
Integration Tests
// Test with real Jetton wallet
it("should accept transfer from real jetton wallet", async () => {
// Deploy actual Jetton minter and wallet
const jettonMinter = await blockchain.openContract(JettonMinter.create());
const userJettonWallet = await jettonMinter.getWalletAddress(user.address);
// Set jetton wallet in contract
await contract.setJettonWallet(userJettonWallet);
// Real transfer from Jetton wallet
const result = await userJettonWallet.sendTransfer(
user.getSender(),
contract.address,
toNano("100"),
{}
);
expect(result.transactions).toHaveTransaction({
to: contract.address,
success: true,
});
});
9. Additional Resources
- Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/ton/ - TON Documentation: https://docs.ton.org/
- FunC Documentation: https://docs.ton.org/develop/func/overview
- TON Blueprint: https://github.com/ton-org/blueprint
- Jetton Standard: https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md
10. Quick Reference Checklist
Before completing TON contract audit:
Boolean Logic (HIGH):
- All boolean values use -1 (true) and 0 (false)
- NO positive integers (1, 2, etc.) used as booleans
- Functions returning booleans return -1 for true
- Boolean logic with
~,&,|uses correct values - Tests verify boolean operations work correctly
Jetton Security (CRITICAL):
-
transfer_notificationhandler validates sender address - Sender checked against stored Jetton wallet address
- Jetton wallet address stored during initialization
- Admin function to set/update Jetton wallet
- Cannot trust forward_payload without sender validation
- Tests with fake Jetton contracts verify rejection
Gas & Forward Amounts (HIGH):
- Forward TON amounts are fixed/bounded
- OR user-provided amounts validated:
msg_value >= tx_fee + forward_amount - Contract balance protected from drainage
- Appropriate
send_raw_messageflags used - Tests verify cannot drain contract with excessive forward amounts
Testing:
- Unit tests for all three vulnerability types
- Integration tests with real Jetton contracts
- Gas cost analysis for all operations
- Testnet deployment before mainnet
/claude-in-chrome-troubleshooting
Source: ~/.claude/skills/tob-claude-in-chrome-troubleshooting/skills/claude-in-chrome-troubleshooting/SKILL.md
name: claude-in-chrome-troubleshooting description: Diagnose and fix Claude in Chrome MCP extension connectivity issues. Use when mcp__claude-in-chrome__* tools fail, return "Browser extension is not connected", or behave erratically.
Claude in Chrome MCP Troubleshooting
Use this skill when Claude in Chrome MCP tools fail to connect or work unreliably.
When to Use
mcp__claude-in-chrome__*tools fail with "Browser extension is not connected"- Browser automation works erratically or times out
- After updating Claude Code or Claude.app
- When switching between Claude Code CLI and Claude.app (Cowork)
- Native host process is running but MCP tools still fail
When NOT to Use
- Linux or Windows users - This skill covers macOS-specific paths and tools (
~/Library/Application Support/,osascript) - General Chrome automation issues unrelated to the Claude extension
- Claude.app desktop issues (not browser-related)
- Network connectivity problems
- Chrome extension installation issues (use Chrome Web Store support)
The Claude.app vs Claude Code Conflict (Primary Issue)
Background: When Claude.app added Cowork support (browser automation from the desktop app), it introduced a competing native messaging host that conflicts with Claude Code CLI.
Two Native Hosts, Two Socket Formats
| Component | Native Host Binary | Socket Location |
|---|---|---|
| Claude.app (Cowork) | /Applications/Claude.app/Contents/Helpers/chrome-native-host |
/tmp/claude-mcp-browser-bridge-$USER/<PID>.sock |
| Claude Code CLI | ~/.local/share/claude/versions/<version> --chrome-native-host |
$TMPDIR/claude-mcp-browser-bridge-$USER (single file) |
Why They Conflict
-
Both register native messaging configs in Chrome:
com.anthropic.claude_browser_extension.json→ Claude.app helpercom.anthropic.claude_code_browser_extension.json→ Claude Code wrapper
-
Chrome extension requests a native host by name
-
If the wrong config is active, the wrong binary runs
-
The wrong binary creates sockets in a format/location the MCP client doesn't expect
-
Result: "Browser extension is not connected" even though everything appears to be running
The Fix: Disable Claude.app's Native Host
If you use Claude Code CLI for browser automation (not Cowork):
# Disable the Claude.app native messaging config
mv ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_browser_extension.json \
~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_browser_extension.json.disabled
# Ensure the Claude Code config exists and points to the wrapper
cat ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_code_browser_extension.json
If you use Cowork (Claude.app) for browser automation:
# Disable the Claude Code native messaging config
mv ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_code_browser_extension.json \
~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_code_browser_extension.json.disabled
You cannot use both simultaneously. Pick one and disable the other.
Toggle Script
Add this to ~/.zshrc or run directly:
chrome-mcp-toggle() {
local CONFIG_DIR=~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts
local CLAUDE_APP="$CONFIG_DIR/com.anthropic.claude_browser_extension.json"
local CLAUDE_CODE="$CONFIG_DIR/com.anthropic.claude_code_browser_extension.json"
if [[ -f "$CLAUDE_APP" && ! -f "$CLAUDE_APP.disabled" ]]; then
# Currently using Claude.app, switch to Claude Code
mv "$CLAUDE_APP" "$CLAUDE_APP.disabled"
[[ -f "$CLAUDE_CODE.disabled" ]] && mv "$CLAUDE_CODE.disabled" "$CLAUDE_CODE"
echo "Switched to Claude Code CLI"
echo "Restart Chrome and Claude Code to apply"
elif [[ -f "$CLAUDE_CODE" && ! -f "$CLAUDE_CODE.disabled" ]]; then
# Currently using Claude Code, switch to Claude.app
mv "$CLAUDE_CODE" "$CLAUDE_CODE.disabled"
[[ -f "$CLAUDE_APP.disabled" ]] && mv "$CLAUDE_APP.disabled" "$CLAUDE_APP"
echo "Switched to Claude.app (Cowork)"
echo "Restart Chrome to apply"
else
echo "Current state unclear. Check configs:"
ls -la "$CONFIG_DIR"/com.anthropic*.json* 2>/dev/null
fi
}
Usage: chrome-mcp-toggle then restart Chrome (and Claude Code if switching to CLI).
Quick Diagnosis
# 1. Which native host binary is running?
ps aux | grep chrome-native-host | grep -v grep
# Claude.app: /Applications/Claude.app/Contents/Helpers/chrome-native-host
# Claude Code: ~/.local/share/claude/versions/X.X.X --chrome-native-host
# 2. Where is the socket?
# For Claude Code (single file in TMPDIR):
ls -la "$(getconf DARWIN_USER_TEMP_DIR)/claude-mcp-browser-bridge-$USER" 2>&1
# For Claude.app (directory with PID files):
ls -la /tmp/claude-mcp-browser-bridge-$USER/ 2>&1
# 3. What's the native host connected to?
lsof -U 2>&1 | grep claude-mcp-browser-bridge
# 4. Which configs are active?
ls ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic*.json
Critical Insight
MCP connects at startup. If the browser bridge wasn't ready when Claude Code started, the connection will fail for the entire session. The fix is usually: ensure Chrome + extension are running with correct config, THEN restart Claude Code.
Full Reset Procedure (Claude Code CLI)
# 1. Ensure correct config is active
mv ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_browser_extension.json \
~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_browser_extension.json.disabled 2>/dev/null
# 2. Update the wrapper to use latest Claude Code version
cat > ~/.claude/chrome/chrome-native-host << 'EOF'
#!/bin/bash
LATEST=$(ls -t ~/.local/share/claude/versions/ 2>/dev/null | head -1)
exec "$HOME/.local/share/claude/versions/$LATEST" --chrome-native-host
EOF
chmod +x ~/.claude/chrome/chrome-native-host
# 3. Kill existing native host and clean sockets
pkill -f chrome-native-host
rm -rf /tmp/claude-mcp-browser-bridge-$USER/
rm -f "$(getconf DARWIN_USER_TEMP_DIR)/claude-mcp-browser-bridge-$USER"
# 4. Restart Chrome
osascript -e 'quit app "Google Chrome"' && sleep 2 && open -a "Google Chrome"
# 5. Wait for Chrome, click Claude extension icon
# 6. Verify correct native host is running
ps aux | grep chrome-native-host | grep -v grep
# Should show: ~/.local/share/claude/versions/X.X.X --chrome-native-host
# 7. Verify socket exists
ls -la "$(getconf DARWIN_USER_TEMP_DIR)/claude-mcp-browser-bridge-$USER"
# 8. Restart Claude Code
Other Common Causes
Multiple Chrome Profiles
If you have the Claude extension installed in multiple Chrome profiles, each spawns its own native host and socket. This can cause confusion.
Fix: Only enable the Claude extension in ONE Chrome profile.
Multiple Claude Code Sessions
Running multiple Claude Code instances can cause socket conflicts.
Fix: Only run one Claude Code session at a time, or use /mcp to reconnect after closing other sessions.
Hardcoded Version in Wrapper
The wrapper at ~/.claude/chrome/chrome-native-host may have a hardcoded version that becomes stale after updates.
Diagnosis:
cat ~/.claude/chrome/chrome-native-host
# Bad: exec "/Users/.../.local/share/claude/versions/2.0.76" --chrome-native-host
# Good: Uses $(ls -t ...) to find latest
Fix: Use the dynamic version wrapper shown in the Full Reset Procedure above.
TMPDIR Not Set
Claude Code expects TMPDIR to be set to find the socket.
# Check
echo $TMPDIR
# Should show: /var/folders/XX/.../T/
# Fix: Add to ~/.zshrc
export TMPDIR="${TMPDIR:-$(getconf DARWIN_USER_TEMP_DIR)}"
Diagnostic Deep Dive
echo "=== Native Host Binary ==="
ps aux | grep chrome-native-host | grep -v grep
echo -e "\n=== Socket (Claude Code location) ==="
ls -la "$(getconf DARWIN_USER_TEMP_DIR)/claude-mcp-browser-bridge-$USER" 2>&1
echo -e "\n=== Socket (Claude.app location) ==="
ls -la /tmp/claude-mcp-browser-bridge-$USER/ 2>&1
echo -e "\n=== Native Host Open Files ==="
pgrep -f chrome-native-host | xargs -I {} lsof -p {} 2>/dev/null | grep -E "(sock|claude-mcp)"
echo -e "\n=== Active Native Messaging Configs ==="
ls ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic*.json 2>/dev/null
echo -e "\n=== Custom Wrapper Contents ==="
cat ~/.claude/chrome/chrome-native-host 2>/dev/null || echo "No custom wrapper"
echo -e "\n=== TMPDIR ==="
echo "TMPDIR=$TMPDIR"
echo "Expected: $(getconf DARWIN_USER_TEMP_DIR)"
File Reference
| File | Purpose |
|---|---|
~/.claude/chrome/chrome-native-host |
Custom wrapper script for Claude Code |
/Applications/Claude.app/Contents/Helpers/chrome-native-host |
Claude.app (Cowork) native host |
~/.local/share/claude/versions/<version> |
Claude Code binary (run with --chrome-native-host) |
~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_browser_extension.json |
Config for Claude.app native host |
~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_code_browser_extension.json |
Config for Claude Code native host |
$TMPDIR/claude-mcp-browser-bridge-$USER |
Socket file (Claude Code) |
/tmp/claude-mcp-browser-bridge-$USER/<PID>.sock |
Socket files (Claude.app) |
Summary
- Primary issue: Claude.app (Cowork) and Claude Code use different native hosts with incompatible socket formats
- Fix: Disable the native messaging config for whichever one you're NOT using
- After any fix: Must restart Chrome AND Claude Code (MCP connects at startup)
- One profile: Only have Claude extension in one Chrome profile
- One session: Only run one Claude Code instance
Original skill by @jeffzwang from @ExaAILabs. Enhanced and updated for current versions of Claude Desktop and Claude Code.
/constant-time-analysis
Source: ~/.claude/skills/tob-constant-time-analysis/skills/constant-time-analysis/SKILL.md
name: constant-time-analysis description: Detects timing side-channel vulnerabilities in cryptographic code. Use when implementing or reviewing crypto code, encountering division on secrets, secret-dependent branches, or constant-time programming questions in C, C++, Go, Rust, Swift, Java, Kotlin, C#, PHP, JavaScript, TypeScript, Python, or Ruby.
Constant-Time Analysis
Analyze cryptographic code to detect operations that leak secret data through execution timing variations.
When to Use
User writing crypto code? ──yes──> Use this skill
│
no
│
v
User asking about timing attacks? ──yes──> Use this skill
│
no
│
v
Code handles secret keys/tokens? ──yes──> Use this skill
│
no
│
v
Skip this skill
Concrete triggers:
- User implements signature, encryption, or key derivation
- Code contains
/or%operators on secret-derived values - User mentions "constant-time", "timing attack", "side-channel", "KyberSlash"
- Reviewing functions named
sign,verify,encrypt,decrypt,derive_key
When NOT to Use
- Non-cryptographic code (business logic, UI, etc.)
- Public data processing where timing leaks don't matter
- Code that doesn't handle secrets, keys, or authentication tokens
- High-level API usage where timing is handled by the library
Language Selection
Based on the file extension or language context, refer to the appropriate guide:
| Language | File Extensions | Guide |
|---|---|---|
| C, C++ | .c, .h, .cpp, .cc, .hpp |
references/compiled.md |
| Go | .go |
references/compiled.md |
| Rust | .rs |
references/compiled.md |
| Swift | .swift |
references/swift.md |
| Java | .java |
references/vm-compiled.md |
| Kotlin | .kt, .kts |
references/kotlin.md |
| C# | .cs |
references/vm-compiled.md |
| PHP | .php |
references/php.md |
| JavaScript | .js, .mjs, .cjs |
references/javascript.md |
| TypeScript | .ts, .tsx |
references/javascript.md |
| Python | .py |
references/python.md |
| Ruby | .rb |
references/ruby.md |
Quick Start
# Analyze any supported file type
uv run {baseDir}/ct_analyzer/analyzer.py <source_file>
# Include conditional branch warnings
uv run {baseDir}/ct_analyzer/analyzer.py --warnings <source_file>
# Filter to specific functions
uv run {baseDir}/ct_analyzer/analyzer.py --func 'sign|verify' <source_file>
# JSON output for CI
uv run {baseDir}/ct_analyzer/analyzer.py --json <source_file>
Native Compiled Languages Only (C, C++, Go, Rust)
# Cross-architecture testing (RECOMMENDED)
uv run {baseDir}/ct_analyzer/analyzer.py --arch x86_64 crypto.c
uv run {baseDir}/ct_analyzer/analyzer.py --arch arm64 crypto.c
# Multiple optimization levels
uv run {baseDir}/ct_analyzer/analyzer.py --opt-level O0 crypto.c
uv run {baseDir}/ct_analyzer/analyzer.py --opt-level O3 crypto.c
VM-Compiled Languages (Java, Kotlin, C#)
# Analyze Java bytecode
uv run {baseDir}/ct_analyzer/analyzer.py CryptoUtils.java
# Analyze Kotlin bytecode (Android/JVM)
uv run {baseDir}/ct_analyzer/analyzer.py CryptoUtils.kt
# Analyze C# IL
uv run {baseDir}/ct_analyzer/analyzer.py CryptoUtils.cs
Note: Java, Kotlin, and C# compile to bytecode (JVM/CIL) that runs on a virtual machine with JIT compilation. The analyzer examines the bytecode directly, not the JIT-compiled native code. The --arch and --opt-level flags do not apply to these languages.
Swift (iOS/macOS)
# Analyze Swift for native architecture
uv run {baseDir}/ct_analyzer/analyzer.py crypto.swift
# Analyze for specific architecture (iOS devices)
uv run {baseDir}/ct_analyzer/analyzer.py --arch arm64 crypto.swift
# Analyze with different optimization levels
uv run {baseDir}/ct_analyzer/analyzer.py --opt-level O0 crypto.swift
Note: Swift compiles to native code like C/C++/Go/Rust, so it uses assembly-level analysis and supports --arch and --opt-level flags.
Prerequisites
| Language | Requirements |
|---|---|
| C, C++, Go, Rust | Compiler in PATH (gcc/clang, go, rustc) |
| Swift | Xcode or Swift toolchain (swiftc in PATH) |
| Java | JDK with javac and javap in PATH |
| Kotlin | Kotlin compiler (kotlinc) + JDK (javap) in PATH |
| C# | .NET SDK + ilspycmd (dotnet tool install -g ilspycmd) |
| PHP | PHP with VLD extension or OPcache |
| JavaScript/TypeScript | Node.js in PATH |
| Python | Python 3.x in PATH |
| Ruby | Ruby with --dump=insns support |
macOS users: Homebrew installs Java and .NET as "keg-only". You must add them to your PATH:
# For Java (add to ~/.zshrc)
export PATH="/opt/homebrew/opt/openjdk@21/bin:$PATH"
# For .NET tools (add to ~/.zshrc)
export PATH="$HOME/.dotnet/tools:$PATH"
See references/vm-compiled.md for detailed setup instructions and troubleshooting.
Quick Reference
| Problem | Detection | Fix |
|---|---|---|
| Division on secrets | DIV, IDIV, SDIV, UDIV | Barrett reduction or multiply-by-inverse |
| Branch on secrets | JE, JNE, BEQ, BNE | Constant-time selection (cmov, bit masking) |
| Secret comparison | Early-exit memcmp | Use crypto/subtle or constant-time compare |
| Weak RNG | rand(), mt_rand, Math.random | Use crypto-secure RNG |
| Table lookup by secret | Array subscript on secret index | Bit-sliced lookups |
Interpreting Results
PASSED - No variable-time operations detected.
FAILED - Dangerous instructions found. Example:
[ERROR] SDIV
Function: decompose_vulnerable
Reason: SDIV has early termination optimization; execution time depends on operand values
Verifying Results (Avoiding False Positives)
CRITICAL: Not every flagged operation is a vulnerability. The tool has no data flow analysis - it flags ALL potentially dangerous operations regardless of whether they involve secrets.
For each flagged violation, ask: Does this operation's input depend on secret data?
-
Identify the secret inputs to the function (private keys, plaintext, signatures, tokens)
-
Trace data flow from the flagged instruction back to inputs
-
Common false positive patterns:
// FALSE POSITIVE: Division uses public constant, not secret int num_blocks = data_len / 16; // data_len is length, not content // TRUE POSITIVE: Division involves secret-derived value int32_t q = secret_coef / GAMMA2; // secret_coef from private key -
Document your analysis for each flagged item
Quick Triage Questions
| Question | If Yes | If No |
|---|---|---|
| Is the operand a compile-time constant? | Likely false positive | Continue |
| Is the operand a public parameter (length, count)? | Likely false positive | Continue |
| Is the operand derived from key/plaintext/secret? | TRUE POSITIVE | Likely false positive |
| Can an attacker influence the operand value? | TRUE POSITIVE | Likely false positive |
Limitations
-
Static Analysis Only: Analyzes assembly/bytecode, not runtime behavior. Cannot detect cache timing or microarchitectural side-channels.
-
No Data Flow Analysis: Flags all dangerous operations regardless of whether they process secrets. Manual review required.
-
Compiler/Runtime Variations: Different compilers, optimization levels, and runtime versions may produce different output.
Real-World Impact
- KyberSlash (2023): Division instructions in post-quantum ML-KEM implementations allowed key recovery
- Lucky Thirteen (2013): Timing differences in CBC padding validation enabled plaintext recovery
- RSA Timing Attacks: Early implementations leaked private key bits through division timing
References
- Cryptocoding Guidelines - Defensive coding for crypto
- KyberSlash - Division timing in post-quantum crypto
- BearSSL Constant-Time - Practical constant-time techniques
/interpreting-culture-index
Source: ~/.claude/skills/tob-culture-index/skills/interpreting-culture-index/SKILL.md
name: interpreting-culture-index description: Use when interpreting Culture Index surveys, CI profiles, behavioral assessments, or personality data. Supports individual interpretation, team composition (gas/brake/glue), burnout detection, profile comparison, hiring profiles, manager coaching, interview transcript analysis for trait prediction, candidate debrief, onboarding planning, and conflict mediation. Handles PDF vision or JSON input.
<essential_principles>
Culture Index measures behavioral traits, not intelligence or skills. There is no "good" or "bad" profile.
**Never compare absolute trait values between people.**The 0-10 scale is just a ruler. What matters is distance from the red arrow (population mean at 50th percentile). The arrow position varies between surveys based on EU.
Why the arrow moves: Higher EU scores cause the arrow to plot further right; lower EU causes it to plot further left. This does not affect validity—we always measure distance from wherever the arrow lands.
Wrong: "Dan has higher autonomy than Jim because his A is 8 vs 5" Right: "Dan is +3 centiles from his arrow; Jim is +1 from his arrow"
Always ask: Where is the arrow, and how far is the dot from it?
**Survey = who you ARE. Job = who you're TRYING TO BE.**"You can't send a duck to Eagle school." Traits are hardwired—you can only modify behaviors temporarily, at the cost of energy.
- Top graph (Survey Traits): Hardwired by age 12-16. Does not change. Writing with your dominant hand.
- Bottom graph (Job Behaviors): Adaptive behavior at work. Can change. Writing with your non-dominant hand.
Large differences between graphs indicate behavior modification, which drains energy and causes burnout if sustained 3-6+ months.
**Distance from arrow determines trait strength.**| Distance | Label | Percentile | Interpretation |
|---|---|---|---|
| On arrow | Normative | 50th | Flexible, situational |
| ±1 centile | Tendency | ~67th | Easier to modify |
| ±2 centiles | Pronounced | ~84th | Noticeable difference |
| ±4+ centiles | Extreme | ~98th | Hardwired, compulsive, predictable |
Key insight: Every 2 centiles of distance = 1 standard deviation.
Extreme traits drive extreme results but are harder to modify and less relatable to average people.
**L (Logic) and I (Ingenuity) use absolute values.**Unlike A, B, C, D, you CAN compare L and I scores directly between people:
- Logic 8 means "High Logic" regardless of arrow position
- Ingenuity 2 means "Low Ingenuity" for anyone
Only these two traits break the "no absolute comparison" rule.
</essential_principles>
<input_formats>
JSON (Use if available)
If JSON data is already extracted, use it directly:
import json
with open("person_name.json") as f:
profile = json.load(f)
JSON format:
{
"name": "Person Name",
"archetype": "Architect",
"survey": {
"eu": 21,
"arrow": 2.3,
"a": [5, 2.7],
"b": [0, -2.3],
"c": [1, -1.3],
"d": [3, 0.7],
"logic": [5, null],
"ingenuity": [2, null]
},
"job": { "..." : "same structure as survey" },
"analysis": {
"energy_utilization": 148,
"status": "stress"
}
}
Note: Trait values are [absolute, relative_to_arrow] tuples. Use the relative value for interpretation.
Check same directory as PDF for matching .json file, or ask user if they have extracted JSON.
PDF Input (MUST EXTRACT FIRST)
⚠️ NEVER use visual estimation for trait values. Visual estimation has 20-30% error rate.
When given a PDF:
- Check if JSON already exists (same directory as PDF, or ask user)
- If not, run extraction with verification:
uv run {baseDir}/scripts/extract_pdf.py --verify /path/to/file.pdf [output.json] - Visually confirm the verification summary matches the PDF
- Use the extracted JSON for interpretation
If uv is not installed: Stop and instruct user to install it (brew install uv or pip install uv). Do NOT fall back to vision.
PDF Vision (Reference Only)
Vision may be used ONLY to verify extracted values look reasonable, NOT to extract trait scores.
</input_formats>
Step 0: Do you have JSON or PDF?
- If JSON provided or found: Use it directly (skip extraction)
- Check same directory as PDF for
.jsonfile with matching name - Check if user provided JSON path
- Check same directory as PDF for
- If only PDF: Run extraction script with
--verifyflaguv run {baseDir}/scripts/extract_pdf.py --verify /path/to/file.pdf [output.json] - If extraction fails: Report error, do NOT fall back to vision
Step 1: What data do you have?
- CI Survey JSON → Proceed to Step 2
- CI Survey PDF → Extract first (Step 0), then proceed to Step 2
- Interview transcript only → Go to option 8 (predict traits from interview)
- No data yet → "Please provide Culture Index profile (PDF or JSON) or interview transcript"
Step 2: What would you like to do?
Profile Analysis:
- Interpret an individual profile - Understand one person's traits, strengths, and challenges
- Analyze team composition - Assess gas/brake/glue balance, identify gaps
- Detect burnout signals - Compare Survey vs Job, flag stress/frustration
- Compare multiple profiles - Understand compatibility, collaboration dynamics
- Get motivator recommendations - Learn how to engage and retain someone
Hiring & Candidates: 6. Define hiring profile - Determine ideal CI traits for a role 7. Coach manager on direct report - Adjust management style based on both profiles 8. Predict traits from interview - Analyze interview transcript to estimate CI traits 9. Interview debrief - Assess candidate fit based on predicted traits
Team Development: 10. Plan onboarding - Design first 90 days based on new hire and team profiles 11. Mediate conflict - Understand friction between two people using their profiles
Provide the profile data (JSON or PDF) and select an option, or describe what you need.
| Response | Workflow |
|---|---|
| "extract", "parse pdf", "convert pdf", "get json from pdf" | workflows/extract-from-pdf.md |
| 1, "individual", "interpret", "understand", "analyze one", "single profile" | workflows/interpret-individual.md |
| 2, "team", "composition", "gaps", "balance", "gas brake glue" | workflows/analyze-team.md |
| 3, "burnout", "stress", "frustration", "survey vs job", "energy", "flight risk" | workflows/detect-burnout.md |
| 4, "compare", "compatibility", "collaboration", "multiple", "two profiles" | workflows/compare-profiles.md |
| 5, "motivate", "engage", "retain", "communicate" | Read references/motivators.md directly |
| 6, "hire", "hiring profile", "role profile", "recruit", "what profile for" | workflows/define-hiring-profile.md |
| 7, "manage", "coach", "1:1", "direct report", "manager" | workflows/coach-manager.md |
| 8, "transcript", "interview", "predict traits", "guess", "estimate", "recording" | workflows/predict-from-interview.md |
| 9, "debrief", "should we hire", "candidate fit", "proceed", "offer" | workflows/interview-debrief.md |
| 10, "onboard", "new hire", "integrate", "starting", "first 90 days" | workflows/plan-onboarding.md |
| 11, "conflict", "friction", "mediate", "not working together", "clash" | workflows/mediate-conflict.md |
| "conversation starters", "how to talk to", "engage with" | Read references/conversation-starters.md directly |
After reading the workflow, follow it exactly.
<verification_loop>
After every interpretation, verify:
- Did you use relative positions? Never stated "A is 8" without context
- Did you reference the arrow? All trait interpretations relative to arrow
- Did you compare Survey vs Job? Identified any behavior modification
- Did you avoid value judgments? No traits called "good" or "bad"
- Did you check EU? Energy utilization calculated if both graphs present
Report to user:
- "Interpretation complete"
- Key findings (2-3 bullet points)
- Recommended actions
</verification_loop>
<reference_index>
Domain Knowledge (in references/):
Primary Traits:
primary-traits.md- A (Autonomy), B (Social), C (Pace), D (Conformity)
Secondary Traits:
secondary-traits.md- EU (Energy Units), L (Logic), I (Ingenuity)
Patterns:
patterns-archetypes.md- Behavioral patterns, trait combinations, archetypes
Application:
motivators.md- How to motivate each trait typeteam-composition.md- Gas, brake, glue frameworkanti-patterns.md- Common interpretation mistakesconversation-starters.md- How to engage each pattern and trait typeinterview-trait-signals.md- Signals for predicting traits from interviews
</reference_index>
<workflows_index>
Workflows (in workflows/):
| File | Purpose |
|---|---|
extract-from-pdf.md |
Extract profile data from Culture Index PDF to JSON format |
interpret-individual.md |
Analyze single profile, identify archetype, summarize strengths/challenges |
analyze-team.md |
Assess team balance (gas/brake/glue), identify gaps, recommend hires |
detect-burnout.md |
Compare Survey vs Job, calculate EU utilization, flag risk signals |
compare-profiles.md |
Compare multiple profiles, assess compatibility, collaboration dynamics |
define-hiring-profile.md |
Define ideal CI traits for a role, identify acceptable patterns and red flags |
coach-manager.md |
Help managers adjust their style for specific direct reports |
predict-from-interview.md |
Analyze interview transcripts to predict CI traits before survey |
interview-debrief.md |
Assess candidate fit using predicted traits from transcript analysis |
plan-onboarding.md |
Design first 90 days based on new hire profile and team composition |
mediate-conflict.md |
Understand and address friction between team members using their profiles |
</workflows_index>
<quick_reference>
Trait Colors:
| Trait | Color | Measures |
|---|---|---|
| A | Maroon | Autonomy, initiative, self-confidence |
| B | Yellow | Social ability, need for interaction |
| C | Blue | Pace/Patience, urgency level |
| D | Green | Conformity, attention to detail |
| L | Purple | Logic, emotional processing |
| I | Cyan | Ingenuity, inventiveness |
Energy Utilization Formula:
Utilization = (Job EU / Survey EU) × 100
70-130% = Healthy
>130% = STRESS (burnout risk)
<70% = FRUSTRATION (flight risk)
Gas/Brake/Glue:
| Role | Trait | Function |
|---|---|---|
| Gas | High A | Growth, risk-taking, driving results |
| Brake | High D | Quality control, risk aversion, finishing |
| Glue | High B | Relationships, morale, culture |
Score Precision:
| Value | Precision | Example |
|---|---|---|
| Traits (A,B,C,D,L,I) | Integer 0-10 | 0, 1, 2, ... 10 |
| Arrow position | Tenths | 0.4, 2.2, 3.8 |
| Energy Units (EU) | Integer | 11, 31, 45 |
</quick_reference>
<success_criteria>
A well-interpreted Culture Index profile:
- Uses relative positions (distance from arrow), never absolute values alone
- Identifies the archetype/pattern correctly
- Highlights 2-3 key strengths based on leading traits
- Notes 2-3 challenges or development areas
- Compares Survey vs Job if both are available
- Provides actionable recommendations
- Avoids value judgments ("good"/"bad")
- Acknowledges Culture Index is one data point, not a complete picture
</success_criteria>
/devcontainer-setup
Source: ~/.claude/skills/tob-devcontainer-setup/skills/devcontainer-setup/SKILL.md
name: devcontainer-setup description: Creates devcontainers with Claude Code, language-specific tooling (Python/Node/Rust/Go), and persistent volumes. Use when adding devcontainer support to a project, setting up isolated development environments, or configuring sandboxed Claude Code workspaces.
Devcontainer Setup Skill
Creates a pre-configured devcontainer with Claude Code and language-specific tooling.
When to Use
- User asks to "set up a devcontainer" or "add devcontainer support"
- User wants a sandboxed Claude Code development environment
- User needs isolated development environments with persistent configuration
When NOT to Use
- User already has a devcontainer configuration and just needs modifications
- User is asking about general Docker or container questions
- User wants to deploy production containers (this is for development only)
Workflow
flowchart TB
start([User requests devcontainer])
recon[1. Project Reconnaissance]
detect[2. Detect Languages]
generate[3. Generate Configuration]
write[4. Write files to .devcontainer/]
done([Done])
start --> recon
recon --> detect
detect --> generate
generate --> write
write --> done
Phase 1: Project Reconnaissance
Infer Project Name
Check in order (use first match):
package.json→namefieldpyproject.toml→project.nameCargo.toml→package.namego.mod→ module path (last segment after/)- Directory name as fallback
Convert to slug: lowercase, replace spaces/underscores with hyphens.
Detect Language Stack
| Language | Detection Files |
|---|---|
| Python | pyproject.toml, *.py |
| Node/TypeScript | package.json, tsconfig.json |
| Rust | Cargo.toml |
| Go | go.mod, go.sum |
Multi-Language Projects
If multiple languages are detected, configure all of them in the following priority order:
- Python - Primary language, uses Dockerfile for uv + Python installation
- Node/TypeScript - Uses devcontainer feature
- Rust - Uses devcontainer feature
- Go - Uses devcontainer feature
For multi-language postCreateCommand, chain all setup commands:
uv run /opt/post_install.py && uv sync && npm ci
Extensions and settings from all detected languages should be merged into the configuration.
Phase 2: Generate Configuration
Start with base templates from resources/ directory. Substitute:
{{PROJECT_NAME}}→ Human-readable name (e.g., "My Project"){{PROJECT_SLUG}}→ Slug for volumes (e.g., "my-project")
Then apply language-specific modifications below.
Base Template Features
The base template includes:
- Claude Code with marketplace plugins (anthropics/skills, trailofbits/skills)
- Python 3.13 via uv (fast binary download)
- Node 22 via fnm (Fast Node Manager)
- ast-grep for AST-based code search
- Network isolation tools (iptables, ipset) with NET_ADMIN capability
- Modern CLI tools: ripgrep, fd, fzf, tmux, git-delta
Language-Specific Sections
Python Projects
Detection: pyproject.toml, requirements.txt, setup.py, or *.py files
Dockerfile additions:
The base Dockerfile already includes Python 3.13 via uv. If a different version is required (detected from pyproject.toml), modify the Python installation:
# Install Python via uv (fast binary download, not source compilation)
RUN uv python install <version> --default
devcontainer.json extensions:
Add to customizations.vscode.extensions:
"ms-python.python",
"ms-python.vscode-pylance",
"charliermarsh.ruff"
Add to customizations.vscode.settings:
"python.defaultInterpreterPath": ".venv/bin/python",
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
}
postCreateCommand:
If pyproject.toml exists, chain commands:
rm -rf .venv && uv sync && uv run /opt/post_install.py
Node/TypeScript Projects
Detection: package.json or tsconfig.json
No Dockerfile additions needed: The base template includes Node 22 via fnm (Fast Node Manager).
devcontainer.json extensions:
Add to customizations.vscode.extensions:
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
Add to customizations.vscode.settings:
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
postCreateCommand: Detect package manager from lockfile and chain with base command:
pnpm-lock.yaml→uv run /opt/post_install.py && pnpm install --frozen-lockfileyarn.lock→uv run /opt/post_install.py && yarn install --frozen-lockfilepackage-lock.json→uv run /opt/post_install.py && npm ci- No lockfile →
uv run /opt/post_install.py && npm install
Rust Projects
Detection: Cargo.toml
Features to add:
"ghcr.io/devcontainers/features/rust:1": {}
devcontainer.json extensions:
Add to customizations.vscode.extensions:
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml"
Add to customizations.vscode.settings:
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
}
postCreateCommand:
If Cargo.lock exists, use locked builds:
uv run /opt/post_install.py && cargo build --locked
If no lockfile, use standard build:
uv run /opt/post_install.py && cargo build
Go Projects
Detection: go.mod
Features to add:
"ghcr.io/devcontainers/features/go:1": {
"version": "latest"
}
devcontainer.json extensions:
Add to customizations.vscode.extensions:
"golang.go"
Add to customizations.vscode.settings:
"[go]": {
"editor.defaultFormatter": "golang.go"
},
"go.useLanguageServer": true
postCreateCommand:
uv run /opt/post_install.py && go mod download
Reference Material
For additional guidance, see:
references/dockerfile-best-practices.md- Layer optimization, multi-stage builds, architecture supportreferences/features-vs-dockerfile.md- When to use devcontainer features vs custom Dockerfile
Adding Persistent Volumes
Pattern for new mounts in devcontainer.json:
"mounts": [
"source={{PROJECT_SLUG}}-<purpose>-${devcontainerId},target=<container-path>,type=volume"
]
Common additions:
source={{PROJECT_SLUG}}-cargo-${devcontainerId},target=/home/vscode/.cargo,type=volume(Rust)source={{PROJECT_SLUG}}-go-${devcontainerId},target=/home/vscode/go,type=volume(Go)
Output Files
Generate these files in the project's .devcontainer/ directory:
Dockerfile- Container build instructionsdevcontainer.json- VS Code/devcontainer configurationpost_install.py- Post-creation setup script.zshrc- Shell configurationinstall.sh- CLI helper for managing the devcontainer (devccommand)
Validation Checklist
Before presenting files to the user, verify:
- All
{{PROJECT_NAME}}placeholders are replaced with the human-readable name - All
{{PROJECT_SLUG}}placeholders are replaced with the slugified name - JSON syntax is valid in
devcontainer.json(no trailing commas, proper nesting) - Language-specific extensions are added for all detected languages
postCreateCommandincludes all required setup commands (chained with&&)
User Instructions
After generating, inform the user:
- How to start: "Open in VS Code and select 'Reopen in Container'"
- Alternative:
devcontainer up --workspace-folder . - CLI helper: Run
.devcontainer/install.sh self-installto add thedevccommand to PATH
/differential-review
Source: ~/.claude/skills/tob-differential-review/skills/differential-review/SKILL.md
name: differential-review description: > Performs security-focused differential review of code changes (PRs, commits, diffs). Adapts analysis depth to codebase size, uses git history for context, calculates blast radius, checks test coverage, and generates comprehensive markdown reports. Automatically detects and prevents security regressions. allowed-tools:
- Read
- Write
- Grep
- Glob
- Bash
Differential Security Review
Security-focused code review for PRs, commits, and diffs.
Core Principles
- Risk-First: Focus on auth, crypto, value transfer, external calls
- Evidence-Based: Every finding backed by git history, line numbers, attack scenarios
- Adaptive: Scale to codebase size (SMALL/MEDIUM/LARGE)
- Honest: Explicitly state coverage limits and confidence level
- Output-Driven: Always generate comprehensive markdown report file
Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "Small PR, quick review" | Heartbleed was 2 lines | Classify by RISK, not size |
| "I know this codebase" | Familiarity breeds blind spots | Build explicit baseline context |
| "Git history takes too long" | History reveals regressions | Never skip Phase 1 |
| "Blast radius is obvious" | You'll miss transitive callers | Calculate quantitatively |
| "No tests = not my problem" | Missing tests = elevated risk rating | Flag in report, elevate severity |
| "Just a refactor, no security impact" | Refactors break invariants | Analyze as HIGH until proven LOW |
| "I'll explain verbally" | No artifact = findings lost | Always write report |
Quick Reference
Codebase Size Strategy
| Codebase Size | Strategy | Approach |
|---|---|---|
| SMALL (<20 files) | DEEP | Read all deps, full git blame |
| MEDIUM (20-200) | FOCUSED | 1-hop deps, priority files |
| LARGE (200+) | SURGICAL | Critical paths only |
Risk Level Triggers
| Risk Level | Triggers |
|---|---|
| HIGH | Auth, crypto, external calls, value transfer, validation removal |
| MEDIUM | Business logic, state changes, new public APIs |
| LOW | Comments, tests, UI, logging |
Workflow Overview
Pre-Analysis → Phase 0: Triage → Phase 1: Code Analysis → Phase 2: Test Coverage
↓ ↓ ↓ ↓
Phase 3: Blast Radius → Phase 4: Deep Context → Phase 5: Adversarial → Phase 6: Report
Decision Tree
Starting a review?
├─ Need detailed phase-by-phase methodology?
│ └─ Read: methodology.md
│ (Pre-Analysis + Phases 0-4: triage, code analysis, test coverage, blast radius)
│
├─ Analyzing HIGH RISK change?
│ └─ Read: adversarial.md
│ (Phase 5: Attacker modeling, exploit scenarios, exploitability rating)
│
├─ Writing the final report?
│ └─ Read: reporting.md
│ (Phase 6: Report structure, templates, formatting guidelines)
│
├─ Looking for specific vulnerability patterns?
│ └─ Read: patterns.md
│ (Regressions, reentrancy, access control, overflow, etc.)
│
└─ Quick triage only?
└─ Use Quick Reference above, skip detailed docs
Quality Checklist
Before delivering:
- All changed files analyzed
- Git blame on removed security code
- Blast radius calculated for HIGH risk
- Attack scenarios are concrete (not generic)
- Findings reference specific line numbers + commits
- Report file generated
- User notified with summary
Integration
audit-context-building skill:
- Pre-Analysis: Build baseline context
- Phase 4: Deep context on HIGH RISK changes
issue-writer skill:
- Transform findings into formal audit reports
- Command:
issue-writer --input DIFFERENTIAL_REVIEW_REPORT.md --format audit-report
Example Usage
Quick Triage (Small PR)
Input: 5 file PR, 2 HIGH RISK files
Strategy: Use Quick Reference
1. Classify risk level per file (2 HIGH, 3 LOW)
2. Focus on 2 HIGH files only
3. Git blame removed code
4. Generate minimal report
Time: ~30 minutes
Standard Review (Medium Codebase)
Input: 80 files, 12 HIGH RISK changes
Strategy: FOCUSED (see methodology.md)
1. Full workflow on HIGH RISK files
2. Surface scan on MEDIUM
3. Skip LOW risk files
4. Complete report with all sections
Time: ~3-4 hours
Deep Audit (Large, Critical Change)
Input: 450 files, auth system rewrite
Strategy: SURGICAL + audit-context-building
1. Baseline context with audit-context-building
2. Deep analysis on auth changes only
3. Blast radius analysis
4. Adversarial modeling
5. Comprehensive report
Time: ~6-8 hours
When NOT to Use This Skill
- Greenfield code (no baseline to compare)
- Documentation-only changes (no security impact)
- Formatting/linting (cosmetic changes)
- User explicitly requests quick summary only (they accept risk)
For these cases, use standard code review instead.
Red Flags (Stop and Investigate)
Immediate escalation triggers:
- Removed code from "security", "CVE", or "fix" commits
- Access control modifiers removed (onlyOwner, internal → external)
- Validation removed without replacement
- External calls added without checks
- High blast radius (50+ callers) + HIGH risk change
These patterns require adversarial analysis even in quick triage.
Tips for Best Results
Do:
- Start with git blame for removed code
- Calculate blast radius early to prioritize
- Generate concrete attack scenarios
- Reference specific line numbers and commits
- Be honest about coverage limitations
- Always generate the output file
Don't:
- Skip git history analysis
- Make generic findings without evidence
- Claim full analysis when time-limited
- Forget to check test coverage
- Miss high blast radius changes
- Output report only to chat (file required)
Supporting Documentation
- methodology.md - Detailed phase-by-phase workflow (Phases 0-4)
- adversarial.md - Attacker modeling and exploit scenarios (Phase 5)
- reporting.md - Report structure and formatting (Phase 6)
- patterns.md - Common vulnerability patterns reference
For first-time users: Start with methodology.md to understand the complete workflow.
For experienced users: Use this page's Quick Reference and Decision Tree to navigate directly to needed content.
/dwarf-expert
Source: ~/.claude/skills/tob-dwarf-expert/skills/dwarf-expert/SKILL.md
name: dwarf-expert description: Provides expertise for analyzing DWARF debug files and understanding the DWARF debug format/standard (v3-v5). Triggers when understanding DWARF information, interacting with DWARF files, answering DWARF-related questions, or working with code that parses DWARF data. allowed-tools:
- Read
- Bash
- Grep
- Glob
- WebSearch
Overview
This skill provides technical knowledge and expertise about the DWARF standard and how to interact with DWARF files. Tasks include answering questions about the DWARF standard, providing examples of various DWARF features, parsing and/or creating DWARF files, and writing/modifying/analyzing code that interacts with DWARF data.
When to Use This Skill
- Understanding or parsing DWARF debug information from compiled binaries
- Answering questions about the DWARF standard (v3, v4, v5)
- Writing or reviewing code that interacts with DWARF data
- Using
dwarfdumporreadelfto extract debug information - Verifying DWARF data integrity with
llvm-dwarfdump --verify - Working with DWARF parsing libraries (libdwarf, pyelftools, gimli, etc.)
When NOT to Use This Skill
- DWARF v1/v2 Analysis: Expertise limited to versions 3, 4, and 5.
- General ELF Parsing: Use standard ELF tools if DWARF data isn't needed.
- Executable Debugging: Use dedicated debugging tools (gdb, lldb, etc) for debugging executable code/runtime behavior.
- Binary Reverse Engineering: Use dedicated RE tools (Ghidra, IDA) unless specifically analyzing DWARF sections.
- Compiler Debugging: DWARF generation issues are compiler-specific, not covered here.
Authoritative Sources
When specific DWARF standard information is needed, use these authoritative sources:
-
Official DWARF Standards (dwarfstd.org): Use web search to find specific sections of the official DWARF specification at dwarfstd.org. Search queries like "DWARF5 DW_TAG_subprogram attributes site:dwarfstd.org" are effective.
-
LLVM DWARF Implementation: The LLVM project's DWARF handling code at
llvm/lib/DebugInfo/DWARF/serves as a reliable reference implementation. Key files include:DWARFDie.cpp- DIE handling and attribute accessDWARFUnit.cpp- Compilation unit parsingDWARFDebugLine.cpp- Line number informationDWARFVerifier.cpp- Validation logic
-
libdwarf: The reference C implementation at github.com/davea42/libdwarf-code provides detailed handling of DWARF data structures.
Verification Workflows
Use llvm-dwarfdump verification options to validate DWARF data integrity:
Structural Validation
# Verify DWARF structure (compile units, DIE relationships, address ranges)
llvm-dwarfdump --verify <binary>
# Detailed error output with summary
llvm-dwarfdump --verify --error-display=full <binary>
# Machine-readable JSON error summary
llvm-dwarfdump --verify --verify-json=errors.json <binary>
Quality Metrics
# Output debug info quality metrics as JSON
llvm-dwarfdump --statistics <binary>
The --statistics output helps compare debug info quality across compiler versions and optimization levels.
Common Verification Patterns
- After compilation: Verify binaries have valid DWARF before distribution
- Comparing builds: Use
--statisticsto detect debug info quality regressions - Debugging debuggers: Identify malformed DWARF causing debugger issues
- DWARF tool development: Validate parser output against known-good binaries
Parsing DWARF Debug Information
readelf
ELF files can be parsed via the readelf command ({baseDir}/reference/readelf.md). Use this for general ELF information, but prefer dwarfdump for DWARF-specific parsing.
dwarfdump
DWARF files can be parsed via the dwarfdump command, which is more effective at parsing and displaying complex DWARF information than readelf and should be used for most DWARF parsing tasks ({baseDir}/reference/dwarfdump.md).
Working With Code
This skill supports writing, modifying, and reviewing code that interacts with DWARF data. This may involve code that parses DWARF debug data from scratch or code that leverages libraries to parse and interact with DWARF data ({baseDir}/reference/coding.md).
Choosing Your Approach
┌─ Need to verify DWARF data integrity?
│ └─ Use `llvm-dwarfdump --verify` (see Verification Workflows above)
├─ Need to answer questions about the DWARF standard?
│ └─ Search dwarfstd.org or reference LLVM/libdwarf source
├─ Need simple section dump or general ELF info?
│ └─ Use `readelf` ({baseDir}/reference/readelf.md)
├─ Need to parse, search, and/or dump DWARF DIE nodes?
│ └─ Use `dwarfdump` ({baseDir}/reference/dwarfdump.md)
└─ Need to write, modify, or review code that interacts with DWARF data?
└─ Refer to the coding reference ({baseDir}/reference/coding.md)
/entry-point-analyzer
Source: ~/.claude/skills/tob-entry-point-analyzer/skills/entry-point-analyzer/SKILL.md
name: entry-point-analyzer description: Analyzes smart contract codebases to identify state-changing entry points for security auditing. Detects externally callable functions that modify state, categorizes them by access level (public, admin, role-restricted, contract-only), and generates structured audit reports. Excludes view/pure/read-only functions. Use when auditing smart contracts (Solidity, Vyper, Solana/Rust, Move, TON, CosmWasm) or when asked to find entry points, audit flows, external functions, access control patterns, or privileged operations. allowed-tools:
- Read
- Grep
- Glob
- Bash
Entry Point Analyzer
Systematically identify all state-changing entry points in a smart contract codebase to guide security audits.
When to Use
Use this skill when:
- Starting a smart contract security audit to map the attack surface
- Asked to find entry points, external functions, or audit flows
- Analyzing access control patterns across a codebase
- Identifying privileged operations and role-restricted functions
- Building an understanding of which functions can modify contract state
When NOT to Use
Do NOT use this skill for:
- Vulnerability detection (use audit-context-building or domain-specific-audits)
- Writing exploit POCs (use solidity-poc-builder)
- Code quality or gas optimization analysis
- Non-smart-contract codebases
- Analyzing read-only functions (this skill excludes them)
Scope: State-Changing Functions Only
This skill focuses exclusively on functions that can modify state. Excluded:
| Language | Excluded Patterns |
|---|---|
| Solidity | view, pure functions |
| Vyper | @view, @pure functions |
| Solana | Functions without mut account references |
| Move | Non-entry public fun (module-callable only) |
| TON | get methods (FunC), read-only receivers (Tact) |
| CosmWasm | query entry point and its handlers |
Why exclude read-only functions? They cannot directly cause loss of funds or state corruption. While they may leak information, the primary audit focus is on functions that can change state.
Workflow
- Detect Language - Identify contract language(s) from file extensions and syntax
- Use Tooling (if available) - For Solidity, check if Slither is available and use it
- Locate Contracts - Find all contract/module files (apply directory filter if specified)
- Extract Entry Points - Parse each file for externally callable, state-changing functions
- Classify Access - Categorize each function by access level
- Generate Report - Output structured markdown report
Slither Integration (Solidity)
For Solidity codebases, Slither can automatically extract entry points. Before manual analysis:
1. Check if Slither is Available
which slither
2. If Slither is Detected, Run Entry Points Printer
slither . --print entry-points
This outputs a table of all state-changing entry points with:
- Contract name
- Function name
- Visibility
- Modifiers applied
3. Use Slither Output as Foundation
- Parse the Slither output table to populate your analysis
- Cross-reference with manual inspection for access control classification
- Slither may miss some patterns (callbacks, dynamic access control)—supplement with manual review
- If Slither fails (compilation errors, unsupported features), fall back to manual analysis
4. When Slither is NOT Available
If which slither returns nothing, proceed with manual analysis using the language-specific reference files.
Language Detection
| Extension | Language | Reference |
|---|---|---|
.sol |
Solidity | {baseDir}/references/solidity.md |
.vy |
Vyper | {baseDir}/references/vyper.md |
.rs + Cargo.toml with solana-program |
Solana (Rust) | {baseDir}/references/solana.md |
.move + Move.toml with edition |
{baseDir}/references/move-sui.md | |
.move + Move.toml with Aptos |
{baseDir}/references/move-aptos.md | |
.fc, .func, .tact |
TON (FunC/Tact) | {baseDir}/references/ton.md |
.rs + Cargo.toml with cosmwasm-std |
CosmWasm | {baseDir}/references/cosmwasm.md |
Load the appropriate reference file(s) based on detected language before analysis.
Access Classification
Classify each state-changing entry point into one of these categories:
1. Public (Unrestricted)
Functions callable by anyone without restrictions.
2. Role-Restricted
Functions limited to specific roles. Common patterns to detect:
- Explicit role names:
admin,owner,governance,guardian,operator,manager,minter,pauser,keeper,relayer,lender,borrower - Role-checking patterns:
onlyRole,hasRole,require(msg.sender == X),assert_owner,#[access_control] - When role is ambiguous, flag as "Restricted (review required)" with the restriction pattern noted
3. Contract-Only (Internal Integration Points)
Functions callable only by other contracts, not by EOAs. Indicators:
- Callbacks:
onERC721Received,uniswapV3SwapCallback,flashLoanCallback - Interface implementations with contract-caller checks
- Functions that revert if
tx.origin == msg.sender - Cross-contract hooks
Output Format
Generate a markdown report with this structure:
# Entry Point Analysis: [Project Name]
**Analyzed**: [timestamp]
**Scope**: [directories analyzed or "full codebase"]
**Languages**: [detected languages]
**Focus**: State-changing functions only (view/pure excluded)
## Summary
| Category | Count |
|----------|-------|
| Public (Unrestricted) | X |
| Role-Restricted | X |
| Restricted (Review Required) | X |
| Contract-Only | X |
| **Total** | **X** |
---
## Public Entry Points (Unrestricted)
State-changing functions callable by anyone—prioritize for attack surface analysis.
| Function | File | Notes |
|----------|------|-------|
| `functionName(params)` | `path/to/file.sol:L42` | Brief note if relevant |
---
## Role-Restricted Entry Points
### Admin / Owner
| Function | File | Restriction |
|----------|------|-------------|
| `setFee(uint256)` | `Config.sol:L15` | `onlyOwner` |
### Governance
| Function | File | Restriction |
|----------|------|-------------|
### Guardian / Pauser
| Function | File | Restriction |
|----------|------|-------------|
### Other Roles
| Function | File | Restriction | Role |
|----------|------|-------------|------|
---
## Restricted (Review Required)
Functions with access control patterns that need manual verification.
| Function | File | Pattern | Why Review |
|----------|------|---------|------------|
| `execute(bytes)` | `Executor.sol:L88` | `require(trusted[msg.sender])` | Dynamic trust list |
---
## Contract-Only (Internal Integration Points)
Functions only callable by other contracts—useful for understanding trust boundaries.
| Function | File | Expected Caller |
|----------|------|-----------------|
| `onFlashLoan(...)` | `Vault.sol:L200` | Flash loan provider |
---
## Files Analyzed
- `path/to/file1.sol` (X state-changing entry points)
- `path/to/file2.sol` (X state-changing entry points)
Filtering
When user specifies a directory filter:
- Only analyze files within that path
- Note the filter in the report header
- Example: "Analyze only
src/core/" → scope =src/core/
Analysis Guidelines
- Be thorough: Don't skip files. Every state-changing externally callable function matters.
- Be conservative: When uncertain about access level, flag for review rather than miscategorize.
- Skip read-only: Exclude
view,pure, and equivalent read-only functions. - Note inheritance: If a function's access control comes from a parent contract, note this.
- Track modifiers: List all access-related modifiers/decorators applied to each function.
- Identify patterns: Look for common patterns like:
- Initializer functions (often unrestricted on first call)
- Upgrade functions (high-privilege)
- Emergency/pause functions (guardian-level)
- Fee/parameter setters (admin-level)
- Token transfers and approvals (often public)
Common Role Patterns by Protocol Type
| Protocol Type | Common Roles |
|---|---|
| DEX | owner, feeManager, pairCreator |
| Lending | admin, guardian, liquidator, oracle |
| Governance | proposer, executor, canceller, timelock |
| NFT | minter, admin, royaltyReceiver |
| Bridge | relayer, guardian, validator, operator |
| Vault/Yield | strategist, keeper, harvester, manager |
Rationalizations to Reject
When analyzing entry points, reject these shortcuts:
- "This function looks standard" → Still classify it; standard functions can have non-standard access control
- "The modifier name is clear" → Verify the modifier's actual implementation
- "This is obviously admin-only" → Trace the actual restriction; "obvious" assumptions miss subtle bypasses
- "I'll skip the callbacks" → Callbacks define trust boundaries; always include them
- "It doesn't modify much state" → Any state change can be exploited; include all non-view functions
Error Handling
If a file cannot be parsed:
- Note it in the report under "Analysis Warnings"
- Continue with remaining files
- Suggest manual review for unparsable files
/firebase-apk-scanner
Source: ~/.claude/skills/tob-firebase-apk-scanner/skills/firebase-apk-scanner/SKILL.md
name: firebase-apk-scanner description: Scans Android APKs for Firebase security misconfigurations including open databases, storage buckets, authentication issues, and exposed cloud functions. Use when analyzing APK files for Firebase vulnerabilities, performing mobile app security audits, or testing Firebase endpoint security. For authorized security research only. argument-hint: [apk-file-or-directory] allowed-tools: Bash({baseDir}/scanner.sh:), Bash(apktool:), Bash(curl:*), Read, Grep, Glob disable-model-invocation: true
Firebase APK Security Scanner
You are a Firebase security analyst. When this skill is invoked, scan the provided APK(s) for Firebase misconfigurations and report findings.
When to Use
- Auditing Android applications for Firebase security misconfigurations
- Testing Firebase endpoints extracted from APKs (Realtime Database, Firestore, Storage)
- Checking authentication security (open signup, anonymous auth, email enumeration)
- Enumerating Cloud Functions and testing for unauthenticated access
- Mobile app security assessments involving Firebase backends
- Authorized penetration testing of Firebase-backed applications
When NOT to Use
- Scanning apps you do not have explicit authorization to test
- Testing production Firebase projects without written permission
- When you only need to extract Firebase config without testing (use manual grep/strings instead)
- For non-Android targets (iOS, web apps) - this skill is APK-specific
- When the target app does not use Firebase
Rationalizations to Reject
When auditing, reject these common rationalizations that lead to missed or downplayed findings:
- "The database is read-only so it's fine" - Data exposure is still a critical finding; PII, API keys, and business data may be leaked
- "It's just anonymous auth, not real accounts" - Anonymous tokens bypass
auth != nullrules and can access "authenticated-only" resources - "The API key is public anyway" - A public API key does not justify open database rules or disabled auth restrictions
- "There's no sensitive data in there" - You cannot know what data will be stored in the future; insecure rules are vulnerabilities regardless of current content
- "It's an internal app" - APKs can be extracted from any device; "internal" apps are not protected from reverse engineering
- "We'll fix it before launch" - Document the finding; pre-launch vulnerabilities frequently ship to production
Reference Documentation
For detailed vulnerability patterns and exploitation techniques, consult:
How to Use This Skill
The user will provide an APK file or directory: $ARGUMENTS
Workflow
Step 1: Validate Input
First, verify the target exists:
ls -la $ARGUMENTS
If $ARGUMENTS is empty, ask the user to provide an APK path.
Step 2: Run the Scanner
Execute the bundled scanner script on the target:
{baseDir}/scanner.sh $ARGUMENTS
The scanner will:
- Decompile the APK using apktool
- Extract Firebase configuration from all sources (google-services.json, XML resources, assets, smali code, DEX strings)
- Test authentication endpoints (open signup, anonymous auth, email enumeration)
- Test Realtime Database (unauthenticated read/write, auth bypass)
- Test Firestore (document access, collection enumeration)
- Test Storage buckets (listing, write access)
- Test Cloud Functions (enumeration, unauthenticated access)
- Test Remote Config exposure
- Generate reports in text and JSON format
Step 3: Present Results
After the scanner completes, read and summarize the results:
cat firebase_scan_*/scan_report.txt
Present findings in this format:
Scan Summary
| Metric | Value |
|---|---|
| APKs Scanned | X |
| Vulnerable | X |
| Total Issues | X |
Extracted Configuration
| Field | Value |
|---|---|
| Project ID | extracted_value |
| Database URL | extracted_value |
| Storage Bucket | extracted_value |
| API Key | extracted_value |
| Auth Domain | extracted_value |
Vulnerabilities Found
| Severity | Issue | Evidence |
|---|---|---|
| CRITICAL | Description | Brief evidence |
| HIGH | Description | Brief evidence |
Remediation
Provide specific fixes for each vulnerability found. Reference the Vulnerability Patterns for secure code examples.
Manual Testing (If Scanner Fails)
If the scanner script is unavailable or fails, perform manual extraction and testing:
Extract Configuration
Search for Firebase config in decompiled APK:
# Decompile
apktool d -f -o ./decompiled $ARGUMENTS
# Find google-services.json
find ./decompiled -name "google-services.json"
# Search XML resources
grep -r "firebaseio.com\|appspot.com\|AIza" ./decompiled/res/
# Search assets (hybrid apps)
grep -r "firebaseio.com\|AIza" ./decompiled/assets/
Test Endpoints
Once you have the PROJECT_ID and API_KEY:
Authentication:
# Test open signup
curl -s -X POST -H "Content-Type: application/json" \
-d '{"email":"test@test.com","password":"Test123!","returnSecureToken":true}' \
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=API_KEY"
# Test anonymous auth
curl -s -X POST -H "Content-Type: application/json" \
-d '{"returnSecureToken":true}' \
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=API_KEY"
Database:
# Realtime Database read
curl -s "https://PROJECT_ID.firebaseio.com/.json"
# Firestore read
curl -s "https://firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents"
Storage:
# List bucket
curl -s "https://firebasestorage.googleapis.com/v0/b/PROJECT_ID.appspot.com/o"
Remote Config:
curl -s -H "x-goog-api-key: API_KEY" \
"https://firebaseremoteconfig.googleapis.com/v1/projects/PROJECT_ID/remoteConfig"
Severity Classification
- CRITICAL: Unauthenticated database read/write, storage write, open signup on private apps
- HIGH: Anonymous auth enabled, storage bucket listing, collection enumeration
- MEDIUM: Email enumeration, accessible cloud functions, remote config exposure
- LOW: Information disclosure without sensitive data
Important Guidelines
/fix-review
Source: ~/.claude/skills/tob-fix-review/skills/fix-review/SKILL.md
name: fix-review description: > Verifies that git commits address security audit findings without introducing bugs. This skill should be used when the user asks to "verify these commits fix the audit findings", "check if TOB-XXX was addressed", "review the fix branch", "validate remediation commits", "did these changes address the security report", "post-audit remediation review", "compare fix commits to audit report", or when reviewing commits against security audit reports. allowed-tools:
- Read
- Write
- Grep
- Glob
- Bash
- WebFetch
Fix Review
Differential analysis to verify commits address security findings without introducing bugs.
When to Use
- Reviewing fix branches against security audit reports
- Validating that remediation commits actually address findings
- Checking if specific findings (TOB-XXX format) have been fixed
- Analyzing commit ranges for bug introduction patterns
- Cross-referencing code changes with audit recommendations
When NOT to Use
- Initial security audits (use audit-context-building or differential-review)
- Code review without a specific baseline or finding set
- Greenfield development with no prior audit
- Documentation-only changes
Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "The commit message says it fixes TOB-XXX" | Messages lie; code tells truth | Verify the actual code change addresses the finding |
| "Small fix, no new bugs possible" | Small changes cause big bugs | Analyze all changes for anti-patterns |
| "I'll check the important findings" | All findings matter | Systematically check every finding |
| "The tests pass" | Tests may not cover the fix | Verify fix logic, not just test status |
| "Same developer, they know the code" | Familiarity breeds blind spots | Fresh analysis of every change |
Quick Reference
Input Requirements
| Input | Required | Format |
|---|---|---|
| Source commit | Yes | Git commit hash or ref (baseline before fixes) |
| Target commit(s) | Yes | One or more commit hashes to analyze |
| Security report | No | Local path, URL, or Google Drive link |
Finding Status Values
| Status | Meaning |
|---|---|
| FIXED | Code change directly addresses the finding |
| PARTIALLY_FIXED | Some aspects addressed, others remain |
| NOT_ADDRESSED | No relevant changes found |
| CANNOT_DETERMINE | Insufficient context to verify |
Workflow
Phase 1: Input Gathering
Collect required inputs from user:
Source commit: [hash/ref before fixes]
Target commit: [hash/ref to analyze]
Report: [optional: path, URL, or "none"]
If user provides multiple target commits, process each separately with the same source.
Phase 2: Report Retrieval
When a security report is provided, retrieve it based on format:
Local file (PDF, MD, JSON, HTML): Read the file directly using the Read tool. Claude processes PDFs natively.
URL: Fetch web content using the WebFetch tool.
Google Drive URL that fails:
See references/report-parsing.md for Google Drive fallback logic using gdrive CLI.
Phase 3: Finding Extraction
Parse the report to extract findings:
Trail of Bits format:
- Look for "Detailed Findings" section
- Extract findings matching pattern:
TOB-[A-Z]+-[0-9]+ - Capture: ID, title, severity, description, affected files
Other formats:
- Numbered findings (Finding 1, Finding 2)
- Severity-based sections (Critical, High, Medium, Low)
- JSON with
findingsarray
See references/report-parsing.md for detailed parsing strategies.
Phase 4: Commit Analysis
For each target commit, analyze the commit range:
# Get commit list from source to target
git log <source>..<target> --oneline
# Get full diff
git diff <source>..<target>
# Get changed files
git diff <source>..<target> --name-only
For each commit in the range:
- Examine the diff for bug introduction patterns
- Check for security anti-patterns (see
references/bug-detection.md) - Map changes to relevant findings
Phase 5: Finding Verification
For each finding in the report:
-
Identify relevant commits - Match by:
- File paths mentioned in finding
- Function/variable names in finding description
- Commit messages referencing the finding ID
-
Verify the fix - Check that:
- The root cause is addressed (not just symptoms)
- The fix follows the report's recommendation
- No new vulnerabilities are introduced
-
Assign status - Based on evidence:
- FIXED: Clear code change addresses the finding
- PARTIALLY_FIXED: Some aspects fixed, others remain
- NOT_ADDRESSED: No relevant changes
- CANNOT_DETERMINE: Need more context
-
Document evidence - For each finding:
- Commit hash(es) that address it
- Specific file and line changes
- How the fix addresses the root cause
See references/finding-matching.md for detailed matching strategies.
Phase 6: Output Generation
Generate two outputs:
1. Report file (FIX_REVIEW_REPORT.md):
# Fix Review Report
**Source:** <commit>
**Target:** <commit>
**Report:** <path or "none">
**Date:** <date>
## Executive Summary
[Brief overview: X findings reviewed, Y fixed, Z concerns]
## Finding Status
| ID | Title | Severity | Status | Evidence |
|----|-------|----------|--------|----------|
| TOB-XXX-1 | Finding title | High | FIXED | abc123 |
| TOB-XXX-2 | Another finding | Medium | NOT_ADDRESSED | - |
## Bug Introduction Concerns
[Any potential bugs or regressions detected in the changes]
## Per-Commit Analysis
### Commit abc123: "Fix reentrancy in withdraw()"
**Files changed:** contracts/Vault.sol
**Findings addressed:** TOB-XXX-1
**Concerns:** None
[Detailed analysis]
## Recommendations
[Any follow-up actions needed]
2. Conversation summary:
Provide a concise summary in the conversation:
- Total findings: X
- Fixed: Y
- Not addressed: Z
- Concerns: [list any bug introduction risks]
Bug Detection
Analyze commits for security anti-patterns. Key patterns to watch:
- Access control weakening (modifiers removed)
- Validation removal (require/assert deleted)
- Error handling reduction (try/catch removed)
- External call reordering (state after call)
- Integer operation changes (SafeMath removed)
- Cryptographic weakening
See references/bug-detection.md for comprehensive detection patterns and examples.
Integration with Other Skills
differential-review: For initial security review of changes (before audit)
issue-writer: To format findings into formal audit reports
audit-context-building: For deep context when analyzing complex fixes
Tips for Effective Reviews
Do:
- Verify the actual code change, not just commit messages
- Check that fixes address root causes, not symptoms
- Look for unintended side effects in adjacent code
- Cross-reference multiple findings that may interact
- Document evidence for every status assignment
Don't:
- Trust commit messages as proof of fix
- Skip findings because they seem minor
- Assume passing tests mean correct fixes
- Ignore changes outside the "fix" scope
- Mark FIXED without clear evidence
Reference Files
For detailed guidance, consult:
references/finding-matching.md- Strategies for matching commits to findingsreferences/bug-detection.md- Comprehensive anti-pattern detectionreferences/report-parsing.md- Parsing different report formats, Google Drive fallback
/insecure-defaults
Source: ~/.claude/skills/tob-insecure-defaults/skills/insecure-defaults/SKILL.md
name: insecure-defaults description: "Detects fail-open insecure defaults (hardcoded secrets, weak auth, permissive security) that allow apps to run insecurely in production. Use when auditing security, reviewing config management, or analyzing environment variable handling." allowed-tools:
- Read
- Grep
- Glob
- Bash
Insecure Defaults Detection
Finds fail-open vulnerabilities where apps run insecurely with missing configuration. Distinguishes exploitable defaults from fail-secure patterns that crash safely.
- Fail-open (CRITICAL):
SECRET = env.get('KEY') or 'default'→ App runs with weak secret - Fail-secure (SAFE):
SECRET = env['KEY']→ App crashes if missing
When to Use
- Security audits of production applications (auth, crypto, API security)
- Configuration review of deployment files, IaC templates, Docker configs
- Code review of environment variable handling and secrets management
- Pre-deployment checks for hardcoded credentials or weak defaults
When NOT to Use
Do not use this skill for:
- Test fixtures explicitly scoped to test environments (files in
test/,spec/,__tests__/) - Example/template files (
.example,.template,.samplesuffixes) - Development-only tools (local Docker Compose for dev, debug scripts)
- Documentation examples in README.md or docs/ directories
- Build-time configuration that gets replaced during deployment
- Crash-on-missing behavior where app won't start without proper config (fail-secure)
When in doubt: trace the code path to determine if the app runs with the default or crashes.
Rationalizations to Reject
- "It's just a development default" → If it reaches production code, it's a finding
- "The production config overrides it" → Verify prod config exists; code-level vulnerability remains if not
- "This would never run without proper config" → Prove it with code trace; many apps fail silently
- "It's behind authentication" → Defense in depth; compromised session still exploits weak defaults
- "We'll fix it before release" → Document now; "later" rarely comes
Workflow
Follow this workflow for every potential finding:
1. SEARCH: Perform Project Discovery and Find Insecure Defaults
Determine language, framework, and project conventions. Use this information to further discover things like secret storage locations, secret usage patterns, credentialed third-party integrations, cryptography, and any other relevant configuration. Further use information to analyze insecure default configurations.
Example
Search for patterns in **/config/, **/auth/, **/database/, and env files:
- Fallback secrets:
getenv.*\) or ['"],process\.env\.[A-Z_]+ \|\| ['"],ENV\.fetch.*default: - Hardcoded credentials:
password.*=.*['"][^'"]{8,}['"],api[_-]?key.*=.*['"][^'"]+['"] - Weak defaults:
DEBUG.*=.*true,AUTH.*=.*false,CORS.*=.*\* - Crypto algorithms:
MD5|SHA1|DES|RC4|ECBin security contexts
Tailor search approach based on discovery results.
Focus on production-reachable code, not test fixtures or example files.
2. VERIFY: Actual Behavior
For each match, trace the code path to understand runtime behavior.
Questions to answer:
- When is this code executed? (Startup vs. runtime)
- What happens if a configuration variable is missing?
- Is there validation that enforces secure configuration?
3. CONFIRM: Production Impact
Determine if this issue reaches production:
If production config provides the variable → Lower severity (but still a code-level vulnerability) If production config missing or uses default → CRITICAL
4. REPORT: with Evidence
Example report:
Finding: Hardcoded JWT Secret Fallback
Location: src/auth/jwt.ts:15
Pattern: const secret = process.env.JWT_SECRET || 'default';
Verification: App starts without JWT_SECRET; secret used in jwt.sign() at line 42
Production Impact: Dockerfile missing JWT_SECRET
Exploitation: Attacker forges JWTs using 'default', gains unauthorized access
Quick Verification Checklist
Fallback Secrets: SECRET = env.get(X) or Y
→ Verify: App starts without env var? Secret used in crypto/auth?
→ Skip: Test fixtures, example files
Default Credentials: Hardcoded username/password pairs
→ Verify: Active in deployed config? No runtime override?
→ Skip: Disabled accounts, documentation examples
Fail-Open Security: AUTH_REQUIRED = env.get(X, 'false')
→ Verify: Default is insecure (false/disabled/permissive)?
→ Safe: App crashes or default is secure (true/enabled/restricted)
Weak Crypto: MD5/SHA1/DES/RC4/ECB in security contexts → Verify: Used for passwords, encryption, or tokens? → Skip: Checksums, non-security hashing
Permissive Access: CORS *, permissions 0777, public-by-default
→ Verify: Default allows unauthorized access?
→ Skip: Explicitly configured permissiveness with justification
Debug Features: Stack traces, introspection, verbose errors → Verify: Enabled by default? Exposed in responses? → Skip: Logging-only, not user-facing
For detailed examples and counter-examples, see examples.md.
/modern-python
Source: ~/.claude/skills/tob-modern-python/skills/modern-python/SKILL.md
name: modern-python description: Configures Python projects with modern tooling (uv, ruff, ty). Use when creating projects, writing standalone scripts, or migrating from pip/Poetry/mypy/black.
Modern Python
Guide for modern Python tooling and best practices, based on trailofbits/cookiecutter-python.
When to Use This Skill
- Creating a new Python project or package
- Setting up
pyproject.tomlconfiguration - Configuring development tools (linting, formatting, testing)
- Writing Python scripts with external dependencies
- Migrating from legacy tools (when user requests it)
When NOT to Use This Skill
- User wants to keep legacy tooling: Respect existing workflows if explicitly requested
- Python < 3.11 required: These tools target modern Python
- Non-Python projects: Mixed codebases where Python isn't primary
Anti-Patterns to Avoid
| Avoid | Use Instead |
|---|---|
[tool.ty] python-version |
[tool.ty.environment] python-version |
uv pip install |
uv add and uv sync |
| Editing pyproject.toml manually to add deps | uv add <pkg> / uv remove <pkg> |
hatchling build backend |
uv_build (simpler, sufficient for most cases) |
| Poetry | uv (faster, simpler, better ecosystem integration) |
| requirements.txt | PEP 723 for scripts, pyproject.toml for projects |
| mypy / pyright | ty (faster, from Astral team) |
[project.optional-dependencies] for dev tools |
[dependency-groups] (PEP 735) |
Manual virtualenv activation (source .venv/bin/activate) |
uv run <cmd> |
| pre-commit | prek (faster, no Python runtime needed) |
Key principles:
- Always use
uv addanduv removeto manage dependencies - Never manually activate or manage virtual environments—use
uv runfor all commands - Use
[dependency-groups]for dev/test/docs dependencies, not[project.optional-dependencies]
Decision Tree
What are you doing?
│
├─ Single-file script with dependencies?
│ └─ Use PEP 723 inline metadata (./references/pep723-scripts.md)
│
├─ New multi-file project (not distributed)?
│ └─ Minimal uv setup (see Quick Start below)
│
├─ New reusable package/library?
│ └─ Full project setup (see Full Setup below)
│
└─ Migrating existing project?
└─ See Migration Guide below
Tool Overview
| Tool | Purpose | Replaces |
|---|---|---|
| uv | Package/dependency management | pip, virtualenv, pip-tools, pipx, pyenv |
| ruff | Linting AND formatting | flake8, black, isort, pyupgrade, pydocstyle |
| ty | Type checking | mypy, pyright (faster alternative) |
| pytest | Testing with coverage | unittest |
| prek | Pre-commit hooks (setup) | pre-commit (faster, Rust-native) |
Security Tools
| Tool | Purpose | When It Runs |
|---|---|---|
| shellcheck | Shell script linting | pre-commit |
| detect-secrets | Secret detection | pre-commit |
| actionlint | Workflow syntax validation | pre-commit, CI |
| zizmor | Workflow security audit | pre-commit, CI |
| pip-audit | Dependency vulnerability scanning | CI, manual |
| Dependabot | Automated dependency updates | scheduled |
See security-setup.md for configuration and usage.
Quick Start: Minimal Project
For simple multi-file projects not intended for distribution:
# Create project with uv
uv init myproject
cd myproject
# Add dependencies
uv add requests rich
# Add dev dependencies
uv add --group dev pytest ruff ty
# Run code
uv run python src/myproject/main.py
# Run tools
uv run pytest
uv run ruff check .
Full Project Setup
If starting from scratch, ask the user if they prefer to use the Trail of Bits cookiecutter template to bootstrap a complete project with already preconfigured tooling.
uvx cookiecutter gh:trailofbits/cookiecutter-python
1. Create Project Structure
uv init --package myproject
cd myproject
This creates:
myproject/
├── pyproject.toml
├── README.md
├── src/
│ └── myproject/
│ └── __init__.py
└── .python-version
2. Configure pyproject.toml
See pyproject.md for complete configuration reference.
Key sections:
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []
[dependency-groups]
dev = [{include-group = "lint"}, {include-group = "test"}, {include-group = "audit"}]
lint = ["ruff", "ty"]
test = ["pytest", "pytest-cov"]
audit = ["pip-audit"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["ALL"]
ignore = ["D", "COM812", "ISC001"]
[tool.pytest]
addopts = ["--cov=myproject", "--cov-fail-under=80"]
[tool.ty.terminal]
error-on-warning = true
[tool.ty.environment]
python-version = "3.11"
[tool.ty.rules]
# Strict from day 1 for new projects
possibly-unresolved-reference = "error"
unused-ignore-comment = "warn"
3. Install Dependencies
# Install all dependency groups
uv sync --all-groups
# Or install specific groups
uv sync --group dev
4. Add Makefile
.PHONY: dev lint format test build
dev:
uv sync --all-groups
lint:
uv run ruff format --check && uv run ruff check && uv run ty check src/
format:
uv run ruff format .
test:
uv run pytest
build:
uv build
Migration Guide
When a user requests migration from legacy tooling:
From requirements.txt + pip
First, determine the nature of the code:
For standalone scripts: Convert to PEP 723 inline metadata (see pep723-scripts.md)
For projects:
# Initialize uv in existing project
uv init --bare
# Add dependencies using uv (not by editing pyproject.toml)
uv add requests rich # add each package
# Or import from requirements.txt (review each package before adding)
# Note: Complex version specifiers may need manual handling
grep -v '^#' requirements.txt | grep -v '^-' | grep -v '^\s*$' | while read -r pkg; do
uv add "$pkg" || echo "Failed to add: $pkg"
done
uv sync
Then:
- Delete
requirements.txt,requirements-dev.txt - Delete virtual environment (
venv/,.venv/) - Add
uv.lockto version control
From setup.py / setup.cfg
- Run
uv init --bareto create pyproject.toml - Use
uv addto add each dependency frominstall_requires - Use
uv add --group devfor dev dependencies - Copy non-dependency metadata (name, version, description, etc.) to
[project] - Delete
setup.py,setup.cfg,MANIFEST.in
From flake8 + black + isort
- Remove flake8, black, isort via
uv remove - Delete
.flake8,pyproject.toml [tool.black],[tool.isort]configs - Add ruff:
uv add --group dev ruff - Add ruff configuration (see ruff-config.md)
- Run
uv run ruff check --fix .to apply fixes - Run
uv run ruff format .to format
From mypy / pyright
- Remove mypy/pyright via
uv remove - Delete
mypy.ini,pyrightconfig.json, or[tool.mypy]/[tool.pyright]sections - Add ty:
uv add --group dev ty - Run
uv run ty check src/
Quick Reference: uv Commands
| Command | Description |
|---|---|
uv init |
Create new project |
uv init --package |
Create distributable package |
uv add <pkg> |
Add dependency |
uv add --group dev <pkg> |
Add to dependency group |
uv remove <pkg> |
Remove dependency |
uv sync |
Install dependencies |
uv sync --all-groups |
Install all dependency groups |
uv run <cmd> |
Run command in venv |
uv run --with <pkg> <cmd> |
Run with temporary dependency |
uv build |
Build package |
uv publish |
Publish to PyPI |
Ad-hoc Dependencies with --with
Use uv run --with for one-off commands that need packages not in your project:
# Run Python with a temporary package
uv run --with requests python -c "import requests; print(requests.get('https://httpbin.org/ip').json())"
# Run a module with temporary deps
uv run --with rich python -m rich.progress
# Multiple packages
uv run --with requests --with rich python script.py
# Combine with project deps (adds to existing venv)
uv run --with httpx pytest # project deps + httpx
When to use --with vs uv add:
uv add: Package is a project dependency (goes in pyproject.toml/uv.lock)--with: One-off usage, testing, or scripts outside a project context
See uv-commands.md for complete reference.
Quick Reference: Dependency Groups
[dependency-groups]
dev = ["ruff", "ty"]
test = ["pytest", "pytest-cov", "hypothesis"]
docs = ["sphinx", "myst-parser"]
Install with: uv sync --group dev --group test
Best Practices Checklist
- Use
src/layout for packages - Set
requires-python = ">=3.11" - Configure ruff with
select = ["ALL"]and explicit ignores - Use ty for type checking
- Enforce test coverage minimum (80%+)
- Use dependency groups instead of extras for dev tools
- Add
uv.lockto version control - Use PEP 723 for standalone scripts
Read Next
- migration-checklist.md - Step-by-step migration cleanup
- pyproject.md - Complete pyproject.toml reference
- uv-commands.md - uv command reference
- ruff-config.md - Ruff linting/formatting configuration
- testing.md - pytest and coverage setup
- pep723-scripts.md - PEP 723 inline script metadata
- prek.md - Fast pre-commit hooks with prek
- security-setup.md - Security hooks and dependency scanning
- dependabot.md - Automated dependency updates
/property-based-testing
Source: ~/.claude/skills/tob-property-based-testing/skills/property-based-testing/SKILL.md
name: property-based-testing description: Provides guidance for property-based testing across multiple languages and smart contracts. Use when writing tests, reviewing code with serialization/validation/parsing patterns, designing features, or when property-based testing would provide stronger coverage than example-based tests.
Property-Based Testing Guide
Use this skill proactively during development when you encounter patterns where PBT provides stronger coverage than example-based tests.
When to Invoke (Automatic Detection)
Invoke this skill when you detect:
- Serialization pairs:
encode/decode,serialize/deserialize,toJSON/fromJSON,pack/unpack - Parsers: URL parsing, config parsing, protocol parsing, string-to-structured-data
- Normalization:
normalize,sanitize,clean,canonicalize,format - Validators:
is_valid,validate,check_*(especially with normalizers) - Data structures: Custom collections with
add/remove/getoperations - Mathematical/algorithmic: Pure functions, sorting, ordering, comparators
- Smart contracts: Solidity/Vyper contracts, token operations, state invariants, access control
Priority by pattern:
| Pattern | Property | Priority |
|---|---|---|
| encode/decode pair | Roundtrip | HIGH |
| Pure function | Multiple | HIGH |
| Validator | Valid after normalize | MEDIUM |
| Sorting/ordering | Idempotence + ordering | MEDIUM |
| Normalization | Idempotence | MEDIUM |
| Builder/factory | Output invariants | LOW |
| Smart contract | State invariants | HIGH |
When NOT to Use
Do NOT use this skill for:
- Simple CRUD operations without transformation logic
- One-off scripts or throwaway code
- Code with side effects that cannot be isolated (network calls, database writes)
- Tests where specific example cases are sufficient and edge cases are well-understood
- Integration or end-to-end testing (PBT is best for unit/component testing)
Property Catalog (Quick Reference)
| Property | Formula | When to Use |
|---|---|---|
| Roundtrip | decode(encode(x)) == x |
Serialization, conversion pairs |
| Idempotence | f(f(x)) == f(x) |
Normalization, formatting, sorting |
| Invariant | Property holds before/after | Any transformation |
| Commutativity | f(a, b) == f(b, a) |
Binary/set operations |
| Associativity | f(f(a,b), c) == f(a, f(b,c)) |
Combining operations |
| Identity | f(x, identity) == x |
Operations with neutral element |
| Inverse | f(g(x)) == x |
encrypt/decrypt, compress/decompress |
| Oracle | new_impl(x) == reference(x) |
Optimization, refactoring |
| Easy to Verify | is_sorted(sort(x)) |
Complex algorithms |
| No Exception | No crash on valid input | Baseline property |
Strength hierarchy (weakest to strongest): No Exception → Type Preservation → Invariant → Idempotence → Roundtrip
Decision Tree
Based on the current task, read the appropriate section:
TASK: Writing new tests
→ Read [{baseDir}/references/generating.md]({baseDir}/references/generating.md) (test generation patterns and examples)
→ Then [{baseDir}/references/strategies.md]({baseDir}/references/strategies.md) if input generation is complex
TASK: Designing a new feature
→ Read [{baseDir}/references/design.md]({baseDir}/references/design.md) (Property-Driven Development approach)
TASK: Code is difficult to test (mixed I/O, missing inverses)
→ Read [{baseDir}/references/refactoring.md]({baseDir}/references/refactoring.md) (refactoring patterns for testability)
TASK: Reviewing existing PBT tests
→ Read [{baseDir}/references/reviewing.md]({baseDir}/references/reviewing.md) (quality checklist and anti-patterns)
TASK: Need library reference
→ Read [{baseDir}/references/libraries.md]({baseDir}/references/libraries.md) (PBT libraries by language, includes smart contract tools)
How to Suggest PBT
When you detect a high-value pattern while writing tests, offer PBT as an option:
"I notice
encode_message/decode_messageis a serialization pair. Property-based testing with a roundtrip property would provide stronger coverage than example tests. Want me to use that approach?"
If codebase already uses a PBT library (Hypothesis, fast-check, proptest, Echidna), be more direct:
"This codebase uses Hypothesis. I'll write property-based tests for this serialization pair using a roundtrip property."
If user declines, write good example-based tests without further prompting.
When NOT to Use PBT
- Simple CRUD without complex validation
- UI/presentation logic
- Integration tests requiring complex external setup
- Prototyping where requirements are fluid
- User explicitly requests example-based tests only
Red Flags
- Recommending trivial getters/setters
- Missing paired operations (encode without decode)
- Ignoring type hints (well-typed = easier to test)
- Overwhelming user with candidates (limit to top 5-10)
- Being pushy after user declines
/second-opinion
Source: ~/.claude/skills/tob-second-opinion/skills/second-opinion/SKILL.md
name: second-opinion description: "Runs external LLM code reviews (OpenAI Codex or Google Gemini CLI) on uncommitted changes, branch diffs, or specific commits. Use when the user asks for a second opinion, external review, codex review, gemini review, or mentions /second-opinion." allowed-tools:
- Bash
- Read
- Glob
- Grep
- AskUserQuestion
Second Opinion
Shell out to external LLM CLIs for an independent code review powered by a separate model. Supports OpenAI Codex CLI and Google Gemini CLI.
When to Use
- Getting a second opinion on code changes from a different model
- Reviewing branch diffs before opening a PR
- Checking uncommitted work for issues before committing
- Running a focused review (security, performance, error handling)
- Comparing review output from multiple models
When NOT to Use
- Neither Codex CLI nor Gemini CLI is installed
- No API key or subscription configured for either tool
- Reviewing non-code files (documentation, config)
- You want Claude's own review (just ask Claude directly)
Safety Note
Gemini CLI is invoked with --yolo, which auto-approves all
tool calls without confirmation. This is required for headless
(non-interactive) operation but means Gemini will execute any
tool actions its extensions request without prompting.
Quick Reference
# Codex
codex review --uncommitted
codex review --base <branch>
codex review --commit <sha>
# Gemini (code review extension)
gemini -p "/code-review" --yolo -e code-review
# Gemini (headless with diff — see references/ for full heredoc pattern)
git diff HEAD > /tmp/review-diff.txt
cat <<'PROMPT' | gemini -p - --yolo
Review this diff...
$(cat /tmp/review-diff.txt)
PROMPT
Invocation
1. Gather context interactively
Use AskUserQuestion to collect review parameters in one shot.
Adapt the questions based on what the user already provided
in their invocation (skip questions they already answered).
Combine all applicable questions into a single AskUserQuestion
call (max 4 questions).
Question 1 — Tool (skip if user already specified):
header: "Review tool"
question: "Which tool should run the review?"
options:
- "Both Codex and Gemini (Recommended)" → run both in parallel
- "Codex only" → codex review
- "Gemini only" → gemini CLI
Question 2 — Scope (skip if user already specified):
header: "Review scope"
question: "What should be reviewed?"
options:
- "Uncommitted changes" → --uncommitted / git diff HEAD
- "Branch diff vs main" → --base (auto-detect default branch)
- "Specific commit" → --commit (follow up for SHA)
Question 3 — Project context (skip if neither CLAUDE.md nor AGENTS.md exists):
Check for CLAUDE.md first, then AGENTS.md in the repo root. Only show this question if at least one exists.
header: "Project context"
question: "Include project conventions file so the review
checks against your standards?"
options:
- "Yes, include it"
- "No, standard review"
Note: Project context only applies to Gemini and to Codex
with --uncommitted. For Codex with --base/--commit, the
positional prompt is not supported — inform the user that Codex
will review without custom instructions in this mode (it still
reads AGENTS.md if one exists in the repo).
Question 4 — Review focus (always ask):
header: "Review focus"
question: "Any specific focus areas for the review?"
options:
- "General review" → no custom prompt
- "Security & auth" → security-focused prompt
- "Performance" → performance-focused prompt
- "Error handling" → error handling-focused prompt
2. Run the tool directly
Do not pre-check tool availability. Run the selected tool immediately. If the command fails with "command not found" or an extension is missing, report the install command from the Error Handling table below and skip that tool (if "Both" was selected, run only the available one).
Diff Preview
After collecting answers, show the diff stats:
# For uncommitted:
git diff --stat HEAD
# For branch diff:
git diff --stat <branch>...HEAD
# For specific commit:
git diff --stat <sha>~1..<sha>
If the diff is empty, stop and tell the user.
If the diff is very large (>2000 lines changed), warn the user that high-effort reasoning on a large diff will be slow and ask whether to proceed or narrow the scope.
Auto-detect Default Branch
For branch diff scope, detect the default branch name:
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null \
| sed 's@^refs/remotes/origin/@@' || echo main
Codex Invocation
See references/codex-invocation.md for full details on command syntax, prompt passing, and model fallback.
Summary:
- Model:
gpt-5.3-codex, reasoning:xhigh --uncommittedtakes a positional prompt--baseand--commitdo NOT accept custom prompts (Codex readsAGENTS.mdif present, but the skill will not create one; note this limitation to the user)- Falls back to
gpt-5.2-codexon auth errors - Output is verbose — summarize findings, don't dump raw (see references/codex-invocation.md § Parsing Output)
- Set
timeout: 600000on the Bash call
Gemini Invocation
See references/gemini-invocation.md for full details on flags, scope mapping, and extension usage.
Summary:
- Model:
gemini-3-pro-preview, flags:--yolo,-e,-m - For uncommitted general review:
gemini -p "/code-review" --yolo -e code-review - For branch/commit diffs: pipe
git diffintogemini -p - Security extension name is
gemini-cli-security(notsecurity) /security:analyzeis interactive-only — use-pwith a security prompt instead- Run
/security:scan-depsas bonus when security focus selected - Set
timeout: 600000on the Bash call
Scope mapping for git diff (Gemini has no built-in scope flags):
| Scope | Diff command |
|---|---|
| Uncommitted | git diff HEAD |
| Branch diff | git diff <branch>...HEAD |
| Specific commit | git diff <sha>~1..<sha> |
Running Both
When the user picks "Both" (the default):
- Run Codex and Gemini in parallel — issue both Bash tool calls in a single response. Both commands are read-only (they review diffs via external APIs) so there is no shared state or git lock contention.
- Collect both results, then present with clear headers:
## Codex Review (gpt-5.3-codex)
<codex output>
## Gemini Review (gemini-3-pro-preview)
<gemini output>
Summarize where the two reviews agree and differ.
Error Handling
| Error | Action |
|---|---|
codex: command not found |
Tell user: npm i -g @openai/codex |
gemini: command not found |
Tell user: npm i -g @google/gemini-cli |
Gemini code-review extension missing |
Tell user: gemini extensions install https://github.com/gemini-cli-extensions/code-review |
Gemini gemini-cli-security extension missing |
Tell user: gemini extensions install https://github.com/gemini-cli-extensions/security |
| Model auth error (Codex) | Retry with gpt-5.2-codex |
| Empty diff | Tell user there are no changes to review |
| Timeout | Inform user and suggest narrowing the diff scope |
| Tool partially unavailable | Run only the available tool, note the skip |
Examples
Both tools (default):
User: /second-opinion
Claude: [asks 4 questions: tool, scope, context, focus]
User: picks "Both", "Branch diff", "Yes include CLAUDE.md", "Security"
Claude: [detects default branch = main]
Claude: [shows diff --stat: 6 files, +103 -15]
Claude: [runs Codex review with security prompt]
Claude: [runs Gemini review with security prompt + dep scan]
Claude: [presents both reviews, highlights agreements/differences]
Codex only with inline args:
User: /second-opinion check uncommitted changes for bugs
Claude: [scope known: uncommitted, focus known: custom]
Claude: [asks 2 questions: tool, project context]
User: picks "Codex only", "No context"
Claude: [shows diff --stat: 3 files, +45 -10]
Claude: [runs codex review --uncommitted with prompt]
Claude: [presents review]
Gemini only:
User: /second-opinion
Claude: [asks 4 questions]
User: picks "Gemini only", "Uncommitted", "No", "General"
Claude: [shows diff --stat: 2 files, +20 -5]
Claude: [runs gemini -p "/code-review" --yolo -e code-review]
Claude: [presents review]
Large diff warning:
User: /second-opinion
Claude: [asks questions] → user picks "Both", "Uncommitted", "General"
Claude: [shows diff --stat: 45 files, +3200 -890]
Claude: "Large diff (3200+ lines). High-effort reasoning will be
slow. Proceed, or narrow the scope?"
User: "proceed"
Claude: [runs both reviews]
/semgrep-rule-creator
Source: ~/.claude/skills/tob-semgrep-rule-creator/skills/semgrep-rule-creator/SKILL.md
name: semgrep-rule-creator description: Creates custom Semgrep rules for detecting security vulnerabilities, bug patterns, and code patterns. Use when writing Semgrep rules or building custom static analysis detections. allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
- WebFetch
Semgrep Rule Creator
Create production-quality Semgrep rules with proper testing and validation.
When to Use
Ideal scenarios:
- Writing Semgrep rules for specific bug patterns
- Writing rules to detect security vulnerabilities in your codebase
- Writing taint mode rules for data flow vulnerabilities
- Writing rules to enforce coding standards
When NOT to Use
Do NOT use this skill for:
- Running existing Semgrep rulesets
- General static analysis without custom rules (use
static-analysisskill)
Rationalizations to Reject
When writing Semgrep rules, reject these common shortcuts:
- "The pattern looks complete" → Still run
semgrep --test --config <rule-id>.yaml <rule-id>.<ext>to verify. Untested rules have hidden false positives/negatives. - "It matches the vulnerable case" → Matching vulnerabilities is half the job. Verify safe cases don't match (false positives break trust).
- "Taint mode is overkill for this" → If data flows from user input to a dangerous sink, taint mode gives better precision than pattern matching.
- "One test is enough" → Include edge cases: different coding styles, sanitized inputs, safe alternatives, and boundary conditions.
- "I'll optimize the patterns first" → Write correct patterns first, optimize after all tests pass. Premature optimization causes regressions.
- "The AST dump is too complex" → The AST reveals exactly how Semgrep sees code. Skipping it leads to patterns that miss syntactic variations.
Anti-Patterns
Too broad - matches everything, useless for detection:
# BAD: Matches any function call
pattern: $FUNC(...)
# GOOD: Specific dangerous function
pattern: eval(...)
Missing safe cases in tests - leads to undetected false positives:
# BAD: Only tests vulnerable case
# ruleid: my-rule
dangerous(user_input)
# GOOD: Include safe cases to verify no false positives
# ruleid: my-rule
dangerous(user_input)
# ok: my-rule
dangerous(sanitize(user_input))
# ok: my-rule
dangerous("hardcoded_safe_value")
Overly specific patterns - misses variations:
# BAD: Only matches exact format
pattern: os.system("rm " + $VAR)
# GOOD: Matches all os.system calls with taint tracking
mode: taint
pattern-sinks:
- pattern: os.system(...)
Strictness Level
This workflow is strict - do not skip steps:
- Read documentation first: See Documentation before writing Semgrep rules
- Test-first is mandatory: Never write a rule without tests
- 100% test pass is required: "Most tests pass" is not acceptable
- Optimization comes last: Only simplify patterns after all tests pass
- Avoid generic patterns: Rules must be specific, not match broad patterns
- Prioritize taint mode: For data flow vulnerabilities
- One YAML file - one Semgrep rule: Each YAML file must contain only one Semgrep rule; don't combine multiple rules in a single file
- No generic rules: When targeting a specific language for Semgrep rules - avoid generic pattern matching (
languages: generic) - Forbidden
todookandtodoruleidtest annotations:todoruleid: <rule-id>andtodook: <rule-id>annotations in tests files for future rule improvements are forbidden
Overview
This skill guides creation of Semgrep rules that detect security vulnerabilities and code patterns. Rules are created iteratively: analyze the problem, write tests first, analyze AST structure, write the rule, iterate until all tests pass, optimize the rule.
Approach selection:
- Taint mode (prioritize): Data flow issues where untrusted input reaches dangerous sinks
- Pattern matching: Simple syntactic patterns without data flow requirements
Why prioritize taint mode? Pattern matching finds syntax but misses context. A pattern eval($X) matches both eval(user_input) (vulnerable) and eval("safe_literal") (safe). Taint mode tracks data flow, so it only alerts when untrusted data actually reaches the sink—dramatically reducing false positives for injection vulnerabilities.
Iterating between approaches: It's okay to experiment. If you start with taint mode and it's not working well (e.g., taint doesn't propagate as expected, too many false positives/negatives), switch to pattern matching. Conversely, if pattern matching produces too many false positives on safe cases, try taint mode instead. The goal is a working rule—not rigid adherence to one approach.
Output structure - exactly 2 files in a directory named after the rule-id:
<rule-id>/
├── <rule-id>.yaml # Semgrep rule
└── <rule-id>.<ext> # Test file with ruleid/ok annotations
Quick Start
rules:
- id: insecure-eval
languages: [python]
severity: HIGH
message: User input passed to eval() allows code execution
mode: taint
pattern-sources:
- pattern: request.args.get(...)
pattern-sinks:
- pattern: eval(...)
Test file (insecure-eval.py):
# ruleid: insecure-eval
eval(request.args.get('code'))
# ok: insecure-eval
eval("print('safe')")
Run tests (from rule directory): semgrep --test --config <rule-id>.yaml <rule-id>.<ext>
Quick Reference
- For commands, pattern operators, and taint mode syntax, see quick-reference.md.
- For detailed workflow and examples, you MUST see workflow.md
Workflow
Copy this checklist and track progress:
Semgrep Rule Progress:
- [ ] Step 1: Analyze the Problem
- [ ] Step 2: Write Tests First
- [ ] Step 3: Analyze AST structure
- [ ] Step 4: Write the rule
- [ ] Step 5: Iterate until all tests pass (semgrep --test)
- [ ] Step 6: Optimize the rule (remove redundancies, re-test)
- [ ] Step 7: Final Run
Documentation
REQUIRED: Before writing any rule, use WebFetch to read all of these 4 links with Semgrep documentation:
/semgrep-rule-variant-creator
Source: ~/.claude/skills/tob-semgrep-rule-variant-creator/skills/semgrep-rule-variant-creator/SKILL.md
name: semgrep-rule-variant-creator description: Creates language variants of existing Semgrep rules. Use when porting a Semgrep rule to specified target languages. Takes an existing rule and target languages as input, produces independent rule+test directories for each language. allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
- WebFetch
Semgrep Rule Variant Creator
Port existing Semgrep rules to new target languages with proper applicability analysis and test-driven validation.
When to Use
Ideal scenarios:
- Porting an existing Semgrep rule to one or more target languages
- Creating language-specific variants of a universal vulnerability pattern
- Expanding rule coverage across a polyglot codebase
- Translating rules between languages with equivalent constructs
When NOT to Use
Do NOT use this skill for:
- Creating a new Semgrep rule from scratch (use
semgrep-rule-creatorinstead) - Running existing rules against code
- Languages where the vulnerability pattern fundamentally doesn't apply
- Minor syntax variations within the same language
Input Specification
This skill requires:
- Existing Semgrep rule - YAML file path or YAML rule content
- Target languages - One or more languages to port to (e.g., "Golang and Java")
Output Specification
For each applicable target language, produces:
<original-rule-id>-<language>/
├── <original-rule-id>-<language>.yaml # Ported Semgrep rule
└── <original-rule-id>-<language>.<ext> # Test file with annotations
Example output for porting sql-injection to Go and Java:
sql-injection-golang/
├── sql-injection-golang.yaml
└── sql-injection-golang.go
sql-injection-java/
├── sql-injection-java.yaml
└── sql-injection-java.java
Rationalizations to Reject
When porting Semgrep rules, reject these common shortcuts:
| Rationalization | Why It Fails | Correct Approach |
|---|---|---|
| "Pattern structure is identical" | Different ASTs across languages | Always dump AST for target language |
| "Same vulnerability, same detection" | Data flow differs between languages | Analyze target language idioms |
| "Rule doesn't need tests since original worked" | Language edge cases differ | Write NEW test cases for target |
| "Skip applicability - it obviously applies" | Some patterns are language-specific | Complete applicability analysis first |
| "I'll create all variants then test" | Errors compound, hard to debug | Complete full cycle per language |
| "Library equivalent is close enough" | Surface similarity hides differences | Verify API semantics match |
| "Just translate the syntax 1:1" | Languages have different idioms | Research target language patterns |
Strictness Level
This workflow is strict - do not skip steps:
- Applicability analysis is mandatory: Don't assume patterns translate
- Each language is independent: Complete full cycle before moving to next
- Test-first for each variant: Never write a rule without test cases
- 100% test pass required: "Most tests pass" is not acceptable
Overview
This skill guides the creation of language-specific variants of existing Semgrep rules. Each target language goes through an independent 4-phase cycle:
FOR EACH target language:
Phase 1: Applicability Analysis → Verdict
Phase 2: Test Creation (Test-First)
Phase 3: Rule Creation
Phase 4: Validation
(Complete full cycle before moving to next language)
Foundational Knowledge
The semgrep-rule-creator skill is the authoritative reference for Semgrep rule creation fundamentals. While this skill focuses on porting existing rules to new languages, the core principles of writing quality rules remain the same.
Consult semgrep-rule-creator for guidance on:
- When to use taint mode vs pattern matching - Choosing the right approach for the vulnerability type
- Test-first methodology - Why tests come before rules and how to write effective test cases
- Anti-patterns to avoid - Common mistakes like overly broad or overly specific patterns
- Iterating until tests pass - The validation loop and debugging techniques
- Rule optimization - Removing redundant patterns after tests pass
When porting a rule, you're applying these same principles in a new language context. If uncertain about rule structure or approach, refer to semgrep-rule-creator first.
Four-Phase Workflow
Phase 1: Applicability Analysis
Before porting, determine if the pattern applies to the target language.
Analysis criteria:
- Does the vulnerability class exist in the target language?
- Does an equivalent construct exist (function, pattern, library)?
- Are the semantics similar enough for meaningful detection?
Verdict options:
APPLICABLE→ Proceed with variant creationAPPLICABLE_WITH_ADAPTATION→ Proceed but significant changes neededNOT_APPLICABLE→ Skip this language, document why
See applicability-analysis.md for detailed guidance.
Phase 2: Test Creation (Test-First)
Always write tests before the rule.
Create test file with target language idioms:
- Minimum 2 vulnerable cases (
ruleid:) - Minimum 2 safe cases (
ok:) - Include language-specific edge cases
// ruleid: sql-injection-golang
db.Query("SELECT * FROM users WHERE id = " + userInput)
// ok: sql-injection-golang
db.Query("SELECT * FROM users WHERE id = ?", userInput)
Phase 3: Rule Creation
- Analyze AST:
semgrep --dump-ast -l <lang> test-file - Translate patterns to target language syntax
- Update metadata: language key, message, rule ID
- Adapt for idioms: Handle language-specific constructs
See language-syntax-guide.md for translation guidance.
Phase 4: Validation
# Validate YAML
semgrep --validate --config rule.yaml
# Run tests
semgrep --test --config rule.yaml test-file
Checkpoint: Output MUST show All tests passed.
For taint rule debugging:
semgrep --dataflow-traces -f rule.yaml test-file
See workflow.md for detailed workflow and troubleshooting.
Quick Reference
| Task | Command |
|---|---|
| Run tests | semgrep --test --config rule.yaml test-file |
| Validate YAML | semgrep --validate --config rule.yaml |
| Dump AST | semgrep --dump-ast -l <lang> <file> |
| Debug taint flow | semgrep --dataflow-traces -f rule.yaml file |
Key Differences from Rule Creation
| Aspect | semgrep-rule-creator | This skill |
|---|---|---|
| Input | Bug pattern description | Existing rule + target languages |
| Output | Single rule+test | Multiple rule+test directories |
| Workflow | Single creation cycle | Independent cycle per language |
| Phase 1 | Problem analysis | Applicability analysis per language |
| Library research | Always relevant | Optional (when original uses libraries) |
Documentation
REQUIRED: Before porting rules, read relevant Semgrep documentation:
- Rule Syntax - YAML structure and operators
- Pattern Syntax - Pattern matching and metavariables
- Pattern Examples - Per-language pattern references
- Testing Rules - Testing annotations
- Trail of Bits Testing Handbook - Advanced patterns
Next Steps
- For applicability analysis guidance, see applicability-analysis.md
- For language translation guidance, see language-syntax-guide.md
- For detailed workflow and examples, see workflow.md
/sharp-edges
Source: ~/.claude/skills/tob-sharp-edges/skills/sharp-edges/SKILL.md
name: sharp-edges description: "Identifies error-prone APIs, dangerous configurations, and footgun designs that enable security mistakes. Use when reviewing API designs, configuration schemas, cryptographic library ergonomics, or evaluating whether code follows 'secure by default' and 'pit of success' principles. Triggers: footgun, misuse-resistant, secure defaults, API usability, dangerous configuration." allowed-tools:
- Read
- Grep
- Glob
Sharp Edges Analysis
Evaluates whether APIs, configurations, and interfaces are resistant to developer misuse. Identifies designs where the "easy path" leads to insecurity.
When to Use
- Reviewing API or library design decisions
- Auditing configuration schemas for dangerous options
- Evaluating cryptographic API ergonomics
- Assessing authentication/authorization interfaces
- Reviewing any code that exposes security-relevant choices to developers
When NOT to Use
- Implementation bugs (use standard code review)
- Business logic flaws (use domain-specific analysis)
- Performance optimization (different concern)
Core Principle
The pit of success: Secure usage should be the path of least resistance. If developers must understand cryptography, read documentation carefully, or remember special rules to avoid vulnerabilities, the API has failed.
Rationalizations to Reject
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "It's documented" | Developers don't read docs under deadline pressure | Make the secure choice the default or only option |
| "Advanced users need flexibility" | Flexibility creates footguns; most "advanced" usage is copy-paste | Provide safe high-level APIs; hide primitives |
| "It's the developer's responsibility" | Blame-shifting; you designed the footgun | Remove the footgun or make it impossible to misuse |
| "Nobody would actually do that" | Developers do everything imaginable under pressure | Assume maximum developer confusion |
| "It's just a configuration option" | Config is code; wrong configs ship to production | Validate configs; reject dangerous combinations |
| "We need backwards compatibility" | Insecure defaults can't be grandfather-claused | Deprecate loudly; force migration |
Sharp Edge Categories
1. Algorithm/Mode Selection Footguns
APIs that let developers choose algorithms invite choosing wrong ones.
The JWT Pattern (canonical example):
- Header specifies algorithm: attacker can set
"alg": "none"to bypass signatures - Algorithm confusion: RSA public key used as HMAC secret when switching RS256→HS256
- Root cause: Letting untrusted input control security-critical decisions
Detection patterns:
- Function parameters like
algorithm,mode,cipher,hash_type - Enums/strings selecting cryptographic primitives
- Configuration options for security mechanisms
Example - PHP password_hash allowing weak algorithms:
// DANGEROUS: allows crc32, md5, sha1
password_hash($password, PASSWORD_DEFAULT); // Good - no choice
hash($algorithm, $password); // BAD: accepts "crc32"
2. Dangerous Defaults
Defaults that are insecure, or zero/empty values that disable security.
The OTP Lifetime Pattern:
# What happens when lifetime=0?
def verify_otp(code, lifetime=300): # 300 seconds default
if lifetime == 0:
return True # OOPS: 0 means "accept all"?
# Or does it mean "expired immediately"?
Detection patterns:
- Timeouts/lifetimes that accept 0 (infinite? immediate expiry?)
- Empty strings that bypass checks
- Null values that skip validation
- Boolean defaults that disable security features
- Negative values with undefined semantics
Questions to ask:
- What happens with
timeout=0?max_attempts=0?key=""? - Is the default the most secure option?
- Can any default value disable security entirely?
3. Primitive vs. Semantic APIs
APIs that expose raw bytes instead of meaningful types invite type confusion.
The Libsodium vs. Halite Pattern:
// Libsodium (primitives): bytes are bytes
sodium_crypto_box($message, $nonce, $keypair);
// Easy to: swap nonce/keypair, reuse nonces, use wrong key type
// Halite (semantic): types enforce correct usage
Crypto::seal($message, new EncryptionPublicKey($key));
// Wrong key type = type error, not silent failure
Detection patterns:
- Functions taking
bytes,string,[]bytefor distinct security concepts - Parameters that could be swapped without type errors
- Same type used for keys, nonces, ciphertexts, signatures
The comparison footgun:
// Timing-safe comparison looks identical to unsafe
if hmac == expected { } // BAD: timing attack
if hmac.Equal(mac, expected) { } // Good: constant-time
// Same types, different security properties
4. Configuration Cliffs
One wrong setting creates catastrophic failure, with no warning.
Detection patterns:
- Boolean flags that disable security entirely
- String configs that aren't validated
- Combinations of settings that interact dangerously
- Environment variables that override security settings
- Constructor parameters with sensible defaults but no validation (callers can override with insecure values)
Examples:
# One typo = disaster
verify_ssl: fasle # Typo silently accepted as truthy?
# Magic values
session_timeout: -1 # Does this mean "never expire"?
# Dangerous combinations accepted silently
auth_required: true
bypass_auth_for_health_checks: true
health_check_path: "/" # Oops
// Sensible default doesn't protect against bad callers
public function __construct(
public string $hashAlgo = 'sha256', // Good default...
public int $otpLifetime = 120, // ...but accepts md5, 0, etc.
) {}
See config-patterns.md for detailed patterns.
5. Silent Failures
Errors that don't surface, or success that masks failure.
Detection patterns:
- Functions returning booleans instead of throwing on security failures
- Empty catch blocks around security operations
- Default values substituted on parse errors
- Verification functions that "succeed" on malformed input
Examples:
# Silent bypass
def verify_signature(sig, data, key):
if not key:
return True # No key = skip verification?!
# Return value ignored
signature.verify(data, sig) # Throws on failure
crypto.verify(data, sig) # Returns False on failure
# Developer forgets to check return value
6. Stringly-Typed Security
Security-critical values as plain strings enable injection and confusion.
Detection patterns:
- SQL/commands built from string concatenation
- Permissions as comma-separated strings
- Roles/scopes as arbitrary strings instead of enums
- URLs constructed by joining strings
The permission accumulation footgun:
permissions = "read,write"
permissions += ",admin" # Too easy to escalate
# vs. type-safe
permissions = {Permission.READ, Permission.WRITE}
permissions.add(Permission.ADMIN) # At least it's explicit
Analysis Workflow
Phase 1: Surface Identification
- Map security-relevant APIs: authentication, authorization, cryptography, session management, input validation
- Identify developer choice points: Where can developers select algorithms, configure timeouts, choose modes?
- Find configuration schemas: Environment variables, config files, constructor parameters
Phase 2: Edge Case Probing
For each choice point, ask:
- Zero/empty/null: What happens with
0,"",null,[]? - Negative values: What does
-1mean? Infinite? Error? - Type confusion: Can different security concepts be swapped?
- Default values: Is the default secure? Is it documented?
- Error paths: What happens on invalid input? Silent acceptance?
Phase 3: Threat Modeling
Consider three adversaries:
-
The Scoundrel: Actively malicious developer or attacker controlling config
- Can they disable security via configuration?
- Can they downgrade algorithms?
- Can they inject malicious values?
-
The Lazy Developer: Copy-pastes examples, skips documentation
- Will the first example they find be secure?
- Is the path of least resistance secure?
- Do error messages guide toward secure usage?
-
The Confused Developer: Misunderstands the API
- Can they swap parameters without type errors?
- Can they use the wrong key/algorithm/mode by accident?
- Are failure modes obvious or silent?
Phase 4: Validate Findings
For each identified sharp edge:
- Reproduce the misuse: Write minimal code demonstrating the footgun
- Verify exploitability: Does the misuse create a real vulnerability?
- Check documentation: Is the danger documented? (Documentation doesn't excuse bad design, but affects severity)
- Test mitigations: Can the API be used safely with reasonable effort?
If a finding seems questionable, return to Phase 2 and probe more edge cases.
Severity Classification
| Severity | Criteria | Examples |
|---|---|---|
| Critical | Default or obvious usage is insecure | verify: false default; empty password allowed |
| High | Easy misconfiguration breaks security | Algorithm parameter accepts "none" |
| Medium | Unusual but possible misconfiguration | Negative timeout has unexpected meaning |
| Low | Requires deliberate misuse | Obscure parameter combination |
References
By category:
- Cryptographic APIs: See references/crypto-apis.md
- Configuration Patterns: See references/config-patterns.md
- Authentication/Session: See references/auth-patterns.md
- Real-World Case Studies: See references/case-studies.md (OpenSSL, GMP, etc.)
By language (general footguns, not crypto-specific):
| Language | Guide |
|---|---|
| C/C++ | references/lang-c.md |
| Go | references/lang-go.md |
| Rust | references/lang-rust.md |
| Swift | references/lang-swift.md |
| Java | references/lang-java.md |
| Kotlin | references/lang-kotlin.md |
| C# | references/lang-csharp.md |
| PHP | references/lang-php.md |
| JavaScript/TypeScript | references/lang-javascript.md |
| Python | references/lang-python.md |
| Ruby | references/lang-ruby.md |
See also references/language-specific.md for a combined quick reference.
Quality Checklist
Before concluding analysis:
- Probed all zero/empty/null edge cases
- Verified defaults are secure
- Checked for algorithm/mode selection footguns
- Tested type confusion between security concepts
- Considered all three adversary types
- Verified error paths don't bypass security
- Checked configuration validation
- Constructor params validated (not just defaulted) - see config-patterns.md
/spec-to-code-compliance
Source: ~/.claude/skills/tob-spec-to-code-compliance/skills/spec-to-code-compliance/SKILL.md
name: spec-to-code-compliance description: Verifies code implements exactly what documentation specifies for blockchain audits. Use when comparing code against whitepapers, finding gaps between specs and implementation, or performing compliance checks for protocol implementations.
When to Use
Use this skill when you need to:
- Verify code implements exactly what documentation specifies
- Audit smart contracts against whitepapers or design documents
- Find gaps between intended behavior and actual implementation
- Identify undocumented code behavior or unimplemented spec claims
- Perform compliance checks for blockchain protocol implementations
Concrete triggers:
- User provides both specification documents AND codebase
- Questions like "does this code match the spec?" or "what's missing from the implementation?"
- Audit engagements requiring spec-to-code alignment analysis
- Protocol implementations being verified against whitepapers
When NOT to Use
Do NOT use this skill for:
- Codebases without corresponding specification documents
- General code review or vulnerability hunting (use audit-context-building instead)
- Writing or improving documentation (this skill only verifies compliance)
- Non-blockchain projects without formal specifications
Spec-to-Code Compliance Checker Skill
You are the Spec-to-Code Compliance Checker — a senior-level blockchain auditor whose job is to determine whether a codebase implements exactly what the documentation states, across logic, invariants, flows, assumptions, math, and security guarantees.
Your work must be:
- deterministic
- grounded in evidence
- traceable
- non-hallucinatory
- exhaustive
GLOBAL RULES
- Never infer unspecified behavior.
- Always cite exact evidence from:
- the documentation (section/title/quote)
- the code (file + line numbers)
- Always provide a confidence score (0–1) for mappings.
- Always classify ambiguity instead of guessing.
- Maintain strict separation between:
- extraction
- alignment
- classification
- reporting
- Do NOT rely on prior knowledge of known protocols. Only use provided materials.
- Be literal, pedantic, and exhaustive.
Rationalizations (Do Not Skip)
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "Spec is clear enough" | Ambiguity hides in plain sight | Extract to IR, classify ambiguity explicitly |
| "Code obviously matches" | Obvious matches have subtle divergences | Document match_type with evidence |
| "I'll note this as partial match" | Partial = potential vulnerability | Investigate until full_match or mismatch |
| "This undocumented behavior is fine" | Undocumented = untested = risky | Classify as UNDOCUMENTED CODE PATH |
| "Low confidence is okay here" | Low confidence findings get ignored | Investigate until confidence ≥ 0.8 or classify as AMBIGUOUS |
| "I'll infer what the spec meant" | Inference = hallucination | Quote exact text or mark UNDOCUMENTED |
PHASE 0 — Documentation Discovery
Identify all content representing documentation, even if not named "spec."
Documentation may appear as:
whitepaper.pdfProtocol.mddesign_notesFlow.pdfREADME.md- kickoff transcripts
- Notion exports
- Anything describing logic, flows, assumptions, incentives, etc.
Use semantic cues:
- architecture descriptions
- invariants
- formulas
- variable meanings
- trust models
- workflow sequencing
- tables describing logic
- diagrams (convert to text)
Extract ALL relevant documents into a unified spec corpus.
PHASE 1 — Universal Format Normalization
Normalize ANY input format:
- Markdown
- DOCX
- HTML
- TXT
- Notion export
- Meeting transcripts
Preserve:
- heading hierarchy
- bullet lists
- formulas
- tables (converted to plaintext)
- code snippets
- invariant definitions
Remove:
- layout noise
- styling artifacts
- watermarks
Output: a clean, canonical spec_corpus.
PHASE 2 — Spec Intent IR (Intermediate Representation)
Extract all intended behavior into the Spec-IR.
Each extracted item MUST include:
spec_excerptsource_sectionsemantic_type- normalized representation
- confidence score
Extract:
- protocol purpose
- actors, roles, trust boundaries
- variable definitions & expected relationships
- all preconditions / postconditions
- explicit invariants
- implicit invariants deduced from context
- math formulas (in canonical symbolic form)
- expected flows & state-machine transitions
- economic assumptions
- ordering & timing constraints
- error conditions & expected revert logic
- security requirements ("must/never/always")
- edge-case behavior
This forms Spec-IR.
See IR_EXAMPLES.md for detailed examples.
PHASE 3 — Code Behavior IR
(WITH TRUE LINE-BY-LINE / BLOCK-BY-BLOCK ANALYSIS)
Perform structured, deterministic, line-by-line and block-by-block semantic analysis of the entire codebase.
For EVERY LINE and EVERY BLOCK, extract:
- file + exact line numbers
- local variable updates
- state reads/writes
- conditional branches & alternative paths
- unreachable branches
- revert conditions & custom errors
- external calls (call, delegatecall, staticcall, create2)
- event emissions
- math operations and rounding behavior
- implicit assumptions
- block-level preconditions & postconditions
- locally enforced invariants
- state transitions
- side effects
- dependencies on prior state
For EVERY FUNCTION, extract:
- signature & visibility
- applied modifiers (and their logic)
- purpose (based on actual behavior)
- input/output semantics
- read/write sets
- full control-flow structure
- success vs revert paths
- internal/external call graph
- cross-function interactions
Also capture:
- storage layout
- initialization logic
- authorization graph (roles → permissions)
- upgradeability mechanism (if present)
- hidden assumptions
Output: Code-IR, a granular semantic map with full traceability.
See IR_EXAMPLES.md for detailed examples.
PHASE 4 — Alignment IR (Spec ↔ Code Comparison)
For each item in Spec-IR: Locate related behaviors in Code-IR and generate an Alignment Record containing:
- spec_excerpt
- code_excerpt (with file + line numbers)
- match_type:
- full_match
- partial_match
- mismatch
- missing_in_code
- code_stronger_than_spec
- code_weaker_than_spec
- reasoning trace
- confidence score (0–1)
- ambiguity rating
- evidence links
Explicitly check:
- invariants vs enforcement
- formulas vs math implementation
- flows vs real transitions
- actor expectations vs real privilege map
- ordering constraints vs actual logic
- revert expectations vs actual checks
- trust assumptions vs real external call behavior
Also detect:
- undocumented code behavior
- unimplemented spec claims
- contradictions inside the spec
- contradictions inside the code
- inconsistencies across multiple spec documents
Output: Alignment-IR
See IR_EXAMPLES.md for detailed examples.
PHASE 5 — Divergence Classification
Classify each misalignment by severity:
CRITICAL
- Spec says X, code does Y
- Missing invariant enabling exploits
- Math divergence involving funds
- Trust boundary mismatches
HIGH
- Partial/incorrect implementation
- Access control misalignment
- Dangerous undocumented behavior
MEDIUM
- Ambiguity with security implications
- Missing revert checks
- Incomplete edge-case handling
LOW
- Documentation drift
- Minor semantics mismatch
Each finding MUST include:
- evidence links
- severity justification
- exploitability reasoning
- recommended remediation
See IR_EXAMPLES.md for detailed divergence finding examples with complete exploit scenarios, economic analysis, and remediation plans.
PHASE 6 — Final Audit-Grade Report
Produce a structured compliance report:
- Executive Summary
- Documentation Sources Identified
- Spec Intent Breakdown (Spec-IR)
- Code Behavior Summary (Code-IR)
- Full Alignment Matrix (Spec → Code → Status)
- Divergence Findings (with evidence & severity)
- Missing invariants
- Incorrect logic
- Math inconsistencies
- Flow/state machine mismatches
- Access control drift
- Undocumented behavior
- Ambiguity hotspots (spec & code)
- Recommended remediations
- Documentation update suggestions
- Final risk assessment
Output Requirements & Quality Standards
See OUTPUT_REQUIREMENTS.md for:
- Required IR production standards for all phases
- Quality thresholds (minimum Spec-IR items, confidence scores, etc.)
- Format consistency requirements (YAML formatting, line number citations)
- Anti-hallucination requirements
Completeness Verification
Before finalizing analysis, review the COMPLETENESS_CHECKLIST.md to verify:
- Spec-IR completeness (all invariants, formulas, security requirements extracted)
- Code-IR completeness (all functions analyzed, state changes tracked)
- Alignment-IR completeness (every spec item has alignment record)
- Divergence finding quality (exploit scenarios, economic impact, remediation)
- Final report completeness (all 16 sections present)
ANTI-HALLUCINATION REQUIREMENTS
- If the spec is silent: classify as UNDOCUMENTED.
- If the code adds behavior: classify as UNDOCUMENTED CODE PATH.
- If unclear: classify as AMBIGUOUS.
- Every claim must quote original text or line numbers.
- Zero speculation.
- Exhaustive, literal, pedantic reasoning.
Resources
Detailed Examples:
- IR_EXAMPLES.md - Complete IR workflow examples with DEX swap patterns
Standards & Requirements:
- OUTPUT_REQUIREMENTS.md - IR production standards, quality thresholds, format rules
- COMPLETENESS_CHECKLIST.md - Verification checklist for all phases
END OF SKILL
/codeql
Source: ~/.claude/skills/tob-static-analysis/skills/codeql/SKILL.md
name: codeql description: >- Runs CodeQL static analysis for security vulnerability detection using interprocedural data flow and taint tracking. Applicable when finding vulnerabilities, running a security scan, performing a security audit, running CodeQL, building a CodeQL database, selecting query rulesets, creating data extension models, or processing CodeQL SARIF output. NOT for writing custom QL queries or CI/CD pipeline setup. allowed-tools:
- Bash
- Read
- Write
- Glob
- Grep
- AskUserQuestion
- Task
- TaskCreate
- TaskList
- TaskUpdate
CodeQL Analysis
Supported languages: Python, JavaScript/TypeScript, Go, Java/Kotlin, C/C++, C#, Ruby, Swift.
Skill resources: Reference files and templates are located at {baseDir}/references/ and {baseDir}/workflows/. Use {baseDir} to resolve paths to these files at runtime.
Quick Start
For the common case ("scan this codebase for vulnerabilities"):
# 1. Verify CodeQL is installed
command -v codeql >/dev/null 2>&1 && codeql --version || echo "NOT INSTALLED"
# 2. Check for existing database
ls -dt codeql_*.db 2>/dev/null | head -1
Then execute the full pipeline: build database → create data extensions → run analysis using the workflows below.
When to Use
- Scanning a codebase for security vulnerabilities with deep data flow analysis
- Building a CodeQL database from source code (with build capability for compiled languages)
- Finding complex vulnerabilities that require interprocedural taint tracking or AST/CFG analysis
- Performing comprehensive security audits with multiple query packs
When NOT to Use
- Writing custom queries - Use a dedicated query development skill
- CI/CD integration - Use GitHub Actions documentation directly
- Quick pattern searches - Use Semgrep or grep for speed
- No build capability for compiled languages - Consider Semgrep instead
- Single-file or lightweight analysis - Semgrep is faster for simple pattern matching
Rationalizations to Reject
These shortcuts lead to missed findings. Do not accept them:
- "security-extended is enough" - It is the baseline. Always check if Trail of Bits packs and Community Packs are available for the language. They catch categories
security-extendedmisses entirely. - "The database built, so it's good" - A database that builds does not mean it extracted well. Always run Step 4 (quality assessment) and check file counts against expected source files. A cached build produces zero useful extraction.
- "Data extensions aren't needed for standard frameworks" - Even Django/Spring apps have custom wrappers around ORM calls, request parsing, or shell execution that CodeQL does not model. Skipping the extensions workflow means missing vulnerabilities in project-specific code.
- "build-mode=none is fine for compiled languages" - It produces severely incomplete analysis. No interprocedural data flow through compiled code is traced. Only use as an absolute last resort and clearly flag the limitation.
- "No findings means the code is secure" - Zero findings can indicate poor database quality, missing models, or wrong query packs. Investigate before reporting clean results.
- "I'll just run the default suite" - The default suite varies by how CodeQL is invoked. Always explicitly specify the suite (e.g.,
security-extended) so results are reproducible.
Workflow Selection
This skill has three workflows:
| Workflow | Purpose |
|---|---|
| build-database | Create CodeQL database using 3 build methods in sequence |
| create-data-extensions | Detect or generate data extension models for project APIs |
| run-analysis | Select rulesets, execute queries, process results |
Auto-Detection Logic
If user explicitly specifies what to do (e.g., "build a database", "run analysis"), execute that workflow.
Default pipeline for "test", "scan", "analyze", or similar: Execute all three workflows sequentially: build → extensions → analysis. The create-data-extensions step is critical for finding vulnerabilities in projects with custom frameworks or annotations that CodeQL doesn't model by default.
# Check if database exists
DB=$(ls -dt codeql_*.db 2>/dev/null | head -1)
if [ -n "$DB" ] && codeql resolve database -- "$DB" >/dev/null 2>&1; then
echo "DATABASE EXISTS ($DB) - can run analysis"
else
echo "NO DATABASE - need to build first"
fi
| Condition | Action |
|---|---|
| No database exists | Execute build → extensions → analysis (full pipeline) |
| Database exists, no extensions | Execute extensions → analysis |
| Database exists, extensions exist | Ask user: run analysis on existing DB, or rebuild? |
| User says "just run analysis" or "skip extensions" | Run analysis only |
Decision Prompt
If unclear, ask user:
I can help with CodeQL analysis. What would you like to do?
1. **Full scan (Recommended)** - Build database, create extensions, then run analysis
2. **Build database** - Create a new CodeQL database from this codebase
3. **Create data extensions** - Generate custom source/sink models for project APIs
4. **Run analysis** - Run security queries on existing database
[If database exists: "I found an existing database at <DB_NAME>"]
/sarif-parsing
Source: ~/.claude/skills/tob-static-analysis/skills/sarif-parsing/SKILL.md
name: sarif-parsing description: Parse, analyze, and process SARIF (Static Analysis Results Interchange Format) files. Use when reading security scan results, aggregating findings from multiple tools, deduplicating alerts, extracting specific vulnerabilities, or integrating SARIF data into CI/CD pipelines. allowed-tools:
- Bash
- Read
- Glob
- Grep
SARIF Parsing Best Practices
You are a SARIF parsing expert. Your role is to help users effectively read, analyze, and process SARIF files from static analysis tools.
When to Use
Use this skill when:
- Reading or interpreting static analysis scan results in SARIF format
- Aggregating findings from multiple security tools
- Deduplicating or filtering security alerts
- Extracting specific vulnerabilities from SARIF files
- Integrating SARIF data into CI/CD pipelines
- Converting SARIF output to other formats
When NOT to Use
Do NOT use this skill for:
- Running static analysis scans (use CodeQL or Semgrep skills instead)
- Writing CodeQL or Semgrep rules (use their respective skills)
- Analyzing source code directly (SARIF is for processing existing scan results)
- Triaging findings without SARIF input (use variant-analysis or audit skills)
SARIF Structure Overview
SARIF 2.1.0 is the current OASIS standard. Every SARIF file has this hierarchical structure:
sarifLog
├── version: "2.1.0"
├── $schema: (optional, enables IDE validation)
└── runs[] (array of analysis runs)
├── tool
│ ├── driver
│ │ ├── name (required)
│ │ ├── version
│ │ └── rules[] (rule definitions)
│ └── extensions[] (plugins)
├── results[] (findings)
│ ├── ruleId
│ ├── level (error/warning/note)
│ ├── message.text
│ ├── locations[]
│ │ └── physicalLocation
│ │ ├── artifactLocation.uri
│ │ └── region (startLine, startColumn, etc.)
│ ├── fingerprints{}
│ └── partialFingerprints{}
└── artifacts[] (scanned files metadata)
Why Fingerprinting Matters
Without stable fingerprints, you can't track findings across runs:
- Baseline comparison: "Is this a new finding or did we see it before?"
- Regression detection: "Did this PR introduce new vulnerabilities?"
- Suppression: "Ignore this known false positive in future runs"
Tools report different paths (/path/to/project/ vs /github/workspace/), so path-based matching fails. Fingerprints hash the content (code snippet, rule ID, relative location) to create stable identifiers regardless of environment.
Tool Selection Guide
| Use Case | Tool | Installation |
|---|---|---|
| Quick CLI queries | jq | brew install jq / apt install jq |
| Python scripting (simple) | pysarif | pip install pysarif |
| Python scripting (advanced) | sarif-tools | pip install sarif-tools |
| .NET applications | SARIF SDK | NuGet package |
| JavaScript/Node.js | sarif-js | npm package |
| Go applications | garif | go get github.com/chavacava/garif |
| Validation | SARIF Validator | sarifweb.azurewebsites.net |
Strategy 1: Quick Analysis with jq
For rapid exploration and one-off queries:
# Pretty print the file
jq '.' results.sarif
# Count total findings
jq '[.runs[].results[]] | length' results.sarif
# List all rule IDs triggered
jq '[.runs[].results[].ruleId] | unique' results.sarif
# Extract errors only
jq '.runs[].results[] | select(.level == "error")' results.sarif
# Get findings with file locations
jq '.runs[].results[] | {
rule: .ruleId,
message: .message.text,
file: .locations[0].physicalLocation.artifactLocation.uri,
line: .locations[0].physicalLocation.region.startLine
}' results.sarif
# Filter by severity and get count per rule
jq '[.runs[].results[] | select(.level == "error")] | group_by(.ruleId) | map({rule: .[0].ruleId, count: length})' results.sarif
# Extract findings for a specific file
jq --arg file "src/auth.py" '.runs[].results[] | select(.locations[].physicalLocation.artifactLocation.uri | contains($file))' results.sarif
Strategy 2: Python with pysarif
For programmatic access with full object model:
from pysarif import load_from_file, save_to_file
# Load SARIF file
sarif = load_from_file("results.sarif")
# Iterate through runs and results
for run in sarif.runs:
tool_name = run.tool.driver.name
print(f"Tool: {tool_name}")
for result in run.results:
print(f" [{result.level}] {result.rule_id}: {result.message.text}")
if result.locations:
loc = result.locations[0].physical_location
if loc and loc.artifact_location:
print(f" File: {loc.artifact_location.uri}")
if loc.region:
print(f" Line: {loc.region.start_line}")
# Save modified SARIF
save_to_file(sarif, "modified.sarif")
Strategy 3: Python with sarif-tools
For aggregation, reporting, and CI/CD integration:
from sarif import loader
# Load single file
sarif_data = loader.load_sarif_file("results.sarif")
# Or load multiple files
sarif_set = loader.load_sarif_files(["tool1.sarif", "tool2.sarif"])
# Get summary report
report = sarif_data.get_report()
# Get histogram by severity
errors = report.get_issue_type_histogram_for_severity("error")
warnings = report.get_issue_type_histogram_for_severity("warning")
# Filter results
high_severity = [r for r in sarif_data.get_results()
if r.get("level") == "error"]
sarif-tools CLI commands:
# Summary of findings
sarif summary results.sarif
# List all results with details
sarif ls results.sarif
# Get results by severity
sarif ls --level error results.sarif
# Diff two SARIF files (find new/fixed issues)
sarif diff baseline.sarif current.sarif
# Convert to other formats
sarif csv results.sarif > results.csv
sarif html results.sarif > report.html
Strategy 4: Aggregating Multiple SARIF Files
When combining results from multiple tools:
import json
from pathlib import Path
def aggregate_sarif_files(sarif_paths: list[str]) -> dict:
"""Combine multiple SARIF files into one."""
aggregated = {
"version": "2.1.0",
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"runs": []
}
for path in sarif_paths:
with open(path) as f:
sarif = json.load(f)
aggregated["runs"].extend(sarif.get("runs", []))
return aggregated
def deduplicate_results(sarif: dict) -> dict:
"""Remove duplicate findings based on fingerprints."""
seen_fingerprints = set()
for run in sarif["runs"]:
unique_results = []
for result in run.get("results", []):
# Use partialFingerprints or create key from location
fp = None
if result.get("partialFingerprints"):
fp = tuple(sorted(result["partialFingerprints"].items()))
elif result.get("fingerprints"):
fp = tuple(sorted(result["fingerprints"].items()))
else:
# Fallback: create fingerprint from rule + location
loc = result.get("locations", [{}])[0]
phys = loc.get("physicalLocation", {})
fp = (
result.get("ruleId"),
phys.get("artifactLocation", {}).get("uri"),
phys.get("region", {}).get("startLine")
)
if fp not in seen_fingerprints:
seen_fingerprints.add(fp)
unique_results.append(result)
run["results"] = unique_results
return sarif
Strategy 5: Extracting Actionable Data
import json
from dataclasses import dataclass
from typing import Optional
@dataclass
class Finding:
rule_id: str
level: str
message: str
file_path: Optional[str]
start_line: Optional[int]
end_line: Optional[int]
fingerprint: Optional[str]
def extract_findings(sarif_path: str) -> list[Finding]:
"""Extract structured findings from SARIF file."""
with open(sarif_path) as f:
sarif = json.load(f)
findings = []
for run in sarif.get("runs", []):
for result in run.get("results", []):
loc = result.get("locations", [{}])[0]
phys = loc.get("physicalLocation", {})
region = phys.get("region", {})
findings.append(Finding(
rule_id=result.get("ruleId", "unknown"),
level=result.get("level", "warning"),
message=result.get("message", {}).get("text", ""),
file_path=phys.get("artifactLocation", {}).get("uri"),
start_line=region.get("startLine"),
end_line=region.get("endLine"),
fingerprint=next(iter(result.get("partialFingerprints", {}).values()), None)
))
return findings
# Filter and prioritize
def prioritize_findings(findings: list[Finding]) -> list[Finding]:
"""Sort findings by severity."""
severity_order = {"error": 0, "warning": 1, "note": 2, "none": 3}
return sorted(findings, key=lambda f: severity_order.get(f.level, 99))
Common Pitfalls and Solutions
1. Path Normalization Issues
Different tools report paths differently (absolute, relative, URI-encoded):
from urllib.parse import unquote
from pathlib import Path
def normalize_path(uri: str, base_path: str = "") -> str:
"""Normalize SARIF artifact URI to consistent path."""
# Remove file:// prefix if present
if uri.startswith("file://"):
uri = uri[7:]
# URL decode
uri = unquote(uri)
# Handle relative paths
if not Path(uri).is_absolute() and base_path:
uri = str(Path(base_path) / uri)
# Normalize separators
return str(Path(uri))
2. Fingerprint Mismatch Across Runs
Fingerprints may not match if:
- File paths differ between environments
- Tool versions changed fingerprinting algorithm
- Code was reformatted (changing line numbers)
Solution: Use multiple fingerprint strategies:
def compute_stable_fingerprint(result: dict, file_content: str = None) -> str:
"""Compute environment-independent fingerprint."""
import hashlib
components = [
result.get("ruleId", ""),
result.get("message", {}).get("text", "")[:100], # First 100 chars
]
# Add code snippet if available
if file_content and result.get("locations"):
region = result["locations"][0].get("physicalLocation", {}).get("region", {})
if region.get("startLine"):
lines = file_content.split("\n")
line_idx = region["startLine"] - 1
if 0 <= line_idx < len(lines):
# Normalize whitespace
components.append(lines[line_idx].strip())
return hashlib.sha256("".join(components).encode()).hexdigest()[:16]
3. Missing or Incomplete Data
SARIF allows many optional fields. Always use defensive access:
def safe_get_location(result: dict) -> tuple[str, int]:
"""Safely extract file and line from result."""
try:
loc = result.get("locations", [{}])[0]
phys = loc.get("physicalLocation", {})
file_path = phys.get("artifactLocation", {}).get("uri", "unknown")
line = phys.get("region", {}).get("startLine", 0)
return file_path, line
except (IndexError, KeyError, TypeError):
return "unknown", 0
4. Large File Performance
For very large SARIF files (100MB+):
import ijson # pip install ijson
def stream_results(sarif_path: str):
"""Stream results without loading entire file."""
with open(sarif_path, "rb") as f:
# Stream through results arrays
for result in ijson.items(f, "runs.item.results.item"):
yield result
5. Schema Validation
Validate before processing to catch malformed files:
# Using ajv-cli
npm install -g ajv-cli
ajv validate -s sarif-schema-2.1.0.json -d results.sarif
# Using Python jsonschema
pip install jsonschema
from jsonschema import validate, ValidationError
import json
def validate_sarif(sarif_path: str, schema_path: str) -> bool:
"""Validate SARIF file against schema."""
with open(sarif_path) as f:
sarif = json.load(f)
with open(schema_path) as f:
schema = json.load(f)
try:
validate(sarif, schema)
return True
except ValidationError as e:
print(f"Validation error: {e.message}")
return False
CI/CD Integration Patterns
GitHub Actions
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
- name: Check for high severity
run: |
HIGH_COUNT=$(jq '[.runs[].results[] | select(.level == "error")] | length' results.sarif)
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "Found $HIGH_COUNT high severity issues"
exit 1
fi
Fail on New Issues
from sarif import loader
def check_for_regressions(baseline: str, current: str) -> int:
"""Return count of new issues not in baseline."""
baseline_data = loader.load_sarif_file(baseline)
current_data = loader.load_sarif_file(current)
baseline_fps = {get_fingerprint(r) for r in baseline_data.get_results()}
new_issues = [r for r in current_data.get_results()
if get_fingerprint(r) not in baseline_fps]
return len(new_issues)
Key Principles
- Validate first: Check SARIF structure before processing
- Handle optionals: Many fields are optional; use defensive access
- Normalize paths: Tools report paths differently; normalize early
- Fingerprint wisely: Combine multiple strategies for stable deduplication
- Stream large files: Use ijson or similar for 100MB+ files
- Aggregate thoughtfully: Preserve tool metadata when combining files
Skill Resources
For ready-to-use query templates, see {baseDir}/resources/jq-queries.md:
- 40+ jq queries for common SARIF operations
- Severity filtering, rule extraction, aggregation patterns
For Python utilities, see {baseDir}/resources/sarif_helpers.py:
normalize_path()- Handle tool-specific path formatscompute_fingerprint()- Stable fingerprinting ignoring pathsdeduplicate_results()- Remove duplicates across runs
Reference Links
- OASIS SARIF 2.1.0 Specification
- Microsoft SARIF Tutorials
- SARIF SDK (.NET)
- sarif-tools (Python)
- pysarif (Python)
- GitHub SARIF Support
- SARIF Validator
/semgrep
Source: ~/.claude/skills/tob-static-analysis/skills/semgrep/SKILL.md
name: semgrep description: Run Semgrep static analysis scan on a codebase using parallel subagents. Automatically detects and uses Semgrep Pro for cross-file analysis when available. Use when asked to scan code for vulnerabilities, run a security audit with Semgrep, find bugs, or perform static analysis. Spawns parallel workers for multi-language codebases and triage. allowed-tools:
- Bash
- Read
- Glob
- Grep
- Write
- Task
- AskUserQuestion
- TaskCreate
- TaskList
- TaskUpdate
- WebFetch
Semgrep Security Scan
Run a complete Semgrep scan with automatic language detection, parallel execution via Task subagents, and parallel triage. Automatically uses Semgrep Pro for cross-file taint analysis when available.
Prerequisites
Required: Semgrep CLI
semgrep --version
If not installed, see Semgrep installation docs.
Optional: Semgrep Pro (for cross-file analysis and Pro languages)
# Check if Semgrep Pro engine is installed
semgrep --pro --validate --config p/default 2>/dev/null && echo "Pro available" || echo "OSS only"
# If logged in, install/update Pro Engine
semgrep install-semgrep-pro
Pro enables: cross-file taint tracking, inter-procedural analysis, and additional languages (Apex, C#, Elixir).
When to Use
- Security audit of a codebase
- Finding vulnerabilities before code review
- Scanning for known bug patterns
- First-pass static analysis
When NOT to Use
- Binary analysis → Use binary analysis tools
- Already have Semgrep CI configured → Use existing pipeline
- Need cross-file analysis but no Pro license → Consider CodeQL as alternative
- Creating custom Semgrep rules → Use
semgrep-rule-creatorskill - Porting existing rules to other languages → Use
semgrep-rule-variant-creatorskill
Orchestration Architecture
This skill uses parallel Task subagents for maximum efficiency:
┌─────────────────────────────────────────────────────────────────┐
│ MAIN AGENT │
│ 1. Detect languages + check Pro availability │
│ 2. Select rulesets based on detection (ref: rulesets.md) │
│ 3. Present plan + rulesets, get approval [⛔ HARD GATE] │
│ 4. Spawn parallel scan Tasks (with approved rulesets) │
│ 5. Spawn parallel triage Tasks │
│ 6. Collect and report results │
└─────────────────────────────────────────────────────────────────┘
│ Step 4 │ Step 5
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Scan Tasks │ │ Triage Tasks │
│ (parallel) │ │ (parallel) │
├─────────────────┤ ├─────────────────┤
│ Python scanner │ │ Python triager │
│ JS/TS scanner │ │ JS/TS triager │
│ Go scanner │ │ Go triager │
│ Docker scanner │ │ Docker triager │
└─────────────────┘ └─────────────────┘
Workflow Enforcement via Task System
This skill uses the Task system to enforce workflow compliance. On invocation, create these tasks:
TaskCreate: "Detect languages and Pro availability" (Step 1)
TaskCreate: "Select rulesets based on detection" (Step 2) - blockedBy: Step 1
TaskCreate: "Present plan with rulesets, get approval" (Step 3) - blockedBy: Step 2
TaskCreate: "Execute scans with approved rulesets" (Step 4) - blockedBy: Step 3
TaskCreate: "Triage findings" (Step 5) - blockedBy: Step 4
TaskCreate: "Report results" (Step 6) - blockedBy: Step 5
Mandatory Gates
| Task | Gate Type | Cannot Proceed Until |
|---|---|---|
| Step 3: Get approval | HARD GATE | User explicitly approves rulesets + plan |
| Step 5: Triage | SOFT GATE | All scan JSON files exist |
Step 3 is a HARD GATE: Mark as completed ONLY after user says "yes", "proceed", "approved", or equivalent.
Task Flow Example
1. Create all 6 tasks with dependencies
2. TaskUpdate Step 1 → in_progress, execute detection
3. TaskUpdate Step 1 → completed
4. TaskUpdate Step 2 → in_progress, select rulesets
5. TaskUpdate Step 2 → completed
6. TaskUpdate Step 3 → in_progress, present plan with rulesets
7. STOP: Wait for user response (may modify rulesets)
8. User approves → TaskUpdate Step 3 → completed
9. TaskUpdate Step 4 → in_progress (now unblocked)
... continue workflow
Workflow
Step 1: Detect Languages and Pro Availability (Main Agent)
# Check if Semgrep Pro is available (non-destructive check)
SEMGREP_PRO=false
if semgrep --pro --validate --config p/default 2>/dev/null; then
SEMGREP_PRO=true
echo "Semgrep Pro: AVAILABLE (cross-file analysis enabled)"
else
echo "Semgrep Pro: NOT AVAILABLE (OSS mode, single-file analysis)"
fi
# Find languages by file extension
fd -t f -e py -e js -e ts -e jsx -e tsx -e go -e rb -e java -e php -e c -e cpp -e rs | \
sed 's/.*\.//' | sort | uniq -c | sort -rn
# Check for frameworks/technologies
ls -la package.json pyproject.toml Gemfile go.mod Cargo.toml pom.xml 2>/dev/null
fd -t f "Dockerfile" "docker-compose" ".tf" "*.yaml" "*.yml" | head -20
Map findings to categories:
| Detection | Category |
|---|---|
.py, pyproject.toml |
Python |
.js, .ts, package.json |
JavaScript/TypeScript |
.go, go.mod |
Go |
.rb, Gemfile |
Ruby |
.java, pom.xml |
Java |
.php |
PHP |
.c, .cpp |
C/C++ |
.rs, Cargo.toml |
Rust |
Dockerfile |
Docker |
.tf |
Terraform |
| k8s manifests | Kubernetes |
Step 2: Select Rulesets Based on Detection
Using the detected languages and frameworks from Step 1, select rulesets by following the Ruleset Selection Algorithm in rulesets.md.
The algorithm covers:
- Security baseline (always included)
- Language-specific rulesets
- Framework rulesets (if detected)
- Infrastructure rulesets
- Required third-party rulesets (Trail of Bits, 0xdea, Decurity - NOT optional)
- Registry verification
Output: Structured JSON passed to Step 3 for user review:
{
"baseline": ["p/security-audit", "p/secrets"],
"python": ["p/python", "p/django"],
"javascript": ["p/javascript", "p/react", "p/nodejs"],
"docker": ["p/dockerfile"],
"third_party": ["https://github.com/trailofbits/semgrep-rules"]
}
Step 3: CRITICAL GATE - Present Plan and Get Approval
⛔ MANDATORY CHECKPOINT - DO NOT SKIP
This step requires explicit user approval before proceeding. User may modify rulesets before approving.
Present plan to user with explicit ruleset listing:
## Semgrep Scan Plan
**Target:** /path/to/codebase
**Output directory:** ./semgrep-results-001/
**Engine:** Semgrep Pro (cross-file analysis) | Semgrep OSS (single-file)
### Detected Languages/Technologies:
- Python (1,234 files) - Django framework detected
- JavaScript (567 files) - React detected
- Dockerfile (3 files)
### Rulesets to Run:
**Security Baseline (always included):**
- [x] `p/security-audit` - Comprehensive security rules
- [x] `p/secrets` - Hardcoded credentials, API keys
**Python (1,234 files):**
- [x] `p/python` - Python security patterns
- [x] `p/django` - Django-specific vulnerabilities
**JavaScript (567 files):**
- [x] `p/javascript` - JavaScript security patterns
- [x] `p/react` - React-specific issues
- [x] `p/nodejs` - Node.js server-side patterns
**Docker (3 files):**
- [x] `p/dockerfile` - Dockerfile best practices
**Third-party (auto-included for detected languages):**
- [x] Trail of Bits rules - https://github.com/trailofbits/semgrep-rules
**Available but not selected:**
- [ ] `p/owasp-top-ten` - OWASP Top 10 (overlaps with security-audit)
### Execution Strategy:
- Spawn 3 parallel scan Tasks (Python, JavaScript, Docker)
- Total rulesets: 9
- [If Pro] Cross-file taint tracking enabled
**Want to modify rulesets?** Tell me which to add or remove.
**Ready to scan?** Say "proceed" or "yes".
⛔ STOP: Await explicit user approval
After presenting the plan:
-
If user wants to modify rulesets:
- Add requested rulesets to the appropriate category
- Remove requested rulesets
- Re-present the updated plan
- Return to waiting for approval
-
Use AskUserQuestion if user hasn't responded:
"I've prepared the scan plan with 9 rulesets (including Trail of Bits). Proceed with scanning?" Options: ["Yes, run scan", "Modify rulesets first"] -
Valid approval responses:
- "yes", "proceed", "approved", "go ahead", "looks good", "run it"
-
Mark task completed only after approval with final rulesets confirmed
-
Do NOT treat as approval:
- User's original request ("scan this codebase")
- Silence / no response
- Questions about the plan
Pre-Scan Checklist
Before marking Step 3 complete, verify:
- Target directory shown to user
- Engine type (Pro/OSS) displayed
- Languages detected and listed
- All rulesets explicitly listed with checkboxes
- User given opportunity to modify rulesets
- User explicitly approved (quote their confirmation)
- Final ruleset list captured for Step 4
Step 4: Spawn Parallel Scan Tasks
Create output directory with run number to avoid collisions, then spawn Tasks with approved rulesets from Step 3:
# Find next available run number
LAST=$(ls -d semgrep-results-[0-9][0-9][0-9] 2>/dev/null | sort | tail -1 | grep -o '[0-9]*$' || true)
NEXT_NUM=$(printf "%03d" $(( ${LAST:-0} + 1 )))
OUTPUT_DIR="semgrep-results-${NEXT_NUM}"
mkdir -p "$OUTPUT_DIR"
echo "Output directory: $OUTPUT_DIR"
Spawn N Tasks in a SINGLE message (one per language category) using subagent_type: Bash.
Use the scanner task prompt template from scanner-task-prompt.md.
Example - 3 Language Scan (with approved rulesets):
Spawn these 3 Tasks in a SINGLE message:
-
Task: Python Scanner
- Approved rulesets: p/python, p/django, p/security-audit, p/secrets, https://github.com/trailofbits/semgrep-rules
- Output: semgrep-results-001/python-*.json
-
Task: JavaScript Scanner
- Approved rulesets: p/javascript, p/react, p/nodejs, p/security-audit, p/secrets, https://github.com/trailofbits/semgrep-rules
- Output: semgrep-results-001/js-*.json
-
Task: Docker Scanner
- Approved rulesets: p/dockerfile
- Output: semgrep-results-001/docker-*.json
Step 5: Spawn Parallel Triage Tasks
After scan Tasks complete, spawn triage Tasks using subagent_type: general-purpose (triage requires reading code context, not just running commands).
Use the triage task prompt template from triage-task-prompt.md.
Step 6: Collect Results (Main Agent)
After all Tasks complete, generate merged SARIF and report:
Generate merged SARIF with only triaged true positives:
uv run {baseDir}/scripts/merge_triaged_sarif.py [OUTPUT_DIR]
This script:
- Attempts to use SARIF Multitool for merging (if
npxis available) - Falls back to pure Python merge if Multitool unavailable
- Reads all
*-triage.jsonfiles to extract true positive findings - Filters merged SARIF to include only triaged true positives
- Writes output to
[OUTPUT_DIR]/findings-triaged.sarif
Optional: Install SARIF Multitool for better merge quality:
npm install -g @microsoft/sarif-multitool
Report to user:
## Semgrep Scan Complete
**Scanned:** 1,804 files
**Rulesets used:** 9 (including Trail of Bits)
**Total raw findings:** 156
**After triage:** 32 true positives
### By Severity:
- ERROR: 5
- WARNING: 18
- INFO: 9
### By Category:
- SQL Injection: 3
- XSS: 7
- Hardcoded secrets: 2
- Insecure configuration: 12
- Code quality: 8
Results written to:
- semgrep-results-001/findings-triaged.sarif (SARIF, true positives only)
- semgrep-results-001/*-triage.json (triage details per language)
- semgrep-results-001/*.json (raw scan results)
- semgrep-results-001/*.sarif (raw SARIF per ruleset)
Common Mistakes
| Mistake | Correct Approach |
|---|---|
Running without --metrics=off |
Always use --metrics=off to prevent telemetry |
| Running rulesets sequentially | Run in parallel with & and wait |
| Not scoping rulesets to languages | Use --include="*.py" for language-specific rules |
| Reporting raw findings without triage | Always triage to remove false positives |
| Single-threaded for multi-lang | Spawn parallel Tasks per language |
| Sequential Tasks | Spawn all Tasks in SINGLE message for parallelism |
| Using OSS when Pro is available | Check login status; use --pro for deeper analysis |
| Assuming Pro is unavailable | Always check with login detection before scanning |
Limitations
- OSS mode: Cannot track data flow across files (login with
semgrep loginand runsemgrep install-semgrep-proto enable) - Pro mode: Cross-file analysis uses
-j 1(single job) which is slower per ruleset, but parallel rulesets compensate - Triage requires reading code context - parallelized via Tasks
- Some false positive patterns require human judgment
Rationalizations to Reject
| Shortcut | Why It's Wrong |
|---|---|
| "User asked for scan, that's approval" | Original request ≠ plan approval; user must confirm specific parameters. Present plan, use AskUserQuestion, await explicit "yes" |
| "Step 3 task is blocking, just mark complete" | Lying about task status defeats enforcement. Only mark complete after real approval |
| "I already know what they want" | Assumptions cause scanning wrong directories/rulesets. Present plan with all parameters for verification |
| "Just use default rulesets" | User must see and approve exact rulesets before scan |
| "Add extra rulesets without asking" | Modifying approved list without consent breaks trust |
| "Skip showing ruleset list" | User can't make informed decision without seeing what will run |
| "Third-party rulesets are optional" | Trail of Bits, 0xdea, Decurity rules catch vulnerabilities not in official registry - they are REQUIRED when language matches |
| "Skip triage, report everything" | Floods user with noise; true issues get lost |
| "Run one ruleset at a time" | Wastes time; parallel execution is faster |
| "Use --config auto" | Sends metrics; less control over rulesets |
| "Triage later" | Findings without context are harder to evaluate |
| "One Task at a time" | Defeats parallelism; spawn all Tasks together |
| "Pro is too slow, skip --pro" | Cross-file analysis catches 250% more true positives; worth the time |
| "Don't bother checking for Pro" | Missing Pro = missing critical cross-file vulnerabilities |
| "OSS is good enough" | OSS misses inter-file taint flows; always prefer Pro when available |
/address-sanitizer
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/address-sanitizer/SKILL.md
name: address-sanitizer type: technique description: > AddressSanitizer detects memory errors during fuzzing. Use when fuzzing C/C++ code to find buffer overflows and use-after-free bugs.
AddressSanitizer (ASan)
AddressSanitizer (ASan) is a widely adopted memory error detection tool used extensively during software testing, particularly fuzzing. It helps detect memory corruption bugs that might otherwise go unnoticed, such as buffer overflows, use-after-free errors, and other memory safety violations.
Overview
ASan is a standard practice in fuzzing due to its effectiveness in identifying memory vulnerabilities. It instruments code at compile time to track memory allocations and accesses, detecting illegal operations at runtime.
Key Concepts
| Concept | Description |
|---|---|
| Instrumentation | ASan adds runtime checks to memory operations during compilation |
| Shadow Memory | Maps 20TB of virtual memory to track allocation state |
| Performance Cost | Approximately 2-4x slowdown compared to non-instrumented code |
| Detection Scope | Finds buffer overflows, use-after-free, double-free, and memory leaks |
When to Apply
Apply this technique when:
- Fuzzing C/C++ code for memory safety vulnerabilities
- Testing Rust code with unsafe blocks
- Debugging crashes related to memory corruption
- Running unit tests where memory errors are suspected
Skip this technique when:
- Running production code (ASan can reduce security)
- Platform is Windows or macOS (limited ASan support)
- Performance overhead is unacceptable for your use case
- Fuzzing pure safe languages without FFI (e.g., pure Go, pure Java)
Quick Reference
| Task | Command/Pattern |
|---|---|
| Enable ASan (Clang/GCC) | -fsanitize=address |
| Enable verbosity | ASAN_OPTIONS=verbosity=1 |
| Disable leak detection | ASAN_OPTIONS=detect_leaks=0 |
| Force abort on error | ASAN_OPTIONS=abort_on_error=1 |
| Multiple options | ASAN_OPTIONS=verbosity=1:abort_on_error=1 |
Step-by-Step
Step 1: Compile with ASan
Compile and link your code with the -fsanitize=address flag:
clang -fsanitize=address -g -o my_program my_program.c
The -g flag is recommended to get better stack traces when ASan detects errors.
Step 2: Configure ASan Options
Set the ASAN_OPTIONS environment variable to configure ASan behavior:
export ASAN_OPTIONS=verbosity=1:abort_on_error=1:detect_leaks=0
Step 3: Run Your Program
Execute the ASan-instrumented binary. When memory errors are detected, ASan will print detailed reports:
./my_program
Step 4: Adjust Fuzzer Memory Limits
ASan requires approximately 20TB of virtual memory. Disable fuzzer memory restrictions:
- libFuzzer:
-rss_limit_mb=0 - AFL++:
-m none
Common Patterns
Pattern: Basic ASan Integration
Use Case: Standard fuzzing setup with ASan
Before:
clang -o fuzz_target fuzz_target.c
./fuzz_target
After:
clang -fsanitize=address -g -o fuzz_target fuzz_target.c
ASAN_OPTIONS=verbosity=1:abort_on_error=1 ./fuzz_target
Pattern: ASan with Unit Tests
Use Case: Enable ASan for unit test suite
Before:
gcc -o test_suite test_suite.c -lcheck
./test_suite
After:
gcc -fsanitize=address -g -o test_suite test_suite.c -lcheck
ASAN_OPTIONS=detect_leaks=1 ./test_suite
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
Use -g flag |
Provides detailed stack traces for debugging |
Set verbosity=1 |
Confirms ASan is enabled before program starts |
| Disable leaks during fuzzing | Leak detection doesn't cause immediate crashes, clutters output |
Enable abort_on_error=1 |
Some fuzzers require abort() instead of _exit() |
Understanding ASan Reports
When ASan detects a memory error, it prints a detailed report including:
- Error type: Buffer overflow, use-after-free, etc.
- Stack trace: Where the error occurred
- Allocation/deallocation traces: Where memory was allocated/freed
- Memory map: Shadow memory state around the error
Example ASan report:
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60300000eff4 at pc 0x00000048e6a3
READ of size 4 at 0x60300000eff4 thread T0
#0 0x48e6a2 in main /path/to/file.c:42
Combining Sanitizers
ASan can be combined with other sanitizers for comprehensive detection:
clang -fsanitize=address,undefined -g -o fuzz_target fuzz_target.c
Platform-Specific Considerations
Linux: Full ASan support with best performance macOS: Limited support, some features may not work Windows: Experimental support, not recommended for production fuzzing
Anti-Patterns
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| Using ASan in production | Can make applications less secure | Use ASan only for testing |
| Not disabling memory limits | Fuzzer may kill process due to 20TB virtual memory | Set -rss_limit_mb=0 or -m none |
| Ignoring leak reports | Memory leaks indicate resource management issues | Review leak reports at end of fuzzing campaign |
Tool-Specific Guidance
libFuzzer
Compile with both fuzzer and address sanitizer:
clang++ -fsanitize=fuzzer,address -g harness.cc -o fuzz
Run with unlimited RSS:
./fuzz -rss_limit_mb=0
Integration tips:
- Always combine
-fsanitize=fuzzerwith-fsanitize=address - Use
-gfor detailed stack traces in crash reports - Consider
ASAN_OPTIONS=abort_on_error=1for better crash handling
See: libFuzzer: AddressSanitizer
AFL++
Use the AFL_USE_ASAN environment variable:
AFL_USE_ASAN=1 afl-clang-fast++ -g harness.cc -o fuzz
Run with unlimited memory:
afl-fuzz -m none -i input_dir -o output_dir ./fuzz
Integration tips:
AFL_USE_ASAN=1automatically adds proper compilation flags- Use
-m noneto disable AFL++'s memory limit - Consider
AFL_MAP_SIZEfor programs with large coverage maps
cargo-fuzz (Rust)
Use the --sanitizer=address flag:
cargo fuzz run fuzz_target --sanitizer=address
Or configure in fuzz/Cargo.toml:
[profile.release]
opt-level = 3
debug = true
Integration tips:
- ASan is useful for fuzzing unsafe Rust code or FFI boundaries
- Safe Rust code may not benefit as much (compiler already prevents many errors)
- Focus on unsafe blocks, raw pointers, and C library bindings
See: cargo-fuzz: AddressSanitizer
honggfuzz
Compile with ASan and link with honggfuzz:
honggfuzz -i input_dir -o output_dir -- ./fuzz_target_asan
Compile the target:
hfuzz-clang -fsanitize=address -g target.c -o fuzz_target_asan
Integration tips:
- honggfuzz works well with ASan out of the box
- Use feedback-driven mode for better coverage with sanitizers
- Monitor memory usage, as ASan increases memory footprint
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Fuzzer kills process immediately | Memory limit too low for ASan's 20TB virtual memory | Use -rss_limit_mb=0 (libFuzzer) or -m none (AFL++) |
| "ASan runtime not initialized" | Wrong linking order or missing runtime | Ensure -fsanitize=address used in both compile and link |
| Leak reports clutter output | LeakSanitizer enabled by default | Set ASAN_OPTIONS=detect_leaks=0 |
| Poor performance (>4x slowdown) | Debug mode or unoptimized build | Compile with -O2 or -O3 alongside -fsanitize=address |
| ASan not detecting obvious bugs | Binary not instrumented | Check with ASAN_OPTIONS=verbosity=1 that ASan prints startup info |
| False positives | Interceptor conflicts | Check ASan FAQ for known issues with specific libraries |
Related Skills
Tools That Use This Technique
| Skill | How It Applies |
|---|---|
| libfuzzer | Compile with -fsanitize=fuzzer,address for integrated fuzzing with memory error detection |
| aflpp | Use AFL_USE_ASAN=1 environment variable during compilation |
| cargo-fuzz | Use --sanitizer=address flag to enable ASan for Rust fuzz targets |
| honggfuzz | Compile target with -fsanitize=address for ASan-instrumented fuzzing |
Related Techniques
| Skill | Relationship |
|---|---|
| undefined-behavior-sanitizer | Often used together with ASan for comprehensive bug detection (undefined behavior + memory errors) |
| fuzz-harness-writing | Harnesses must be designed to handle ASan-detected crashes and avoid false positives |
| coverage-analysis | Coverage-guided fuzzing helps trigger code paths where ASan can detect memory errors |
Resources
Key External Resources
AddressSanitizer on Google Sanitizers Wiki
The official ASan documentation covers:
- Algorithm and implementation details
- Complete list of detected error types
- Performance characteristics and overhead
- Platform-specific behavior
- Known limitations and incompatibilities
Common configuration flags shared across all sanitizers:
verbosity: Control diagnostic output levellog_path: Redirect sanitizer output to filessymbolize: Enable/disable symbol resolution in reportsexternal_symbolizer_path: Use custom symbolizer
ASan-specific configuration options:
detect_leaks: Control memory leak detectionabort_on_error: Callabort()vs_exit()on errordetect_stack_use_after_return: Detect stack use-after-return bugscheck_initialization_order: Find initialization order bugs
Common pitfalls and solutions:
- Linking order issues
- Conflicts with other tools
- Platform-specific problems
- Performance tuning tips
Clang AddressSanitizer Documentation
Clang-specific guidance:
- Compilation flags and options
- Interaction with other Clang features
- Supported platforms and architectures
GCC-specific ASan documentation:
- GCC-specific flags and behavior
- Differences from Clang implementation
- Platform support in GCC
AddressSanitizer: A Fast Address Sanity Checker (USENIX Paper)
Original research paper with technical details:
- Shadow memory algorithm
- Virtual memory requirements (historically 16TB, now ~20TB)
- Performance benchmarks
- Design decisions and tradeoffs
/aflpp
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/aflpp/SKILL.md
name: aflpp type: fuzzer description: > AFL++ is a fork of AFL with better fuzzing performance and advanced features. Use for multi-core fuzzing of C/C++ projects.
AFL++
AFL++ is a fork of the original AFL fuzzer that offers better fuzzing performance and more advanced features while maintaining stability. A major benefit over libFuzzer is that AFL++ has stable support for running fuzzing campaigns on multiple cores, making it ideal for large-scale fuzzing efforts.
When to Use
| Fuzzer | Best For | Complexity |
|---|---|---|
| AFL++ | Multi-core fuzzing, diverse mutations, mature projects | Medium |
| libFuzzer | Quick setup, single-threaded, simple harnesses | Low |
| LibAFL | Custom fuzzers, research, advanced use cases | High |
Choose AFL++ when:
- You need multi-core fuzzing to maximize throughput
- Your project can be compiled with Clang or GCC
- You want diverse mutation strategies and mature tooling
- libFuzzer has plateaued and you need more coverage
- You're fuzzing production codebases that benefit from parallel execution
Quick Start
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Call your code with fuzzer-provided data
check_buf((char*)data, size);
return 0;
}
Compile and run:
# Setup AFL++ wrapper script first (see Installation)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
mkdir seeds && echo "aaaa" > seeds/minimal_seed
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
Installation
AFL++ has many dependencies including LLVM, Python, and Rust. We recommend using a current Debian or Ubuntu distribution for fuzzing with AFL++.
| Method | When to Use | Supported Compilers |
|---|---|---|
| Ubuntu/Debian repos | Recent Ubuntu, basic features only | Ubuntu 23.10: Clang 14 & GCC 13 Debian 12: Clang 14 & GCC 12 |
| Docker (from Docker Hub) | Specific AFL++ version, Apple Silicon support | As of 4.35c: Clang 19 & GCC 11 |
| Docker (from source) | Test unreleased features, apply patches | Configurable in Dockerfile |
| From source | Avoid Docker, need specific patches | Adjustable via LLVM_CONFIG env var |
Ubuntu/Debian
Prior to installing afl++, check the clang version dependency of the packge with apt-cache show afl++, and install the matching lld version (e.g., lld-17).
apt install afl++ lld-17
Docker (from Docker Hub)
docker pull aflplusplus/aflplusplus:stable
Docker (from source)
git clone --depth 1 --branch stable https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
docker build -t aflplusplus .
From source
Refer to the Dockerfile for Ubuntu version requirements and dependencies. Set LLVM_CONFIG to specify Clang version (e.g., llvm-config-18).
Wrapper Script Setup
Create a wrapper script to run AFL++ on host or Docker:
cat <<'EOF' > ./afl++
#!/bin/sh
AFL_VERSION="${AFL_VERSION:-"stable"}"
case "$1" in
host)
shift
bash -c "$*"
;;
docker)
shift
/usr/bin/env docker run -ti \
--privileged \
-v ./:/src \
--rm \
--name afl_fuzzing \
"aflplusplus/aflplusplus:$AFL_VERSION" \
bash -c "cd /src && bash -c \"$*\""
;;
*)
echo "Usage: $0 {host|docker}"
exit 1
;;
esac
EOF
chmod +x ./afl++
Security Warning: The afl-system-config and afl-persistent-config scripts require root privileges and disable OS security features. Do not fuzz on production systems or your development environment. Use a dedicated VM instead.
System Configuration
Run after each reboot for up to 15% more executions per second:
./afl++ <host/docker> afl-system-config
For maximum performance, disable kernel security mitigations (requires grub bootloader, not supported in Docker):
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ <host/docker> afl-system-config
Verify with cat /proc/cmdline - output should include mitigations=off.
Writing a Harness
Harness Structure
AFL++ supports libFuzzer-style harnesses:
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. Validate input size if needed
if (size < MIN_SIZE || size > MAX_SIZE) return 0;
// 2. Call target function with fuzz data
target_function(data, size);
// 3. Return 0 (non-zero reserved for future use)
return 0;
}
Harness Rules
| Do | Don't |
|---|---|
| Reset global state between runs | Rely on state from previous runs |
| Handle edge cases gracefully | Exit on invalid input |
| Keep harness deterministic | Use random number generators |
| Free allocated memory | Create memory leaks |
| Validate input sizes | Process unbounded input |
See Also: For detailed harness writing techniques, patterns for handling complex inputs, and advanced strategies, see the fuzz-harness-writing technique skill.
Compilation
AFL++ offers multiple compilation modes with different trade-offs.
Compilation Mode Decision Tree
Choose your compilation mode:
- LTO mode (
afl-clang-lto): Best performance and instrumentation. Try this first. - LLVM mode (
afl-clang-fast): Fall back if LTO fails to compile. - GCC plugin (
afl-gcc-fast): For projects requiring GCC.
Basic Compilation (LLVM mode)
./afl++ <host/docker> afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
GCC Compilation
./afl++ <host/docker> afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Important: GCC version must match the version used to compile the AFL++ GCC plugin.
With Sanitizers
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
See Also: For detailed sanitizer configuration, common issues, and advanced flags, see the address-sanitizer and undefined-behavior-sanitizer technique skills.
Build Flags
Note that -g is not necessary, it is added by default by the AFL++ compilers.
| Flag | Purpose |
|---|---|
-DNO_MAIN=1 |
Skip main function when using libFuzzer harness |
-O2 |
Production optimization level (recommended for fuzzing) |
-fsanitize=fuzzer |
Enable libFuzzer compatibility mode and adds the fuzzer runtime when linking executable |
-fsanitize=fuzzer-no-link |
Instrument without linking fuzzer runtime (for static libraries and object files) |
Corpus Management
Creating Initial Corpus
AFL++ requires at least one non-empty seed file:
mkdir seeds
echo "aaaa" > seeds/minimal_seed
For real projects, gather representative inputs:
- Download example files for the format you're fuzzing
- Extract test cases from the project's test suite
- Use minimal valid inputs for your file format
Corpus Minimization
After a campaign, minimize the corpus to keep only unique coverage:
./afl++ <host/docker> afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
See Also: For corpus creation strategies, dictionaries, and seed selection, see the fuzzing-corpus technique skill.
Running Campaigns
Basic Run
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
Setting Environment Variables
./afl++ <host/docker> AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
Interpreting Output
The AFL++ UI shows real-time fuzzing statistics:
| Output | Meaning |
|---|---|
| execs/sec | Execution speed - higher is better |
| cycles done | Number of queue passes completed |
| corpus count | Number of unique test cases in queue |
| saved crashes | Number of unique crashes found |
| stability | % of stable edges (should be near 100%) |
Output Directory Structure
out/default/
├── cmdline # How was the SUT invoked?
├── crashes/ # Inputs that crash the SUT
│ └── id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
├── hangs/ # Inputs that hang the SUT
├── queue/ # Test cases reproducing final fuzzer state
│ ├── id:000000,time:0,execs:0,orig:minimal_seed
│ └── id:000001,src:000000,time:0,execs:8,op:havoc,rep:6,+cov
├── fuzzer_stats # Campaign statistics
└── plot_data # Data for plotting
Analyzing Results
View live campaign statistics:
./afl++ <host/docker> afl-whatsup out
Create coverage plots:
apt install gnuplot
./afl++ <host/docker> afl-plot out/default out_graph/
Re-executing Test Cases
./afl++ <host/docker> ./fuzz out/default/crashes/<test_case>
Fuzzer Options
| Option | Purpose |
|---|---|
-G 4000 |
Maximum test input length (default: 1048576 bytes) |
-t 1000 |
Timeout in milliseconds for each test case (default: 1000ms) |
-m 1000 |
Memory limit in megabytes (default: 0 = unlimited) |
-x ./dict.dict |
Use dictionary file to guide mutations |
Multi-Core Fuzzing
AFL++ excels at multi-core fuzzing with two major advantages:
- More executions per second (scales linearly with physical cores)
- Asymmetrical fuzzing (e.g., one ASan job, rest without sanitizers)
Starting a Campaign
Start the primary fuzzer (in background):
./afl++ <host/docker> afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error &
Start secondary fuzzers (as many as you have cores):
./afl++ <host/docker> afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error &
./afl++ <host/docker> afl-fuzz -S secondary02 -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
Monitoring Multi-Core Campaigns
List all running jobs:
jobs
View live statistics (updates every second):
./afl++ <host/docker> watch -n1 --color afl-whatsup state/
Stopping All Fuzzers
kill $(jobs -p)
Coverage Analysis
AFL++ automatically tracks coverage through edge instrumentation. Coverage information is stored in fuzzer_stats and plot_data.
Measuring Coverage
Use afl-plot to visualize coverage over time:
./afl++ <host/docker> afl-plot out/default out_graph/
Improving Coverage
- Use dictionaries for format-aware fuzzing
- Run longer campaigns (cycles_wo_finds indicates plateau)
- Try different mutation strategies with multi-core fuzzing
- Analyze coverage gaps and add targeted seed inputs
See Also: For detailed coverage analysis techniques, identifying coverage gaps, and systematic coverage improvement, see the coverage-analysis technique skill.
CMPLOG
CMPLOG/RedQueen is the best path constraint solving mechanism available in any fuzzer. To enable it, the fuzz target needs to be instrumented for it. Before building the fuzzing target set the environment variable:
./afl++ <host/docker> AFL_LLVM_CMPLOG=1 make
No special action is needed for compiling and linking the harness.
To run a fuzzer instance with a CMPLOG instrumented fuzzing target, add -c0 to the command like arguments:
./afl++ <host/docker> afl-fuzz -c0 -S cmplog -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
Sanitizer Integration
Sanitizers are essential for finding memory corruption bugs that don't cause immediate crashes.
AddressSanitizer (ASan)
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Note: Memory limit (-m) is not supported with ASan due to 20TB virtual memory reservation.
UndefinedBehaviorSanitizer (UBSan)
./afl++ <host/docker> AFL_USE_UBSAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer,undefined harness.cc main.cc -o fuzz
Common Sanitizer Issues
| Issue | Solution |
|---|---|
| ASan slows fuzzing | Use only 1 ASan job in multi-core setup |
| Stack exhaustion | Increase stack with ASAN_OPTIONS=stack_size=... |
| GCC version mismatch | Ensure system GCC matches AFL++ plugin version |
See Also: For comprehensive sanitizer configuration and troubleshooting, see the address-sanitizer technique skill.
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
| Use LLVMFuzzerTestOneInput harnesses where possible | If a fuzzing campaign has at least 85% stability then this is the most efficient fuzzing style. If not then try standard input or file input fuzzing |
| Use dictionaries | Helps fuzzer discover format-specific keywords and magic bytes |
| Set realistic timeouts | Prevents false positives from system load |
| Limit input size | Larger inputs don't necessarily explore more space |
| Monitor stability | Low stability indicates non-deterministic behavior |
Standard Input Fuzzing
AFL++ can fuzz programs reading from stdin without a libFuzzer harness:
./afl++ <host/docker> afl-clang-fast++ -O2 main_stdin.c -o fuzz_stdin
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_stdin
This is slower than persistent mode but requires no harness code.
File Input Fuzzing
For programs that read files, use @@ placeholder:
./afl++ <host/docker> afl-clang-fast++ -O2 main_file.c -o fuzz_file
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_file @@
For better performance, use fmemopen to create file descriptors from memory.
Argument Fuzzing
Fuzz command-line arguments using argv-fuzz-inl.h:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __AFL_COMPILER
#include "argv-fuzz-inl.h"
#endif
void check_buf(char *buf, size_t buf_len) {
if(buf_len > 0 && buf[0] == 'a') {
if(buf_len > 1 && buf[1] == 'b') {
if(buf_len > 2 && buf[2] == 'c') {
abort();
}
}
}
}
int main(int argc, char *argv[]) {
#ifdef __AFL_COMPILER
AFL_INIT_ARGV();
#endif
if (argc < 2) {
fprintf(stderr, "Usage: %s <input_string>\n", argv[0]);
return 1;
}
char *input_buf = argv[1];
size_t len = strlen(input_buf);
check_buf(input_buf, len);
return 0;
}
Download the header:
curl -O https://raw.githubusercontent.com/AFLplusplus/AFLplusplus/stable/utils/argv_fuzzing/argv-fuzz-inl.h
Compile and run:
./afl++ <host/docker> afl-clang-fast++ -O2 main_arg.c -o fuzz_arg
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_arg
Performance Tuning
| Setting | Impact |
|---|---|
| CPU core count | Linear scaling with physical cores |
| Persistent mode | 10-20x faster than fork server |
-G input size limit |
Smaller = faster, but may miss bugs |
| ASan ratio | 1 ASan job per 4-8 non-ASan jobs |
Real-World Examples
Example: libpng
Fuzzing libpng demonstrates fuzzing a C project with static libraries:
# Get source
curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
# Install dependencies
apt install zlib1g-dev
# Configure and build static library
export CC=afl-clang-fast CFLAGS=-fsanitize=fuzzer-no-link
export CXX=afl-clang-fast++ CXXFLAGS="$CFLAGS"
./configure --enable-shared=no
export AFL_LLVM_CMPLOG=1
export AFL_USE_ASAN=1
make
# Download harness
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
# Link fuzzer
export AFL_USE_ASAN=1
$CXX -fsanitize=fuzzer libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
# Prepare seeds and dictionary
mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
# Start fuzzing
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
Example: CMake-based Project
project(BuggyProgram)
cmake_minimum_required(VERSION 3.0)
add_executable(buggy_program main.cc)
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -O2 -fsanitize=fuzzer-no-link)
target_link_libraries(fuzz -fsanitize=fuzzer)
Build and fuzz:
# Build non-instrumented binary
./afl++ <host/docker> cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
./afl++ <host/docker> cmake --build . --target buggy_program
# Build fuzzer
./afl++ <host/docker> cmake -DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=afl-clang-fast++ .
./afl++ <host/docker> cmake --build . --target fuzz
# Fuzz
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Low exec/sec (<1k) | Not using persistent mode | Create a LLVMFuzzerTestOneInput style harness |
| Low stability (<85%) | Non-deterministic code | Fuzz a program via stdin or file inputs, or create such a harness |
| GCC plugin error | GCC version mismatch | Ensure system GCC matches AFL++ build and install gcc-$GCC_VERSION-plugin-dev |
| No crashes found | Need sanitizers | Recompile with AFL_USE_ASAN=1 |
| Memory limit exceeded | ASan uses 20TB virtual | Remove -m flag when using ASan |
| Docker performance loss | Virtualization overhead | Use bare metal or VM for production fuzzing |
Related Skills
Technique Skills
| Skill | Use Case |
|---|---|
| fuzz-harness-writing | Detailed guidance on writing effective harnesses |
| address-sanitizer | Memory error detection during fuzzing |
| undefined-behavior-sanitizer | Detect undefined behavior bugs |
| fuzzing-corpus | Building and managing seed corpora |
| fuzzing-dictionaries | Creating dictionaries for format-aware fuzzing |
Related Fuzzers
| Skill | When to Consider |
|---|---|
| libfuzzer | Quick prototyping, single-threaded fuzzing is sufficient |
| libafl | Need custom mutators or research-grade features |
Resources
Key External Resources
AFL++ GitHub Repository Official repository with comprehensive documentation, examples, and issue tracker.
Fuzzing in Depth Advanced documentation by the AFL++ team covering instrumentation modes, optimization techniques, and advanced use cases.
AFL++ Under The Hood Technical deep-dive into AFL++ internals, mutation strategies, and coverage tracking mechanisms.
AFL++: Combining Incremental Steps of Fuzzing Research Research paper describing AFL++ architecture and performance improvements over original AFL.
Video Resources
- Fuzzing cURL - Trail of Bits blog post on using AFL++ argument fuzzing for cURL
- Sudo Vulnerability Walkthrough - LiveOverflow series on rediscovering CVE-2021-3156
- Rediscovery of libpng bug - LiveOverflow video on finding CVE-2023-4863
/atheris
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/atheris/SKILL.md
name: atheris type: fuzzer description: > Atheris is a coverage-guided Python fuzzer based on libFuzzer. Use for fuzzing pure Python code and Python C extensions.
Atheris
Atheris is a coverage-guided Python fuzzer built on libFuzzer. It enables fuzzing of both pure Python code and Python C extensions with integrated AddressSanitizer support for detecting memory corruption issues.
When to Use
| Fuzzer | Best For | Complexity |
|---|---|---|
| Atheris | Python code and C extensions | Low-Medium |
| Hypothesis | Property-based testing | Low |
| python-afl | AFL-style fuzzing | Medium |
Choose Atheris when:
- Fuzzing pure Python code with coverage guidance
- Testing Python C extensions for memory corruption
- Integration with libFuzzer ecosystem is desired
- AddressSanitizer support is needed
Quick Start
import sys
import atheris
@atheris.instrument_func
def test_one_input(data: bytes):
if len(data) == 4:
if data[0] == 0x46: # "F"
if data[1] == 0x55: # "U"
if data[2] == 0x5A: # "Z"
if data[3] == 0x5A: # "Z"
raise RuntimeError("You caught me")
def main():
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
if __name__ == "__main__":
main()
Run:
python fuzz.py
Installation
Atheris supports 32-bit and 64-bit Linux, and macOS. We recommend fuzzing on Linux because it's simpler to manage and often faster.
Prerequisites
- Python 3.7 or later
- Recent version of clang (preferably latest release)
- For Docker users: Docker Desktop
Linux/macOS
uv pip install atheris
Docker Environment (Recommended)
For a fully operational Linux environment with all dependencies configured:
# https://hub.docker.com/_/python
ARG PYTHON_VERSION=3.11
FROM python:$PYTHON_VERSION-slim-bookworm
RUN python --version
RUN apt update && apt install -y \
ca-certificates \
wget \
&& rm -rf /var/lib/apt/lists/*
# LLVM builds version 15-19 for Debian 12 (Bookworm)
# https://apt.llvm.org/bookworm/dists/
ARG LLVM_VERSION=19
RUN echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list
RUN echo "deb-src http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" >> /etc/apt/sources.list.d/llvm.list
RUN wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key > /etc/apt/trusted.gpg.d/apt.llvm.org.asc
RUN apt update && apt install -y \
build-essential \
clang-$LLVM_VERSION \
&& rm -rf /var/lib/apt/lists/*
ENV APP_DIR "/app"
RUN mkdir $APP_DIR
WORKDIR $APP_DIR
ENV VIRTUAL_ENV "/opt/venv"
RUN python -m venv $VIRTUAL_ENV
ENV PATH "$VIRTUAL_ENV/bin:$PATH"
# https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#step-1-compiling-your-extension
ENV CC="clang-$LLVM_VERSION"
ENV CFLAGS "-fsanitize=address,fuzzer-no-link"
ENV CXX="clang++-$LLVM_VERSION"
ENV CXXFLAGS "-fsanitize=address,fuzzer-no-link"
ENV LDSHARED="clang-$LLVM_VERSION -shared"
ENV LDSHAREDXX="clang++-$LLVM_VERSION -shared"
ENV ASAN_SYMBOLIZER_PATH="/usr/bin/llvm-symbolizer-$LLVM_VERSION"
# Allow Atheris to find fuzzer sanitizer shared libs
# https://github.com/google/atheris#building-from-source
RUN LIBFUZZER_LIB=$($CC -print-file-name=libclang_rt.fuzzer_no_main-$(uname -m).a) \
python -m pip install --no-binary atheris atheris
# https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#option-a-sanitizerlibfuzzer-preloads
ENV LD_PRELOAD "$VIRTUAL_ENV/lib/python3.11/site-packages/asan_with_fuzzer.so"
# 1. Skip memory allocation failures for now, they are common, and low impact (DoS)
# 2. https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#leak-detection
ENV ASAN_OPTIONS "allocator_may_return_null=1,detect_leaks=0"
CMD ["/bin/bash"]
Build and run:
docker build -t atheris .
docker run -it atheris
Verification
python -c "import atheris; print(atheris.__version__)"
Writing a Harness
Harness Structure for Pure Python
import sys
import atheris
@atheris.instrument_func
def test_one_input(data: bytes):
"""
Fuzzing entry point. Called with random byte sequences.
Args:
data: Random bytes generated by the fuzzer
"""
# Add input validation if needed
if len(data) < 1:
return
# Call your target function
try:
your_target_function(data)
except ValueError:
# Expected exceptions should be caught
pass
# Let unexpected exceptions crash (that's what we're looking for!)
def main():
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
if __name__ == "__main__":
main()
Harness Rules
| Do | Don't |
|---|---|
Use @atheris.instrument_func for coverage |
Forget to instrument target code |
| Catch expected exceptions | Catch all exceptions indiscriminately |
Use atheris.instrument_imports() for libraries |
Import modules after atheris.Setup() |
| Keep harness deterministic | Use randomness or time-based behavior |
See Also: For detailed harness writing techniques, patterns for handling complex inputs, and advanced strategies, see the fuzz-harness-writing technique skill.
Fuzzing Pure Python Code
For fuzzing broader parts of an application or library, use instrumentation functions:
import atheris
with atheris.instrument_imports():
import your_module
from another_module import target_function
def test_one_input(data: bytes):
target_function(data)
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
Instrumentation Options:
atheris.instrument_func- Decorator for single function instrumentationatheris.instrument_imports()- Context manager for instrumenting all imported modulesatheris.instrument_all()- Instrument all Python code system-wide
Fuzzing Python C Extensions
Python C extensions require compilation with specific flags for instrumentation and sanitizer support.
Environment Configuration
If using the provided Dockerfile, these are already configured. For local setup:
export CC="clang"
export CFLAGS="-fsanitize=address,fuzzer-no-link"
export CXX="clang++"
export CXXFLAGS="-fsanitize=address,fuzzer-no-link"
export LDSHARED="clang -shared"
Example: Fuzzing cbor2
Install the extension from source:
CBOR2_BUILD_C_EXTENSION=1 python -m pip install --no-binary cbor2 cbor2==5.6.4
The --no-binary flag ensures the C extension is compiled locally with instrumentation.
Create cbor2-fuzz.py:
import sys
import atheris
# _cbor2 ensures the C library is imported
from _cbor2 import loads
def test_one_input(data: bytes):
try:
loads(data)
except Exception:
# We're searching for memory corruption, not Python exceptions
pass
def main():
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
if __name__ == "__main__":
main()
Run:
python cbor2-fuzz.py
Important: When running locally (not in Docker), you must set
LD_PRELOADmanually.
Corpus Management
Creating Initial Corpus
mkdir corpus
# Add seed inputs
echo "test data" > corpus/seed1
echo '{"key": "value"}' > corpus/seed2
Run with corpus:
python fuzz.py corpus/
Corpus Minimization
Atheris inherits corpus minimization from libFuzzer:
python fuzz.py -merge=1 new_corpus/ old_corpus/
See Also: For corpus creation strategies, dictionaries, and seed selection, see the fuzzing-corpus technique skill.
Running Campaigns
Basic Run
python fuzz.py
With Corpus Directory
python fuzz.py corpus/
Common Options
# Run for 10 minutes
python fuzz.py -max_total_time=600
# Limit input size
python fuzz.py -max_len=1024
# Run with multiple workers
python fuzz.py -workers=4 -jobs=4
Interpreting Output
| Output | Meaning |
|---|---|
NEW cov: X |
Found new coverage, corpus expanded |
pulse cov: X |
Periodic status update |
exec/s: X |
Executions per second (throughput) |
corp: X/Yb |
Corpus size: X inputs, Y bytes total |
ERROR: libFuzzer |
Crash detected |
Sanitizer Integration
AddressSanitizer (ASan)
AddressSanitizer is automatically integrated when using the provided Docker environment or when compiling with appropriate flags.
For local setup:
export CFLAGS="-fsanitize=address,fuzzer-no-link"
export CXXFLAGS="-fsanitize=address,fuzzer-no-link"
Configure ASan behavior:
export ASAN_OPTIONS="allocator_may_return_null=1,detect_leaks=0"
LD_PRELOAD Configuration
For native extension fuzzing:
export LD_PRELOAD="$(python -c 'import atheris; import os; print(os.path.join(os.path.dirname(atheris.__file__), "asan_with_fuzzer.so"))')"
See Also: For detailed sanitizer configuration, common issues, and advanced flags, see the address-sanitizer and undefined-behavior-sanitizer technique skills.
Common Sanitizer Issues
| Issue | Solution |
|---|---|
LD_PRELOAD not set |
Export LD_PRELOAD to point to asan_with_fuzzer.so |
| Memory allocation failures | Set ASAN_OPTIONS=allocator_may_return_null=1 |
| Leak detection noise | Set ASAN_OPTIONS=detect_leaks=0 |
| Missing symbolizer | Set ASAN_SYMBOLIZER_PATH to llvm-symbolizer |
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
Use atheris.instrument_imports() early |
Ensures all imports are instrumented for coverage |
Start with small max_len |
Faster initial fuzzing, gradually increase |
| Use dictionaries for structured formats | Helps fuzzer understand format tokens |
| Run multiple parallel instances | Better coverage exploration |
Custom Instrumentation
Fine-tune what gets instrumented:
import atheris
# Instrument only specific modules
with atheris.instrument_imports():
import target_module
# Don't instrument test harness code
def test_one_input(data: bytes):
target_module.parse(data)
Performance Tuning
| Setting | Impact |
|---|---|
-max_len=N |
Smaller values = faster execution |
-workers=N -jobs=N |
Parallel fuzzing for faster coverage |
ASAN_OPTIONS=fast_unwind_on_malloc=0 |
Better stack traces, slower execution |
UndefinedBehaviorSanitizer (UBSan)
Add UBSan to catch additional bugs:
export CFLAGS="-fsanitize=address,undefined,fuzzer-no-link"
export CXXFLAGS="-fsanitize=address,undefined,fuzzer-no-link"
Note: Modify flags in Dockerfile if using containerized setup.
Real-World Examples
Example: Pure Python Parser
import sys
import atheris
import json
@atheris.instrument_func
def test_one_input(data: bytes):
try:
# Fuzz Python's JSON parser
json.loads(data.decode('utf-8', errors='ignore'))
except (ValueError, UnicodeDecodeError):
pass
def main():
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
if __name__ == "__main__":
main()
Example: HTTP Request Parsing
import sys
import atheris
with atheris.instrument_imports():
from urllib3 import HTTPResponse
from io import BytesIO
def test_one_input(data: bytes):
try:
# Fuzz HTTP response parsing
fake_response = HTTPResponse(
body=BytesIO(data),
headers={},
preload_content=False
)
fake_response.read()
except Exception:
pass
def main():
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
if __name__ == "__main__":
main()
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| No coverage increase | Poor seed corpus or target not instrumented | Add better seeds, verify instrument_imports() |
| Slow execution | ASan overhead or large inputs | Reduce max_len, use ASAN_OPTIONS=fast_unwind_on_malloc=1 |
| Import errors | Modules imported before instrumentation | Move imports inside instrument_imports() context |
| Segfault without ASan output | Missing LD_PRELOAD |
Set LD_PRELOAD to asan_with_fuzzer.so path |
| Build failures | Wrong compiler or missing flags | Verify CC, CFLAGS, and clang version |
Related Skills
Technique Skills
| Skill | Use Case |
|---|---|
| fuzz-harness-writing | Detailed guidance on writing effective harnesses |
| address-sanitizer | Memory error detection during fuzzing |
| undefined-behavior-sanitizer | Catching undefined behavior in C extensions |
| coverage-analysis | Measuring and improving code coverage |
| fuzzing-corpus | Building and managing seed corpora |
Related Fuzzers
| Skill | When to Consider |
|---|---|
| hypothesis | Property-based testing with type-aware generation |
| python-afl | AFL-style fuzzing for Python when Atheris isn't available |
Resources
Key External Resources
Atheris GitHub Repository Official repository with installation instructions, examples, and documentation for fuzzing both pure Python and native extensions.
Native Extension Fuzzing Guide Comprehensive guide covering compilation flags, LD_PRELOAD setup, sanitizer configuration, and troubleshooting for Python C extensions.
Continuously Fuzzing Python C Extensions Trail of Bits blog post covering CI/CD integration, ClusterFuzzLite setup, and real-world examples of fuzzing Python C extensions in continuous integration pipelines.
ClusterFuzzLite Python Integration Guide for integrating Atheris fuzzing into CI/CD pipelines using ClusterFuzzLite for automated continuous fuzzing.
Video Resources
Videos and tutorials are available in the main Atheris documentation and libFuzzer resources.
/cargo-fuzz
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/cargo-fuzz/SKILL.md
name: cargo-fuzz type: fuzzer description: > cargo-fuzz is the de facto fuzzing tool for Rust projects using Cargo. Use for fuzzing Rust code with libFuzzer backend.
cargo-fuzz
cargo-fuzz is the de facto choice for fuzzing Rust projects when using Cargo. It uses libFuzzer as the backend and provides a convenient Cargo subcommand that automatically enables relevant compilation flags for your Rust project, including support for sanitizers like AddressSanitizer.
When to Use
cargo-fuzz is currently the primary and most mature fuzzing solution for Rust projects using Cargo.
| Fuzzer | Best For | Complexity |
|---|---|---|
| cargo-fuzz | Cargo-based Rust projects, quick setup | Low |
| AFL++ | Multi-core fuzzing, non-Cargo projects | Medium |
| LibAFL | Custom fuzzers, research, advanced use cases | High |
Choose cargo-fuzz when:
- Your project uses Cargo (required)
- You want simple, quick setup with minimal configuration
- You need integrated sanitizer support
- You're fuzzing Rust code with or without unsafe blocks
Quick Start
#![no_main]
use libfuzzer_sys::fuzz_target;
fn harness(data: &[u8]) {
your_project::check_buf(data);
}
fuzz_target!(|data: &[u8]| {
harness(data);
});
Initialize and run:
cargo fuzz init
# Edit fuzz/fuzz_targets/fuzz_target_1.rs with your harness
cargo +nightly fuzz run fuzz_target_1
Installation
cargo-fuzz requires the nightly Rust toolchain because it uses features only available in nightly.
Prerequisites
- Rust and Cargo installed via rustup
- Nightly toolchain
Linux/macOS
# Install nightly toolchain
rustup install nightly
# Install cargo-fuzz
cargo install cargo-fuzz
Verification
cargo +nightly --version
cargo fuzz --version
Writing a Harness
Project Structure
cargo-fuzz works best when your code is structured as a library crate. If you have a binary project, split your main.rs into:
src/main.rs # Entry point (main function)
src/lib.rs # Code to fuzz (public functions)
Cargo.toml
Initialize fuzzing:
cargo fuzz init
This creates:
fuzz/
├── Cargo.toml
└── fuzz_targets/
└── fuzz_target_1.rs
Harness Structure
#![no_main]
use libfuzzer_sys::fuzz_target;
fn harness(data: &[u8]) {
// 1. Validate input size if needed
if data.is_empty() {
return;
}
// 2. Call target function with fuzz data
your_project::target_function(data);
}
fuzz_target!(|data: &[u8]| {
harness(data);
});
Harness Rules
| Do | Don't |
|---|---|
| Structure code as library crate | Keep everything in main.rs |
Use fuzz_target! macro |
Write custom main function |
Handle Result::Err gracefully |
Panic on expected errors |
| Keep harness deterministic | Use random number generators |
See Also: For detailed harness writing techniques and structure-aware fuzzing with the
arbitrarycrate, see the fuzz-harness-writing technique skill.
Structure-Aware Fuzzing
cargo-fuzz integrates with the arbitrary crate for structure-aware fuzzing:
// In your library crate
use arbitrary::Arbitrary;
#[derive(Debug, Arbitrary)]
pub struct Name {
data: String
}
// In your fuzz target
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: your_project::Name| {
data.check_buf();
});
Add to your library's Cargo.toml:
[dependencies]
arbitrary = { version = "1", features = ["derive"] }
Running Campaigns
Basic Run
cargo +nightly fuzz run fuzz_target_1
Without Sanitizers (Safe Rust)
If your project doesn't use unsafe Rust, disable sanitizers for 2x performance boost:
cargo +nightly fuzz run --sanitizer none fuzz_target_1
Check if your project uses unsafe code:
cargo install cargo-geiger
cargo geiger
Re-executing Test Cases
# Run a specific test case (e.g., a crash)
cargo +nightly fuzz run fuzz_target_1 fuzz/artifacts/fuzz_target_1/crash-<hash>
# Run all corpus entries without fuzzing
cargo +nightly fuzz run fuzz_target_1 fuzz/corpus/fuzz_target_1 -- -runs=0
Using Dictionaries
cargo +nightly fuzz run fuzz_target_1 -- -dict=./dict.dict
Interpreting Output
| Output | Meaning |
|---|---|
NEW |
New coverage-increasing input discovered |
pulse |
Periodic status update |
INITED |
Fuzzer initialized successfully |
| Crash with stack trace | Bug found, saved to fuzz/artifacts/ |
Corpus location: fuzz/corpus/fuzz_target_1/
Crashes location: fuzz/artifacts/fuzz_target_1/
Sanitizer Integration
AddressSanitizer (ASan)
ASan is enabled by default and detects memory errors:
cargo +nightly fuzz run fuzz_target_1
Disabling Sanitizers
For pure safe Rust (no unsafe blocks in your code or dependencies):
cargo +nightly fuzz run --sanitizer none fuzz_target_1
Performance impact: ASan adds ~2x overhead. Disable for safe Rust to improve fuzzing speed.
Checking for Unsafe Code
cargo install cargo-geiger
cargo geiger
See Also: For detailed sanitizer configuration, flags, and troubleshooting, see the address-sanitizer technique skill.
Coverage Analysis
cargo-fuzz integrates with Rust's coverage tools to analyze fuzzing effectiveness.
Prerequisites
rustup toolchain install nightly --component llvm-tools-preview
cargo install cargo-binutils
cargo install rustfilt
Generating Coverage Reports
# Generate coverage data from corpus
cargo +nightly fuzz coverage fuzz_target_1
Create coverage generation script:
cat <<'EOF' > ./generate_html
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Error: Name of fuzz target is required."
echo "Usage: $0 fuzz_target [sources...]"
exit 1
fi
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
"target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
-instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
-show-line-counts-or-regions -show-instantiations \
-format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html
Generate HTML report:
./generate_html fuzz_target_1 src/lib.rs
HTML report saved to: fuzz_html/
See Also: For detailed coverage analysis techniques and systematic coverage improvement, see the coverage-analysis technique skill.
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
| Start with a seed corpus | Dramatically speeds up initial coverage discovery |
Use --sanitizer none for safe Rust |
2x performance improvement |
| Check coverage regularly | Identifies gaps in harness or seed corpus |
| Use dictionaries for parsers | Helps overcome magic value checks |
| Structure code as library | Required for cargo-fuzz integration |
libFuzzer Options
Pass options to libFuzzer after --:
# See all options
cargo +nightly fuzz run fuzz_target_1 -- -help=1
# Set timeout per run
cargo +nightly fuzz run fuzz_target_1 -- -timeout=10
# Use dictionary
cargo +nightly fuzz run fuzz_target_1 -- -dict=dict.dict
# Limit maximum input size
cargo +nightly fuzz run fuzz_target_1 -- -max_len=1024
Multi-Core Fuzzing
# Experimental forking support (not recommended)
cargo +nightly fuzz run --jobs 1 fuzz_target_1
Note: The multi-core fuzzing feature is experimental and not recommended. For parallel fuzzing, consider running multiple instances manually or using AFL++.
Real-World Examples
Example: ogg Crate
The ogg crate parses Ogg media container files. Parsers are excellent fuzzing targets because they handle untrusted data.
# Clone and initialize
git clone https://github.com/RustAudio/ogg.git
cd ogg/
cargo fuzz init
Harness at fuzz/fuzz_targets/fuzz_target_1.rs:
#![no_main]
use ogg::{PacketReader, PacketWriter};
use ogg::writing::PacketWriteEndInfo;
use std::io::Cursor;
use libfuzzer_sys::fuzz_target;
fn harness(data: &[u8]) {
let mut pck_rdr = PacketReader::new(Cursor::new(data.to_vec()));
pck_rdr.delete_unread_packets();
let output = Vec::new();
let mut pck_wtr = PacketWriter::new(Cursor::new(output));
if let Ok(_) = pck_rdr.read_packet() {
if let Ok(r) = pck_rdr.read_packet() {
match r {
Some(pck) => {
let inf = if pck.last_in_stream() {
PacketWriteEndInfo::EndStream
} else if pck.last_in_page() {
PacketWriteEndInfo::EndPage
} else {
PacketWriteEndInfo::NormalPacket
};
let stream_serial = pck.stream_serial();
let absgp_page = pck.absgp_page();
let _ = pck_wtr.write_packet(
pck.data, stream_serial, inf, absgp_page
);
}
None => return,
}
}
}
}
fuzz_target!(|data: &[u8]| {
harness(data);
});
Seed the corpus:
mkdir fuzz/corpus/fuzz_target_1/
curl -o fuzz/corpus/fuzz_target_1/320x240.ogg \
https://commons.wikimedia.org/wiki/File:320x240.ogg
Run:
cargo +nightly fuzz run fuzz_target_1
Analyze coverage:
cargo +nightly fuzz coverage fuzz_target_1
./generate_html fuzz_target_1 src/lib.rs
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| "requires nightly" error | Using stable toolchain | Use cargo +nightly fuzz |
| Slow fuzzing performance | ASan enabled for safe Rust | Add --sanitizer none flag |
| "cannot find binary" | No library crate | Move code from main.rs to lib.rs |
| Sanitizer compilation issues | Wrong nightly version | Try different nightly: rustup install nightly-2024-01-01 |
| Low coverage | Missing seed corpus | Add sample inputs to fuzz/corpus/fuzz_target_1/ |
| Magic value not found | No dictionary | Create dictionary file with magic values |
Related Skills
Technique Skills
| Skill | Use Case |
|---|---|
| fuzz-harness-writing | Structure-aware fuzzing with arbitrary crate |
| address-sanitizer | Understanding ASan output and configuration |
| coverage-analysis | Measuring and improving fuzzing effectiveness |
| fuzzing-corpus | Building and managing seed corpora |
| fuzzing-dictionaries | Creating dictionaries for format-aware fuzzing |
Related Fuzzers
| Skill | When to Consider |
|---|---|
| libfuzzer | Fuzzing C/C++ code with similar workflow |
| aflpp | Multi-core fuzzing or non-Cargo Rust projects |
| libafl | Advanced fuzzing research or custom fuzzer development |
Resources
Rust Fuzz Book - cargo-fuzz Official documentation for cargo-fuzz covering installation, usage, and advanced features.
arbitrary crate documentation Guide to structure-aware fuzzing with automatic derivation for Rust types.
cargo-fuzz GitHub Repository Source code, issue tracker, and examples for cargo-fuzz.
/constant-time-testing
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/constant-time-testing/SKILL.md
name: constant-time-testing type: domain description: > Constant-time testing detects timing side channels in cryptographic code. Use when auditing crypto implementations for timing vulnerabilities.
Constant-Time Testing
Timing attacks exploit variations in execution time to extract secret information from cryptographic implementations. Unlike cryptanalysis that targets theoretical weaknesses, timing attacks leverage implementation flaws - and they can affect any cryptographic code.
Background
Timing attacks were introduced by Kocher in 1996. Since then, researchers have demonstrated practical attacks on RSA (Schindler), OpenSSL (Brumley and Boneh), AES implementations, and even post-quantum algorithms like Kyber.
Key Concepts
| Concept | Description |
|---|---|
| Constant-time | Code path and memory accesses independent of secret data |
| Timing leakage | Observable execution time differences correlated with secrets |
| Side channel | Information extracted from implementation rather than algorithm |
| Microarchitecture | CPU-level timing differences (cache, division, shifts) |
Why This Matters
Timing vulnerabilities can:
- Expose private keys - Extract secret exponents in RSA/ECDH
- Enable remote attacks - Network-observable timing differences
- Bypass cryptographic security - Undermine theoretical guarantees
- Persist silently - Often undetected without specialized analysis
Two prerequisites enable exploitation:
- Access to oracle - Sufficient queries to the vulnerable implementation
- Timing dependency - Correlation between execution time and secret data
Common Constant-Time Violation Patterns
Four patterns account for most timing vulnerabilities:
// 1. Conditional jumps - most severe timing differences
if(secret == 1) { ... }
while(secret > 0) { ... }
// 2. Array access - cache-timing attacks
lookup_table[secret];
// 3. Integer division (processor dependent)
data = secret / m;
// 4. Shift operation (processor dependent)
data = a << secret;
Conditional jumps cause different code paths, leading to vast timing differences.
Array access dependent on secrets enables cache-timing attacks, as shown in AES cache-timing research.
Integer division and shift operations leak secrets on certain CPU architectures and compiler configurations.
When patterns cannot be avoided, employ masking techniques to remove correlation between timing and secrets.
Example: Modular Exponentiation Timing Attacks
Modular exponentiation (used in RSA and Diffie-Hellman) is susceptible to timing attacks. RSA decryption computes:
$$ct^{d} \mod{N}$$
where $d$ is the secret exponent. The exponentiation by squaring optimization reduces multiplications to $\log{d}$:
$$ \begin{align*} & \textbf{Input: } \text{base }y,\text{exponent } d={d_n,\cdots,d_0}_2,\text{modulus } N \ & r = 1 \ & \textbf{for } i=|n| \text{ downto } 0: \ & \quad\textbf{if } d_i == 1: \ & \quad\quad r = r * y \mod{N} \ & \quad y = y * y \mod{N} \ & \textbf{return }r \end{align*} $$
The code branches on exponent bit $d_i$, violating constant-time principles. When $d_i = 1$, an additional multiplication occurs, increasing execution time and leaking bit information.
Montgomery multiplication (commonly used for modular arithmetic) also leaks timing: when intermediate values exceed modulus $N$, an additional reduction step is required. An attacker constructs inputs $y$ and $y'$ such that:
$$ \begin{align*} y^2 < y^3 < N \ y'^2 < N \leq y'^3 \end{align*} $$
For $y$, both multiplications take time $t_1+t_1$. For $y'$, the second multiplication requires reduction, taking time $t_1+t_2$. This timing difference reveals whether $d_i$ is 0 or 1.
When to Use
Apply constant-time analysis when:
- Auditing cryptographic implementations (primitives, protocols)
- Code handles secret keys, passwords, or sensitive cryptographic material
- Implementing crypto algorithms from scratch
- Reviewing PRs that touch crypto code
- Investigating potential timing vulnerabilities
Consider alternatives when:
- Code does not process secret data
- Public algorithms with no secret inputs
- Non-cryptographic timing requirements (performance optimization)
Quick Reference
| Scenario | Recommended Approach | Skill |
|---|---|---|
| Prove absence of leaks | Formal verification | SideTrail, ct-verif, FaCT |
| Detect statistical timing differences | Statistical testing | dudect |
| Track secret data flow at runtime | Dynamic analysis | timecop |
| Find cache-timing vulnerabilities | Symbolic execution | Binsec, pitchfork |
Constant-Time Tooling Categories
The cryptographic community has developed four categories of timing analysis tools:
| Category | Approach | Pros | Cons |
|---|---|---|---|
| Formal | Mathematical proof on model | Guarantees absence of leaks | Complexity, modeling assumptions |
| Symbolic | Symbolic execution paths | Concrete counterexamples | Time-intensive path exploration |
| Dynamic | Runtime tracing with marked secrets | Granular, flexible | Limited coverage to executed paths |
| Statistical | Measure real execution timing | Practical, simple setup | No root cause, noise sensitivity |
1. Formal Tools
Formal verification mathematically proves timing properties on an abstraction (model) of code. Tools create a model from source/binary and verify it satisfies specified properties (e.g., variables annotated as secret).
Popular tools:
Strengths: Proof of absence, language-agnostic (LLVM bytecode) Weaknesses: Requires expertise, modeling assumptions may miss real-world issues
2. Symbolic Tools
Symbolic execution analyzes how paths and memory accesses depend on symbolic variables (secrets). Provides concrete counterexamples. Focus on cache-timing attacks.
Popular tools:
Strengths: Concrete counterexamples aid debugging Weaknesses: Path explosion leads to long execution times
3. Dynamic Tools
Dynamic analysis marks sensitive memory regions and traces execution to detect timing-dependent operations.
Popular tools:
Strengths: Granular control, targeted analysis Weaknesses: Coverage limited to executed paths
Detailed Guidance: See the timecop skill for setup and usage.
4. Statistical Tools
Execute code with various inputs, measure elapsed time, and detect inconsistencies. Tests actual implementation including compiler optimizations and architecture.
Popular tools:
- dudect (see below)
- tlsfuzzer
Strengths: Simple setup, practical real-world results Weaknesses: No root cause info, noise obscures weak signals
Detailed Guidance: See the dudect skill for setup and usage.
Testing Workflow
Phase 1: Static Analysis Phase 2: Statistical Testing
┌─────────────────┐ ┌─────────────────┐
│ Identify secret │ → │ Detect timing │
│ data flow │ │ differences │
│ Tool: ct-verif │ │ Tool: dudect │
└─────────────────┘ └─────────────────┘
↓ ↓
Phase 4: Root Cause Phase 3: Dynamic Tracing
┌─────────────────┐ ┌─────────────────┐
│ Pinpoint leak │ ← │ Track secret │
│ location │ │ propagation │
│ Tool: Timecop │ │ Tool: Timecop │
└─────────────────┘ └─────────────────┘
Recommended approach:
- Start with dudect - Quick statistical check for timing differences
- If leaks found - Use Timecop to pinpoint root cause
- For high-assurance - Apply formal verification (ct-verif, SideTrail)
- Continuous monitoring - Integrate dudect into CI pipeline
Tools and Approaches
Dudect - Statistical Analysis
Dudect measures execution time for two input classes (fixed vs random) and uses Welch's t-test to detect statistically significant differences.
Detailed Guidance: See the dudect skill for complete setup, usage patterns, and CI integration.
Quick Start for Constant-Time Analysis
#define DUDECT_IMPLEMENTATION
#include "dudect.h"
uint8_t do_one_computation(uint8_t *data) {
// Code to measure goes here
}
void prepare_inputs(dudect_config_t *c, uint8_t *input_data, uint8_t *classes) {
for (size_t i = 0; i < c->number_measurements; i++) {
classes[i] = randombit();
uint8_t *input = input_data + (size_t)i * c->chunk_size;
if (classes[i] == 0) {
// Fixed input class
} else {
// Random input class
}
}
}
Key advantages:
- Simple C header-only integration
- Statistical rigor via Welch's t-test
- Works with compiled binaries (real-world conditions)
Key limitations:
- No root cause information when leak detected
- Sensitive to measurement noise
- Cannot guarantee absence of leaks (statistical confidence only)
Timecop - Dynamic Tracing
Timecop wraps Valgrind to detect runtime operations dependent on secret memory regions.
Detailed Guidance: See the timecop skill for installation, examples, and debugging.
Quick Start for Constant-Time Analysis
#include "valgrind/memcheck.h"
#define poison(addr, len) VALGRIND_MAKE_MEM_UNDEFINED(addr, len)
#define unpoison(addr, len) VALGRIND_MAKE_MEM_DEFINED(addr, len)
int main() {
unsigned long long secret_key = 0x12345678;
// Mark secret as poisoned
poison(&secret_key, sizeof(secret_key));
// Any branching or memory access dependent on secret_key
// will be reported by Valgrind
crypto_operation(secret_key);
unpoison(&secret_key, sizeof(secret_key));
}
Run with Valgrind:
valgrind --leak-check=full --track-origins=yes ./binary
Key advantages:
- Pinpoints exact line of timing leak
- No code instrumentation required
- Tracks secret propagation through execution
Key limitations:
- Cannot detect microarchitecture timing differences
- Coverage limited to executed paths
- Performance overhead (runs on synthetic CPU)
Implementation Guide
Phase 1: Initial Assessment
Identify cryptographic code handling secrets:
- Private keys, exponents, nonces
- Password hashes, authentication tokens
- Encryption/decryption operations
Quick statistical check:
- Write dudect harness for the crypto function
- Run for 5-10 minutes with
timeout 600 ./ct_test - Monitor t-value: high absolute values indicate leakage
Tools: dudect Expected time: 1-2 hours (harness writing + initial run)
Phase 2: Detailed Analysis
If dudect detects leakage:
Root cause investigation:
- Mark secret variables with Timecop
poison() - Run under Valgrind to identify exact line
- Review the four common violation patterns
- Check assembly output for conditional branches
Tools: Timecop, compiler output (objdump -d)
Phase 3: Remediation
Fix the timing leak:
- Replace conditional branches with constant-time selection (bitwise operations)
- Use constant-time comparison functions
- Replace array lookups with constant-time alternatives or masking
- Verify compiler doesn't optimize away constant-time code
Re-verify:
- Run dudect again for extended period (30+ minutes)
- Test across different compilers and optimization levels
- Test on different CPU architectures
Phase 4: Continuous Monitoring
Integrate into CI:
- Add dudect tests to test suite
- Run for fixed duration (5-10 minutes in CI)
- Fail build if leakage detected
See the dudect skill for CI integration examples.
Common Vulnerabilities
| Vulnerability | Description | Detection | Severity |
|---|---|---|---|
| Secret-dependent branch | if (secret_bit) { ... } |
dudect, Timecop | CRITICAL |
| Secret-dependent array access | table[secret_index] |
Timecop, Binsec | HIGH |
| Variable-time division | result = x / secret |
Timecop | MEDIUM |
| Variable-time shift | result = x << secret |
Timecop | MEDIUM |
| Montgomery reduction leak | Extra reduction when intermediate > N | dudect | HIGH |
Secret-Dependent Branch: Deep Dive
The vulnerability: Execution time differs based on whether branch is taken. Common in optimized modular exponentiation (square-and-multiply).
How to detect with dudect:
uint8_t do_one_computation(uint8_t *data) {
uint64_t base = ((uint64_t*)data)[0];
uint64_t exponent = ((uint64_t*)data)[1]; // Secret!
return mod_exp(base, exponent, MODULUS);
}
void prepare_inputs(dudect_config_t *c, uint8_t *input_data, uint8_t *classes) {
for (size_t i = 0; i < c->number_measurements; i++) {
classes[i] = randombit();
uint64_t *input = (uint64_t*)(input_data + i * c->chunk_size);
input[0] = rand(); // Random base
input[1] = (classes[i] == 0) ? FIXED_EXPONENT : rand(); // Fixed vs random
}
}
How to detect with Timecop:
poison(&exponent, sizeof(exponent));
result = mod_exp(base, exponent, modulus);
unpoison(&exponent, sizeof(exponent));
Valgrind will report:
Conditional jump or move depends on uninitialised value(s)
at 0x40115D: mod_exp (example.c:14)
Case Studies
Case Study: OpenSSL RSA Timing Attack
Brumley and Boneh (2005) extracted RSA private keys from OpenSSL over a network. The vulnerability exploited Montgomery multiplication's variable-time reduction step.
Attack vector: Timing differences in modular exponentiation Detection approach: Statistical analysis (precursor to dudect) Impact: Remote key extraction
Tools used: Custom timing measurement Techniques applied: Statistical analysis, chosen-ciphertext queries
Case Study: KyberSlash
Post-quantum algorithm Kyber's reference implementation contained timing vulnerabilities in polynomial operations. Division operations leaked secret coefficients.
Attack vector: Secret-dependent division timing Detection approach: Dynamic analysis and statistical testing Impact: Secret key recovery in post-quantum cryptography
Tools used: Timing measurement tools Techniques applied: Differential timing analysis
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
Pin dudect to isolated CPU core (taskset -c 2) |
Reduces OS noise, improves signal detection |
| Test multiple compilers (gcc, clang, MSVC) | Optimizations may introduce or remove leaks |
| Run dudect for extended periods (hours) | Increases statistical confidence |
| Minimize non-crypto code in harness | Reduces noise that masks weak signals |
Check assembly output (objdump -d) |
Verify compiler didn't introduce branches |
Use -O3 -march=native in testing |
Matches production optimization levels |
Common Mistakes
| Mistake | Why It's Wrong | Correct Approach |
|---|---|---|
| Only testing one input distribution | May miss leaks visible with other patterns | Test fixed-vs-random, fixed-vs-fixed-different, etc. |
| Short dudect runs (< 1 minute) | Insufficient measurements for weak signals | Run 5-10+ minutes, longer for high assurance |
| Ignoring compiler optimization levels | -O0 may hide leaks present in -O3 |
Test at production optimization level |
| Not testing on target architecture | x86 vs ARM have different timing characteristics | Test on deployment platform |
| Marking too much as secret in Timecop | False positives, unclear results | Mark only true secrets (keys, not public data) |
Related Skills
Tool Skills
| Skill | Primary Use in Constant-Time Analysis |
|---|---|
| dudect | Statistical detection of timing differences via Welch's t-test |
| timecop | Dynamic tracing to pinpoint exact location of timing leaks |
Technique Skills
| Skill | When to Apply |
|---|---|
| coverage-analysis | Ensure test inputs exercise all code paths in crypto function |
| ci-integration | Automate constant-time testing in continuous integration pipeline |
Related Domain Skills
| Skill | Relationship |
|---|---|
| crypto-testing | Constant-time analysis is essential component of cryptographic testing |
| fuzzing | Fuzzing crypto code may trigger timing-dependent paths |
Skill Dependency Map
┌─────────────────────────┐
│ constant-time-analysis │
│ (this skill) │
└───────────┬─────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ dudect │ │ timecop │
│ (statistical) │ │ (dynamic) │
└────────┬──────────┘ └────────┬──────────┘
│ │
└───────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ Supporting Techniques │
│ coverage, CI integration │
└──────────────────────────────┘
Resources
Key External Resources
These results must be false: A usability evaluation of constant-time analysis tools Comprehensive usability study of constant-time analysis tools. Key findings: developers struggle with false positives, need better error messages, and benefit from tool integration. Evaluates FaCT, ct-verif, dudect, and Memsan across multiple cryptographic implementations. Recommends improved tooling UX and better documentation.
List of constant-time tools - CROCS Curated catalog of constant-time analysis tools with tutorials. Covers formal tools (ct-verif, FaCT), dynamic tools (Memsan, Timecop), symbolic tools (Binsec), and statistical tools (dudect). Includes practical tutorials for setup and usage.
Paul Kocher: Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other Systems Original 1996 paper introducing timing attacks. Demonstrates attacks on modular exponentiation in RSA and Diffie-Hellman. Essential historical context for understanding timing vulnerabilities.
Remote Timing Attacks are Practical (Brumley & Boneh) Demonstrates practical remote timing attacks against OpenSSL. Shows network-level timing differences are sufficient to extract RSA keys. Proves timing attacks work in realistic network conditions.
Cache-timing attacks on AES Shows AES implementations using lookup tables are vulnerable to cache-timing attacks. Demonstrates practical attacks extracting AES keys via cache timing side channels.
KyberSlash: Division Timings Leak Secrets Recent discovery of timing vulnerabilities in Kyber (NIST post-quantum standard). Shows division operations leak secret coefficients. Highlights that constant-time issues persist even in modern post-quantum cryptography.
Video Resources
- Trail of Bits: Constant-Time Programming - Overview of constant-time programming principles and tools
/coverage-analysis
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/coverage-analysis/SKILL.md
name: coverage-analysis type: technique description: > Coverage analysis measures code exercised during fuzzing. Use when assessing harness effectiveness or identifying fuzzing blockers.
Coverage Analysis
Coverage analysis is essential for understanding which parts of your code are exercised during fuzzing. It helps identify fuzzing blockers like magic value checks and tracks the effectiveness of harness improvements over time.
Overview
Code coverage during fuzzing serves two critical purposes:
- Assessing harness effectiveness: Understand which parts of your application are actually executed by your fuzzing harnesses
- Tracking fuzzing progress: Monitor how coverage changes when updating harnesses, fuzzers, or the system under test (SUT)
Coverage is a proxy for fuzzer capability and performance. While coverage is not ideal for measuring fuzzer performance in absolute terms, it reliably indicates whether your harness works effectively in a given setup.
Key Concepts
| Concept | Description |
|---|---|
| Coverage instrumentation | Compiler flags that track which code paths are executed |
| Corpus coverage | Coverage achieved by running all test cases in a fuzzing corpus |
| Magic value checks | Hard-to-discover conditional checks that block fuzzer progress |
| Coverage-guided fuzzing | Fuzzing strategy that prioritizes inputs that discover new code paths |
| Coverage report | Visual or textual representation of executed vs. unexecuted code |
When to Apply
Apply this technique when:
- Starting a new fuzzing campaign to establish a baseline
- Fuzzer appears to plateau without finding new paths
- After harness modifications to verify improvements
- When migrating between different fuzzers
- Identifying areas requiring dictionary entries or seed inputs
- Debugging why certain code paths aren't reached
Skip this technique when:
- Fuzzing campaign is actively finding crashes
- Coverage infrastructure isn't set up yet
- Working with extremely large codebases where full coverage reports are impractical
- Fuzzer's internal coverage metrics are sufficient for your needs
Quick Reference
| Task | Command/Pattern |
|---|---|
| LLVM coverage instrumentation (C/C++) | -fprofile-instr-generate -fcoverage-mapping |
| GCC coverage instrumentation | -ftest-coverage -fprofile-arcs |
| cargo-fuzz coverage (Rust) | cargo +nightly fuzz coverage <target> |
| Generate LLVM profile data | llvm-profdata merge -sparse file.profraw -o file.profdata |
| LLVM coverage report | llvm-cov report ./binary -instr-profile=file.profdata |
| LLVM HTML report | llvm-cov show ./binary -instr-profile=file.profdata -format=html -output-dir html/ |
| gcovr HTML report | gcovr --html-details -o coverage.html |
Ideal Coverage Workflow
The following workflow represents best practices for integrating coverage analysis into your fuzzing campaigns:
[Fuzzing Campaign]
|
v
[Generate Corpus]
|
v
[Coverage Analysis]
|
+---> Coverage Increased? --> Continue fuzzing with larger corpus
|
+---> Coverage Decreased? --> Fix harness or investigate SUT changes
|
+---> Coverage Plateaued? --> Add dictionary entries or seed inputs
Key principle: Use the corpus generated after each fuzzing campaign to calculate coverage, rather than real-time fuzzer statistics. This approach provides reproducible, comparable measurements across different fuzzing tools.
Step-by-Step
Step 1: Build with Coverage Instrumentation
Choose your instrumentation method based on toolchain:
LLVM/Clang (C/C++):
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec
GCC (C/C++):
g++ -ftest-coverage -fprofile-arcs \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec_gcov
Rust:
rustup toolchain install nightly --component llvm-tools-preview
cargo +nightly fuzz coverage fuzz_target_1
Step 2: Create Execution Runtime (C/C++ only)
For C/C++ projects, create a runtime that executes your corpus:
// execute-rt.cc
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
void load_file_and_test(const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
printf("Failed to open file: %s\n", filename);
return;
}
fseek(file, 0, SEEK_END);
long filesize = ftell(file);
rewind(file);
uint8_t *buffer = (uint8_t*) malloc(filesize);
if (buffer == NULL) {
printf("Failed to allocate memory for file: %s\n", filename);
fclose(file);
return;
}
long read_size = (long) fread(buffer, 1, filesize, file);
if (read_size != filesize) {
printf("Failed to read file: %s\n", filename);
free(buffer);
fclose(file);
return;
}
LLVMFuzzerTestOneInput(buffer, filesize);
free(buffer);
fclose(file);
}
int main(int argc, char **argv) {
if (argc != 2) {
printf("Usage: %s <directory>\n", argv[0]);
return 1;
}
DIR *dir = opendir(argv[1]);
if (dir == NULL) {
printf("Failed to open directory: %s\n", argv[1]);
return 1;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG) {
char filepath[1024];
snprintf(filepath, sizeof(filepath), "%s/%s", argv[1], entry->d_name);
load_file_and_test(filepath);
}
}
closedir(dir);
return 0;
}
Step 3: Execute on Corpus
LLVM (C/C++):
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/
GCC (C/C++):
./fuzz_exec_gcov corpus/
Rust:
Coverage data is automatically generated when running cargo fuzz coverage.
Step 4: Process Coverage Data
LLVM:
# Merge raw profile data
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
# Generate text report
llvm-cov report ./fuzz_exec \
-instr-profile=fuzz.profdata \
-ignore-filename-regex='harness.cc|execute-rt.cc'
# Generate HTML report
llvm-cov show ./fuzz_exec \
-instr-profile=fuzz.profdata \
-ignore-filename-regex='harness.cc|execute-rt.cc' \
-format=html -output-dir fuzz_html/
GCC with gcovr:
# Install gcovr (via pip for latest version)
python3 -m venv venv
source venv/bin/activate
pip3 install gcovr
# Generate report
gcovr --gcov-executable "llvm-cov gcov" \
--exclude harness.cc --exclude execute-rt.cc \
--root . --html-details -o coverage.html
Rust:
# Install required tools
cargo install cargo-binutils rustfilt
# Create HTML generation script
cat <<'EOF' > ./generate_html
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Error: Name of fuzz target is required."
echo "Usage: $0 fuzz_target [sources...]"
exit 1
fi
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
"target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
-instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
-show-line-counts-or-regions -show-instantiations \
-format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html
# Generate HTML report
./generate_html fuzz_target_1 src/lib.rs
Step 5: Analyze Results
Review the coverage report to identify:
- Uncovered code blocks: Areas that may need better seed inputs or dictionary entries
- Magic value checks: Conditional statements with hardcoded values that block progress
- Dead code: Functions that may not be reachable through your harness
- Coverage changes: Compare against baseline to track improvements or regressions
Common Patterns
Pattern: Identifying Magic Values
Problem: Fuzzer cannot discover paths guarded by magic value checks.
Coverage reveals:
// Coverage shows this block is never executed
if (buf == 0x7F454C46) { // ELF magic number
// start parsing buf
}
Solution: Add magic values to dictionary file:
# magic.dict
"\x7F\x45\x4C\x46"
Pattern: Handling Crashing Inputs
Problem: Coverage generation fails when corpus contains crashing inputs.
Before:
./fuzz_exec corpus/ # Crashes on bad input, no coverage generated
After:
// Fork before executing to isolate crashes
int main(int argc, char **argv) {
// ... directory opening code ...
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG) {
pid_t pid = fork();
if (pid == 0) {
// Child process - crash won't affect parent
char filepath[1024];
snprintf(filepath, sizeof(filepath), "%s/%s", argv[1], entry->d_name);
load_file_and_test(filepath);
exit(0);
} else {
// Parent waits for child
waitpid(pid, NULL, 0);
}
}
}
}
Pattern: CMake Integration
Use Case: Adding coverage builds to CMake projects.
project(FuzzingProject)
cmake_minimum_required(VERSION 3.0)
# Main binary
add_executable(program main.cc)
# Fuzzing binary
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2 -fsanitize=fuzzer)
target_link_libraries(fuzz -fsanitize=fuzzer)
# Coverage execution binary
add_executable(fuzz_exec main.cc harness.cc execute-rt.cc)
target_compile_definitions(fuzz_exec PRIVATE NO_MAIN)
target_compile_options(fuzz_exec PRIVATE -O2 -fprofile-instr-generate -fcoverage-mapping)
target_link_libraries(fuzz_exec -fprofile-instr-generate)
Build:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target fuzz_exec
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
Use LLVM 18+ with -show-directory-coverage |
Organizes large reports by directory structure instead of flat file list |
| Export to lcov format for better HTML | llvm-cov export -format=lcov + genhtml provides cleaner per-file reports |
| Compare coverage across campaigns | Store .profdata files with timestamps to track progress over time |
| Filter harness code from reports | Use -ignore-filename-regex to focus on SUT coverage only |
| Automate coverage in CI/CD | Generate coverage reports automatically after scheduled fuzzing runs |
| Use gcovr 5.1+ for Clang 14+ | Older gcovr versions have compatibility issues with recent LLVM |
Incremental Coverage Updates
GCC's gcov instrumentation incrementally updates .gcda files across multiple runs. This is useful for tracking coverage as you add test cases:
# First run
./fuzz_exec_gcov corpus_batch_1/
gcovr --html coverage_v1.html
# Second run (adds to existing coverage)
./fuzz_exec_gcov corpus_batch_2/
gcovr --html coverage_v2.html
# Start fresh
gcovr --delete # Remove .gcda files
./fuzz_exec_gcov corpus/
Handling Large Codebases
For projects with hundreds of source files:
-
Filter by prefix: Only generate reports for relevant directories
llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata /path/to/src/ -
Use directory coverage: Group by directory to reduce clutter (LLVM 18+)
llvm-cov show -show-directory-coverage -format=html -output-dir html/ -
Generate JSON for programmatic analysis:
llvm-cov export -format=lcov > coverage.json
Differential Coverage
Compare coverage between two fuzzing campaigns:
# Campaign 1
LLVM_PROFILE_FILE=campaign1.profraw ./fuzz_exec corpus1/
llvm-profdata merge -sparse campaign1.profraw -o campaign1.profdata
# Campaign 2
LLVM_PROFILE_FILE=campaign2.profraw ./fuzz_exec corpus2/
llvm-profdata merge -sparse campaign2.profraw -o campaign2.profdata
# Compare
llvm-cov show ./fuzz_exec \
-instr-profile=campaign2.profdata \
-instr-profile=campaign1.profdata \
-show-line-counts-or-regions
Anti-Patterns
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| Using fuzzer-reported coverage for comparisons | Different fuzzers calculate coverage differently, making cross-tool comparison meaningless | Use dedicated coverage tools (llvm-cov, gcovr) for reproducible measurements |
| Generating coverage with optimizations | -O3 optimizations can eliminate code, making coverage misleading |
Use -O2 or -O0 for coverage builds |
| Not filtering harness code | Harness coverage inflates numbers and obscures SUT coverage | Use -ignore-filename-regex or --exclude to filter harness files |
| Mixing LLVM and GCC instrumentation | Incompatible formats cause parsing failures | Stick to one toolchain for coverage builds |
| Ignoring crashing inputs | Crashes prevent coverage generation, hiding real coverage data | Fix crashes first, or use process forking to isolate them |
| Not tracking coverage over time | One-time coverage checks miss regressions and improvements | Store coverage data with timestamps and track trends |
Tool-Specific Guidance
libFuzzer
libFuzzer uses LLVM's SanitizerCoverage by default for guiding fuzzing, but you need separate instrumentation for generating reports.
Build for coverage:
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec
Execute corpus and generate report:
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata -format=html -output-dir html/
Integration tips:
- Don't use
-fsanitize=fuzzerfor coverage builds (it conflicts with profile instrumentation) - Reuse the same harness function (
LLVMFuzzerTestOneInput) with a different main function - Use the
-ignore-filename-regexflag to exclude harness code from coverage reports - Consider using llvm-cov's
-show-instantiationflag for template-heavy C++ code
AFL++
AFL++ provides its own coverage feedback mechanism, but for detailed reports use standard LLVM/GCC tools.
Build for coverage with LLVM:
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 main.cc harness.cc execute-rt.cc -o fuzz_exec
Build for coverage with GCC:
AFL_USE_ASAN=0 afl-gcc -ftest-coverage -fprofile-arcs \
main.cc harness.cc execute-rt.cc -o fuzz_exec_gcov
Execute and generate report:
# LLVM approach
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec afl_output/queue/
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
llvm-cov report ./fuzz_exec -instr-profile=fuzz.profdata
# GCC approach
./fuzz_exec_gcov afl_output/queue/
gcovr --html-details -o coverage.html
Integration tips:
- Don't use AFL++'s instrumentation (
afl-clang-fast) for coverage builds - Use standard compilers with coverage flags instead
- AFL++'s
queue/directory contains your corpus - AFL++'s built-in coverage statistics are useful for real-time monitoring but not for detailed analysis
cargo-fuzz (Rust)
cargo-fuzz provides built-in coverage generation using LLVM tools.
Install prerequisites:
rustup toolchain install nightly --component llvm-tools-preview
cargo install cargo-binutils rustfilt
Generate coverage data:
cargo +nightly fuzz coverage fuzz_target_1
Create HTML report script:
cat <<'EOF' > ./generate_html
#!/bin/sh
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
"target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
-instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
-show-line-counts-or-regions -show-instantiations \
-format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html
Generate report:
./generate_html fuzz_target_1 src/lib.rs
Integration tips:
- Always use the nightly toolchain for coverage
- The
-Xdemangler=rustfiltflag makes function names readable - Filter by source files (e.g.,
src/lib.rs) to focus on crate code - Use
-show-line-counts-or-regionsand-show-instantiationsfor better Rust-specific output - Corpus is located in
fuzz/corpus/<target>/
honggfuzz
honggfuzz works with standard LLVM/GCC coverage instrumentation.
Build for coverage:
# Use standard compiler, not honggfuzz compiler
clang -fprofile-instr-generate -fcoverage-mapping \
-O2 harness.c execute-rt.c -o fuzz_exec
Execute corpus:
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec honggfuzz_workspace/
Integration tips:
- Don't use
hfuzz-clangfor coverage builds - honggfuzz corpus is typically in a workspace directory
- Use the same LLVM workflow as libFuzzer
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
error: no profile data available |
Profile wasn't generated or wrong path | Verify LLVM_PROFILE_FILE was set and .profraw file exists |
Failed to load coverage |
Mismatch between binary and profile data | Rebuild binary with same flags used during execution |
| Coverage reports show 0% | Wrong binary used for report generation | Use the instrumented binary, not the fuzzing binary |
no_working_dir_found error (gcovr) |
.gcda files in unexpected location |
Add --gcov-ignore-errors=no_working_dir_found flag |
| Crashes prevent coverage generation | Corpus contains crashing inputs | Filter crashes or use forking approach to isolate failures |
| Coverage decreases after harness change | Harness now skips certain code paths | Review harness logic; may need to support more input formats |
| HTML report is flat file list | Using older LLVM version | Upgrade to LLVM 18+ and use -show-directory-coverage |
incompatible instrumentation |
Mixing LLVM and GCC coverage | Rebuild everything with same toolchain |
Related Skills
Tools That Use This Technique
| Skill | How It Applies |
|---|---|
| libfuzzer | Uses SanitizerCoverage for feedback; coverage analysis evaluates harness effectiveness |
| aflpp | Uses edge coverage for feedback; detailed analysis requires separate instrumentation |
| cargo-fuzz | Built-in cargo fuzz coverage command for Rust projects |
| honggfuzz | Uses edge coverage; analyze with standard LLVM/GCC tools |
Related Techniques
| Skill | Relationship |
|---|---|
| fuzz-harness-writing | Coverage reveals which code paths harness reaches; guides harness improvements |
| fuzzing-dictionaries | Coverage identifies magic value checks that need dictionary entries |
| corpus-management | Coverage analysis helps curate corpora by identifying redundant test cases |
| sanitizers | Coverage helps verify sanitizer-instrumented code is actually executed |
Resources
Key External Resources
LLVM Source-Based Code Coverage Comprehensive guide to LLVM's profile instrumentation, including advanced features like branch coverage, region coverage, and integration with existing build systems. Covers compiler flags, runtime behavior, and profile data formats.
llvm-cov Command Guide
Detailed CLI reference for llvm-cov commands including show, report, and export. Documents all filtering options, output formats, and integration with llvm-profdata.
gcovr Documentation Complete guide to gcovr tool for generating coverage reports from gcov data. Covers HTML themes, filtering options, multi-directory projects, and CI/CD integration patterns.
SanitizerCoverage Documentation Low-level documentation for LLVM's SanitizerCoverage instrumentation. Explains inline 8-bit counters, PC tables, and how fuzzers use coverage feedback for guidance.
On the Evaluation of Fuzzer Performance Research paper examining limitations of coverage as a fuzzing performance metric. Argues for more nuanced evaluation methods beyond simple code coverage percentages.
Video Resources
Not applicable - coverage analysis is primarily a tooling and workflow topic best learned through documentation and hands-on practice.
/fuzzing-dictionary
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/fuzzing-dictionary/SKILL.md
name: fuzzing-dictionary type: technique description: > Fuzzing dictionaries guide fuzzers with domain-specific tokens. Use when fuzzing parsers, protocols, or format-specific code.
Fuzzing Dictionary
A fuzzing dictionary provides domain-specific tokens to guide the fuzzer toward interesting inputs. Instead of purely random mutations, the fuzzer incorporates known keywords, magic numbers, protocol commands, and format-specific strings that are more likely to reach deeper code paths in parsers, protocol handlers, and file format processors.
Overview
Dictionaries are text files containing quoted strings that represent meaningful tokens for your target. They help fuzzers bypass early validation checks and explore code paths that would be difficult to reach through blind mutation alone.
Key Concepts
| Concept | Description |
|---|---|
| Dictionary Entry | A quoted string (e.g., "keyword") or key-value pair (e.g., kw="value") |
| Hex Escapes | Byte sequences like "\xF7\xF8" for non-printable characters |
| Token Injection | Fuzzer inserts dictionary entries into generated inputs |
| Cross-Fuzzer Format | Dictionary files work with libFuzzer, AFL++, and cargo-fuzz |
When to Apply
Apply this technique when:
- Fuzzing parsers (JSON, XML, config files)
- Fuzzing protocol implementations (HTTP, DNS, custom protocols)
- Fuzzing file format handlers (PNG, PDF, media codecs)
- Coverage plateaus early without reaching deeper logic
- Target code checks for specific keywords or magic values
Skip this technique when:
- Fuzzing pure algorithms without format expectations
- Target has no keyword-based parsing
- Corpus already achieves high coverage
Quick Reference
| Task | Command/Pattern |
|---|---|
| Use with libFuzzer | ./fuzz -dict=./dictionary.dict ... |
| Use with AFL++ | afl-fuzz -x ./dictionary.dict ... |
| Use with cargo-fuzz | cargo fuzz run fuzz_target -- -dict=./dictionary.dict |
| Extract from header | grep -o '".*"' header.h > header.dict |
| Generate from binary | strings ./binary | sed 's/^/"&/; s/$/&"/' > strings.dict |
Step-by-Step
Step 1: Create Dictionary File
Create a text file with quoted strings on each line. Use comments (#) for documentation.
Example dictionary format:
# Lines starting with '#' and empty lines are ignored.
# Adds "blah" (w/o quotes) to the dictionary.
kw1="blah"
# Use \\ for backslash and \" for quotes.
kw2="\"ac\\dc\""
# Use \xAB for hex values
kw3="\xF7\xF8"
# the name of the keyword followed by '=' may be omitted:
"foo\x0Abar"
Step 2: Generate Dictionary Content
Choose a generation method based on what's available:
From LLM: Prompt ChatGPT or Claude with:
A dictionary can be used to guide the fuzzer. Write me a dictionary file for fuzzing a <PNG parser>. Each line should be a quoted string or key-value pair like kw="value". Include magic bytes, chunk types, and common header values. Use hex escapes like "\xF7\xF8" for binary values.
From header files:
grep -o '".*"' header.h > header.dict
From man pages (for CLI tools):
man curl | grep -oP '^\s*(--|-)\K\S+' | sed 's/[,.]$//' | sed 's/^/"&/; s/$/&"/' | sort -u > man.dict
From binary strings:
strings ./binary | sed 's/^/"&/; s/$/&"/' > strings.dict
Step 3: Pass Dictionary to Fuzzer
Use the appropriate flag for your fuzzer (see Quick Reference above).
Common Patterns
Pattern: Protocol Keywords
Use Case: Fuzzing HTTP or custom protocol handlers
Dictionary content:
# HTTP methods
"GET"
"POST"
"PUT"
"DELETE"
"HEAD"
# Headers
"Content-Type"
"Authorization"
"Host"
# Protocol markers
"HTTP/1.1"
"HTTP/2.0"
Pattern: Magic Bytes and File Format Headers
Use Case: Fuzzing image parsers, media decoders, archive handlers
Dictionary content:
# PNG magic bytes and chunks
png_magic="\x89PNG\r\n\x1a\n"
ihdr="IHDR"
plte="PLTE"
idat="IDAT"
iend="IEND"
# JPEG markers
jpeg_soi="\xFF\xD8"
jpeg_eoi="\xFF\xD9"
Pattern: Configuration File Keywords
Use Case: Fuzzing config file parsers (YAML, TOML, INI)
Dictionary content:
# Common config keywords
"true"
"false"
"null"
"version"
"enabled"
"disabled"
# Section headers
"[general]"
"[network]"
"[security]"
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
| Combine multiple generation methods | LLM-generated keywords + strings from binary covers broad surface |
| Include boundary values | "0", "-1", "2147483647" trigger edge cases |
| Add format delimiters | :, =, {, } help fuzzer construct valid structures |
| Keep dictionaries focused | 50-200 entries perform better than thousands |
| Test dictionary effectiveness | Run with and without dict, compare coverage |
Auto-Generated Dictionaries (AFL++)
When using afl-clang-lto compiler, AFL++ automatically extracts dictionary entries from string comparisons in the binary. This happens at compile time via the AUTODICTIONARY feature.
Enable auto-dictionary:
export AFL_LLVM_DICT2FILE=auto.dict
afl-clang-lto++ target.cc -o target
# Dictionary saved to auto.dict
afl-fuzz -x auto.dict -i in -o out -- ./target
Combining Multiple Dictionaries
Some fuzzers support multiple dictionary files:
# AFL++ with multiple dictionaries
afl-fuzz -x keywords.dict -x formats.dict -i in -o out -- ./target
Anti-Patterns
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| Including full sentences | Fuzzer needs atomic tokens, not prose | Break into individual keywords |
| Duplicating entries | Wastes mutation budget | Use sort -u to deduplicate |
| Over-sized dictionaries | Slows fuzzer, dilutes useful tokens | Keep focused: 50-200 most relevant entries |
| Missing hex escapes | Non-printable bytes become mangled | Use \xXX for binary values |
| No comments | Hard to maintain and audit | Document sections with # comments |
Tool-Specific Guidance
libFuzzer
clang++ -fsanitize=fuzzer,address harness.cc -o fuzz
./fuzz -dict=./dictionary.dict corpus/
Integration tips:
- Dictionary tokens are inserted/replaced during mutations
- Combine with
-max_lento control input size - Use
-print_final_stats=1to see dictionary effectiveness metrics - Dictionary entries longer than
-max_lenare ignored
AFL++
afl-fuzz -x ./dictionary.dict -i input/ -o output/ -- ./target @@
Integration tips:
- AFL++ supports multiple
-xflags for multiple dictionaries - Use
AFL_LLVM_DICT2FILEwithafl-clang-ltofor auto-generated dictionaries - Dictionary effectiveness shown in fuzzer stats UI
- Tokens are used during deterministic and havoc stages
cargo-fuzz (Rust)
cargo fuzz run fuzz_target -- -dict=./dictionary.dict
Integration tips:
- cargo-fuzz uses libFuzzer backend, so all libFuzzer dict flags work
- Place dictionary file in
fuzz/directory alongside harness - Reference from harness directory:
cargo fuzz run target -- -dict=../dictionary.dict
go-fuzz (Go)
go-fuzz does not have built-in dictionary support, but you can manually seed the corpus with dictionary entries:
# Convert dictionary to corpus files
grep -o '".*"' dict.txt | while read line; do
echo -n "$line" | base64 > corpus/$(echo "$line" | md5sum | cut -d' ' -f1)
done
go-fuzz -bin=./target-fuzz.zip -workdir=.
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Dictionary file not loaded | Wrong path or format error | Check fuzzer output for dict parsing errors; verify file format |
| No coverage improvement | Dictionary tokens not relevant | Analyze target code for actual keywords; try different generation method |
| Syntax errors in dict file | Unescaped quotes or invalid escapes | Use \\ for backslash, \" for quotes; validate with test run |
| Fuzzer ignores long entries | Entries exceed -max_len |
Keep entries under max input length, or increase -max_len |
| Too many entries slow fuzzer | Dictionary too large | Prune to 50-200 most relevant entries |
Related Skills
Tools That Use This Technique
| Skill | How It Applies |
|---|---|
| libfuzzer | Native dictionary support via -dict= flag |
| aflpp | Native dictionary support via -x flag; auto-generation with AUTODICTIONARIES |
| cargo-fuzz | Uses libFuzzer backend, inherits -dict= support |
Related Techniques
| Skill | Relationship |
|---|---|
| fuzzing-corpus | Dictionaries complement corpus: corpus provides structure, dictionary provides keywords |
| coverage-analysis | Use coverage data to validate dictionary effectiveness |
| harness-writing | Harness structure determines which dictionary tokens are useful |
Resources
Key External Resources
AFL++ Dictionaries Pre-built dictionaries for common formats (HTML, XML, JSON, SQL, etc.). Good starting point for format-specific fuzzing.
libFuzzer Dictionary Documentation Official libFuzzer documentation on dictionary format and usage. Explains token insertion strategy and performance implications.
Additional Examples
OSS-Fuzz Dictionaries
Real-world dictionaries from Google's continuous fuzzing service. Search project directories for *.dict files to see production examples.
/fuzzing-obstacles
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/fuzzing-obstacles/SKILL.md
name: fuzzing-obstacles type: technique description: > Techniques for patching code to overcome fuzzing obstacles. Use when checksums, global state, or other barriers block fuzzer progress.
Overcoming Fuzzing Obstacles
Codebases often contain anti-fuzzing patterns that prevent effective coverage. Checksums, global state (like time-seeded PRNGs), and validation checks can block the fuzzer from exploring deeper code paths. This technique shows how to patch your System Under Test (SUT) to bypass these obstacles during fuzzing while preserving production behavior.
Overview
Many real-world programs were not designed with fuzzing in mind. They may:
- Verify checksums or cryptographic hashes before processing input
- Rely on global state (e.g., system time, environment variables)
- Use non-deterministic random number generators
- Perform complex validation that makes it difficult for the fuzzer to generate valid inputs
These patterns make fuzzing difficult because:
- Checksums: The fuzzer must guess correct hash values (astronomically unlikely)
- Global state: Same input produces different behavior across runs (breaks determinism)
- Complex validation: The fuzzer spends effort hitting validation failures instead of exploring deeper code
The solution is conditional compilation: modify code behavior during fuzzing builds while keeping production code unchanged.
Key Concepts
| Concept | Description |
|---|---|
| SUT Patching | Modifying System Under Test to be fuzzing-friendly |
| Conditional Compilation | Code that behaves differently based on compile-time flags |
| Fuzzing Build Mode | Special build configuration that enables fuzzing-specific patches |
| False Positives | Crashes found during fuzzing that cannot occur in production |
| Determinism | Same input always produces same behavior (critical for fuzzing) |
When to Apply
Apply this technique when:
- The fuzzer gets stuck at checksum or hash verification
- Coverage reports show large blocks of unreachable code behind validation
- Code uses time-based seeds or other non-deterministic global state
- Complex validation makes it nearly impossible to generate valid inputs
- You see the fuzzer repeatedly hitting the same validation failures
Skip this technique when:
- The obstacle can be overcome with a good seed corpus or dictionary
- The validation is simple enough for the fuzzer to learn (e.g., magic bytes)
- You're doing grammar-based or structure-aware fuzzing that handles validation
- Skipping the check would introduce too many false positives
- The code is already fuzzing-friendly
Quick Reference
| Task | C/C++ | Rust |
|---|---|---|
| Check if fuzzing build | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
cfg!(fuzzing) |
| Skip check during fuzzing | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return -1; #endif |
if !cfg!(fuzzing) { return Err(...) } |
| Common obstacles | Checksums, PRNGs, time-based logic | Checksums, PRNGs, time-based logic |
| Supported fuzzers | libFuzzer, AFL++, LibAFL, honggfuzz | cargo-fuzz, libFuzzer |
Step-by-Step
Step 1: Identify the Obstacle
Run the fuzzer and analyze coverage to find code that's unreachable. Common patterns:
- Look for checksum/hash verification before deeper processing
- Check for calls to
rand(),time(), orsrand()with system seeds - Find validation functions that reject most inputs
- Identify global state initialization that differs across runs
Tools to help:
- Coverage reports (see coverage-analysis technique)
- Profiling with
-fprofile-instr-generate - Manual code inspection of entry points
Step 2: Add Conditional Compilation
Modify the obstacle to bypass it during fuzzing builds.
C/C++ Example:
// Before: Hard obstacle
if (checksum != expected_hash) {
return -1; // Fuzzer never gets past here
}
// After: Conditional bypass
if (checksum != expected_hash) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
return -1; // Only enforced in production
#endif
}
// Fuzzer can now explore code beyond this check
Rust Example:
// Before: Hard obstacle
if checksum != expected_hash {
return Err(MyError::Hash); // Fuzzer never gets past here
}
// After: Conditional bypass
if checksum != expected_hash {
if !cfg!(fuzzing) {
return Err(MyError::Hash); // Only enforced in production
}
}
// Fuzzer can now explore code beyond this check
Step 3: Verify Coverage Improvement
After patching:
- Rebuild with fuzzing instrumentation
- Run the fuzzer for a short time
- Compare coverage to the unpatched version
- Confirm new code paths are being explored
Step 4: Assess False Positive Risk
Consider whether skipping the check introduces impossible program states:
- Does code after the check assume validated properties?
- Could skipping validation cause crashes that cannot occur in production?
- Is there implicit state dependency?
If false positives are likely, consider a more targeted patch (see Common Patterns below).
Common Patterns
Pattern: Bypass Checksum Validation
Use Case: Hash/checksum blocks all fuzzer progress
Before:
uint32_t computed = hash_function(data, size);
if (computed != expected_checksum) {
return ERROR_INVALID_HASH;
}
process_data(data, size);
After:
uint32_t computed = hash_function(data, size);
if (computed != expected_checksum) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
return ERROR_INVALID_HASH;
#endif
}
process_data(data, size);
False positive risk: LOW - If data processing doesn't depend on checksum correctness
Pattern: Deterministic PRNG Seeding
Use Case: Non-deterministic random state prevents reproducibility
Before:
void initialize() {
srand(time(NULL)); // Different seed each run
}
After:
void initialize() {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
srand(12345); // Fixed seed for fuzzing
#else
srand(time(NULL));
#endif
}
False positive risk: LOW - Fuzzer can explore all code paths with fixed seed
Pattern: Careful Validation Skip
Use Case: Validation must be skipped but downstream code has assumptions
Before (Dangerous):
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!validate_config(&config)) {
return -1; // Ensures config.x != 0
}
#endif
int32_t result = 100 / config.x; // CRASH: Division by zero in fuzzing!
After (Safe):
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!validate_config(&config)) {
return -1;
}
#else
// During fuzzing, use safe defaults for failed validation
if (!validate_config(&config)) {
config.x = 1; // Prevent division by zero
config.y = 1;
}
#endif
int32_t result = 100 / config.x; // Safe in both builds
False positive risk: MITIGATED - Provides safe defaults instead of skipping
Pattern: Bypass Complex Format Validation
Use Case: Multi-step validation makes valid input generation nearly impossible
Rust Example:
// Before: Multiple validation stages
pub fn parse_message(data: &[u8]) -> Result<Message, Error> {
validate_magic_bytes(data)?;
validate_structure(data)?;
validate_checksums(data)?;
validate_crypto_signature(data)?;
deserialize_message(data)
}
// After: Skip expensive validation during fuzzing
pub fn parse_message(data: &[u8]) -> Result<Message, Error> {
validate_magic_bytes(data)?; // Keep cheap checks
if !cfg!(fuzzing) {
validate_structure(data)?;
validate_checksums(data)?;
validate_crypto_signature(data)?;
}
deserialize_message(data)
}
False positive risk: MEDIUM - Deserialization must handle malformed data gracefully
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
| Keep cheap validation | Magic bytes and size checks guide fuzzer without much cost |
| Use fixed seeds for PRNGs | Makes behavior deterministic while exploring all code paths |
| Patch incrementally | Skip one obstacle at a time and measure coverage impact |
| Add defensive defaults | When skipping validation, provide safe fallback values |
| Document all patches | Future maintainers need to understand fuzzing vs. production differences |
Real-World Examples
OpenSSL: Uses FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION to modify cryptographic algorithm behavior. For example, in crypto/cmp/cmp_vfy.c, certain signature checks are relaxed during fuzzing to allow deeper exploration of certificate validation logic.
ogg crate (Rust): Uses cfg!(fuzzing) to skip checksum verification during fuzzing. This allows the fuzzer to explore audio processing code without spending effort guessing correct checksums.
Measuring Patch Effectiveness
After applying patches, quantify the improvement:
- Line coverage: Use
llvm-covorcargo-covto see new reachable lines - Basic block coverage: More fine-grained than line coverage
- Function coverage: How many more functions are now reachable?
- Corpus size: Does the fuzzer generate more diverse inputs?
Effective patches typically increase coverage by 10-50% or more.
Combining with Other Techniques
Obstacle patching works well with:
- Corpus seeding: Provide valid inputs that get past initial parsing
- Dictionaries: Help fuzzer learn magic bytes and common values
- Structure-aware fuzzing: Use protobuf or grammar definitions for complex formats
- Harness improvements: Better harness can sometimes avoid obstacles entirely
Anti-Patterns
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| Skip all validation wholesale | Creates false positives and unstable fuzzing | Skip only specific obstacles that block coverage |
| No risk assessment | False positives waste time and hide real bugs | Analyze downstream code for assumptions |
| Forget to document patches | Future maintainers don't understand the differences | Add comments explaining why patch is safe |
| Patch without measuring | Don't know if it helped | Compare coverage before and after |
| Over-patching | Makes fuzzing build diverge too much from production | Minimize differences between builds |
Tool-Specific Guidance
libFuzzer
libFuzzer automatically defines FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION during compilation.
# C++ compilation
clang++ -g -fsanitize=fuzzer,address -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
harness.cc target.cc -o fuzzer
# The macro is usually defined automatically by -fsanitize=fuzzer
clang++ -g -fsanitize=fuzzer,address harness.cc target.cc -o fuzzer
Integration tips:
- The macro is defined automatically; manual definition is usually unnecessary
- Use
#ifdefto check for the macro - Combine with sanitizers to detect bugs in newly reachable code
AFL++
AFL++ also defines FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION when using its compiler wrappers.
# Compilation with AFL++ wrappers
afl-clang-fast++ -g -fsanitize=address target.cc harness.cc -o fuzzer
# The macro is defined automatically by afl-clang-fast
Integration tips:
- Use
afl-clang-fastorafl-clang-ltofor automatic macro definition - Persistent mode harnesses benefit most from obstacle patching
- Consider using
AFL_LLVM_LAF_ALLfor additional input-to-state transformations
honggfuzz
honggfuzz also supports the macro when building targets.
# Compilation
hfuzz-clang++ -g -fsanitize=address target.cc harness.cc -o fuzzer
Integration tips:
- Use
hfuzz-clangorhfuzz-clang++wrappers - The macro is available for conditional compilation
- Combine with honggfuzz's feedback-driven fuzzing
cargo-fuzz (Rust)
cargo-fuzz automatically sets the fuzzing cfg option during builds.
# Build fuzz target (cfg!(fuzzing) is automatically set)
cargo fuzz build fuzz_target_name
# Run fuzz target
cargo fuzz run fuzz_target_name
Integration tips:
- Use
cfg!(fuzzing)for runtime checks in production builds - Use
#[cfg(fuzzing)]for compile-time conditional compilation - The fuzzing cfg is only set during
cargo fuzzbuilds, not regularcargo build - Can be manually enabled with
RUSTFLAGS="--cfg fuzzing"for testing
LibAFL
LibAFL supports the C/C++ macro for targets written in C/C++.
# Compilation
clang++ -g -fsanitize=address -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
target.cc -c -o target.o
Integration tips:
- Define the macro manually or use compiler flags
- Works the same as with libFuzzer
- Useful when building custom LibAFL-based fuzzers
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Coverage doesn't improve after patching | Wrong obstacle identified | Profile execution to find actual bottleneck |
| Many false positive crashes | Downstream code has assumptions | Add defensive defaults or partial validation |
| Code compiles differently | Macro not defined in all build configs | Verify macro in all source files and dependencies |
| Fuzzer finds bugs in patched code | Patch introduced invalid states | Review patch for state invariants; consider safer approach |
| Can't reproduce production bugs | Build differences too large | Minimize patches; keep validation for state-critical checks |
Related Skills
Tools That Use This Technique
| Skill | How It Applies |
|---|---|
| libfuzzer | Defines FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION automatically |
| aflpp | Supports the macro via compiler wrappers |
| honggfuzz | Uses the macro for conditional compilation |
| cargo-fuzz | Sets cfg!(fuzzing) for Rust conditional compilation |
Related Techniques
| Skill | Relationship |
|---|---|
| fuzz-harness-writing | Better harnesses may avoid obstacles; patching enables deeper exploration |
| coverage-analysis | Use coverage to identify obstacles and measure patch effectiveness |
| corpus-seeding | Seed corpus can help overcome obstacles without patching |
| dictionary-generation | Dictionaries help with magic bytes but not checksums or complex validation |
Resources
Key External Resources
OpenSSL Fuzzing Documentation
OpenSSL's fuzzing infrastructure demonstrates large-scale use of FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION. The project uses this macro to modify cryptographic validation, certificate parsing, and other security-critical code paths to enable deeper fuzzing while maintaining production correctness.
LibFuzzer Documentation on Flags Official LLVM documentation for libFuzzer, including how the fuzzer defines compiler macros and how to use them effectively. Covers integration with sanitizers and coverage instrumentation.
Rust cfg Attribute Reference
Complete reference for Rust conditional compilation, including cfg!(fuzzing) and cfg!(test). Explains compile-time vs. runtime conditional compilation and best practices.
/harness-writing
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/harness-writing/SKILL.md
name: harness-writing type: technique description: > Techniques for writing effective fuzzing harnesses across languages. Use when creating new fuzz targets or improving existing harness code.
Writing Fuzzing Harnesses
A fuzzing harness is the entrypoint function that receives random data from the fuzzer and routes it to your system under test (SUT). The quality of your harness directly determines which code paths get exercised and whether critical bugs are found. A poorly written harness can miss entire subsystems or produce non-reproducible crashes.
Overview
The harness is the bridge between the fuzzer's random byte generation and your application's API. It must parse raw bytes into meaningful inputs, call target functions, and handle edge cases gracefully. The most important part of any fuzzing setup is the harness—if written poorly, critical parts of your application may not be covered.
Key Concepts
| Concept | Description |
|---|---|
| Harness | Function that receives fuzzer input and calls target code under test |
| SUT | System Under Test—the code being fuzzed |
| Entry point | Function signature required by the fuzzer (e.g., LLVMFuzzerTestOneInput) |
| FuzzedDataProvider | Helper class for structured extraction of typed data from raw bytes |
| Determinism | Property that ensures same input always produces same behavior |
| Interleaved fuzzing | Single harness that exercises multiple operations based on input |
When to Apply
Apply this technique when:
- Creating a new fuzz target for the first time
- Fuzz campaign has low code coverage or isn't finding bugs
- Crashes found during fuzzing are not reproducible
- Target API requires complex or structured inputs
- Multiple related functions should be tested together
Skip this technique when:
- Using existing well-tested harnesses from your project
- Tool provides automatic harness generation that meets your needs
- Target already has comprehensive fuzzing infrastructure
Quick Reference
| Task | Pattern |
|---|---|
| Minimal C++ harness | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) |
| Minimal Rust harness | `fuzz_target!( |
| Size validation | if (size < MIN_SIZE) return 0; |
| Cast to integers | uint32_t val = *(uint32_t*)(data); |
| Use FuzzedDataProvider | FuzzedDataProvider fuzzed_data(data, size); |
| Extract typed data (C++) | auto val = fuzzed_data.ConsumeIntegral<uint32_t>(); |
| Extract string (C++) | auto str = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF); |
Step-by-Step
Step 1: Identify Entry Points
Find functions in your codebase that:
- Accept external input (parsers, validators, protocol handlers)
- Parse complex data formats (JSON, XML, binary protocols)
- Perform security-critical operations (authentication, cryptography)
- Have high cyclomatic complexity or many branches
Good targets are typically:
- Protocol parsers
- File format parsers
- Serialization/deserialization functions
- Input validation routines
Step 2: Write Minimal Harness
Start with the simplest possible harness that calls your target function:
C/C++:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
target_function(data, size);
return 0;
}
Rust:
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
target_function(data);
});
Step 3: Add Input Validation
Reject inputs that are too small or too large to be meaningful:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Ensure minimum size for meaningful input
if (size < MIN_INPUT_SIZE || size > MAX_INPUT_SIZE) {
return 0;
}
target_function(data, size);
return 0;
}
Rationale: The fuzzer generates random inputs of all sizes. Your harness must handle empty, tiny, huge, or malformed inputs without causing unexpected issues in the harness itself (crashes in the SUT are fine—that's what we're looking for).
Step 4: Structure the Input
For APIs that require typed data (integers, strings, etc.), use casting or helpers like FuzzedDataProvider:
Simple casting:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size != 2 * sizeof(uint32_t)) {
return 0;
}
uint32_t numerator = *(uint32_t*)(data);
uint32_t denominator = *(uint32_t*)(data + sizeof(uint32_t));
divide(numerator, denominator);
return 0;
}
Using FuzzedDataProvider:
#include "FuzzedDataProvider.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FuzzedDataProvider fuzzed_data(data, size);
size_t allocation_size = fuzzed_data.ConsumeIntegral<size_t>();
std::vector<char> str1 = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF);
std::vector<char> str2 = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF);
concat(&str1[0], str1.size(), &str2[0], str2.size(), allocation_size);
return 0;
}
Step 5: Test and Iterate
Run the fuzzer and monitor:
- Code coverage (are all interesting paths reached?)
- Executions per second (is it fast enough?)
- Crash reproducibility (can you reproduce crashes with saved inputs?)
Iterate on the harness to improve these metrics.
Common Patterns
Pattern: Beyond Byte Arrays—Casting to Integers
Use Case: When target expects primitive types like integers or floats
Implementation:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Ensure exactly 2 4-byte numbers
if (size != 2 * sizeof(uint32_t)) {
return 0;
}
// Split input into two integers
uint32_t numerator = *(uint32_t*)(data);
uint32_t denominator = *(uint32_t*)(data + sizeof(uint32_t));
divide(numerator, denominator);
return 0;
}
Rust equivalent:
fuzz_target!(|data: &[u8]| {
if data.len() != 2 * std::mem::size_of::<i32>() {
return;
}
let numerator = i32::from_ne_bytes([data[0], data[1], data[2], data[3]]);
let denominator = i32::from_ne_bytes([data[4], data[5], data[6], data[7]]);
divide(numerator, denominator);
});
Why it works: Any 8-byte input is valid. The fuzzer learns that inputs must be exactly 8 bytes, and every bit flip produces a new, potentially interesting input.
Pattern: FuzzedDataProvider for Complex Inputs
Use Case: When target requires multiple strings, integers, or variable-length data
Implementation:
#include "FuzzedDataProvider.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FuzzedDataProvider fuzzed_data(data, size);
// Extract different types of data
size_t allocation_size = fuzzed_data.ConsumeIntegral<size_t>();
// Consume variable-length strings with terminator
std::vector<char> str1 = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF);
std::vector<char> str2 = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF);
char* result = concat(&str1[0], str1.size(), &str2[0], str2.size(), allocation_size);
if (result != NULL) {
free(result);
}
return 0;
}
Why it helps: FuzzedDataProvider handles the complexity of extracting structured data from a byte stream. It's particularly useful for APIs that need multiple parameters of different types.
Pattern: Interleaved Fuzzing
Use Case: When multiple related operations should be tested in a single harness
Implementation:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < 1 + 2 * sizeof(int32_t)) {
return 0;
}
// First byte selects operation
uint8_t mode = data[0];
// Next bytes are operands
int32_t numbers[2];
memcpy(numbers, data + 1, 2 * sizeof(int32_t));
int32_t result = 0;
switch (mode % 4) {
case 0:
result = add(numbers[0], numbers[1]);
break;
case 1:
result = subtract(numbers[0], numbers[1]);
break;
case 2:
result = multiply(numbers[0], numbers[1]);
break;
case 3:
result = divide(numbers[0], numbers[1]);
break;
}
// Prevent compiler from optimizing away the calls
printf("%d", result);
return 0;
}
Advantages:
- Faster to write one harness than multiple individual harnesses
- Single shared corpus means interesting inputs for one operation may be interesting for others
- Can discover bugs in interactions between operations
When to use:
Pattern: Structure-Aware Fuzzing with Arbitrary (Rust)
Use Case: When fuzzing Rust code that uses custom structs
Implementation:
use arbitrary::Arbitrary;
#[derive(Debug, Arbitrary)]
pub struct Name {
data: String
}
impl Name {
pub fn check_buf(&self) {
let data = self.data.as_bytes();
if data.len() > 0 && data[0] == b'a' {
if data.len() > 1 && data[1] == b'b' {
if data.len() > 2 && data[2] == b'c' {
process::abort();
}
}
}
}
}
Harness with arbitrary:
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: your_project::Name| {
data.check_buf();
});
Add to Cargo.toml:
[dependencies]
arbitrary = { version = "1", features = ["derive"] }
Why it helps: The arbitrary crate automatically handles deserialization of raw bytes into your Rust structs, reducing boilerplate and ensuring valid struct construction.
Limitation: The arbitrary crate doesn't offer reverse serialization, so you can't manually construct byte arrays that map to specific structs. This works best when starting from an empty corpus (fine for libFuzzer, problematic for AFL++).
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
| Start with parsers | High bug density, clear entry points, easy to harness |
| Mock I/O operations | Prevents hangs from blocking I/O, enables determinism |
| Use FuzzedDataProvider | Simplifies extraction of structured data from raw bytes |
| Reset global state | Ensures each iteration is independent and reproducible |
| Free resources in harness | Prevents memory exhaustion during long campaigns |
| Avoid logging in harness | Logging is slow—fuzzing needs 100s-1000s exec/sec |
| Test harness manually first | Run harness with known inputs before starting campaign |
| Check coverage early | Ensure harness reaches expected code paths |
Structure-Aware Fuzzing with Protocol Buffers
For highly structured input formats, consider using Protocol Buffers as an intermediate format with custom mutators:
// Define your input format in .proto file
// Use libprotobuf-mutator to generate valid mutations
// This ensures fuzzer mutates message contents, not the protobuf encoding itself
This approach is more setup but prevents the fuzzer from wasting time on unparseable inputs. See structure-aware fuzzing documentation for details.
Handling Non-Determinism
Problem: Random values or timing dependencies cause non-reproducible crashes.
Solutions:
- Replace
rand()with deterministic PRNG seeded from fuzzer input:uint32_t seed = fuzzed_data.ConsumeIntegral<uint32_t>(); srand(seed); - Mock system calls that return time, PIDs, or random data
- Avoid reading from
/dev/randomor/dev/urandom
Resetting Global State
If your SUT uses global state (singletons, static variables), reset it between iterations:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Reset global state before each iteration
global_reset();
target_function(data, size);
// Clean up resources
global_cleanup();
return 0;
}
Rationale: Global state can cause crashes after N iterations rather than on a specific input, making bugs non-reproducible.
Practical Harness Rules
Follow these rules to ensure effective fuzzing harnesses:
| Rule | Rationale |
|---|---|
| Handle all input sizes | Fuzzer generates empty, tiny, huge inputs—harness must handle gracefully |
Never call exit() |
Calling exit() stops the fuzzer process. Use abort() in SUT if needed |
| Join all threads | Each iteration must run to completion before next iteration starts |
| Be fast | Aim for 100s-1000s executions/sec. Avoid logging, high complexity, excess memory |
| Maintain determinism | Same input must always produce same behavior for reproducibility |
| Avoid global state | Global state reduces reproducibility—reset between iterations if unavoidable |
| Use narrow targets | Don't fuzz PNG and TCP in same harness—different formats need separate targets |
| Free resources | Prevent memory leaks that cause resource exhaustion during long campaigns |
Note: These guidelines apply not just to harness code, but to the entire SUT. If the SUT violates these rules, consider patching it (see the fuzzing obstacles technique).
Anti-Patterns
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| Global state without reset | Non-deterministic crashes | Reset all globals at start of harness |
| Blocking I/O or network calls | Hangs fuzzer, wastes time | Mock I/O, use in-memory buffers |
| Memory leaks in harness | Resource exhaustion kills campaign | Free all allocations before returning |
Calling exit() in SUT |
Stops entire fuzzing process | Use abort() or return error codes |
| Heavy logging in harness | Reduces exec/sec by orders of magnitude | Disable logging during fuzzing |
| Too many operations per iteration | Slows down fuzzer | Keep iterations fast and focused |
| Mixing unrelated input formats | Corpus entries not useful across formats | Separate harnesses for different formats |
| Not validating input size | Harness crashes on edge cases | Check size before accessing data |
Tool-Specific Guidance
libFuzzer
Harness signature:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Your code here
return 0; // Non-zero return is reserved for future use
}
Compilation:
clang++ -fsanitize=fuzzer,address -g harness.cc -o fuzz_target
Integration tips:
- Use
FuzzedDataProvider.hfor structured input extraction - Compile with
-fsanitize=fuzzerto link the fuzzing runtime - Add sanitizers (
-fsanitize=address,undefined) to detect more bugs - Use
-gfor better stack traces when crashes occur - libFuzzer can start with empty corpus—no seed inputs required
Running:
./fuzz_target corpus_dir/
Resources:
AFL++
AFL++ supports multiple harness styles. For best performance, use persistent mode:
Persistent mode harness:
#include <unistd.h>
int main(int argc, char **argv) {
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif
unsigned char buf[MAX_SIZE];
while (__AFL_LOOP(10000)) {
// Read input from stdin
ssize_t len = read(0, buf, sizeof(buf));
if (len <= 0) break;
// Call target function
target_function(buf, len);
}
return 0;
}
Compilation:
afl-clang-fast++ -g harness.cc -o fuzz_target
Integration tips:
- Use persistent mode (
__AFL_LOOP) for 10-100x speedup - Consider deferred initialization (
__AFL_INIT()) to skip setup overhead - AFL++ requires at least one seed input in the corpus directory
- Use
AFL_USE_ASAN=1orAFL_USE_UBSAN=1for sanitizer builds
Running:
afl-fuzz -i seeds/ -o findings/ -- ./fuzz_target
cargo-fuzz (Rust)
Harness signature:
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
// Your code here
});
With structured input (arbitrary crate):
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: YourStruct| {
data.check();
});
Creating harness:
cargo fuzz init
cargo fuzz add my_target
Integration tips:
- Use
arbitrarycrate for automatic struct deserialization - cargo-fuzz wraps libFuzzer, so all libFuzzer features work
- Compile with sanitizers automatically via cargo-fuzz
- Harnesses go in
fuzz/fuzz_targets/directory
Running:
cargo +nightly fuzz run my_target
Resources:
go-fuzz
Harness signature:
// +build gofuzz
package mypackage
func Fuzz(data []byte) int {
// Call target function
target(data)
// Return codes:
// -1 if input is invalid
// 0 if input is valid but not interesting
// 1 if input is interesting (e.g., added new coverage)
return 0
}
Building:
go-fuzz-build
Integration tips:
- Return 1 for inputs that add coverage (optional—fuzzer can detect automatically)
- Return -1 for invalid inputs to deprioritize similar mutations
- go-fuzz handles persistence automatically
Running:
go-fuzz -bin=./mypackage-fuzz.zip -workdir=fuzz
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Low executions/sec | Harness is too slow (logging, I/O, complexity) | Profile harness, remove bottlenecks, mock I/O |
| No crashes found | Coverage not reaching buggy code | Check coverage, improve harness to reach more paths |
| Non-reproducible crashes | Non-determinism or global state | Remove randomness, reset globals between iterations |
| Fuzzer exits immediately | Harness calls exit() |
Replace exit() with abort() or return error |
| Out of memory errors | Memory leaks in harness or SUT | Free allocations, use leak sanitizer to find leaks |
| Crashes on empty input | Harness doesn't validate size | Add if (size < MIN_SIZE) return 0; |
| Corpus not growing | Inputs too constrained or format too strict | Use FuzzedDataProvider or structure-aware fuzzing |
Related Skills
Tools That Use This Technique
| Skill | How It Applies |
|---|---|
| libfuzzer | Uses LLVMFuzzerTestOneInput harness signature with FuzzedDataProvider |
| aflpp | Supports persistent mode harnesses with __AFL_LOOP for performance |
| cargo-fuzz | Uses Rust-specific fuzz_target! macro with arbitrary crate integration |
| atheris | Python harness takes bytes, calls Python functions |
| ossfuzz | Requires harnesses in specific directory structure for cloud fuzzing |
Related Techniques
| Skill | Relationship |
|---|---|
| coverage-analysis | Measure harness effectiveness—are you reaching target code? |
| address-sanitizer | Detects bugs found by harness (buffer overflows, use-after-free) |
| fuzzing-dictionary | Provide tokens to help fuzzer pass format checks in harness |
| fuzzing-obstacles | Patch SUT when it violates harness rules (exit, non-determinism) |
Resources
Key External Resources
Split Inputs in libFuzzer - Google Fuzzing Docs Explains techniques for handling multiple input parameters in a single fuzzing harness, including use of magic separators and FuzzedDataProvider.
Structure-Aware Fuzzing with Protocol Buffers Advanced technique using protobuf as intermediate format with custom mutators to ensure fuzzer mutates message contents rather than format encoding.
libFuzzer Documentation Official LLVM documentation covering harness requirements, best practices, and advanced features.
cargo-fuzz Book Comprehensive guide to writing Rust fuzzing harnesses with cargo-fuzz and the arbitrary crate.
Video Resources
- Effective File Format Fuzzing - Conference talk on writing harnesses for file format parsers
- Modern Fuzzing of C/C++ Projects - Tutorial covering harness design patterns
/libafl
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/libafl/SKILL.md
name: libafl type: fuzzer description: > LibAFL is a modular fuzzing library for building custom fuzzers. Use for advanced fuzzing needs, custom mutators, or non-standard fuzzing targets.
LibAFL
LibAFL is a modular fuzzing library that implements features from AFL-based fuzzers like AFL++. Unlike traditional fuzzers, LibAFL provides all functionality in a modular and customizable way as a Rust library. It can be used as a drop-in replacement for libFuzzer or as a library to build custom fuzzers from scratch.
When to Use
| Fuzzer | Best For | Complexity |
|---|---|---|
| libFuzzer | Quick setup, single-threaded | Low |
| AFL++ | Multi-core, general purpose | Medium |
| LibAFL | Custom fuzzers, advanced features, research | High |
Choose LibAFL when:
- You need custom mutation strategies or feedback mechanisms
- Standard fuzzers don't support your target architecture
- You want to implement novel fuzzing techniques
- You need fine-grained control over fuzzing components
- You're conducting fuzzing research
Quick Start
LibAFL can be used as a drop-in replacement for libFuzzer with minimal setup:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Call your code with fuzzer-provided data
my_function(data, size);
return 0;
}
Build LibAFL's libFuzzer compatibility layer:
git clone https://github.com/AFLplusplus/LibAFL
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
Compile and run:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
./fuzz corpus/
Installation
Prerequisites
- Clang/LLVM 15-18
- Rust (via rustup)
- Additional system dependencies
Linux/macOS
Install Clang:
apt install clang
Or install a specific version via apt.llvm.org:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
Configure environment for Rust:
export RUSTFLAGS="-C linker=/usr/bin/clang-15"
export CC="clang-15"
export CXX="clang++-15"
Install Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Install additional dependencies:
apt install libssl-dev pkg-config
For libFuzzer compatibility mode, install nightly Rust:
rustup toolchain install nightly --component llvm-tools
Verification
Build LibAFL to verify installation:
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
# Should produce libFuzzer.a
Writing a Harness
LibAFL harnesses follow the same pattern as libFuzzer when using drop-in replacement mode:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Your fuzzing target code here
return 0;
}
When building custom fuzzers with LibAFL as a Rust library, harness logic is integrated directly into the fuzzer. See the "Writing a Custom Fuzzer" section below for the full pattern.
See Also: For detailed harness writing techniques, see the harness-writing technique skill.
Usage Modes
LibAFL supports two primary usage modes:
1. libFuzzer Drop-in Replacement
Use LibAFL as a replacement for libFuzzer with existing harnesses.
Compilation:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
Running:
./fuzz corpus/
Recommended for long campaigns:
./fuzz -fork=1 -ignore_crashes=1 corpus/
2. Custom Fuzzer as Rust Library
Build a fully customized fuzzer using LibAFL components.
Create project:
cargo init --lib my_fuzzer
cd my_fuzzer
cargo add libafl@0.13 libafl_targets@0.13 libafl_bolts@0.13 libafl_cc@0.13 \
--features "libafl_targets@0.13/libfuzzer,libafl_targets@0.13/sancov_pcguard_hitcounts"
Configure Cargo.toml:
[lib]
crate-type = ["staticlib"]
Writing a Custom Fuzzer
See Also: For detailed harness writing techniques, patterns for handling complex inputs, and advanced strategies, see the fuzz-harness-writing technique skill.
Fuzzer Components
A LibAFL fuzzer consists of modular components:
- Observers - Collect execution feedback (coverage, timing)
- Feedback - Determine if inputs are interesting
- Objective - Define fuzzing goals (crashes, timeouts)
- State - Maintain corpus and metadata
- Mutators - Generate new inputs
- Scheduler - Select which inputs to mutate
- Executor - Run the target with inputs
Basic Fuzzer Structure
use libafl::prelude::*;
use libafl_bolts::prelude::*;
use libafl_targets::{libfuzzer_test_one_input, std_edges_map_observer};
#[no_mangle]
pub extern "C" fn libafl_main() {
let mut run_client = |state: Option<_>, mut restarting_mgr, _core_id| {
// 1. Setup observers
let edges_observer = HitcountsMapObserver::new(
unsafe { std_edges_map_observer("edges") }
).track_indices();
let time_observer = TimeObserver::new("time");
// 2. Define feedback
let mut feedback = feedback_or!(
MaxMapFeedback::new(&edges_observer),
TimeFeedback::new(&time_observer)
);
// 3. Define objective
let mut objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new()
);
// 4. Create or restore state
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryCorpus::new(),
OnDiskCorpus::new(&output_dir).unwrap(),
&mut feedback,
&mut objective,
).unwrap()
});
// 5. Setup mutator
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// 6. Setup scheduler
let scheduler = IndexesLenTimeMinimizerScheduler::new(
&edges_observer,
QueueScheduler::new()
);
// 7. Create fuzzer
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// 8. Define harness
let mut harness = |input: &BytesInput| {
let buf = input.target_bytes().as_slice();
libfuzzer_test_one_input(buf);
ExitKind::Ok
};
// 9. Setup executor
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
// 10. Load initial inputs
if state.must_load_initial_inputs() {
state.load_initial_inputs(
&mut fuzzer,
&mut executor,
&mut restarting_mgr,
&input_dir
)?;
}
// 11. Start fuzzing
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
Ok(())
};
// Launch fuzzer
Launcher::builder()
.run_client(&mut run_client)
.cores(&cores)
.build()
.launch()
.unwrap();
}
Compilation
Verbose Mode
Manually specify all instrumentation flags:
clang++-15 -DNO_MAIN -g -O2 \
-fsanitize-coverage=trace-pc-guard \
-fsanitize=address \
-Wl,--whole-archive target/release/libmy_fuzzer.a -Wl,--no-whole-archive \
main.cc harness.cc -o fuzz
Compiler Wrapper (Recommended)
Create a LibAFL compiler wrapper to handle instrumentation automatically.
Create src/bin/libafl_cc.rs:
use libafl_cc::{ClangWrapper, CompilerWrapper, Configuration, ToolWrapper};
pub fn main() {
let args: Vec<String> = env::args().collect();
let mut cc = ClangWrapper::new();
cc.cpp(is_cpp)
.parse_args(&args)
.link_staticlib(&dir, "my_fuzzer")
.add_args(&Configuration::GenerateCoverageMap.to_flags().unwrap())
.add_args(&Configuration::AddressSanitizer.to_flags().unwrap())
.run()
.unwrap();
}
Compile and use:
cargo build --release
target/release/libafl_cxx -DNO_MAIN -g -O2 main.cc harness.cc -o fuzz
See Also: For detailed sanitizer configuration, common issues, and advanced flags, see the address-sanitizer and undefined-behavior-sanitizer technique skills.
Running Campaigns
Basic Run
./fuzz --cores 0 --input corpus/
Multi-Core Fuzzing
./fuzz --cores 0,8-15 --input corpus/
This runs 9 clients: one on core 0, and 8 on cores 8-15.
With Options
./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000
Text User Interface (TUI)
Enable graphical statistics view:
./fuzz -tui=1 corpus/
Interpreting Output
| Output | Meaning |
|---|---|
corpus: N |
Number of interesting test cases found |
objectives: N |
Number of crashes/timeouts found |
executions: N |
Total number of target invocations |
exec/sec: N |
Current execution throughput |
edges: X% |
Code coverage percentage |
clients: N |
Number of parallel fuzzing processes |
The fuzzer emits two main event types:
- UserStats - Regular heartbeat with current statistics
- Testcase - New interesting input discovered
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
Use -fork=1 -ignore_crashes=1 |
Continue fuzzing after first crash |
Use InMemoryOnDiskCorpus |
Persist corpus across restarts |
Enable TUI with -tui=1 |
Better visualization of progress |
| Use specific LLVM version | Avoid compatibility issues |
Set RUSTFLAGS correctly |
Prevent linking errors |
Crash Deduplication
Avoid storing duplicate crashes from the same bug:
Add backtrace observer:
let backtrace_observer = BacktraceObserver::owned(
"BacktraceObserver",
libafl::observers::HarnessType::InProcess
);
Update executor:
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer, backtrace_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
Update objective with hash feedback:
let mut objective = feedback_and!(
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
NewHashFeedback::new(&backtrace_observer)
);
This ensures only crashes with unique backtraces are saved.
Dictionary Fuzzing
Use dictionaries to guide fuzzing toward specific tokens:
Add tokens from file:
let mut tokens = Tokens::new();
if let Some(tokenfile) = &tokenfile {
tokens.add_from_file(tokenfile)?;
}
state.add_metadata(tokens);
Update mutator:
let mutator = StdScheduledMutator::new(
havoc_mutations().merge(tokens_mutations())
);
Hard-coded tokens example (PNG):
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
"IHDR".as_bytes().to_vec(),
"IDAT".as_bytes().to_vec(),
"PLTE".as_bytes().to_vec(),
"IEND".as_bytes().to_vec(),
]));
See Also: For detailed dictionary creation strategies and format-specific dictionaries, see the fuzzing-dictionaries technique skill.
Auto Tokens
Automatically extract magic values and checksums from the program:
Enable in compiler wrapper:
cc.add_pass(LLVMPasses::AutoTokens)
Load auto tokens in fuzzer:
tokens += libafl_targets::autotokens()?;
Verify tokens section:
echo "p (uint8_t *)__token_start" | gdb fuzz
Performance Tuning
| Setting | Impact |
|---|---|
| Multi-core fuzzing | Linear speedup with cores |
InMemoryCorpus |
Faster but non-persistent |
InMemoryOnDiskCorpus |
Balanced speed and persistence |
| Sanitizers | 2-5x slowdown, essential for bugs |
Optimization level -O2 |
Balance between speed and coverage |
Debugging Fuzzer
Run fuzzer in single-process mode for easier debugging:
// Replace launcher with direct call
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();
// Comment out:
// Launcher::builder()
// .run_client(&mut run_client)
// ...
// .launch()
Then debug with GDB:
gdb --args ./fuzz --cores 0 --input corpus/
Real-World Examples
Example: libpng
Fuzzing libpng using LibAFL:
1. Get source code:
curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
apt install zlib1g-dev
2. Set compiler wrapper:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
3. Build static library:
./configure --enable-shared=no
make
4. Get harness:
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
5. Link fuzzer:
$CXX libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
6. Prepare seeds:
mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
7. Get dictionary (optional):
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
8. Start fuzzing:
./fuzz --input seeds/ --cores 0 -x png.dict
Example: CMake Project
Integrate LibAFL with CMake build system:
CMakeLists.txt:
project(BuggyProgram)
cmake_minimum_required(VERSION 3.0)
add_executable(buggy_program main.cc)
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2)
Build non-instrumented binary:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target buggy_program
Build fuzzer:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
cmake -DCMAKE_C_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cc \
-DCMAKE_CXX_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cxx .
cmake --build . --target fuzz
Run fuzzing:
./fuzz --input seeds/ --cores 0
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| No coverage increases | Instrumentation failed | Verify compiler wrapper used, check for -fsanitize-coverage |
| Fuzzer won't start | Empty corpus with no interesting inputs | Provide seed inputs that trigger code paths |
Linker errors with libafl_main |
Runtime not linked | Use -Wl,--whole-archive or -u libafl_main |
| LLVM version mismatch | LibAFL requires LLVM 15-18 | Install compatible LLVM version, set environment variables |
| Rust compilation fails | Outdated Rust or Cargo | Update Rust with rustup update |
| Slow fuzzing | Sanitizers enabled | Expected 2-5x slowdown, necessary for finding bugs |
| Environment variable interference | CC, CXX, RUSTFLAGS set |
Unset after building LibAFL project |
| Cannot attach debugger | Multi-process fuzzing | Run in single-process mode (see Debugging section) |
Related Skills
Technique Skills
| Skill | Use Case |
|---|---|
| fuzz-harness-writing | Detailed guidance on writing effective harnesses |
| address-sanitizer | Memory error detection during fuzzing |
| undefined-behavior-sanitizer | Undefined behavior detection |
| coverage-analysis | Measuring and improving code coverage |
| fuzzing-corpus | Building and managing seed corpora |
| fuzzing-dictionaries | Creating dictionaries for format-aware fuzzing |
Related Fuzzers
| Skill | When to Consider |
|---|---|
| libfuzzer | Simpler setup, don't need LibAFL's advanced features |
| aflpp | Multi-core fuzzing without custom fuzzer development |
| cargo-fuzz | Fuzzing Rust projects with less setup |
Resources
Official Documentation
- LibAFL Book - Official handbook with comprehensive documentation
- LibAFL GitHub - Source code and examples
- LibAFL API Documentation - Rust API reference
Examples and Tutorials
- LibAFL Examples - Collection of example fuzzers
- cargo-fuzz with LibAFL - Using LibAFL as cargo-fuzz backend
- Testing Handbook LibAFL Examples - Complete working examples from this handbook
/libfuzzer
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/libfuzzer/SKILL.md
name: libfuzzer type: fuzzer description: > Coverage-guided fuzzer built into LLVM for C/C++ projects. Use for fuzzing C/C++ code that can be compiled with Clang.
libFuzzer
libFuzzer is an in-process, coverage-guided fuzzer that is part of the LLVM project. It's the recommended starting point for fuzzing C/C++ projects due to its simplicity and integration with the LLVM toolchain. While libFuzzer has been in maintenance-only mode since late 2022, it is easier to install and use than its alternatives, has wide support, and will be maintained for the foreseeable future.
When to Use
| Fuzzer | Best For | Complexity |
|---|---|---|
| libFuzzer | Quick setup, single-project fuzzing | Low |
| AFL++ | Multi-core fuzzing, diverse mutations | Medium |
| LibAFL | Custom fuzzers, research projects | High |
| Honggfuzz | Hardware-based coverage | Medium |
Choose libFuzzer when:
- You need a simple, quick setup for C/C++ code
- Project uses Clang for compilation
- Single-core fuzzing is sufficient initially
- Transitioning to AFL++ later is an option (harnesses are compatible)
Note: Fuzzing harnesses written for libFuzzer are compatible with AFL++, making it easy to transition if you need more advanced features like better multi-core support.
Quick Start
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Validate input if needed
if (size < 1) return 0;
// Call your target function with fuzzer-provided data
my_target_function(data, size);
return 0;
}
Compile and run:
clang++ -fsanitize=fuzzer,address -g -O2 harness.cc target.cc -o fuzz
mkdir corpus/
./fuzz corpus/
Installation
Prerequisites
- LLVM/Clang compiler (includes libFuzzer)
- LLVM tools for coverage analysis (optional)
Linux (Ubuntu/Debian)
apt install clang llvm
For the latest LLVM version:
# Add LLVM repository from apt.llvm.org
# Then install specific version, e.g.:
apt install clang-18 llvm-18
macOS
# Using Homebrew
brew install llvm
# Or using Nix
nix-env -i clang
Windows
Install Clang through Visual Studio. Refer to Microsoft's documentation for setup instructions.
Recommendation: If possible, fuzz on a local x86_64 VM or rent one on DigitalOcean, AWS, or Hetzner. Linux provides the best support for libFuzzer.
Verification
clang++ --version
# Should show LLVM version information
Writing a Harness
Harness Structure
The harness is the entry point for the fuzzer. libFuzzer calls the LLVMFuzzerTestOneInput function repeatedly with different inputs.
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. Optional: Validate input size
if (size < MIN_REQUIRED_SIZE) {
return 0; // Reject inputs that are too small
}
// 2. Optional: Convert raw bytes to structured data
// Example: Parse two integers from byte array
if (size >= 2 * sizeof(uint32_t)) {
uint32_t a = *(uint32_t*)(data);
uint32_t b = *(uint32_t*)(data + sizeof(uint32_t));
my_function(a, b);
}
// 3. Call target function
target_function(data, size);
// 4. Always return 0 (non-zero reserved for future use)
return 0;
}
Harness Rules
| Do | Don't |
|---|---|
| Handle all input types (empty, huge, malformed) | Call exit() - stops fuzzing process |
| Join all threads before returning | Leave threads running |
| Keep harness fast and simple | Add excessive logging or complexity |
| Maintain determinism | Use random number generators or read /dev/random |
| Reset global state between runs | Rely on state from previous executions |
| Use narrow, focused targets | Mix unrelated data formats (PNG + TCP) in one harness |
Rationale:
- Speed matters: Aim for 100s-1000s executions per second per core
- Reproducibility: Crashes must be reproducible after fuzzing completes
- Isolation: Each execution should be independent
Using FuzzedDataProvider for Complex Inputs
For complex inputs (strings, multiple parameters), use the FuzzedDataProvider helper:
#include <stdint.h>
#include <stddef.h>
#include "FuzzedDataProvider.h" // From LLVM project
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FuzzedDataProvider fuzzed_data(data, size);
// Extract structured data
size_t allocation_size = fuzzed_data.ConsumeIntegral<size_t>();
std::vector<char> str1 = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF);
std::vector<char> str2 = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF);
// Call target with extracted data
char* result = concat(&str1[0], str1.size(), &str2[0], str2.size(), allocation_size);
if (result != NULL) {
free(result);
}
return 0;
}
Download FuzzedDataProvider.h from the LLVM repository.
Interleaved Fuzzing
Use a single harness to test multiple related functions:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < 1 + 2 * sizeof(int32_t)) {
return 0;
}
uint8_t mode = data[0];
int32_t numbers[2];
memcpy(numbers, data + 1, 2 * sizeof(int32_t));
// Select function based on first byte
switch (mode % 4) {
case 0: add(numbers[0], numbers[1]); break;
case 1: subtract(numbers[0], numbers[1]); break;
case 2: multiply(numbers[0], numbers[1]); break;
case 3: divide(numbers[0], numbers[1]); break;
}
return 0;
}
See Also: For detailed harness writing techniques, patterns for handling complex inputs, structure-aware fuzzing, and protobuf-based fuzzing, see the fuzz-harness-writing technique skill.
Compilation
Basic Compilation
The key flag is -fsanitize=fuzzer, which:
- Links the libFuzzer runtime (provides
mainfunction) - Enables SanitizerCoverage instrumentation for coverage tracking
- Disables built-in functions like
memcmp
clang++ -fsanitize=fuzzer -g -O2 harness.cc target.cc -o fuzz
Flags explained:
-fsanitize=fuzzer: Enable libFuzzer-g: Add debug symbols (helpful for crash analysis)-O2: Production-level optimizations (recommended for fuzzing)-DNO_MAIN: Define macro if your code has amainfunction
With Sanitizers
AddressSanitizer (recommended):
clang++ -fsanitize=fuzzer,address -g -O2 -U_FORTIFY_SOURCE harness.cc target.cc -o fuzz
Multiple sanitizers:
clang++ -fsanitize=fuzzer,address,undefined -g -O2 harness.cc target.cc -o fuzz
See Also: For detailed sanitizer configuration, common issues, ASAN_OPTIONS flags, and advanced sanitizer usage, see the address-sanitizer and undefined-behavior-sanitizer technique skills.
Build Flags
| Flag | Purpose |
|---|---|
-fsanitize=fuzzer |
Enable libFuzzer runtime and instrumentation |
-fsanitize=address |
Enable AddressSanitizer (memory error detection) |
-fsanitize=undefined |
Enable UndefinedBehaviorSanitizer |
-fsanitize=fuzzer-no-link |
Instrument without linking fuzzer (for libraries) |
-g |
Include debug symbols |
-O2 |
Production optimization level |
-U_FORTIFY_SOURCE |
Disable fortification (can interfere with ASan) |
Building Static Libraries
For projects that produce static libraries:
- Build the library with fuzzing instrumentation:
export CC=clang CFLAGS="-fsanitize=fuzzer-no-link -fsanitize=address"
export CXX=clang++ CXXFLAGS="$CFLAGS"
./configure --enable-shared=no
make
- Link the static library with your harness:
clang++ -fsanitize=fuzzer -fsanitize=address harness.cc libmylib.a -o fuzz
CMake Integration
project(FuzzTarget)
cmake_minimum_required(VERSION 3.0)
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2 -fsanitize=fuzzer -fsanitize=address)
target_link_libraries(fuzz -fsanitize=fuzzer -fsanitize=address)
Build with:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build .
Corpus Management
Creating Initial Corpus
Create a directory for the corpus (can start empty):
mkdir corpus/
Optional but recommended: Provide seed inputs (valid example files):
# For a PNG parser:
cp examples/*.png corpus/
# For a protocol parser:
cp test_packets/*.bin corpus/
Benefits of seed inputs:
- Fuzzer doesn't start from scratch
- Reaches valid code paths faster
- Significantly improves effectiveness
Corpus Structure
The corpus directory contains:
- Input files that trigger unique code paths
- Minimized versions (libFuzzer automatically minimizes)
- Named by content hash (e.g.,
a9993e364706816aba3e25717850c26c9cd0d89d)
Corpus Minimization
libFuzzer automatically minimizes corpus entries during fuzzing. To explicitly minimize:
mkdir minimized_corpus/
./fuzz -merge=1 minimized_corpus/ corpus/
This creates a deduplicated, minimized corpus in minimized_corpus/.
See Also: For corpus creation strategies, seed selection, format-specific corpus building, and corpus maintenance, see the fuzzing-corpus technique skill.
Running Campaigns
Basic Run
./fuzz corpus/
This runs until a crash is found or you stop it (Ctrl+C).
Recommended: Continue After Crashes
./fuzz -fork=1 -ignore_crashes=1 corpus/
The -fork and -ignore_crashes flags (experimental but widely used) allow fuzzing to continue after finding crashes.
Common Options
Control input size:
./fuzz -max_len=4000 corpus/
Rule of thumb: 2x the size of minimal realistic input.
Set timeout:
./fuzz -timeout=2 corpus/
Abort test cases that run longer than 2 seconds.
Use a dictionary:
./fuzz -dict=./format.dict corpus/
Close stdout/stderr (speed up fuzzing):
./fuzz -close_fd_mask=3 corpus/
See all options:
./fuzz -help=1
Multi-Core Fuzzing
Option 1: Jobs and workers (recommended):
./fuzz -jobs=4 -workers=4 -fork=1 -ignore_crashes=1 corpus/
-jobs=4: Run 4 sequential campaigns-workers=4: Process jobs in parallel with 4 processes- Test cases are shared between jobs
Option 2: Fork mode:
./fuzz -fork=4 -ignore_crashes=1 corpus/
Note: For serious multi-core fuzzing, consider switching to AFL++, Honggfuzz, or LibAFL.
Re-executing Test Cases
Re-run a single crash:
./fuzz ./crash-a9993e364706816aba3e25717850c26c9cd0d89d
Test all inputs in a directory without fuzzing:
./fuzz -runs=0 corpus/
Interpreting Output
When fuzzing runs, you'll see statistics like:
INFO: Seed: 3517090860
INFO: Loaded 1 modules (9 inline 8-bit counters)
#2 INITED cov: 3 ft: 4 corp: 1/1b exec/s: 0 rss: 26Mb
#57 NEW cov: 4 ft: 5 corp: 2/4b lim: 4 exec/s: 0 rss: 26Mb
| Output | Meaning |
|---|---|
INITED |
Fuzzing initialized |
NEW |
New coverage found, added to corpus |
REDUCE |
Input minimized while keeping coverage |
cov: N |
Number of coverage edges hit |
corp: X/Yb |
Corpus size: X entries, Y total bytes |
exec/s: N |
Executions per second |
rss: NMb |
Resident memory usage |
On crash:
==11672== ERROR: libFuzzer: deadly signal
artifact_prefix='./'; Test unit written to ./crash-a9993e364706816aba3e25717850c26c9cd0d89d
0x61,0x62,0x63,
abc
Base64: YWJj
The crash is saved to ./crash-<hash> with the input shown in hex, UTF-8, and Base64.
Reproducibility: Use -seed=<value> to reproduce a fuzzing campaign (single-core only).
Fuzzing Dictionary
Dictionaries help the fuzzer discover interesting inputs faster by providing hints about the input format.
Dictionary Format
Create a text file with quoted strings (one per line):
# Lines starting with '#' are comments
# Magic bytes
magic="\x89PNG"
magic2="IEND"
# Keywords
"GET"
"POST"
"Content-Type"
# Hex sequences
delimiter="\xFF\xD8\xFF"
Using a Dictionary
./fuzz -dict=./format.dict corpus/
Generating a Dictionary
From header files:
grep -o '".*"' header.h > header.dict
From man pages:
man curl | grep -oP '^\s*(--|-)\K\S+' | sed 's/[,.]$//' | sed 's/^/"&/; s/$/&"/' | sort -u > man.dict
From binary strings:
strings ./binary | sed 's/^/"&/; s/$/&"/' > strings.dict
Using LLMs: Ask ChatGPT or similar to generate a dictionary for your format (e.g., "Generate a libFuzzer dictionary for a JSON parser").
See Also: For advanced dictionary generation, format-specific dictionaries, and dictionary optimization strategies, see the fuzzing-dictionaries technique skill.
Coverage Analysis
While libFuzzer shows basic coverage stats (cov: N), detailed coverage analysis requires additional tools.
Source-Based Coverage
1. Recompile with coverage instrumentation:
clang++ -fsanitize=fuzzer -fprofile-instr-generate -fcoverage-mapping harness.cc target.cc -o fuzz
2. Run fuzzer to collect coverage:
LLVM_PROFILE_FILE="coverage-%p.profraw" ./fuzz -runs=10000 corpus/
3. Merge coverage data:
llvm-profdata merge -sparse coverage-*.profraw -o coverage.profdata
4. Generate coverage report:
llvm-cov show ./fuzz -instr-profile=coverage.profdata
5. Generate HTML report:
llvm-cov show ./fuzz -instr-profile=coverage.profdata -format=html > coverage.html
Improving Coverage
Tips:
- Provide better seed inputs in corpus
- Use dictionaries for format-aware fuzzing
- Check if harness properly exercises target
- Consider structure-aware fuzzing for complex formats
- Run longer campaigns (days/weeks)
See Also: For detailed coverage analysis techniques, identifying coverage gaps, systematic coverage improvement, and comparing coverage across fuzzers, see the coverage-analysis technique skill.
Sanitizer Integration
AddressSanitizer (ASan)
ASan detects memory errors like buffer overflows and use-after-free bugs. Highly recommended for fuzzing.
Enable ASan:
clang++ -fsanitize=fuzzer,address -g -O2 -U_FORTIFY_SOURCE harness.cc target.cc -o fuzz
Example ASan output:
==1276163==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000c4ab1
WRITE of size 1 at 0x6020000c4ab1 thread T0
#0 0x55555568631a in check_buf(char*, unsigned long) main.cc:13:25
#1 0x5555556860bf in LLVMFuzzerTestOneInput harness.cc:7:3
Configure ASan with environment variables:
ASAN_OPTIONS=verbosity=1:abort_on_error=1 ./fuzz corpus/
Important flags:
verbosity=1: Show ASan is activedetect_leaks=0: Disable leak detection (leaks reported at end)abort_on_error=1: Callabort()instead of_exit()on errors
Drawbacks:
- 2-4x slowdown
- Requires ~20TB virtual memory (disable memory limits:
-rss_limit_mb=0) - Best supported on Linux
See Also: For comprehensive ASan configuration, common pitfalls, symbolization, and combining with other sanitizers, see the address-sanitizer technique skill.
UndefinedBehaviorSanitizer (UBSan)
UBSan detects undefined behavior like integer overflow, null pointer dereference, etc.
Enable UBSan:
clang++ -fsanitize=fuzzer,undefined -g -O2 harness.cc target.cc -o fuzz
Combine with ASan:
clang++ -fsanitize=fuzzer,address,undefined -g -O2 harness.cc target.cc -o fuzz
MemorySanitizer (MSan)
MSan detects uninitialized memory reads. More complex to use (requires rebuilding all dependencies).
clang++ -fsanitize=fuzzer,memory -g -O2 harness.cc target.cc -o fuzz
Common Sanitizer Issues
| Issue | Solution |
|---|---|
| ASan slows fuzzing too much | Use -fsanitize-recover=address for non-fatal errors |
| Out of memory | Set ASAN_OPTIONS=rss_limit_mb=0 or -rss_limit_mb=0 |
| Stack exhaustion | Increase stack size: ASAN_OPTIONS=stack_size=8388608 |
False positives with _FORTIFY_SOURCE |
Use -U_FORTIFY_SOURCE flag |
| MSan reports in dependencies | Rebuild all dependencies with -fsanitize=memory |
Real-World Examples
Example 1: Fuzzing libpng
libpng is a widely-used library for reading/writing PNG images. Bugs can lead to security issues.
1. Get source code:
curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
2. Install dependencies:
apt install zlib1g-dev
3. Compile with fuzzing instrumentation:
export CC=clang CFLAGS="-fsanitize=fuzzer-no-link -fsanitize=address"
export CXX=clang++ CXXFLAGS="$CFLAGS"
./configure --enable-shared=no
make
4. Get a harness (or write your own):
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
5. Prepare corpus and dictionary:
mkdir corpus/
curl -o corpus/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
6. Link and compile fuzzer:
clang++ -fsanitize=fuzzer -fsanitize=address libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
7. Run fuzzing campaign:
./fuzz -close_fd_mask=3 -dict=./png.dict corpus/
Example 2: Simple Division Bug
Harness that finds a division-by-zero bug:
#include <stdint.h>
#include <stddef.h>
double divide(uint32_t numerator, uint32_t denominator) {
// Bug: No check if denominator is zero
return numerator / denominator;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if(size != 2 * sizeof(uint32_t)) {
return 0;
}
uint32_t numerator = *(uint32_t*)(data);
uint32_t denominator = *(uint32_t*)(data + sizeof(uint32_t));
divide(numerator, denominator);
return 0;
}
Compile and fuzz:
clang++ -fsanitize=fuzzer harness.cc -o fuzz
./fuzz
The fuzzer will quickly find inputs causing a crash.
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
| Start with single-core, switch to AFL++ for multi-core | libFuzzer harnesses work with AFL++ |
| Use dictionaries for structured formats | 10-100x faster bug discovery |
Close file descriptors with -close_fd_mask=3 |
Speed boost if SUT writes output |
Set reasonable -max_len |
Prevents wasted time on huge inputs |
| Run for days/weeks, not minutes | Coverage plateaus take time to break |
| Use seed corpus from test suites | Starts fuzzing from valid inputs |
Structure-Aware Fuzzing
For highly structured inputs (e.g., complex protocols, file formats), use libprotobuf-mutator:
- Define input structure using Protocol Buffers
- libFuzzer mutates protobuf messages (structure-preserving mutations)
- Harness converts protobuf to native format
See structure-aware fuzzing documentation for details.
Custom Mutators
libFuzzer allows custom mutators for specialized fuzzing:
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
size_t MaxSize, unsigned int Seed) {
// Custom mutation logic
return new_size;
}
extern "C" size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
const uint8_t *Data2, size_t Size2,
uint8_t *Out, size_t MaxOutSize,
unsigned int Seed) {
// Custom crossover logic
return new_size;
}
Performance Tuning
| Setting | Impact |
|---|---|
-close_fd_mask=3 |
Closes stdout/stderr, speeds up fuzzing |
-max_len=<reasonable_size> |
Avoids wasting time on huge inputs |
-timeout=<seconds> |
Detects hangs, prevents stuck executions |
| Disable ASan for baseline | 2-4x speed boost (but misses memory bugs) |
Use -jobs and -workers |
Limited multi-core support |
| Run on Linux | Best platform support and performance |
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| No crashes found after hours | Poor corpus, low coverage | Add seed inputs, use dictionary, check harness |
| Very slow executions/sec (<100) | Target too complex, excessive logging | Optimize target, use -close_fd_mask=3, reduce logging |
| Out of memory | ASan's 20TB virtual memory | Set -rss_limit_mb=0 to disable RSS limit |
| Fuzzer stops after first crash | Default behavior | Use -fork=1 -ignore_crashes=1 to continue |
| Can't reproduce crash | Non-determinism in harness/target | Remove random number generation, global state |
Linking errors with -fsanitize=fuzzer |
Missing libFuzzer runtime | Ensure using Clang, check LLVM installation |
| GCC project won't compile with Clang | GCC-specific code | Switch to AFL++ with gcc_plugin instead |
| Coverage not improving | Corpus plateau | Run longer, add dictionary, improve seeds, check coverage report |
| Crashes but ASan doesn't trigger | Memory error not detected without ASan | Recompile with -fsanitize=address |
Related Skills
Technique Skills
| Skill | Use Case |
|---|---|
| fuzz-harness-writing | Detailed guidance on writing effective harnesses, structure-aware fuzzing, and FuzzedDataProvider usage |
| address-sanitizer | Memory error detection configuration, ASAN_OPTIONS, and troubleshooting |
| undefined-behavior-sanitizer | Detecting undefined behavior during fuzzing |
| coverage-analysis | Measuring fuzzing effectiveness and identifying untested code paths |
| fuzzing-corpus | Building and managing seed corpora, corpus minimization strategies |
| fuzzing-dictionaries | Creating format-specific dictionaries for faster bug discovery |
Related Fuzzers
| Skill | When to Consider |
|---|---|
| aflpp | When you need serious multi-core fuzzing, or when libFuzzer coverage plateaus |
| honggfuzz | When you want hardware-based coverage feedback on Linux |
| libafl | When building custom fuzzers or conducting fuzzing research |
Resources
Official Documentation
- LLVM libFuzzer Documentation - Official reference
- libFuzzer Tutorial by Google - Step-by-step guide
- SanitizerCoverage - Coverage instrumentation details
Advanced Topics
- Structure-Aware Fuzzing with libprotobuf-mutator
- Split Inputs in libFuzzer
- FuzzedDataProvider Header
Example Projects
- OSS-Fuzz - Continuous fuzzing for open-source projects (many libFuzzer examples)
- AFL++ Dictionary Collection - Reusable dictionaries
/ossfuzz
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/ossfuzz/SKILL.md
name: ossfuzz type: technique description: > OSS-Fuzz provides free continuous fuzzing for open source projects. Use when setting up continuous fuzzing infrastructure or enrolling projects.
OSS-Fuzz
OSS-Fuzz is an open-source project developed by Google that provides free distributed infrastructure for continuous fuzz testing. It streamlines the fuzzing process and facilitates simpler modifications. While only select projects are accepted into OSS-Fuzz, the project's core is open-source, allowing anyone to host their own instance for private projects.
Overview
OSS-Fuzz provides a simple CLI framework for building and starting harnesses or calculating their coverage. Additionally, OSS-Fuzz can be used as a service that hosts static web pages generated from fuzzing outputs such as coverage information.
Key Concepts
| Concept | Description |
|---|---|
| helper.py | CLI script for building images, building fuzzers, and running harnesses locally |
| Base Images | Hierarchical Docker images providing build dependencies and compilers |
| project.yaml | Configuration file defining project metadata for OSS-Fuzz enrollment |
| Dockerfile | Project-specific image with build dependencies |
| build.sh | Script that builds fuzzing harnesses for your project |
| Criticality Score | Metric used by OSS-Fuzz team to evaluate project acceptance |
When to Apply
Apply this technique when:
- Setting up continuous fuzzing for an open-source project
- Need distributed fuzzing infrastructure without managing servers
- Want coverage reports and bug tracking integrated with fuzzing
- Testing existing OSS-Fuzz harnesses locally
- Reproducing crashes from OSS-Fuzz bug reports
Skip this technique when:
- Project is closed-source (unless hosting your own OSS-Fuzz instance)
- Project doesn't meet OSS-Fuzz's criticality score threshold
- Need proprietary or specialized fuzzing infrastructure
- Fuzzing simple scripts that don't warrant infrastructure
Quick Reference
| Task | Command |
|---|---|
| Clone OSS-Fuzz | git clone https://github.com/google/oss-fuzz |
| Build project image | python3 infra/helper.py build_image --pull <project> |
| Build fuzzers with ASan | python3 infra/helper.py build_fuzzers --sanitizer=address <project> |
| Run specific harness | python3 infra/helper.py run_fuzzer <project> <harness> |
| Generate coverage report | python3 infra/helper.py coverage <project> |
| Check helper.py options | python3 infra/helper.py --help |
OSS-Fuzz Project Components
OSS-Fuzz provides several publicly available tools and web interfaces:
Bug Tracker
The bug tracker allows you to:
- Check bugs from specific projects (initially visible only to maintainers, later made public)
- Create new issues and comment on existing ones
- Search for similar bugs across all projects to understand issues
Build Status System
The build status system helps track:
- Build statuses of all included projects
- Date of last successful build
- Build failures and their duration
Fuzz Introspector
Fuzz Introspector displays:
- Coverage data for projects enrolled in OSS-Fuzz
- Hit frequency for covered code
- Performance analysis and blocker identification
Read this case study for examples and explanations.
Step-by-Step: Running a Single Harness
You don't need to host the whole OSS-Fuzz platform to use it. The helper script makes it easy to run individual harnesses locally.
Step 1: Clone OSS-Fuzz
git clone https://github.com/google/oss-fuzz
cd oss-fuzz
python3 infra/helper.py --help
Step 2: Build Project Image
python3 infra/helper.py build_image --pull <project-name>
This downloads and builds the base Docker image for the project.
Step 3: Build Fuzzers with Sanitizers
python3 infra/helper.py build_fuzzers --sanitizer=address <project-name>
Sanitizer options:
--sanitizer=addressfor AddressSanitizer with LeakSanitizer- Other sanitizers available (language support varies)
Note: Fuzzers are built to /build/out/<project-name>/ containing the harness executables, dictionaries, corpus, and crash files.
Step 4: Run the Fuzzer
python3 infra/helper.py run_fuzzer <project-name> <harness-name> [<fuzzer-args>]
The helper script automatically runs any missed steps if you skip them.
Step 5: Coverage Analysis (Optional)
First, install gsutil (skip gcloud initialization).
python3 infra/helper.py build_fuzzers --sanitizer=coverage <project-name>
python3 infra/helper.py coverage <project-name>
Use --no-corpus-download to use only local corpus. The command generates and hosts a coverage report locally.
See official OSS-Fuzz documentation for details.
Common Patterns
Pattern: Running irssi Example
Use Case: Testing OSS-Fuzz setup with a simple enrolled project
# Clone and navigate to OSS-Fuzz
git clone https://github.com/google/oss-fuzz
cd oss-fuzz
# Build and run irssi fuzzer
python3 infra/helper.py build_image --pull irssi
python3 infra/helper.py build_fuzzers --sanitizer=address irssi
python3 infra/helper.py run_fuzzer irssi irssi-fuzz
Expected Output:
INFO:__main__:Running: docker run --rm --privileged --shm-size=2g --platform linux/amd64 -i -e FUZZING_ENGINE=libfuzzer -e SANITIZER=address -e RUN_FUZZER_MODE=interactive -e HELPER=True -v /private/tmp/oss-fuzz/build/out/irssi:/out -t gcr.io/oss-fuzz-base/base-runner run_fuzzer irssi-fuzz.
Using seed corpus: irssi-fuzz_seed_corpus.zip
/out/irssi-fuzz -rss_limit_mb=2560 -timeout=25 /tmp/irssi-fuzz_corpus -max_len=2048 < /dev/null
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1531341664
INFO: Loaded 1 modules (95687 inline 8-bit counters): 95687 [0x1096c80, 0x10ae247),
INFO: Loaded 1 PC tables (95687 PCs): 95687 [0x10ae248,0x1223eb8),
INFO: 719 files found in /tmp/irssi-fuzz_corpus
INFO: seed corpus: files: 719 min: 1b max: 170106b total: 367969b rss: 48Mb
#720 INITED cov: 409 ft: 1738 corp: 640/163Kb exec/s: 0 rss: 62Mb
#762 REDUCE cov: 409 ft: 1738 corp: 640/163Kb lim: 2048 exec/s: 0 rss: 63Mb L: 236/2048 MS: 2 ShuffleBytes-EraseBytes-
Pattern: Enrolling a New Project
Use Case: Adding your project to OSS-Fuzz (or private instance)
Create three files in projects/<your-project>/:
1. project.yaml - Project metadata:
homepage: "https://github.com/yourorg/yourproject"
language: c++
primary_contact: "your-email@example.com"
main_repo: "https://github.com/yourorg/yourproject"
fuzzing_engines:
- libfuzzer
sanitizers:
- address
- undefined
2. Dockerfile - Build dependencies:
FROM gcr.io/oss-fuzz-base/base-builder
RUN apt-get update && apt-get install -y \
autoconf \
automake \
libtool \
pkg-config
RUN git clone --depth 1 https://github.com/yourorg/yourproject
WORKDIR yourproject
COPY build.sh $SRC/
3. build.sh - Build harnesses:
#!/bin/bash -eu
./autogen.sh
./configure --disable-shared
make -j$(nproc)
# Build harnesses
$CXX $CXXFLAGS -std=c++11 -I. \
$SRC/yourproject/fuzz/harness.cc -o $OUT/harness \
$LIB_FUZZING_ENGINE ./libyourproject.a
# Copy corpus and dictionary if available
cp $SRC/yourproject/fuzz/corpus.zip $OUT/harness_seed_corpus.zip
cp $SRC/yourproject/fuzz/dictionary.dict $OUT/harness.dict
Docker Images in OSS-Fuzz
Harnesses are built and executed in Docker containers. All projects share a runner image, but each project has its own build image.
Image Hierarchy
Images build on each other in this sequence:
- base_image - Specific Ubuntu version
- base_clang - Clang compiler; based on
base_image - base_builder - Build dependencies; based on
base_clang- Language-specific variants:
base_builder_go, etc. - See /oss-fuzz/infra/base-images/ for full list
- Language-specific variants:
- Your project Docker image - Project-specific dependencies; based on
base_builderor language variant
Runner Images (Used Separately)
- base_runner - Executes harnesses; based on
base_clang - base_runner_debug - With debug tools; based on
base_runner
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
| Don't manually copy source code | Project Dockerfile likely already pulls latest version |
| Check existing projects | Browse oss-fuzz/projects for examples |
| Keep harnesses in separate repo | Like curl-fuzzer - cleaner organization |
| Use specific compiler versions | Base images provide consistent build environment |
| Install dependencies in Dockerfile | May require approval for OSS-Fuzz enrollment |
Criticality Score
OSS-Fuzz uses a criticality score to evaluate project acceptance. See this example for how scoring works.
Projects with lower scores may still be added to private OSS-Fuzz instances.
Hosting Your Own Instance
Since OSS-Fuzz is open-source, you can host your own instance for:
- Private projects not eligible for public OSS-Fuzz
- Projects with lower criticality scores
- Custom fuzzing infrastructure needs
Anti-Patterns
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| Manually pulling source in build.sh | Doesn't use latest version | Let Dockerfile handle git clone |
| Copying code to OSS-Fuzz repo | Hard to maintain, violates separation | Reference external harness repo |
| Ignoring base image versions | Build inconsistencies | Use provided base images and compilers |
| Skipping local testing | Wastes CI resources | Use helper.py locally before PR |
| Not checking build status | Unnoticed build failures | Monitor build status page regularly |
Tool-Specific Guidance
libFuzzer
OSS-Fuzz primarily uses libFuzzer as the fuzzing engine for C/C++ projects.
Harness signature:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Your fuzzing logic
return 0;
}
Build in build.sh:
$CXX $CXXFLAGS -std=c++11 -I. \
harness.cc -o $OUT/harness \
$LIB_FUZZING_ENGINE ./libproject.a
Integration tips:
- Use
$LIB_FUZZING_ENGINEvariable provided by OSS-Fuzz - Include
-fsanitize=fuzzeris handled automatically - Link against static libraries when possible
AFL++
OSS-Fuzz supports AFL++ as an alternative fuzzing engine.
Enable in project.yaml:
fuzzing_engines:
- afl
- libfuzzer
Integration tips:
- AFL++ harnesses work alongside libFuzzer harnesses
- Use persistent mode for better performance
- OSS-Fuzz handles engine-specific compilation flags
Atheris (Python)
For Python projects with C extensions.
Example from cbor2 integration:
Harness:
import atheris
import sys
import cbor2
@atheris.instrument_func
def TestOneInput(data):
fdp = atheris.FuzzedDataProvider(data)
try:
cbor2.loads(data)
except (cbor2.CBORDecodeError, ValueError):
pass
def main():
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
if __name__ == "__main__":
main()
Build in build.sh:
pip3 install .
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
compile_python_fuzzer $fuzzer
done
Integration tips:
- Use
compile_python_fuzzerhelper provided by OSS-Fuzz - See Continuously Fuzzing Python C Extensions blog post
Rust Projects
Enable in project.yaml:
language: rust
fuzzing_engines:
- libfuzzer
sanitizers:
- address # Only AddressSanitizer supported for Rust
Build in build.sh:
cargo fuzz build -O --debug-assertions
cp fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_target_1 $OUT/
Integration tips:
- Rust supports only AddressSanitizer with libfuzzer
- Use cargo-fuzz for local development
- OSS-Fuzz handles Rust-specific compilation
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Build fails with missing dependencies | Dependencies not in Dockerfile | Add apt-get install or equivalent in Dockerfile |
| Harness crashes immediately | Missing input validation | Add size checks in harness |
| Coverage is 0% | Harness not reaching target code | Verify harness actually calls target functions |
| Build timeout | Complex build process | Optimize build.sh, consider parallel builds |
| Sanitizer errors in build | Incompatible flags | Use flags provided by OSS-Fuzz environment variables |
| Cannot find source code | Wrong working directory in Dockerfile | Set WORKDIR or use absolute paths |
Related Skills
Tools That Use This Technique
| Skill | How It Applies |
|---|---|
| libfuzzer | Primary fuzzing engine used by OSS-Fuzz |
| aflpp | Alternative fuzzing engine supported by OSS-Fuzz |
| atheris | Used for fuzzing Python projects in OSS-Fuzz |
| cargo-fuzz | Used for Rust projects in OSS-Fuzz |
Related Techniques
| Skill | Relationship |
|---|---|
| coverage-analysis | OSS-Fuzz generates coverage reports via helper.py |
| address-sanitizer | Default sanitizer for OSS-Fuzz projects |
| fuzz-harness-writing | Essential for enrolling projects in OSS-Fuzz |
| corpus-management | OSS-Fuzz maintains corpus for enrolled projects |
Resources
Key External Resources
OSS-Fuzz Official Documentation Comprehensive documentation covering enrollment, harness writing, and troubleshooting for the OSS-Fuzz platform.
Getting Started Guide Step-by-step process for enrolling new projects into OSS-Fuzz, including requirements and approval process.
cbor2 OSS-Fuzz Integration PR Real-world example of enrolling a Python project with C extensions into OSS-Fuzz. Shows:
- Initial proposal and project introduction
- Criticality score evaluation
- Complete implementation (project.yaml, Dockerfile, build.sh, harnesses)
Fuzz Introspector Case Studies Examples and explanations of using Fuzz Introspector to analyze coverage and identify fuzzing blockers.
Video Resources
Check OSS-Fuzz documentation for workshop recordings and tutorials on enrollment and harness development.
/ruzzy
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/ruzzy/SKILL.md
name: ruzzy type: fuzzer description: > Ruzzy is a coverage-guided Ruby fuzzer by Trail of Bits. Use for fuzzing pure Ruby code and Ruby C extensions.
Ruzzy
Ruzzy is a coverage-guided fuzzer for Ruby built on libFuzzer. It enables fuzzing both pure Ruby code and Ruby C extensions with sanitizer support for detecting memory corruption and undefined behavior.
When to Use
Ruzzy is currently the only production-ready coverage-guided fuzzer for Ruby.
Choose Ruzzy when:
- Fuzzing Ruby applications or libraries
- Testing Ruby C extensions for memory safety issues
- You need coverage-guided fuzzing for Ruby code
- Working with Ruby gems that have native extensions
Quick Start
Set up environment:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
Test with the included toy example:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
This should quickly find a crash demonstrating that Ruzzy is working correctly.
Installation
Platform Support
Ruzzy supports Linux x86-64 and AArch64/ARM64. For macOS or Windows, use the Dockerfile or development environment.
Prerequisites
- Linux x86-64 or AArch64/ARM64
- Recent version of clang (tested back to 14.0.0, latest release recommended)
- Ruby with gem installed
Installation Command
Install Ruzzy with clang compiler flags:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
gem install ruzzy
Environment variables explained:
MAKE: Overrides make to respect subsequent environment variablesCC,CXX,LDSHARED,LDSHAREDXX: Ensure proper clang binaries are used for latest features
Troubleshooting Installation
If installation fails, enable debug output:
RUZZY_DEBUG=1 gem install --verbose ruzzy
Verification
Verify installation by running the toy example (see Quick Start section).
Writing a Harness
Fuzzing Pure Ruby Code
Pure Ruby fuzzing requires two scripts due to Ruby interpreter implementation details.
Tracer script (test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Harness script (test_harness.rb):
# frozen_string_literal: true
require 'ruzzy'
def fuzzing_target(input)
# Your code to fuzz here
if input.length == 4
if input[0] == 'F'
if input[1] == 'U'
if input[2] == 'Z'
if input[3] == 'Z'
raise
end
end
end
end
end
end
test_one_input = lambda do |data|
fuzzing_target(data)
return 0
end
Ruzzy.fuzz(test_one_input)
Run with:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Fuzzing Ruby C Extensions
C extensions can be fuzzed with a single harness file, no tracer needed.
Example harness for msgpack (fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# We're looking for memory corruption, not Ruby exceptions
end
return 0
end
Ruzzy.fuzz(test_one_input)
Run with:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
Harness Rules
| Do | Don't |
|---|---|
| Catch Ruby exceptions if testing C extensions | Let Ruby exceptions crash the fuzzer |
| Return 0 from test_one_input lambda | Return other values |
| Keep harness deterministic | Use randomness or time-based logic |
| Use tracer script for pure Ruby | Skip tracer for pure Ruby code |
See Also: For detailed harness writing techniques, patterns for handling complex inputs, and advanced strategies, see the fuzz-harness-writing technique skill.
Compilation
Installing Gems with Sanitizers
When installing Ruby gems with C extensions for fuzzing, compile with sanitizer flags:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
CFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
CXXFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
gem install <gem-name>
Build Flags
| Flag | Purpose |
|---|---|
-fsanitize=address,fuzzer-no-link |
Enable AddressSanitizer and fuzzer instrumentation |
-fno-omit-frame-pointer |
Improve stack trace quality |
-fno-common |
Better compatibility with sanitizers |
-fPIC |
Position-independent code for shared libraries |
-g |
Include debug symbols |
Running Campaigns
Environment Setup
Before running any fuzzing campaign, set ASAN_OPTIONS:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
Options explained:
allocator_may_return_null=1: Skip common low-impact allocation failures (DoS)detect_leaks=0: Ruby interpreter leaks data, ignore these for nowuse_sigaltstack=0: Ruby recommends disabling sigaltstack with ASan
Basic Run
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
Note: LD_PRELOAD is required for sanitizer injection. Unlike ASAN_OPTIONS, do not export it as it may interfere with other programs.
With Corpus
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /path/to/corpus
Passing libFuzzer Options
All libFuzzer options can be passed as arguments:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /path/to/corpus -max_len=1024 -timeout=10
See libFuzzer options for full reference.
Reproducing Crashes
Re-run a crash case by passing the crash file:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb ./crash-253420c1158bc6382093d409ce2e9cff5806e980
Interpreting Output
| Output | Meaning |
|---|---|
INFO: Running with entropic power schedule |
Fuzzing campaign started |
ERROR: AddressSanitizer: heap-use-after-free |
Memory corruption detected |
SUMMARY: libFuzzer: fuzz target exited |
Ruby exception occurred |
artifact_prefix='./'; Test unit written to ./crash-* |
Crash input saved |
Base64: ... |
Base64 encoding of crash input |
Sanitizer Integration
AddressSanitizer (ASan)
Ruzzy includes a pre-compiled AddressSanitizer library:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
Use ASan for detecting:
- Heap buffer overflows
- Stack buffer overflows
- Use-after-free
- Double-free
- Memory leaks (disabled by default in Ruzzy)
UndefinedBehaviorSanitizer (UBSan)
Ruzzy also includes UBSan:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH') \
ruby harness.rb
Use UBSan for detecting:
- Signed integer overflow
- Null pointer dereferences
- Misaligned memory access
- Division by zero
Common Sanitizer Issues
| Issue | Solution |
|---|---|
| Ruby interpreter leak warnings | Use ASAN_OPTIONS=detect_leaks=0 |
| Sigaltstack conflicts | Use ASAN_OPTIONS=use_sigaltstack=0 |
| Allocation failure spam | Use ASAN_OPTIONS=allocator_may_return_null=1 |
| LD_PRELOAD interferes with tools | Don't export it; set inline with ruby command |
See Also: For detailed sanitizer configuration, common issues, and advanced flags, see the address-sanitizer and undefined-behavior-sanitizer technique skills.
Real-World Examples
Example: msgpack-ruby
Fuzzing the msgpack MessagePack parser for memory corruption.
Install with sanitizers:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
CFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
CXXFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
gem install msgpack
Harness (fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# We're looking for memory corruption, not Ruby exceptions
end
return 0
end
Ruzzy.fuzz(test_one_input)
Run:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
Example: Pure Ruby Target
Fuzzing pure Ruby code with a custom parser.
Tracer (test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Harness (test_harness.rb):
# frozen_string_literal: true
require 'ruzzy'
require_relative 'my_parser'
test_one_input = lambda do |data|
begin
MyParser.parse(data)
rescue StandardError
# Expected exceptions from malformed input
end
return 0
end
Ruzzy.fuzz(test_one_input)
Run:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Installation fails | Wrong clang version or path | Verify clang path, use clang 14.0.0+ |
cannot open shared object file |
LD_PRELOAD not set | Set LD_PRELOAD inline with ruby command |
| Fuzzer immediately exits | Missing corpus directory | Create corpus directory or pass as argument |
| No coverage progress | Pure Ruby needs tracer | Use tracer script for pure Ruby code |
| Leak detection spam | Ruby interpreter leaks | Set ASAN_OPTIONS=detect_leaks=0 |
| Installation debug needed | Compilation errors | Use RUZZY_DEBUG=1 gem install --verbose ruzzy |
Related Skills
Technique Skills
| Skill | Use Case |
|---|---|
| fuzz-harness-writing | Detailed guidance on writing effective harnesses |
| address-sanitizer | Memory error detection during fuzzing |
| undefined-behavior-sanitizer | Detecting undefined behavior in C extensions |
| libfuzzer | Understanding libFuzzer options (Ruzzy is built on libFuzzer) |
Related Fuzzers
| Skill | When to Consider |
|---|---|
| libfuzzer | When fuzzing Ruby C extension code directly in C/C++ |
| aflpp | Alternative approach for fuzzing Ruby by instrumenting Ruby interpreter |
Resources
Key External Resources
Introducing Ruzzy, a coverage-guided Ruby fuzzer Official Trail of Bits blog post announcing Ruzzy, covering motivation, architecture, and initial results.
Ruzzy GitHub Repository Source code, additional examples, and development instructions.
libFuzzer Documentation Since Ruzzy is built on libFuzzer, understanding libFuzzer options and behavior is valuable.
Fuzzing Ruby C extensions Detailed guide on fuzzing C extensions with compilation flags and examples.
Fuzzing pure Ruby code Detailed guide on the tracer pattern required for pure Ruby fuzzing.
/testing-handbook-generator
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/testing-handbook-generator/SKILL.md
name: testing-handbook-generator description: > Meta-skill that analyzes the Trail of Bits Testing Handbook (appsec.guide) and generates Claude Code skills for security testing tools and techniques. Use when creating new skills based on handbook content.
Testing Handbook Skill Generator
Generate and maintain Claude Code skills from the Trail of Bits Testing Handbook.
When to Use
Invoke this skill when:
- Creating new security testing skills from handbook content
- User mentions "testing handbook", "appsec.guide", or asks about generating skills
- Bulk skill generation or refresh is needed
Do NOT use for:
- General security testing questions (use the generated skills)
- Non-handbook skill creation
Handbook Location
The skill needs the Testing Handbook repository. See discovery.md for full details.
Quick reference: Check ./testing-handbook, ../testing-handbook, ~/testing-handbook → ask user → clone as last resort.
Repository: https://github.com/trailofbits/testing-handbook
Workflow Overview
Phase 0: Setup Phase 1: Discovery
┌─────────────────┐ ┌─────────────────┐
│ Locate handbook │ → │ Analyze handbook│
│ - Find or clone │ │ - Scan sections │
│ - Confirm path │ │ - Classify types│
└─────────────────┘ └─────────────────┘
↓ ↓
Phase 3: Generation Phase 2: Planning
┌─────────────────┐ ┌─────────────────┐
│ TWO-PASS GEN │ ← │ Generate plan │
│ Pass 1: Content │ │ - New skills │
│ Pass 2: X-refs │ │ - Updates │
│ - Write to gen/ │ │ - Present user │
└─────────────────┘ └─────────────────┘
↓
Phase 4: Testing Phase 5: Finalize
┌─────────────────┐ ┌─────────────────┐
│ Validate skills │ → │ Post-generation │
│ - Run validator │ │ - Update README │
│ - Test activation│ │ - Update X-refs │
│ - Fix issues │ │ - Self-improve │
└─────────────────┘ └─────────────────┘
Scope Restrictions
ONLY modify these locations:
plugins/testing-handbook-skills/skills/[skill-name]/*- Generated skills (as siblings to testing-handbook-generator)plugins/testing-handbook-skills/skills/testing-handbook-generator/*- Self-improvement- Repository root
README.md- Add generated skills to table
NEVER modify or analyze:
- Other plugins (
plugins/property-based-testing/,plugins/static-analysis/, etc.) - Other skills outside this plugin
Do not scan or pull into context any skills outside of testing-handbook-skills/. Generate skills based solely on handbook content and resources referenced from it.
Quick Reference
Section → Skill Type Mapping
| Handbook Section | Skill Type | Template |
|---|---|---|
/static-analysis/[tool]/ |
Tool Skill | tool-skill.md |
/fuzzing/[lang]/[fuzzer]/ |
Fuzzer Skill | fuzzer-skill.md |
/fuzzing/techniques/ |
Technique Skill | technique-skill.md |
/crypto/[tool]/ |
Domain Skill | domain-skill.md |
/web/[tool]/ |
Tool Skill | tool-skill.md |
Skill Candidate Signals
| Signal | Indicates |
|---|---|
_index.md with bookCollapseSection: true |
Major tool/topic |
| Numbered files (00-, 10-, 20-) | Structured content |
techniques/ subsection |
Methodology content |
99-resources.md or 91-resources.md |
Has external links |
Exclusion Signals
| Signal | Action |
|---|---|
draft: true in frontmatter |
Skip section |
| Empty directory | Skip section |
| Template/placeholder file | Skip section |
GUI-only tool (e.g., web/burp/) |
Skip section (Claude cannot operate GUI tools) |
Decision Tree
Starting skill generation?
├─ Need to analyze handbook and build plan?
│ └─ Read: discovery.md
│ (Handbook analysis methodology, plan format)
│
├─ Spawning skill generation agents?
│ └─ Read: agent-prompt.md
│ (Full prompt template, variable reference, validation checklist)
│
├─ Generating a specific skill type?
│ └─ Read appropriate template:
│ ├─ Tool (Semgrep, CodeQL) → templates/tool-skill.md
│ ├─ Fuzzer (libFuzzer, AFL++) → templates/fuzzer-skill.md
│ ├─ Technique (harness, coverage) → templates/technique-skill.md
│ └─ Domain (crypto, web) → templates/domain-skill.md
│
├─ Validating generated skills?
│ └─ Run: scripts/validate-skills.py
│ Then read: testing.md for activation testing
│
├─ Finalizing after generation?
│ └─ See: Post-Generation Tasks below
│ (Update main README, update Skills Cross-Reference, self-improvement)
│
└─ Quick generation from specific section?
└─ Use Quick Reference above, apply template directly
Two-Pass Generation (Phase 3)
Generation uses a two-pass approach to solve forward reference problems (skills referencing other skills that don't exist yet).
Pass 1: Content Generation (Parallel)
Generate all skills in parallel without the Related Skills section:
Pass 1 - Generating 5 skills in parallel:
├─ Agent 1: libfuzzer (fuzzer) → skills/libfuzzer/SKILL.md
├─ Agent 2: aflpp (fuzzer) → skills/aflpp/SKILL.md
├─ Agent 3: semgrep (tool) → skills/semgrep/SKILL.md
├─ Agent 4: harness-writing (technique) → skills/harness-writing/SKILL.md
└─ Agent 5: wycheproof (domain) → skills/wycheproof/SKILL.md
Each agent uses: pass=1 (content only, Related Skills left empty)
Pass 1 agents:
- Generate all sections EXCEPT Related Skills
- Leave a placeholder:
## Related Skills\n\n<!-- PASS2: populate after all skills exist --> - Output report includes
references: DEFERRED
Pass 2: Cross-Reference Population (Sequential)
After all Pass 1 agents complete, run Pass 2 to populate Related Skills:
Pass 2 - Populating cross-references:
├─ Read all generated skill names from skills/*/SKILL.md
├─ For each skill, determine related skills based on:
│ ├─ related_sections from discovery (handbook structure)
│ ├─ Skill type relationships (fuzzers → techniques)
│ └─ Explicit mentions in content
└─ Update each SKILL.md's Related Skills section
Pass 2 process:
- Collect all generated skill names:
ls -d skills/*/SKILL.md - For each skill, identify related skills using the mapping from discovery
- Edit each SKILL.md to replace the placeholder with actual links
- Validate cross-references exist (no broken links)
Agent Prompt Template
See agent-prompt.md for the full prompt template with:
- Variable substitution reference (including
passvariable) - Pre-write validation checklist
- Hugo shortcode conversion rules
- Line count splitting rules
- Error handling guidance
- Output report format
Collecting Results
After Pass 1: Aggregate output reports, verify all skills generated. After Pass 2: Run validator to check cross-references.
Handling Agent Failures
If an agent fails or produces invalid output:
| Failure Type | Detection | Recovery Action |
|---|---|---|
| Agent crashed | No output report | Re-run single agent with same inputs |
| Validation failed | Output report shows errors | Check gaps/warnings, manually patch or re-run |
| Wrong skill type | Content doesn't match template | Re-run with corrected type parameter |
| Missing content | Output report lists gaps | Accept if minor, or provide additional related_sections |
| Pass 2 broken ref | Validator shows missing skill | Check if skill was skipped, update reference |
Important: Do NOT re-run the entire parallel batch for a single agent failure. Fix individual failures independently.
Single-Skill Regeneration
To regenerate a single skill without re-running the entire batch:
# Regenerate single skill (Pass 1 - content only)
"Use testing-handbook-generator to regenerate the {skill-name} skill from section {section_path}"
# Example:
"Use testing-handbook-generator to regenerate the libfuzzer skill from section fuzzing/c-cpp/10-libfuzzer"
Regeneration workflow:
- Re-read the handbook section for fresh content
- Apply the appropriate template
- Write to
skills/{skill-name}/SKILL.md(overwrites existing) - Re-run Pass 2 for that skill only to update cross-references
- Run validator on the single skill:
uv run scripts/validate-skills.py --skill {skill-name}
Output Location
Generated skills are written to:
skills/[skill-name]/SKILL.md
Each skill gets its own directory for potential supporting files (as siblings to testing-handbook-generator).
Quality Checklist
Before delivering generated skills:
- All handbook sections analyzed (Phase 1)
- Plan presented to user before generation (Phase 2)
- Parallel agents launched - one per skill (Phase 3)
- Templates applied correctly per skill type
- Validator passes:
uv run scripts/validate-skills.py - Activation testing passed - see testing.md
- Main
README.mdupdated with generated skills table -
README.mdSkills Cross-Reference graph updated - Self-improvement notes captured
- User notified with summary
Post-Generation Tasks
1. Update Main README
After generating skills, update the repository's main README.md to list them.
Format: Add generated skills to the same "Available Plugins" table, directly after testing-handbook-skills. Use plain text testing-handbook-generator as the author (no link).
Example:
| Plugin | Description | Author |
|--------|-------------|--------|
| ... other plugins ... |
| [testing-handbook-skills](plugins/testing-handbook-skills/) | Meta-skill that generates skills from the Testing Handbook | Paweł Płatek |
| [libfuzzer](plugins/testing-handbook-skills/skills/libfuzzer/) | Coverage-guided fuzzing with libFuzzer for C/C++ | testing-handbook-generator |
| [aflpp](plugins/testing-handbook-skills/skills/aflpp/) | Multi-core fuzzing with AFL++ | testing-handbook-generator |
| [semgrep](plugins/testing-handbook-skills/skills/semgrep/) | Fast static analysis for finding bugs | testing-handbook-generator |
2. Update Skills Cross-Reference
After generating skills, update the README.md's Skills Cross-Reference section with the mermaid graph showing skill relationships.
Process:
- Read each generated skill's
SKILL.mdand extract its## Related Skillssection - Build the mermaid graph with nodes grouped by skill type (Fuzzers, Techniques, Tools, Domain)
- Add edges based on the Related Skills relationships:
- Solid arrows (
-->) for primary technique dependencies - Dashed arrows (
-.->) for alternative tool suggestions
- Solid arrows (
- Replace the existing mermaid code block in README.md
Edge classification:
| Relationship | Arrow Style | Example |
|---|---|---|
| Fuzzer → Technique | --> |
libfuzzer --> harness-writing |
| Tool → Tool (alternative) | -.-> |
semgrep -.-> codeql |
| Fuzzer → Fuzzer (alternative) | -.-> |
libfuzzer -.-> aflpp |
| Technique → Technique | --> |
harness-writing --> coverage-analysis |
Validation: After updating, run validate-skills.py to verify all referenced skills exist.
3. Self-Improvement
After each generation run, reflect on what could improve future runs.
Capture improvements to:
- Templates (missing sections, better structure)
- Discovery logic (missed patterns, false positives)
- Content extraction (shortcodes not handled, formatting issues)
Update process:
- Note issues encountered during generation
- Identify patterns that caused problems
- Update relevant files:
SKILL.md- Workflow, decision tree, quick reference updatestemplates/*.md- Template improvementsdiscovery.md- Detection logic updatestesting.md- New validation checks
- Document the improvement in commit message
Example self-improvement:
Issue: libFuzzer skill missing sanitizer flags table
Fix: Updated templates/fuzzer-skill.md to include ## Compiler Flags section
Example Usage
Full Discovery and Generation
User: "Generate skills from the testing handbook"
1. Locate handbook (check common locations, ask user, or clone)
2. Read discovery.md for methodology
3. Scan handbook at {handbook_path}/content/docs/
4. Build candidate list with types
5. Present plan to user
6. On approval, generate each skill using appropriate template
7. Validate generated skills
8. Update main README.md with generated skills table
9. Update README.md Skills Cross-Reference graph from Related Skills sections
10. Self-improve: note any template/discovery issues for future runs
11. Report results
Single Section Generation
User: "Create a skill for the libFuzzer section"
1. Read /testing-handbook/content/docs/fuzzing/c-cpp/10-libfuzzer/
2. Identify type: Fuzzer Skill
3. Read templates/fuzzer-skill.md
4. Extract content, apply template
5. Write to skills/libfuzzer/SKILL.md
6. Validate and report
Tips
Do:
- Always present plan before generating
- Use appropriate template for skill type
- Preserve code blocks exactly
- Validate after generation
Don't:
- Generate without user approval
- Skip fetching non-video external resources (use WebFetch)
- Fetch video URLs (YouTube, Vimeo - titles only)
- Include handbook images directly
- Skip validation step
- Exceed 500 lines per SKILL.md
For first-time use: Start with discovery.md to understand the handbook analysis process.
For template reference: See templates/ directory for skill type templates.
For validation: See testing.md for quality assurance methodology.
/wycheproof
Source: ~/.claude/skills/tob-testing-handbook-skills/skills/wycheproof/SKILL.md
name: wycheproof type: domain description: > Wycheproof provides test vectors for validating cryptographic implementations. Use when testing crypto code for known attacks and edge cases.
Wycheproof
Wycheproof is an extensive collection of test vectors designed to verify the correctness of cryptographic implementations and test against known attacks. Originally developed by Google, it is now a community-managed project where contributors can add test vectors for specific cryptographic constructions.
Background
Key Concepts
| Concept | Description |
|---|---|
| Test vector | Input/output pair for validating crypto implementation correctness |
| Test group | Collection of test vectors sharing attributes (key size, IV size, curve) |
| Result flag | Indicates if test should pass (valid), fail (invalid), or is acceptable |
| Edge case testing | Testing for known vulnerabilities and attack patterns |
Why This Matters
Cryptographic implementations are notoriously difficult to get right. Even small bugs can:
- Expose private keys
- Allow signature forgery
- Enable message decryption
- Create consensus problems when different implementations accept/reject the same inputs
Wycheproof has found vulnerabilities in major libraries including OpenJDK's SHA1withDSA, Bouncy Castle's ECDHC, and the elliptic npm package.
When to Use
Apply Wycheproof when:
- Testing cryptographic implementations (AES-GCM, ECDSA, ECDH, RSA, etc.)
- Validating that crypto code handles edge cases correctly
- Verifying implementations against known attack vectors
- Setting up CI/CD for cryptographic libraries
- Auditing third-party crypto code for correctness
Consider alternatives when:
- Testing for timing side-channels (use constant-time testing tools instead)
- Finding new unknown bugs (use fuzzing instead)
- Testing custom/experimental cryptographic algorithms (Wycheproof only covers established algorithms)
Quick Reference
| Scenario | Recommended Approach | Notes |
|---|---|---|
| AES-GCM implementation | Use aes_gcm_test.json |
316 test vectors across 44 test groups |
| ECDSA verification | Use ecdsa_*_test.json for specific curves |
Tests signature malleability, DER encoding |
| ECDH key exchange | Use ecdh_*_test.json |
Tests invalid curve attacks |
| RSA signatures | Use rsa_*_test.json |
Tests padding oracle attacks |
| ChaCha20-Poly1305 | Use chacha20_poly1305_test.json |
Tests AEAD implementation |
Testing Workflow
Phase 1: Setup Phase 2: Parse Test Vectors
┌─────────────────┐ ┌─────────────────┐
│ Add Wycheproof │ → │ Load JSON file │
│ as submodule │ │ Filter by params│
└─────────────────┘ └─────────────────┘
↓ ↓
Phase 4: CI Integration Phase 3: Write Harness
┌─────────────────┐ ┌─────────────────┐
│ Auto-update │ ← │ Test valid & │
│ test vectors │ │ invalid cases │
└─────────────────┘ └─────────────────┘
Repository Structure
The Wycheproof repository is organized as follows:
┣ 📜 README.md : Project overview
┣ 📂 doc : Documentation
┣ 📂 java : Java JCE interface testing harness
┣ 📂 javascript : JavaScript testing harness
┣ 📂 schemas : Test vector schemas
┣ 📂 testvectors : Test vectors
┗ 📂 testvectors_v1 : Updated test vectors (more detailed)
The essential folders are testvectors and testvectors_v1. While both contain similar files, testvectors_v1 includes more detailed information and is recommended for new integrations.
Supported Algorithms
Wycheproof provides test vectors for a wide range of cryptographic algorithms:
| Category | Algorithms |
|---|---|
| Symmetric Encryption | AES-GCM, AES-EAX, ChaCha20-Poly1305 |
| Signatures | ECDSA, EdDSA, RSA-PSS, RSA-PKCS1 |
| Key Exchange | ECDH, X25519, X448 |
| Hashing | HMAC, HKDF |
| Curves | secp256k1, secp256r1, secp384r1, secp521r1, ed25519, ed448 |
Test File Structure
Each JSON test file tests a specific cryptographic construction. All test files share common attributes:
"algorithm" : The name of the algorithm tested
"schema" : The JSON schema (found in schemas folder)
"generatorVersion" : The version number
"numberOfTests" : The total number of test vectors in this file
"header" : Detailed description of test vectors
"notes" : In-depth explanation of flags in test vectors
"testGroups" : Array of one or multiple test groups
Test Groups
Test groups group sets of tests based on shared attributes such as:
- Key sizes
- IV sizes
- Public keys
- Curves
This classification allows extracting tests that meet specific criteria relevant to the construction being tested.
Test Vector Attributes
Shared Attributes
All test vectors contain four common fields:
- tcId: Unique identifier for the test vector within a file
- comment: Additional information about the test case
- flags: Descriptions of specific test case types and potential dangers (referenced in
notesfield) - result: Expected outcome of the test
The result field can take three values:
| Result | Meaning |
|---|---|
| valid | Test case should succeed |
| acceptable | Test case is allowed to succeed but contains non-ideal attributes |
| invalid | Test case should fail |
Unique Attributes
Unique attributes are specific to the algorithm being tested:
| Algorithm | Unique Attributes |
|---|---|
| AES-GCM | key, iv, aad, msg, ct, tag |
| ECDH secp256k1 | public, private, shared |
| ECDSA | msg, sig, result |
| EdDSA | msg, sig, pk |
Implementation Guide
Phase 1: Add Wycheproof to Your Project
Option 1: Git Submodule (Recommended)
Adding Wycheproof as a git submodule ensures automatic updates:
git submodule add https://github.com/C2SP/wycheproof.git
Option 2: Fetch Specific Test Vectors
If submodules aren't possible, fetch specific JSON files:
#!/bin/bash
TMP_WYCHEPROOF_FOLDER=".wycheproof/"
TEST_VECTORS=('aes_gcm_test.json' 'aes_eax_test.json')
BASE_URL="https://raw.githubusercontent.com/C2SP/wycheproof/master/testvectors_v1/"
# Create wycheproof folder
mkdir -p $TMP_WYCHEPROOF_FOLDER
# Request all test vector files if they don't exist
for i in "${TEST_VECTORS[@]}"; do
if [ ! -f "${TMP_WYCHEPROOF_FOLDER}${i}" ]; then
curl -o "${TMP_WYCHEPROOF_FOLDER}${i}" "${BASE_URL}${i}"
if [ $? -ne 0 ]; then
echo "Failed to download ${i}"
exit 1
fi
fi
done
Phase 2: Parse Test Vectors
Identify the test file for your algorithm and parse the JSON:
Python Example:
import json
def load_wycheproof_test_vectors(path: str):
testVectors = []
try:
with open(path, "r") as f:
wycheproof_json = json.loads(f.read())
except FileNotFoundError:
print(f"No Wycheproof file found at: {path}")
return testVectors
# Attributes that need hex-to-bytes conversion
convert_attr = {"key", "aad", "iv", "msg", "ct", "tag"}
for testGroup in wycheproof_json["testGroups"]:
# Filter test groups based on implementation constraints
if testGroup["ivSize"] < 64 or testGroup["ivSize"] > 1024:
continue
for tv in testGroup["tests"]:
# Convert hex strings to bytes
for attr in convert_attr:
if attr in tv:
tv[attr] = bytes.fromhex(tv[attr])
testVectors.append(tv)
return testVectors
JavaScript Example:
const fs = require('fs').promises;
async function loadWycheproofTestVectors(path) {
const tests = [];
try {
const fileContent = await fs.readFile(path);
const data = JSON.parse(fileContent.toString());
data.testGroups.forEach(testGroup => {
testGroup.tests.forEach(test => {
// Add shared test group properties to each test
test['pk'] = testGroup.publicKey.pk;
tests.push(test);
});
});
} catch (err) {
console.error('Error reading or parsing file:', err);
throw err;
}
return tests;
}
Phase 3: Write Testing Harness
Create test functions that handle both valid and invalid test cases.
Python/pytest Example:
import pytest
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
tvs = load_wycheproof_test_vectors("wycheproof/testvectors_v1/aes_gcm_test.json")
@pytest.mark.parametrize("tv", tvs, ids=[str(tv['tcId']) for tv in tvs])
def test_encryption(tv):
try:
aesgcm = AESGCM(tv['key'])
ct = aesgcm.encrypt(tv['iv'], tv['msg'], tv['aad'])
except ValueError as e:
# Implementation raised error - verify test was expected to fail
assert tv['result'] != 'valid', tv['comment']
return
if tv['result'] == 'valid':
assert ct[:-16] == tv['ct'], f"Ciphertext mismatch: {tv['comment']}"
assert ct[-16:] == tv['tag'], f"Tag mismatch: {tv['comment']}"
elif tv['result'] == 'invalid' or tv['result'] == 'acceptable':
assert ct[:-16] != tv['ct'] or ct[-16:] != tv['tag']
@pytest.mark.parametrize("tv", tvs, ids=[str(tv['tcId']) for tv in tvs])
def test_decryption(tv):
try:
aesgcm = AESGCM(tv['key'])
decrypted_msg = aesgcm.decrypt(tv['iv'], tv['ct'] + tv['tag'], tv['aad'])
except ValueError:
assert tv['result'] != 'valid', tv['comment']
return
except InvalidTag:
assert tv['result'] != 'valid', tv['comment']
assert 'ModifiedTag' in tv['flags'], f"Expected 'ModifiedTag' flag: {tv['comment']}"
return
assert tv['result'] == 'valid', f"No invalid test case should pass: {tv['comment']}"
assert decrypted_msg == tv['msg'], f"Decryption mismatch: {tv['comment']}"
JavaScript/Mocha Example:
const assert = require('assert');
function testFactory(tcId, tests) {
it(`[${tcId + 1}] ${tests[tcId].comment}`, function () {
const test = tests[tcId];
const ed25519 = new eddsa('ed25519');
const key = ed25519.keyFromPublic(toArray(test.pk, 'hex'));
let sig;
if (test.result === 'valid') {
sig = key.verify(test.msg, test.sig);
assert.equal(sig, true, `[${test.tcId}] ${test.comment}`);
} else if (test.result === 'invalid') {
try {
sig = key.verify(test.msg, test.sig);
} catch (err) {
// Point could not be decoded
sig = false;
}
assert.equal(sig, false, `[${test.tcId}] ${test.comment}`);
}
});
}
// Generate tests for all test vectors
for (var tcId = 0; tcId < tests.length; tcId++) {
testFactory(tcId, tests);
}
Phase 4: CI Integration
Ensure test vectors stay up to date by:
- Using git submodules: Update submodule in CI before running tests
- Fetching latest vectors: Run fetch script before test execution
- Scheduled updates: Set up weekly/monthly updates to catch new test vectors
Common Vulnerabilities Detected
Wycheproof test vectors are designed to catch specific vulnerability patterns:
| Vulnerability | Description | Affected Algorithms | Example CVE |
|---|---|---|---|
| Signature malleability | Multiple valid signatures for same message | ECDSA, EdDSA | CVE-2024-42459 |
| Invalid DER encoding | Accepting non-canonical DER signatures | ECDSA | CVE-2024-42460, CVE-2024-42461 |
| Invalid curve attacks | ECDH with invalid curve points | ECDH | Common in many libraries |
| Padding oracle | Timing leaks in padding validation | RSA-PKCS1 | Historical OpenSSL issues |
| Tag forgery | Accepting modified authentication tags | AES-GCM, ChaCha20-Poly1305 | Various implementations |
Signature Malleability: Deep Dive
Problem: Implementations that don't validate signature encoding can accept multiple valid signatures for the same message.
Example (EdDSA): Appending or removing zeros from signature:
Valid signature: ...6a5c51eb6f946b30d
Invalid signature: ...6a5c51eb6f946b30d0000 (should be rejected)
How to detect:
# Add signature length check
if len(sig) != 128: # EdDSA signatures must be exactly 64 bytes (128 hex chars)
return False
Impact: Can lead to consensus problems when different implementations accept/reject the same signatures.
- EdDSA: tcId 37 - "removing 0 byte from signature"
- ECDSA: tcId 06 - "Legacy: ASN encoding of r misses leading 0"
Case Study: Elliptic npm Package
This case study demonstrates how Wycheproof found three CVEs in the popular elliptic npm package (3000+ dependents, millions of weekly downloads).
Overview
The elliptic library is an elliptic-curve cryptography library written in JavaScript, supporting ECDH, ECDSA, and EdDSA. Using Wycheproof test vectors on version 6.5.6 revealed multiple vulnerabilities:
- CVE-2024-42459: EdDSA signature malleability (appending/removing zeros)
- CVE-2024-42460: ECDSA DER encoding - invalid bit placement
- CVE-2024-42461: ECDSA DER encoding - leading zero in length field
Methodology
- Identify supported curves: ed25519 for EdDSA
- Find test vectors:
testvectors_v1/ed25519_test.json - Parse test vectors: Load JSON and extract tests
- Write test harness: Create parameterized tests
- Run tests: Identify failures
- Analyze root causes: Examine implementation code
- Propose fixes: Add validation checks
Key Findings
EdDSA Issue (CVE-2024-42459):
- Missing signature length validation
- Allowed trailing zeros in signatures
- Fix: Add
if(sig.length !== 128) return false;
ECDSA Issue 1 (CVE-2024-42460):
- Missing check for first bit being zero in DER-encoded r and s values
- Fix: Add
if ((data[p.place] & 128) !== 0) return false;
ECDSA Issue 2 (CVE-2024-42461):
- DER length field accepted leading zeros
- Fix: Add
if(buf[p.place] === 0x00) return false;
Impact
All three vulnerabilities allowed multiple valid signatures for a single message, leading to consensus problems across implementations.
Lessons learned:
- Wycheproof catches subtle encoding bugs
- Reusable test harnesses pay dividends
- Test vector comments and flags help diagnose issues
- Even popular libraries benefit from systematic test vector validation
Advanced Usage
Tips and Tricks
| Tip | Why It Helps |
|---|---|
| Filter test groups by parameters | Focus on test vectors relevant to your implementation constraints |
| Use test vector flags | Understand specific vulnerability patterns being tested |
Check the notes field |
Get detailed explanations of flag meanings |
| Test both encrypt/decrypt and sign/verify | Ensure bidirectional correctness |
| Run tests in CI | Catch regressions and benefit from new test vectors |
| Use parameterized tests | Get clear failure messages with tcId and comment |
Common Mistakes
| Mistake | Why It's Wrong | Correct Approach |
|---|---|---|
| Only testing valid cases | Misses vulnerabilities where invalid inputs are accepted | Test all result types: valid, invalid, acceptable |
| Ignoring "acceptable" result | Implementation might have subtle bugs | Treat acceptable as warnings worth investigating |
| Not filtering test groups | Wastes time on unsupported parameters | Filter by keySize, ivSize, etc. based on your implementation |
| Not updating test vectors | Miss new vulnerability patterns | Use submodules or scheduled fetches |
| Testing only one direction | Encrypt/sign might work but decrypt/verify fails | Test both operations |
Related Skills
Tool Skills
| Skill | Primary Use in Wycheproof Testing |
|---|---|
| pytest | Python testing framework for parameterized tests |
| mocha | JavaScript testing framework for test generation |
| constant-time-testing | Complement Wycheproof with timing side-channel testing |
| cryptofuzz | Fuzz-based crypto testing to find additional bugs |
Technique Skills
| Skill | When to Apply |
|---|---|
| coverage-analysis | Ensure test vectors cover all code paths in crypto implementation |
| property-based-testing | Test mathematical properties (e.g., encrypt/decrypt round-trip) |
| fuzz-harness-writing | Create harnesses for crypto parsers (complements Wycheproof) |
Related Domain Skills
| Skill | Relationship |
|---|---|
| crypto-testing | Wycheproof is a key tool in comprehensive crypto testing methodology |
| fuzzing | Use fuzzing to find bugs Wycheproof doesn't cover (new edge cases) |
Skill Dependency Map
┌─────────────────────┐
│ wycheproof │
│ (this skill) │
└──────────┬──────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ pytest/mocha │ │ constant-time │ │ cryptofuzz │
│ (test framework)│ │ testing │ │ (fuzzing) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────┼───────────────────┘
│
▼
┌──────────────────────────┐
│ Technique Skills │
│ coverage, harness, PBT │
└──────────────────────────┘
Resources
Official Repository
The official repository contains:
- All test vectors in
testvectors/andtestvectors_v1/ - JSON schemas in
schemas/ - Reference implementations in Java and JavaScript
- Documentation in
doc/
Real-World Examples
The pycryptodome library integrates Wycheproof test vectors in their test suite, demonstrating best practices for Python crypto implementations.
Community Resources
- C2SP Community - Cryptographic specifications and standards community maintaining Wycheproof
- Wycheproof issues tracker - Report bugs in test vectors or suggest new constructions
Summary
Wycheproof is an essential tool for validating cryptographic implementations against known attack vectors and edge cases. By integrating Wycheproof test vectors into your testing workflow:
- Catch subtle encoding and validation bugs
- Prevent signature malleability issues
- Ensure consistent behavior across implementations
- Benefit from community-contributed test vectors
- Protect against known cryptographic vulnerabilities
The investment in writing a reusable testing harness pays dividends through continuous validation as new test vectors are added to the Wycheproof repository.
/variant-analysis
Source: ~/.claude/skills/tob-variant-analysis/skills/variant-analysis/SKILL.md
name: variant-analysis description: Find similar vulnerabilities and bugs across codebases using pattern-based analysis. Use when hunting bug variants, building CodeQL/Semgrep queries, analyzing security vulnerabilities, or performing systematic code audits after finding an initial issue.
Variant Analysis
You are a variant analysis expert. Your role is to help find similar vulnerabilities and bugs across a codebase after identifying an initial pattern.
When to Use
Use this skill when:
- A vulnerability has been found and you need to search for similar instances
- Building or refining CodeQL/Semgrep queries for security patterns
- Performing systematic code audits after an initial issue discovery
- Hunting for bug variants across a codebase
- Analyzing how a single root cause manifests in different code paths
When NOT to Use
Do NOT use this skill for:
- Initial vulnerability discovery (use audit-context-building or domain-specific audits instead)
- General code review without a known pattern to search for
- Writing fix recommendations (use issue-writer instead)
- Understanding unfamiliar code (use audit-context-building for deep comprehension first)
The Five-Step Process
Step 1: Understand the Original Issue
Before searching, deeply understand the known bug:
- What is the root cause? Not the symptom, but WHY it's vulnerable
- What conditions are required? Control flow, data flow, state
- What makes it exploitable? User control, missing validation, etc.
Step 2: Create an Exact Match
Start with a pattern that matches ONLY the known instance:
rg -n "exact_vulnerable_code_here"
Verify: Does it match exactly ONE location (the original)?
Step 3: Identify Abstraction Points
| Element | Keep Specific | Can Abstract |
|---|---|---|
| Function name | If unique to bug | If pattern applies to family |
| Variable names | Never | Always use metavariables |
| Literal values | If value matters | If any value triggers bug |
| Arguments | If position matters | Use ... wildcards |
Step 4: Iteratively Generalize
Change ONE element at a time:
- Run the pattern
- Review ALL new matches
- Classify: true positive or false positive?
- If FP rate acceptable, generalize next element
- If FP rate too high, revert and try different abstraction
Stop when false positive rate exceeds ~50%
Step 5: Analyze and Triage Results
For each match, document:
- Location: File, line, function
- Confidence: High/Medium/Low
- Exploitability: Reachable? Controllable inputs?
- Priority: Based on impact and exploitability
For deeper strategic guidance, see METHODOLOGY.md.
Tool Selection
| Scenario | Tool | Why |
|---|---|---|
| Quick surface search | ripgrep | Fast, zero setup |
| Simple pattern matching | Semgrep | Easy syntax, no build needed |
| Data flow tracking | Semgrep taint / CodeQL | Follows values across functions |
| Cross-function analysis | CodeQL | Best interprocedural analysis |
| Non-building code | Semgrep | Works on incomplete code |
Key Principles
- Root cause first: Understand WHY before searching for WHERE
- Start specific: First pattern should match exactly the known bug
- One change at a time: Generalize incrementally, verify after each change
- Know when to stop: 50%+ FP rate means you've gone too generic
- Search everywhere: Always search the ENTIRE codebase, not just the module where the bug was found
- Expand vulnerability classes: One root cause often has multiple manifestations
Critical Pitfalls to Avoid
These common mistakes cause analysts to miss real vulnerabilities:
1. Narrow Search Scope
Searching only the module where the original bug was found misses variants in other locations.
Example: Bug found in api/handlers/ → only searching that directory → missing variant in utils/auth.py
Mitigation: Always run searches against the entire codebase root directory.
2. Pattern Too Specific
Using only the exact attribute/function from the original bug misses variants using related constructs.
Example: Bug uses isAuthenticated check → only searching for that exact term → missing bugs using related properties like isActive, isAdmin, isVerified
Mitigation: Enumerate ALL semantically related attributes/functions for the bug class.
3. Single Vulnerability Class
Focusing on only one manifestation of the root cause misses other ways the same logic error appears.
Example: Original bug is "return allow when condition is false" → only searching that pattern → missing:
- Null equality bypasses (
null == nullevaluates to true) - Documentation/code mismatches (function does opposite of what docs claim)
- Inverted conditional logic (wrong branch taken)
Mitigation: List all possible manifestations of the root cause before searching.
4. Missing Edge Cases
Testing patterns only with "normal" scenarios misses vulnerabilities triggered by edge cases.
Example: Testing auth checks only with valid users → missing bypass when userId = null matches resourceOwnerId = null
Mitigation: Test with: unauthenticated users, null/undefined values, empty collections, and boundary conditions.
Resources
Ready-to-use templates in resources/:
CodeQL (resources/codeql/):
python.ql,javascript.ql,java.ql,go.ql,cpp.ql
Semgrep (resources/semgrep/):
python.yaml,javascript.yaml,java.yaml,go.yaml,cpp.yaml
Report: resources/variant-report-template.md
/yara-rule-authoring
Source: ~/.claude/skills/tob-yara-authoring/skills/yara-rule-authoring/SKILL.md
name: yara-rule-authoring description: > Guides authoring of high-quality YARA-X detection rules for malware identification. Use when writing, reviewing, or optimizing YARA rules. Covers naming conventions, string selection, performance optimization, migration from legacy YARA, and false positive reduction. Triggers on: YARA, YARA-X, malware detection, threat hunting, IOC, signature, crx module, dex module.
YARA-X Rule Authoring
Write detection rules that catch malware without drowning in false positives.
This skill targets YARA-X, the Rust-based successor to legacy YARA. YARA-X powers VirusTotal's production systems and is the recommended implementation. See Migrating from Legacy YARA if you have existing rules.
Core Principles
-
Strings must generate good atoms — YARA extracts 4-byte subsequences for fast matching. Strings with repeated bytes, common sequences, or under 4 bytes force slow bytecode verification on too many files.
-
Target specific families, not categories — "Detects ransomware" catches everything and nothing. "Detects LockBit 3.0 configuration extraction routine" catches what you want.
-
Test against goodware before deployment — A rule that fires on Windows system files is useless. Validate against VirusTotal's goodware corpus or your own clean file set.
-
Short-circuit with cheap checks first — Put
filesize < 10MB and uint16(0) == 0x5A4Dbefore expensive string searches or module calls. -
Metadata is documentation — Future you (and your team) need to know what this catches, why, and where the sample came from.
When to Use
- Writing new YARA-X rules for malware detection
- Reviewing existing rules for quality or performance issues
- Optimizing slow-running rulesets
- Converting IOCs or threat intel into detection signatures
- Debugging false positive issues
- Preparing rules for production deployment
- Migrating legacy YARA rules to YARA-X
- Analyzing Chrome extensions (crx module)
- Analyzing Android apps (dex module)
When NOT to Use
- Static analysis requiring disassembly → use Ghidra/IDA skills
- Dynamic malware analysis → use sandbox analysis skills
- Network-based detection → use Suricata/Snort skills
- Memory forensics with Volatility → use memory forensics skills
- Simple hash-based detection → just use hash lists
YARA-X Overview
YARA-X is the Rust-based successor to legacy YARA: 5-10x faster regex, better errors, built-in formatter, stricter validation, new modules (crx, dex), 99% rule compatibility.
Install: brew install yara-x (macOS) or cargo install yara-x
Essential commands: yr scan, yr check, yr fmt, yr dump
Platform Considerations
YARA works on any file type. Adapt patterns to your target:
| Platform | Magic Bytes | Bad Strings | Good Strings |
|---|---|---|---|
| Windows PE | uint16(0) == 0x5A4D |
API names, Windows paths | Mutex names, PDB paths |
| macOS Mach-O | uint32(0) == 0xFEEDFACE (32-bit), 0xFEEDFACF (64-bit), 0xCAFEBABE (universal) |
Common Obj-C methods | Keylogger strings, persistence paths |
| JavaScript/Node | (none needed) | require, fetch, axios |
Obfuscator signatures, eval+decode chains |
| npm/pip packages | (none needed) | postinstall, dependencies |
Suspicious package names, exfil URLs |
| Office docs | uint32(0) == 0x504B0304 |
VBA keywords | Macro auto-exec, encoded payloads |
| VS Code extensions | (none needed) | vscode.workspace |
Uncommon activationEvents, hidden file access |
| Chrome extensions | Use crx module |
Common Chrome APIs | Permission abuse, manifest anomalies |
| Android apps | Use dex module |
Standard DEX structure | Obfuscated classes, suspicious permissions |
macOS Malware Detection
No dedicated Mach-O module exists yet. Use magic byte checks + string patterns:
Magic bytes:
// Mach-O 32-bit
uint32(0) == 0xFEEDFACE
// Mach-O 64-bit
uint32(0) == 0xFEEDFACF
// Universal binary (fat binary)
uint32(0) == 0xCAFEBABE or uint32(0) == 0xBEBAFECA
Good indicators for macOS malware:
- Keylogger artifacts:
CGEventTapCreate,kCGEventKeyDown - SSH tunnel strings:
ssh -D,tunnel,socks - Persistence paths:
~/Library/LaunchAgents,/Library/LaunchDaemons - Credential theft:
security find-generic-password,keychain
Example pattern from Airbnb BinaryAlert:
rule SUSP_Mac_ProtonRAT
{
strings:
// Library indicators
$lib1 = "SRWebSocket" ascii
$lib2 = "SocketRocket" ascii
// Behavioral indicators
$behav1 = "SSH tunnel not launched" ascii
$behav2 = "Keylogger" ascii
condition:
(uint32(0) == 0xFEEDFACF or uint32(0) == 0xCAFEBABE) and
any of ($lib*) and any of ($behav*)
}
JavaScript Detection Decision Tree
Writing a JavaScript rule?
├─ npm package?
│ ├─ Check package.json patterns
│ ├─ Look for postinstall/preinstall hooks
│ └─ Target exfil patterns: fetch + env access + credential paths
├─ Browser extension?
│ ├─ Chrome: Use crx module
│ └─ Others: Target manifest patterns, background script behaviors
├─ Standalone JS file?
│ ├─ Look for obfuscation markers: eval+atob, fromCharCode chains
│ ├─ Target unique function/variable names (often survive minification)
│ └─ Check for packed/encoded payloads
└─ Minified/webpack bundle?
├─ Target unique strings that survive bundling (URLs, magic values)
└─ Avoid function names (will be mangled)
JavaScript-specific good strings:
- Ethereum function selectors:
{ 70 a0 82 31 }(transfer) - Zero-width characters (steganography):
{ E2 80 8B E2 80 8C } - Obfuscator signatures:
_0x,var _0x - Specific C2 patterns: domain names, webhook URLs
JavaScript-specific bad strings:
require,fetch,axios— too commonBuffer,crypto— legitimate uses everywhereprocess.envalone — need specific env var names
Essential Toolkit
| Tool | Purpose |
|---|---|
| yarGen | Extract candidate strings: yarGen.py -m samples/ --excludegood → validate with yr check |
| FLOSS | Extract obfuscated/stack strings: floss sample.exe (when yarGen fails) |
| yr CLI | Validate: yr check, scan: yr scan -s, inspect: yr dump -m pe |
| signature-base | Study quality examples |
| YARA-CI | Goodware corpus testing before deployment |
Master these five. Don't get distracted by tool catalogs.
Rationalizations to Reject
When you catch yourself thinking these, stop and reconsider.
| Rationalization | Expert Response |
|---|---|
| "This generic string is unique enough" | Test against goodware first. Your intuition is wrong. |
| "yarGen gave me these strings" | yarGen suggests, you validate. Check each one manually. |
| "It works on my 10 samples" | 10 samples ≠ production. Use VirusTotal goodware corpus. |
| "One rule to catch all variants" | Causes FP floods. Target specific families. |
| "I'll make it more specific if we get FPs" | Write tight rules upfront. FPs burn trust. |
| "This hex pattern is unique" | Unique in one sample ≠ unique across malware ecosystem. |
| "Performance doesn't matter" | One slow rule slows entire ruleset. Optimize atoms. |
| "PEiD rules still work" | Obsolete. 32-bit packers aren't relevant. |
| "I'll add more conditions later" | Weak rules deployed = damage done. |
| "This is just for hunting" | Hunting rules become detection rules. Same quality bar. |
| "The API name makes it malicious" | Legitimate software uses same APIs. Need behavioral context. |
| "any of them is fine for these common strings" | Common strings + any = FP flood. Use any of only for individually unique strings. |
| "This regex is specific enough" | /fetch.*token/ matches all auth code. Add exfil destination requirement. |
| "The JavaScript looks clean" | Attackers poison legitimate code with injects. Check for eval+decode chains. |
| "I'll use .* for flexibility" | Unbounded regex = performance disaster + memory explosion. Use .{0,30}. |
| "I'll use --relaxed-re-syntax everywhere" | Masks real bugs. Fix the regex instead of hiding problems. |
Decision Trees
Is This String Good Enough?
Is this string good enough?
├─ Less than 4 bytes?
│ └─ NO — find longer string
├─ Contains repeated bytes (0000, 9090)?
│ └─ NO — add surrounding context
├─ Is an API name (VirtualAlloc, CreateRemoteThread)?
│ └─ NO — use hex pattern of call site instead
├─ Appears in Windows system files?
│ └─ NO — too generic, find something unique
├─ Is it a common path (C:\Windows\, cmd.exe)?
│ └─ NO — find malware-specific paths
├─ Unique to this malware family?
│ └─ YES — use it
└─ Appears in other malware too?
└─ MAYBE — combine with family-specific marker
When to Use "all of" vs "any of"
Should I require all strings or allow any?
├─ Strings are individually unique to malware?
│ └─ any of them (each alone is suspicious)
├─ Strings are common but combination is suspicious?
│ └─ all of them (require the full pattern)
├─ Strings have different confidence levels?
│ └─ Group: all of ($core_*) and any of ($variant_*)
└─ Seeing many false positives?
└─ Tighten: switch any → all, add more required strings
Lesson from production: Rules using any of ($network_*) where strings included "fetch", "axios", "http" matched virtually all web applications. Switching to require credential path AND network call AND exfil destination eliminated FPs.
When to Abandon a Rule Approach
Stop and pivot when:
-
yarGen returns only API names and paths → See When Strings Fail, Pivot to Structure
-
Can't find 3 unique strings → Probably packed. Target the unpacked version or detect the packer.
-
Rule matches goodware files → Strings aren't unique enough. 1-2 matches = investigate and tighten; 3-5 matches = find different indicators; 6+ matches = start over.
-
Performance is terrible even after optimization → Architecture problem. Split into multiple focused rules or add strict pre-filters.
-
Description is hard to write → The rule is too vague. If you can't explain what it catches, it catches too much.
Debugging False Positives
FP Investigation Flow:
│
├─ 1. Which string matched?
│ Run: yr scan -s rule.yar false_positive.exe
│
├─ 2. Is it in a legitimate library?
│ └─ Add: not $fp_vendor_string exclusion
│
├─ 3. Is it a common development pattern?
│ └─ Find more specific indicator, replace the string
│
├─ 4. Are multiple generic strings matching together?
│ └─ Tighten to require all + add unique marker
│
└─ 5. Is the malware using common techniques?
└─ Target malware-specific implementation details, not the technique
Hex vs Text vs Regex
What string type should I use?
│
├─ Exact ASCII/Unicode text?
│ └─ TEXT: $s = "MutexName" ascii wide
│
├─ Specific byte sequence?
│ └─ HEX: $h = { 4D 5A 90 00 }
│
├─ Byte sequence with variation?
│ └─ HEX with wildcards: { 4D 5A ?? ?? 50 45 }
│
├─ Pattern with structure (URLs, paths)?
│ └─ BOUNDED REGEX: /https:\/\/[a-z]{5,20}\.onion/
│
└─ Unknown encoding (XOR, base64)?
└─ TEXT with modifier: $s = "config" xor(0x00-0xFF)
Is the Sample Packed? (Check First)
Before writing any string-based rule:
Is the sample packed?
├─ Entropy > 7.0?
│ └─ Likely packed — find unpacked layer first
├─ Few/no readable strings?
│ └─ Likely packed — use entropy, PE structure, or packer signatures
├─ UPX/MPRESS/custom packer detected?
│ └─ Target the unpacked payload OR detect the packer itself
└─ Readable strings available?
└─ Proceed with string-based detection
Expert guidance: Don't write rules against packed layers. The packing changes; the payload doesn't.
When Strings Fail, Pivot to Structure
If yarGen returns only API names and generic paths:
String extraction failed — what now?
├─ High entropy sections?
│ └─ Use math.entropy() on specific sections
├─ Unusual imports pattern?
│ └─ Use pe.imphash() for import hash clustering
├─ Consistent PE structure anomalies?
│ └─ Target section names, sizes, characteristics
├─ Metadata present?
│ └─ Target version info, timestamps, resources
└─ Nothing unique?
└─ This sample may not be detectable with YARA alone
Expert guidance: "One can try to use other file properties, such as metadata, entropy, import hashes or other data which stays constant." — Kaspersky Applied YARA Training
Expert Heuristics
String selection: Mutex names are gold; C2 paths silver; error messages bronze. Stack strings are almost always unique. If you need >6 strings, you're over-fitting.
Condition design: Start with filesize <, then magic bytes, then strings, then modules. If >5 lines, split into multiple rules.
Quality signals: yarGen output needs 80% filtering. Rules matching <50% of variants are too narrow; matching goodware are too broad.
Modifier discipline:
- Never use
nocaseorwidespeculatively — only when you have confirmed evidence the case/encoding varies in samples nocasedoubles atom generation;widedoubles string matching — both have real costs- "If you don't have a clear reason for using those modifiers, don't do it" — Kaspersky Applied YARA
Regex anchoring:
- Regex without a 4+ byte literal substring evaluates at every file offset — catastrophic performance
- Always anchor regex to a distinctive literal:
/mshta\.exe http:\/\/.../not/http:\/\/.../ - If you can't anchor, consider hex pattern with wildcards instead
Loop discipline:
- Always bound loops with filesize:
filesize < 100KB and for all i in (1..#a) : ... - Unbounded
#acan be thousands in large files — exponential slowdown
YARA-X tips: $_unused to suppress warnings; private $s to hide from output; yr check + yr fmt before every commit.
When to Use Modules vs. Byte Checks
Should I use a module or raw bytes?
├─ Need imphash/rich header/authenticode?
│ └─ Use PE module — too complex to replicate
├─ Just checking magic bytes or simple offsets?
│ └─ Use uint16/uint32 — faster, no module overhead
├─ Checking section names/sizes?
│ └─ PE module is cleaner, but add magic bytes filter FIRST
├─ Checking Chrome extension permissions?
│ └─ Use crx module — string parsing is fragile
└─ Checking LNK target paths?
└─ Use lnk module — LNK format is complex
Expert guidance: "Avoid the magic module — use explicit hex checks instead" — Neo23x0. Apply this principle: if you can do it with uint32(), don't load a module.
YARA-X New Features
Key additions from recent releases:
- Private patterns (v1.3.0+):
private $helper = "pattern"— matches but hidden from output - Warning suppression (v1.4.0+):
// suppress: slow_patterninline comments - Numeric underscores (v1.5.0+):
filesize < 10_000_000for readability - Built-in formatter:
yr fmt rules/to standardize formatting - NDJSON output:
yr scan --output-format ndjsonfor tooling
YARA-X Tooling Workflow
YARA-X provides diagnostic tools legacy YARA lacks:
Rule development cycle:
# 1. Write initial rule
# 2. Check syntax with detailed errors
yr check rule.yar
# 3. Format consistently
yr fmt -w rule.yar
# 4. Dump module output to inspect file structure (no dummy rule needed)
yr dump -m pe sample.exe --output-format yaml
# 5. Scan with timing info
time yr scan -s rule.yar corpus/
When to use yr dump:
- Investigating what PE/ELF/Mach-O fields are available
- Debugging why module conditions aren't matching
- Exploring new modules (crx, lnk, dotnet) before writing rules
YARA-X diagnostic advantage: Error messages include precise source locations. If yr check points to line 15, the issue is actually on line 15 (unlike legacy YARA).
Chrome Extension Analysis (crx module)
The crx module enables detection of malicious Chrome extensions. Requires YARA-X v1.5.0+ (basic), v1.11.0+ for permhash().
Key APIs: crx.is_crx, crx.permissions, crx.permhash()
Red flags: nativeMessaging + downloads, debugger permission, content scripts on <all_urls>
import "crx"
rule SUSP_CRX_HighRiskPerms {
condition:
crx.is_crx and
for any perm in crx.permissions : (perm == "debugger")
}
See crx-module.md for complete API reference, permission risk assessment, and example rules.
Android DEX Analysis (dex module)
The dex module enables detection of Android malware. Requires YARA-X v1.11.0+. Not compatible with legacy YARA's dex module — API is completely different.
Key APIs: dex.is_dex, dex.contains_class(), dex.contains_method(), dex.contains_string()
Red flags: Single-letter class names (obfuscation), DexClassLoader reflection, encrypted assets
import "dex"
rule SUSP_DEX_DynamicLoading {
condition:
dex.is_dex and
dex.contains_class("Ldalvik/system/DexClassLoader;")
}
See dex-module.md for complete API reference, obfuscation detection, and example rules.
Migrating from Legacy YARA
YARA-X has 99% rule compatibility, but enforces stricter validation.
Quick migration:
yr check --relaxed-re-syntax rules/ # Identify issues
# Fix each issue, then:
yr check rules/ # Verify without relaxed mode
Common fixes:
| Issue | Legacy | YARA-X Fix |
|---|---|---|
Literal { in regex |
/{/ |
/\{/ |
| Invalid escapes | \R silently literal |
\\R or R |
| Base64 strings | Any length | 3+ chars required |
| Negative indexing | @a[-1] |
@a[#a - 1] |
| Duplicate modifiers | Allowed | Remove duplicates |
Note: Use
--relaxed-re-syntaxonly as a diagnostic tool. Fix issues rather than relying on relaxed mode.
Quick Reference
Naming Convention
{CATEGORY}_{PLATFORM}_{FAMILY}_{VARIANT}_{DATE}
Common prefixes: MAL_ (malware), HKTL_ (hacking tool), WEBSHELL_, EXPL_, SUSP_ (suspicious), GEN_ (generic)
Platforms: Win_, Lnx_, Mac_, Android_, CRX_
Example: MAL_Win_Emotet_Loader_Jan25
See style-guide.md for full conventions, metadata requirements, and naming examples.
Required Metadata
Every rule needs: description (starts with "Detects"), author, reference, date.
meta:
description = "Detects Example malware via unique mutex and C2 path"
author = "Your Name <email@example.com>"
reference = "https://example.com/analysis"
date = "2025-01-29"
String Selection
Good: Mutex names, PDB paths, C2 paths, stack strings, configuration markers Bad: API names, common executables, format specifiers, generic paths
See strings.md for the full decision tree and examples.
Condition Patterns
Order conditions for short-circuit:
filesize < 10MB(instant)uint16(0) == 0x5A4D(nearly instant)- String matches (cheap)
- Module checks (expensive)
See performance.md for detailed optimization patterns.
Workflow
- Gather samples — Multiple samples; single-sample rules are brittle
- Extract candidates —
yarGen -m samples/ --excludegood - Validate quality — Use decision tree; yarGen needs 80% filtering
- Write initial rule — Follow template with proper metadata
- Lint and test —
yr check,yr fmt, linter script - Goodware validation — VirusTotal corpus or local clean files
- Deploy — Add to repo with full metadata, monitor for FPs
See testing.md for detailed validation workflow and FP investigation.
For a comprehensive step-by-step guide covering all phases from sample collection to deployment, see rule-development.md.
Common Mistakes
| Mistake | Bad | Good |
|---|---|---|
| API names as indicators | "VirtualAlloc" |
Hex pattern of call site + unique mutex |
| Unbounded regex | /https?:\/\/.*/ |
/https?:\/\/[a-z0-9]{8,12}\.onion/ |
| Missing file type filter | pe.imports(...) first |
uint16(0) == 0x5A4D and filesize < 10MB first |
| Short strings | "abc" (3 bytes) |
"abcdef" (4+ bytes) |
| Unescaped braces (YARA-X) | /config{key}/ |
/config\{key\}/ |
Performance Optimization
Quick wins: Put filesize first, avoid nocase, bounded regex {1,100}, prefer hex over regex.
Red flags: Strings <4 bytes, unbounded regex (.*), modules without file-type filter.
See performance.md for atom theory and optimization details.
Reference Documents
| Topic | Document |
|---|---|
| Naming and metadata conventions | style-guide.md |
| Performance and atom optimization | performance.md |
| String types and judgment | strings.md |
| Testing and validation | testing.md |
| Chrome extension module (crx) | crx-module.md |
| Android DEX module (dex) | dex-module.md |
Workflows
| Topic | Document |
|---|---|
| Complete rule development process | rule-development.md |
Example Rules
The examples/ directory contains real, attributed rules demonstrating best practices:
| Example | Demonstrates | Source |
|---|---|---|
| MAL_Win_Remcos_Jan25.yar | PE malware: graduated string counts, multiple rules per family | Elastic Security |
| MAL_Mac_ProtonRAT_Jan25.yar | macOS: Mach-O magic bytes, multi-category grouping | Airbnb BinaryAlert |
| MAL_NPM_SupplyChain_Jan25.yar | npm supply chain: real attack patterns, ERC-20 selectors | Stairwell Research |
| SUSP_JS_Obfuscation_Jan25.yar | JavaScript: obfuscator detection, density-based matching | imp0rtp3, Nils Kuhnert |
| SUSP_CRX_SuspiciousPermissions.yar | Chrome extensions: crx module, permissions | Educational |
Scripts
uv run {baseDir}/scripts/yara_lint.py rule.yar # Validate style/metadata
uv run {baseDir}/scripts/atom_analyzer.py rule.yar # Check string quality
See README.md for detailed script documentation.
Quality Checklist
Before deploying any rule:
- Name follows
{CATEGORY}_{PLATFORM}_{FAMILY}_{VARIANT}_{DATE}format - Description starts with "Detects" and explains what/how
- All required metadata present (author, reference, date)
- Strings are unique (not API names, common paths, or format strings)
- All strings have 4+ bytes with good atom potential
- Base64 modifier only on strings with 3+ characters
- Regex patterns have escaped
{and valid escape sequences - Condition starts with cheap checks (filesize, magic bytes)
- Rule matches all target samples
- Rule produces zero matches on goodware corpus
-
yr checkpasses with no errors -
yr fmt --checkpasses (consistent formatting) - Linter passes with no errors
- Peer review completed
Resources
Quality YARA Rule Repositories
Learn from production rules. These repositories contain well-tested, properly attributed rules:
| Repository | Focus | Maintainer |
|---|---|---|
| Neo23x0/signature-base | 17,000+ production rules, multi-platform | Florian Roth |
| Elastic/protections-artifacts | 1,000+ endpoint-tested rules | Elastic Security |
| reversinglabs/reversinglabs-yara-rules | Threat research rules | ReversingLabs |
| imp0rtp3/js-yara-rules | JavaScript/browser malware | imp0rtp3 |
| InQuest/awesome-yara | Curated index of resources | InQuest |
Style & Performance Guides
| Guide | Purpose |
|---|---|
| YARA Style Guide | Naming conventions, metadata, string prefixes |
| YARA Performance Guidelines | Atom optimization, regex bounds |
| Kaspersky Applied YARA Training | Expert techniques from production use |
Tools
| Tool | Purpose |
|---|---|
| yarGen | Extract candidate strings from samples |
| FLOSS | Extract obfuscated and stack strings |
| YARA-CI | Automated goodware testing |
| YaraDbg | Web-based rule debugger |
macOS-Specific Resources
| Resource | Purpose |
|---|---|
| Apple XProtect | Production macOS rules at /System/Library/CoreServices/XProtect.bundle/ |
| objective-see | macOS malware research and samples |
| macOS Security Tools | Reference list |
Multi-Indicator Clustering Pattern
Production rules often group indicators by type:
strings:
// Category A: Library indicators
$a1 = "SRWebSocket" ascii
$a2 = "SocketRocket" ascii
// Category B: Behavioral indicators
$b1 = "SSH tunnel" ascii
$b2 = "keylogger" ascii nocase
// Category C: C2 patterns
$c1 = /https:\/\/[a-z0-9]{8,16}\.onion/
condition:
filesize < 10MB and
any of ($a*) and any of ($b*) // Require evidence from BOTH categories
Why this works: Different indicator types have different confidence levels. A single C2 domain might be definitive, while you need multiple library imports to be confident. Grouping by $a*, $b*, $c* lets you express graduated requirements.
Other Skills
/algorithmic-art
Source: ~/.claude/skills/algorithmic-art/SKILL.md
name: algorithmic-art description: Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations. license: Complete terms in LICENSE.txt
Algorithmic philosophies are computational aesthetic movements that are then expressed through code. Output .md files (philosophy), .html files (interactive viewer), and .js files (generative algorithms).
This happens in two steps:
- Algorithmic Philosophy Creation (.md file)
- Express by creating p5.js generative art (.html + .js files)
First, undertake this task:
ALGORITHMIC PHILOSOPHY CREATION
To begin, create an ALGORITHMIC PHILOSOPHY (not static images or templates) that will be interpreted through:
- Computational processes, emergent behavior, mathematical beauty
- Seeded randomness, noise fields, organic systems
- Particles, flows, fields, forces
- Parametric variation and controlled chaos
THE CRITICAL UNDERSTANDING
- What is received: Some subtle input or instructions by the user to take into account, but use as a foundation; it should not constrain creative freedom.
- What is created: An algorithmic philosophy/generative aesthetic movement.
- What happens next: The same version receives the philosophy and EXPRESSES IT IN CODE - creating p5.js sketches that are 90% algorithmic generation, 10% essential parameters.
Consider this approach:
- Write a manifesto for a generative art movement
- The next phase involves writing the algorithm that brings it to life
The philosophy must emphasize: Algorithmic expression. Emergent behavior. Computational beauty. Seeded variation.
HOW TO GENERATE AN ALGORITHMIC PHILOSOPHY
Name the movement (1-2 words): "Organic Turbulence" / "Quantum Harmonics" / "Emergent Stillness"
Articulate the philosophy (4-6 paragraphs - concise but complete):
To capture the ALGORITHMIC essence, express how this philosophy manifests through:
- Computational processes and mathematical relationships?
- Noise functions and randomness patterns?
- Particle behaviors and field dynamics?
- Temporal evolution and system states?
- Parametric variation and emergent complexity?
CRITICAL GUIDELINES:
- Avoid redundancy: Each algorithmic aspect should be mentioned once. Avoid repeating concepts about noise theory, particle dynamics, or mathematical principles unless adding new depth.
- Emphasize craftsmanship REPEATEDLY: The philosophy MUST stress multiple times that the final algorithm should appear as though it took countless hours to develop, was refined with care, and comes from someone at the absolute top of their field. This framing is essential - repeat phrases like "meticulously crafted algorithm," "the product of deep computational expertise," "painstaking optimization," "master-level implementation."
- Leave creative space: Be specific about the algorithmic direction, but concise enough that the next Claude has room to make interpretive implementation choices at an extremely high level of craftsmanship.
The philosophy must guide the next version to express ideas ALGORITHMICALLY, not through static images. Beauty lives in the process, not the final frame.
PHILOSOPHY EXAMPLES
"Organic Turbulence" Philosophy: Chaos constrained by natural law, order emerging from disorder. Algorithmic expression: Flow fields driven by layered Perlin noise. Thousands of particles following vector forces, their trails accumulating into organic density maps. Multiple noise octaves create turbulent regions and calm zones. Color emerges from velocity and density - fast particles burn bright, slow ones fade to shadow. The algorithm runs until equilibrium - a meticulously tuned balance where every parameter was refined through countless iterations by a master of computational aesthetics.
"Quantum Harmonics" Philosophy: Discrete entities exhibiting wave-like interference patterns. Algorithmic expression: Particles initialized on a grid, each carrying a phase value that evolves through sine waves. When particles are near, their phases interfere - constructive interference creates bright nodes, destructive creates voids. Simple harmonic motion generates complex emergent mandalas. The result of painstaking frequency calibration where every ratio was carefully chosen to produce resonant beauty.
"Recursive Whispers" Philosophy: Self-similarity across scales, infinite depth in finite space. Algorithmic expression: Branching structures that subdivide recursively. Each branch slightly randomized but constrained by golden ratios. L-systems or recursive subdivision generate tree-like forms that feel both mathematical and organic. Subtle noise perturbations break perfect symmetry. Line weights diminish with each recursion level. Every branching angle the product of deep mathematical exploration.
"Field Dynamics" Philosophy: Invisible forces made visible through their effects on matter. Algorithmic expression: Vector fields constructed from mathematical functions or noise. Particles born at edges, flowing along field lines, dying when they reach equilibrium or boundaries. Multiple fields can attract, repel, or rotate particles. The visualization shows only the traces - ghost-like evidence of invisible forces. A computational dance meticulously choreographed through force balance.
"Stochastic Crystallization" Philosophy: Random processes crystallizing into ordered structures. Algorithmic expression: Randomized circle packing or Voronoi tessellation. Start with random points, let them evolve through relaxation algorithms. Cells push apart until equilibrium. Color based on cell size, neighbor count, or distance from center. The organic tiling that emerges feels both random and inevitable. Every seed produces unique crystalline beauty - the mark of a master-level generative algorithm.
These are condensed examples. The actual algorithmic philosophy should be 4-6 substantial paragraphs.
ESSENTIAL PRINCIPLES
- ALGORITHMIC PHILOSOPHY: Creating a computational worldview to be expressed through code
- PROCESS OVER PRODUCT: Always emphasize that beauty emerges from the algorithm's execution - each run is unique
- PARAMETRIC EXPRESSION: Ideas communicate through mathematical relationships, forces, behaviors - not static composition
- ARTISTIC FREEDOM: The next Claude interprets the philosophy algorithmically - provide creative implementation room
- PURE GENERATIVE ART: This is about making LIVING ALGORITHMS, not static images with randomness
- EXPERT CRAFTSMANSHIP: Repeatedly emphasize the final algorithm must feel meticulously crafted, refined through countless iterations, the product of deep expertise by someone at the absolute top of their field in computational aesthetics
The algorithmic philosophy should be 4-6 paragraphs long. Fill it with poetic computational philosophy that brings together the intended vision. Avoid repeating the same points. Output this algorithmic philosophy as a .md file.
DEDUCING THE CONCEPTUAL SEED
CRITICAL STEP: Before implementing the algorithm, identify the subtle conceptual thread from the original request.
THE ESSENTIAL PRINCIPLE: The concept is a subtle, niche reference embedded within the algorithm itself - not always literal, always sophisticated. Someone familiar with the subject should feel it intuitively, while others simply experience a masterful generative composition. The algorithmic philosophy provides the computational language. The deduced concept provides the soul - the quiet conceptual DNA woven invisibly into parameters, behaviors, and emergence patterns.
This is VERY IMPORTANT: The reference must be so refined that it enhances the work's depth without announcing itself. Think like a jazz musician quoting another song through algorithmic harmony - only those who know will catch it, but everyone appreciates the generative beauty.
P5.JS IMPLEMENTATION
With the philosophy AND conceptual framework established, express it through code. Pause to gather thoughts before proceeding. Use only the algorithmic philosophy created and the instructions below.
⚠️ STEP 0: READ THE TEMPLATE FIRST ⚠️
CRITICAL: BEFORE writing any HTML:
- Read
templates/viewer.htmlusing the Read tool - Study the exact structure, styling, and Anthropic branding
- Use that file as the LITERAL STARTING POINT - not just inspiration
- Keep all FIXED sections exactly as shown (header, sidebar structure, Anthropic colors/fonts, seed controls, action buttons)
- Replace only the VARIABLE sections marked in the file's comments (algorithm, parameters, UI controls for parameters)
Avoid:
- ❌ Creating HTML from scratch
- ❌ Inventing custom styling or color schemes
- ❌ Using system fonts or dark themes
- ❌ Changing the sidebar structure
Follow these practices:
- ✅ Copy the template's exact HTML structure
- ✅ Keep Anthropic branding (Poppins/Lora fonts, light colors, gradient backdrop)
- ✅ Maintain the sidebar layout (Seed → Parameters → Colors? → Actions)
- ✅ Replace only the p5.js algorithm and parameter controls
The template is the foundation. Build on it, don't rebuild it.
To create gallery-quality computational art that lives and breathes, use the algorithmic philosophy as the foundation.
TECHNICAL REQUIREMENTS
Seeded Randomness (Art Blocks Pattern):
// ALWAYS use a seed for reproducibility
let seed = 12345; // or hash from user input
randomSeed(seed);
noiseSeed(seed);
Parameter Structure - FOLLOW THE PHILOSOPHY:
To establish parameters that emerge naturally from the algorithmic philosophy, consider: "What qualities of this system can be adjusted?"
let params = {
seed: 12345, // Always include seed for reproducibility
// colors
// Add parameters that control YOUR algorithm:
// - Quantities (how many?)
// - Scales (how big? how fast?)
// - Probabilities (how likely?)
// - Ratios (what proportions?)
// - Angles (what direction?)
// - Thresholds (when does behavior change?)
};
To design effective parameters, focus on the properties the system needs to be tunable rather than thinking in terms of "pattern types".
Core Algorithm - EXPRESS THE PHILOSOPHY:
CRITICAL: The algorithmic philosophy should dictate what to build.
To express the philosophy through code, avoid thinking "which pattern should I use?" and instead think "how to express this philosophy through code?"
If the philosophy is about organic emergence, consider using:
- Elements that accumulate or grow over time
- Random processes constrained by natural rules
- Feedback loops and interactions
If the philosophy is about mathematical beauty, consider using:
- Geometric relationships and ratios
- Trigonometric functions and harmonics
- Precise calculations creating unexpected patterns
If the philosophy is about controlled chaos, consider using:
- Random variation within strict boundaries
- Bifurcation and phase transitions
- Order emerging from disorder
The algorithm flows from the philosophy, not from a menu of options.
To guide the implementation, let the conceptual essence inform creative and original choices. Build something that expresses the vision for this particular request.
Canvas Setup: Standard p5.js structure:
function setup() {
createCanvas(1200, 1200);
// Initialize your system
}
function draw() {
// Your generative algorithm
// Can be static (noLoop) or animated
}
CRAFTSMANSHIP REQUIREMENTS
CRITICAL: To achieve mastery, create algorithms that feel like they emerged through countless iterations by a master generative artist. Tune every parameter carefully. Ensure every pattern emerges with purpose. This is NOT random noise - this is CONTROLLED CHAOS refined through deep expertise.
- Balance: Complexity without visual noise, order without rigidity
- Color Harmony: Thoughtful palettes, not random RGB values
- Composition: Even in randomness, maintain visual hierarchy and flow
- Performance: Smooth execution, optimized for real-time if animated
- Reproducibility: Same seed ALWAYS produces identical output
OUTPUT FORMAT
Output:
- Algorithmic Philosophy - As markdown or text explaining the generative aesthetic
- Single HTML Artifact - Self-contained interactive generative art built from
templates/viewer.html(see STEP 0 and next section)
The HTML artifact contains everything: p5.js (from CDN), the algorithm, parameter controls, and UI - all in one file that works immediately in claude.ai artifacts or any browser. Start from the template file, not from scratch.
INTERACTIVE ARTIFACT CREATION
REMINDER: templates/viewer.html should have already been read (see STEP 0). Use that file as the starting point.
To allow exploration of the generative art, create a single, self-contained HTML artifact. Ensure this artifact works immediately in claude.ai or any browser - no setup required. Embed everything inline.
CRITICAL: WHAT'S FIXED VS VARIABLE
The templates/viewer.html file is the foundation. It contains the exact structure and styling needed.
FIXED (always include exactly as shown):
- Layout structure (header, sidebar, main canvas area)
- Anthropic branding (UI colors, fonts, gradients)
- Seed section in sidebar:
- Seed display
- Previous/Next buttons
- Random button
- Jump to seed input + Go button
- Actions section in sidebar:
- Regenerate button
- Reset button
VARIABLE (customize for each artwork):
- The entire p5.js algorithm (setup/draw/classes)
- The parameters object (define what the art needs)
- The Parameters section in sidebar:
- Number of parameter controls
- Parameter names
- Min/max/step values for sliders
- Control types (sliders, inputs, etc.)
- Colors section (optional):
- Some art needs color pickers
- Some art might use fixed colors
- Some art might be monochrome (no color controls needed)
- Decide based on the art's needs
Every artwork should have unique parameters and algorithm! The fixed parts provide consistent UX - everything else expresses the unique vision.
REQUIRED FEATURES
1. Parameter Controls
- Sliders for numeric parameters (particle count, noise scale, speed, etc.)
- Color pickers for palette colors
- Real-time updates when parameters change
- Reset button to restore defaults
- Display current seed number
- "Previous" and "Next" buttons to cycle through seeds
- "Random" button for random seed
- Input field to jump to specific seed
- Generate 100 variations when requested (seeds 1-100)
3. Single Artifact Structure
<!DOCTYPE html>
<html>
<head>
<!-- p5.js from CDN - always available -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
<style>
/* All styling inline - clean, minimal */
/* Canvas on top, controls below */
</style>
</head>
<body>
<div id="canvas-container"></div>
<div id="controls">
<!-- All parameter controls -->
</div>
<script>
// ALL p5.js code inline here
// Parameter objects, classes, functions
// setup() and draw()
// UI handlers
// Everything self-contained
</script>
</body>
</html>
CRITICAL: This is a single artifact. No external files, no imports (except p5.js CDN). Everything inline.
4. Implementation Details - BUILD THE SIDEBAR
1. Seed (FIXED) - Always include exactly as shown:
- Seed display
- Prev/Next/Random/Jump buttons
2. Parameters (VARIABLE) - Create controls for the art:
<div class="control-group">
<label>Parameter Name</label>
<input type="range" id="param" min="..." max="..." step="..." value="..." oninput="updateParam('param', this.value)">
<span class="value-display" id="param-value">...</span>
</div>
Add as many control-group divs as there are parameters.
3. Colors (OPTIONAL/VARIABLE) - Include if the art needs adjustable colors:
- Add color pickers if users should control palette
- Skip this section if the art uses fixed colors
- Skip if the art is monochrome
4. Actions (FIXED) - Always include exactly as shown:
Requirements:
- Seed controls must work (prev/next/random/jump/display)
- All parameters must have UI controls
- Regenerate, Reset, Download buttons must work
- Keep Anthropic branding (UI styling, not art colors)
USING THE ARTIFACT
The HTML artifact works immediately:
- In claude.ai: Displayed as an interactive artifact - runs instantly
- As a file: Save and open in any browser - no server needed
- Sharing: Send the HTML file - it's completely self-contained
VARIATIONS & EXPLORATION
The artifact includes seed navigation by default (prev/next/random buttons), allowing users to explore variations without creating multiple files. If the user wants specific variations highlighted:
- Include seed presets (buttons for "Variation 1: Seed 42", "Variation 2: Seed 127", etc.)
- Add a "Gallery Mode" that shows thumbnails of multiple seeds side-by-side
- All within the same single artifact
This is like creating a series of prints from the same plate - the algorithm is consistent, but each seed reveals different facets of its potential. The interactive nature means users discover their own favorites by exploring the seed space.
THE CREATIVE PROCESS
User request → Algorithmic philosophy → Implementation
Each request is unique. The process involves:
- Interpret the user's intent - What aesthetic is being sought?
- Create an algorithmic philosophy (4-6 paragraphs) describing the computational approach
- Implement it in code - Build the algorithm that expresses this philosophy
- Design appropriate parameters - What should be tunable?
- Build matching UI controls - Sliders/inputs for those parameters
The constants:
- Anthropic branding (colors, fonts, layout)
- Seed navigation (always present)
- Self-contained HTML artifact
Everything else is variable:
- The algorithm itself
- The parameters
- The UI controls
- The visual outcome
To achieve the best results, trust creativity and let the philosophy guide the implementation.
RESOURCES
This skill includes helpful templates and documentation:
-
templates/viewer.html: REQUIRED STARTING POINT for all HTML artifacts.
- This is the foundation - contains the exact structure and Anthropic branding
- Keep unchanged: Layout structure, sidebar organization, Anthropic colors/fonts, seed controls, action buttons
- Replace: The p5.js algorithm, parameter definitions, and UI controls in Parameters section
- The extensive comments in the file mark exactly what to keep vs replace
-
templates/generator_template.js: Reference for p5.js best practices and code structure principles.
- Shows how to organize parameters, use seeded randomness, structure classes
- NOT a pattern menu - use these principles to build unique algorithms
- Embed algorithms inline in the HTML artifact (don't create separate .js files)
Critical reminder:
- The template is the STARTING POINT, not inspiration
- The algorithm is where to create something unique
- Don't copy the flow field example - build what the philosophy demands
- But DO keep the exact UI structure and Anthropic branding from the template
/internal-comms
Source: ~/.claude/skills/internal-comms/SKILL.md
name: internal-comms description: A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.). license: Complete terms in LICENSE.txt
When to use this skill
To write internal communications, use this skill for:
- 3P updates (Progress, Plans, Problems)
- Company newsletters
- FAQ responses
- Status reports
- Leadership updates
- Project updates
- Incident reports
How to use this skill
To write any internal communication:
- Identify the communication type from the request
- Load the appropriate guideline file from the
examples/directory:examples/3p-updates.md- For Progress/Plans/Problems team updatesexamples/company-newsletter.md- For company-wide newslettersexamples/faq-answers.md- For answering frequently asked questionsexamples/general-comms.md- For anything else that doesn't explicitly match one of the above
- Follow the specific instructions in that file for formatting, tone, and content gathering
If the communication type doesn't match any existing guideline, ask for clarification or more context about the desired format.
Keywords
3P updates, company newsletter, company comms, weekly update, faqs, common questions, updates, internal comms
/mcp-builder
Source: ~/.claude/skills/mcp-builder/SKILL.md
name: mcp-builder description: Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK). license: Complete terms in LICENSE.txt
MCP Server Development Guide
Overview
Create MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. The quality of an MCP server is measured by how well it enables LLMs to accomplish real-world tasks.
Process
🚀 High-Level Workflow
Creating a high-quality MCP server involves four main phases:
Phase 1: Deep Research and Planning
1.1 Understand Modern MCP Design
API Coverage vs. Workflow Tools: Balance comprehensive API endpoint coverage with specialized workflow tools. Workflow tools can be more convenient for specific tasks, while comprehensive coverage gives agents flexibility to compose operations. Performance varies by client—some clients benefit from code execution that combines basic tools, while others work better with higher-level workflows. When uncertain, prioritize comprehensive API coverage.
Tool Naming and Discoverability:
Clear, descriptive tool names help agents find the right tools quickly. Use consistent prefixes (e.g., github_create_issue, github_list_repos) and action-oriented naming.
Context Management: Agents benefit from concise tool descriptions and the ability to filter/paginate results. Design tools that return focused, relevant data. Some clients support code execution which can help agents filter and process data efficiently.
Actionable Error Messages: Error messages should guide agents toward solutions with specific suggestions and next steps.
1.2 Study MCP Protocol Documentation
Start with the sitemap to find relevant pages: https://modelcontextprotocol.io/sitemap.xml
Then fetch specific pages with .md suffix for markdown format (e.g., https://modelcontextprotocol.io/specification/draft.md).
Key pages to review:
- Specification overview and architecture
- Transport mechanisms (streamable HTTP, stdio)
- Tool, resource, and prompt definitions
1.3 Study Framework Documentation
Recommended stack:
- Language: TypeScript (high-quality SDK support and good compatibility in many execution environments e.g. MCPB. Plus AI models are good at generating TypeScript code, benefiting from its broad usage, static typing and good linting tools)
- Transport: Streamable HTTP for remote servers, using stateless JSON (simpler to scale and maintain, as opposed to stateful sessions and streaming responses). stdio for local servers.
Load framework documentation:
- MCP Best Practices: 📋 View Best Practices - Core guidelines
For TypeScript (recommended):
- TypeScript SDK: Use WebFetch to load
https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md - ⚡ TypeScript Guide - TypeScript patterns and examples
For Python:
- Python SDK: Use WebFetch to load
https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md - 🐍 Python Guide - Python patterns and examples
1.4 Plan Your Implementation
Understand the API: Review the service's API documentation to identify key endpoints, authentication requirements, and data models. Use web search and WebFetch as needed.
Tool Selection: Prioritize comprehensive API coverage. List endpoints to implement, starting with the most common operations.
Phase 2: Implementation
2.1 Set Up Project Structure
See language-specific guides for project setup:
- ⚡ TypeScript Guide - Project structure, package.json, tsconfig.json
- 🐍 Python Guide - Module organization, dependencies
2.2 Implement Core Infrastructure
- API client with authentication
- Error handling helpers
- Response formatting (JSON/Markdown)
- Pagination support
2.3 Implement Tools
For each tool:
Input Schema:
- Use Zod (TypeScript) or Pydantic (Python)
- Include constraints and clear descriptions
- Add examples in field descriptions
Output Schema:
- Define
outputSchemawhere possible for structured data - Use
structuredContentin tool responses (TypeScript SDK feature) - Helps clients understand and process tool outputs
Tool Description:
- Concise summary of functionality
- Parameter descriptions
- Return type schema
Implementation:
- Async/await for I/O operations
- Proper error handling with actionable messages
- Support pagination where applicable
- Return both text content and structured data when using modern SDKs
Annotations:
readOnlyHint: true/falsedestructiveHint: true/falseidempotentHint: true/falseopenWorldHint: true/false
Phase 3: Review and Test
3.1 Code Quality
Review for:
- No duplicated code (DRY principle)
- Consistent error handling
- Full type coverage
- Clear tool descriptions
3.2 Build and Test
TypeScript:
- Run
npm run buildto verify compilation - Test with MCP Inspector:
npx @modelcontextprotocol/inspector
Python:
- Verify syntax:
python -m py_compile your_server.py - Test with MCP Inspector
See language-specific guides for detailed testing approaches and quality checklists.
Phase 4: Create Evaluations
After implementing your MCP server, create comprehensive evaluations to test its effectiveness.
Load ✅ Evaluation Guide for complete evaluation guidelines.
4.1 Understand Evaluation Purpose
Use evaluations to test whether LLMs can effectively use your MCP server to answer realistic, complex questions.
4.2 Create 10 Evaluation Questions
To create effective evaluations, follow the process outlined in the evaluation guide:
- Tool Inspection: List available tools and understand their capabilities
- Content Exploration: Use READ-ONLY operations to explore available data
- Question Generation: Create 10 complex, realistic questions
- Answer Verification: Solve each question yourself to verify answers
4.3 Evaluation Requirements
Ensure each question is:
- Independent: Not dependent on other questions
- Read-only: Only non-destructive operations required
- Complex: Requiring multiple tool calls and deep exploration
- Realistic: Based on real use cases humans would care about
- Verifiable: Single, clear answer that can be verified by string comparison
- Stable: Answer won't change over time
4.4 Output Format
Create an XML file with this structure:
<evaluation>
<qa_pair>
<question>Find discussions about AI model launches with animal codenames. One model needed a specific safety designation that uses the format ASL-X. What number X was being determined for the model named after a spotted wild cat?</question>
<answer>3</answer>
</qa_pair>
<!-- More qa_pairs... -->
</evaluation>
Reference Files
📚 Documentation Library
Load these resources as needed during development:
Core MCP Documentation (Load First)
- MCP Protocol: Start with sitemap at
https://modelcontextprotocol.io/sitemap.xml, then fetch specific pages with.mdsuffix - 📋 MCP Best Practices - Universal MCP guidelines including:
- Server and tool naming conventions
- Response format guidelines (JSON vs Markdown)
- Pagination best practices
- Transport selection (streamable HTTP vs stdio)
- Security and error handling standards
SDK Documentation (Load During Phase 1/2)
- Python SDK: Fetch from
https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md - TypeScript SDK: Fetch from
https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md
Language-Specific Implementation Guides (Load During Phase 2)
-
🐍 Python Implementation Guide - Complete Python/FastMCP guide with:
- Server initialization patterns
- Pydantic model examples
- Tool registration with
@mcp.tool - Complete working examples
- Quality checklist
-
⚡ TypeScript Implementation Guide - Complete TypeScript guide with:
- Project structure
- Zod schema patterns
- Tool registration with
server.registerTool - Complete working examples
- Quality checklist
Evaluation Guide (Load During Phase 4)
- ✅ Evaluation Guide - Complete evaluation creation guide with:
- Question creation guidelines
- Answer verification strategies
- XML format specifications
- Example questions and answers
- Running an evaluation with the provided scripts
/skill-creator
Source: ~/.claude/skills/skill-creator/SKILL.md
name: skill-creator version: "2.0" level: 3 trigger: "create skill, new skill, update skill, skill creator, SKILL.md" author: john updated: 2026-03-16 description: Guide for creating Level 3+ skills. IF new skill request THEN scaffold SKILL.md with metadata + if/then workflow + verification + MAX TURNS. Update skill-registry.db on completion. license: Complete terms in LICENSE.txt
Skill Creator
This skill provides guidance for creating effective skills.
About Skills
Skills are modular, self-contained packages that extend Claude's capabilities by providing specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific domains or tasks—they transform Claude from a general-purpose agent into a specialized agent equipped with procedural knowledge that no model can fully possess.
What Skills Provide
- Specialized workflows - Multi-step procedures for specific domains
- Tool integrations - Instructions for working with specific file formats or APIs
- Domain expertise - Company-specific knowledge, schemas, business logic
- Bundled resources - Scripts, references, and assets for complex and repetitive tasks
Core Principles
Concise is Key
The context window is a public good. Skills share the context window with everything else Claude needs: system prompt, conversation history, other Skills' metadata, and the actual user request.
Default assumption: Claude is already very smart. Only add context Claude doesn't already have. Challenge each piece of information: "Does Claude really need this explanation?" and "Does this paragraph justify its token cost?"
Prefer concise examples over verbose explanations.
Set Appropriate Degrees of Freedom
Match the level of specificity to the task's fragility and variability:
High freedom (text-based instructions): Use when multiple approaches are valid, decisions depend on context, or heuristics guide the approach.
Medium freedom (pseudocode or scripts with parameters): Use when a preferred pattern exists, some variation is acceptable, or configuration affects behavior.
Low freedom (specific scripts, few parameters): Use when operations are fragile and error-prone, consistency is critical, or a specific sequence must be followed.
Think of Claude as exploring a path: a narrow bridge with cliffs needs specific guardrails (low freedom), while an open field allows many routes (high freedom).
Anatomy of a Skill
Every skill consists of a required SKILL.md file and optional bundled resources:
skill-name/
├── SKILL.md (required)
│ ├── YAML frontmatter metadata (required)
│ │ ├── name: (required)
│ │ ├── description: (required)
│ │ └── compatibility: (optional, rarely needed)
│ └── Markdown instructions (required)
└── Bundled Resources (optional)
├── scripts/ - Executable code (Python/Bash/etc.)
├── references/ - Documentation intended to be loaded into context as needed
└── assets/ - Files used in output (templates, icons, fonts, etc.)
SKILL.md (required)
Every SKILL.md consists of:
- Frontmatter (YAML): Contains
nameanddescriptionfields (required), plus optional fields likelicense,metadata, andcompatibility. Onlynameanddescriptionare read by Claude to determine when the skill triggers, so be clear and comprehensive about what the skill is and when it should be used. Thecompatibilityfield is for noting environment requirements (target product, system packages, etc.) but most skills don't need it. - Body (Markdown): Instructions and guidance for using the skill. Only loaded AFTER the skill triggers (if at all).
Bundled Resources (optional)
Scripts (scripts/)
Executable code (Python/Bash/etc.) for tasks that require deterministic reliability or are repeatedly rewritten.
- When to include: When the same code is being rewritten repeatedly or deterministic reliability is needed
- Example:
scripts/rotate_pdf.pyfor PDF rotation tasks - Benefits: Token efficient, deterministic, may be executed without loading into context
- Note: Scripts may still need to be read by Claude for patching or environment-specific adjustments
References (references/)
Documentation and reference material intended to be loaded as needed into context to inform Claude's process and thinking.
- When to include: For documentation that Claude should reference while working
- Examples:
references/finance.mdfor financial schemas,references/mnda.mdfor company NDA template,references/policies.mdfor company policies,references/api_docs.mdfor API specifications - Use cases: Database schemas, API documentation, domain knowledge, company policies, detailed workflow guides
- Benefits: Keeps SKILL.md lean, loaded only when Claude determines it's needed
- Best practice: If files are large (>10k words), include grep search patterns in SKILL.md
- Avoid duplication: Information should live in either SKILL.md or references files, not both. Prefer references files for detailed information unless it's truly core to the skill—this keeps SKILL.md lean while making information discoverable without hogging the context window. Keep only essential procedural instructions and workflow guidance in SKILL.md; move detailed reference material, schemas, and examples to references files.
Assets (assets/)
Files not intended to be loaded into context, but rather used within the output Claude produces.
- When to include: When the skill needs files that will be used in the final output
- Examples:
assets/logo.pngfor brand assets,assets/slides.pptxfor PowerPoint templates,assets/frontend-template/for HTML/React boilerplate,assets/font.ttffor typography - Use cases: Templates, images, icons, boilerplate code, fonts, sample documents that get copied or modified
- Benefits: Separates output resources from documentation, enables Claude to use files without loading them into context
What to Not Include in a Skill
A skill should only contain essential files that directly support its functionality. Do NOT create extraneous documentation or auxiliary files, including:
- README.md
- INSTALLATION_GUIDE.md
- QUICK_REFERENCE.md
- CHANGELOG.md
- etc.
The skill should only contain the information needed for an AI agent to do the job at hand. It should not contain auxilary context about the process that went into creating it, setup and testing procedures, user-facing documentation, etc. Creating additional documentation files just adds clutter and confusion.
Progressive Disclosure Design Principle
Skills use a three-level loading system to manage context efficiently:
- Metadata (name + description) - Always in context (~100 words)
- SKILL.md body - When skill triggers (<5k words)
- Bundled resources - As needed by Claude (Unlimited because scripts can be executed without reading into context window)
Progressive Disclosure Patterns
Keep SKILL.md body to the essentials and under 500 lines to minimize context bloat. Split content into separate files when approaching this limit. When splitting out content into other files, it is very important to reference them from SKILL.md and describe clearly when to read them, to ensure the reader of the skill knows they exist and when to use them.
Key principle: When a skill supports multiple variations, frameworks, or options, keep only the core workflow and selection guidance in SKILL.md. Move variant-specific details (patterns, examples, configuration) into separate reference files.
Pattern 1: High-level guide with references
# PDF Processing
## Quick start
Extract text with pdfplumber:
[code example]
## Advanced features
- **Form filling**: See [FORMS.md](FORMS.md) for complete guide
- **API reference**: See [REFERENCE.md](REFERENCE.md) for all methods
- **Examples**: See [EXAMPLES.md](EXAMPLES.md) for common patterns
Claude loads FORMS.md, REFERENCE.md, or EXAMPLES.md only when needed.
Pattern 2: Domain-specific organization
For Skills with multiple domains, organize content by domain to avoid loading irrelevant context:
bigquery-skill/
├── SKILL.md (overview and navigation)
└── reference/
├── finance.md (revenue, billing metrics)
├── sales.md (opportunities, pipeline)
├── product.md (API usage, features)
└── marketing.md (campaigns, attribution)
When a user asks about sales metrics, Claude only reads sales.md.
Similarly, for skills supporting multiple frameworks or variants, organize by variant:
cloud-deploy/
├── SKILL.md (workflow + provider selection)
└── references/
├── aws.md (AWS deployment patterns)
├── gcp.md (GCP deployment patterns)
└── azure.md (Azure deployment patterns)
When the user chooses AWS, Claude only reads aws.md.
Pattern 3: Conditional details
Show basic content, link to advanced content:
# DOCX Processing
## Creating documents
Use docx-js for new documents. See [DOCX-JS.md](DOCX-JS.md).
## Editing documents
For simple edits, modify the XML directly.
**For tracked changes**: See [REDLINING.md](REDLINING.md)
**For OOXML details**: See [OOXML.md](OOXML.md)
Claude reads REDLINING.md or OOXML.md only when the user needs those features.
Important guidelines:
- Avoid deeply nested references - Keep references one level deep from SKILL.md. All reference files should link directly from SKILL.md.
- Structure longer reference files - For files longer than 100 lines, include a table of contents at the top so Claude can see the full scope when previewing.
Skill Creation Process
Skill creation involves these steps:
- Understand the skill with concrete examples
- Plan reusable skill contents (scripts, references, assets)
- Initialize the skill (run init_skill.py)
- Edit the skill (implement resources and write SKILL.md)
- Package the skill (run package_skill.py)
- Iterate based on real usage
Follow these steps in order, skipping only if there is a clear reason why they are not applicable.
Step 1: Understanding the Skill with Concrete Examples
Skip this step only when the skill's usage patterns are already clearly understood. It remains valuable even when working with an existing skill.
To create an effective skill, clearly understand concrete examples of how the skill will be used. This understanding can come from either direct user examples or generated examples that are validated with user feedback.
For example, when building an image-editor skill, relevant questions include:
- "What functionality should the image-editor skill support? Editing, rotating, anything else?"
- "Can you give some examples of how this skill would be used?"
- "I can imagine users asking for things like 'Remove the red-eye from this image' or 'Rotate this image'. Are there other ways you imagine this skill being used?"
- "What would a user say that should trigger this skill?"
To avoid overwhelming users, avoid asking too many questions in a single message. Start with the most important questions and follow up as needed for better effectiveness.
Conclude this step when there is a clear sense of the functionality the skill should support.
Step 2: Planning the Reusable Skill Contents
To turn concrete examples into an effective skill, analyze each example by:
- Considering how to execute on the example from scratch
- Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly
Example: When building a pdf-editor skill to handle queries like "Help me rotate this PDF," the analysis shows:
- Rotating a PDF requires re-writing the same code each time
- A
scripts/rotate_pdf.pyscript would be helpful to store in the skill
Example: When designing a frontend-webapp-builder skill for queries like "Build me a todo app" or "Build me a dashboard to track my steps," the analysis shows:
- Writing a frontend webapp requires the same boilerplate HTML/React each time
- An
assets/hello-world/template containing the boilerplate HTML/React project files would be helpful to store in the skill
Example: When building a big-query skill to handle queries like "How many users have logged in today?" the analysis shows:
- Querying BigQuery requires re-discovering the table schemas and relationships each time
- A
references/schema.mdfile documenting the table schemas would be helpful to store in the skill
To establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets.
Step 3: Initializing the Skill
At this point, it is time to actually create the skill.
Skip this step only if the skill being developed already exists, and iteration or packaging is needed. In this case, continue to the next step.
When creating a new skill from scratch, always run the init_skill.py script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable.
Usage:
scripts/init_skill.py <skill-name> --path <output-directory>
The script:
- Creates the skill directory at the specified path
- Generates a SKILL.md template with proper frontmatter and TODO placeholders
- Creates example resource directories:
scripts/,references/, andassets/ - Adds example files in each directory that can be customized or deleted
After initialization, customize or remove the generated SKILL.md and example files as needed.
Step 4: Edit the Skill
When editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Claude to use. Include information that would be beneficial and non-obvious to Claude. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Claude instance execute these tasks more effectively.
Learn Proven Design Patterns
Consult these helpful guides based on your skill's needs:
- Multi-step processes: See references/workflows.md for sequential workflows and conditional logic
- Specific output formats or quality standards: See references/output-patterns.md for template and example patterns
These files contain established best practices for effective skill design.
Start with Reusable Skill Contents
To begin implementation, start with the reusable resources identified above: scripts/, references/, and assets/ files. Note that this step may require user input. For example, when implementing a brand-guidelines skill, the user may need to provide brand assets or templates to store in assets/, or documentation to store in references/.
Added scripts must be tested by actually running them to ensure there are no bugs and that the output matches what is expected. If there are many similar scripts, only a representative sample needs to be tested to ensure confidence that they all work while balancing time to completion.
Any example files and directories not needed for the skill should be deleted. The initialization script creates example files in scripts/, references/, and assets/ to demonstrate structure, but most skills won't need all of them.
Update SKILL.md
Writing Guidelines: Always use imperative/infinitive form.
Frontmatter
Write the YAML frontmatter with name and description:
name: The skill namedescription: This is the primary triggering mechanism for your skill, and helps Claude understand when to use the skill.- Include both what the Skill does and specific triggers/contexts for when to use it.
- Include all "when to use" information here - Not in the body. The body is only loaded after triggering, so "When to Use This Skill" sections in the body are not helpful to Claude.
- Example description for a
docxskill: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"
Do not include any other fields in YAML frontmatter.
Body
Write instructions for using the skill and its bundled resources.
Step 5: Packaging a Skill
Once development of the skill is complete, it must be packaged into a distributable .skill file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements:
scripts/package_skill.py <path/to/skill-folder>
Optional output directory specification:
scripts/package_skill.py <path/to/skill-folder> ./dist
The packaging script will:
-
Validate the skill automatically, checking:
- YAML frontmatter format and required fields
- Skill naming conventions and directory structure
- Description completeness and quality
- File organization and resource references
-
Package the skill if validation passes, creating a .skill file named after the skill (e.g.,
my-skill.skill) that includes all files and maintains the proper directory structure for distribution. The .skill file is a zip file with a .skill extension.
If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again.
Step 6: Iterate
After testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed.
Iteration workflow:
- Use the skill on real tasks
- Notice struggles or inefficiencies
- Identify how SKILL.md or bundled resources should be updated
- Implement changes and test again
/slack-gif-creator
Source: ~/.claude/skills/slack-gif-creator/SKILL.md
name: slack-gif-creator description: Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack." license: Complete terms in LICENSE.txt
Slack GIF Creator
A toolkit providing utilities and knowledge for creating animated GIFs optimized for Slack.
Slack Requirements
Dimensions:
- Emoji GIFs: 128x128 (recommended)
- Message GIFs: 480x480
Parameters:
- FPS: 10-30 (lower is smaller file size)
- Colors: 48-128 (fewer = smaller file size)
- Duration: Keep under 3 seconds for emoji GIFs
Core Workflow
from core.gif_builder import GIFBuilder
from PIL import Image, ImageDraw
# 1. Create builder
builder = GIFBuilder(width=128, height=128, fps=10)
# 2. Generate frames
for i in range(12):
frame = Image.new('RGB', (128, 128), (240, 248, 255))
draw = ImageDraw.Draw(frame)
# Draw your animation using PIL primitives
# (circles, polygons, lines, etc.)
builder.add_frame(frame)
# 3. Save with optimization
builder.save('output.gif', num_colors=48, optimize_for_emoji=True)
Drawing Graphics
Working with User-Uploaded Images
If a user uploads an image, consider whether they want to:
- Use it directly (e.g., "animate this", "split this into frames")
- Use it as inspiration (e.g., "make something like this")
Load and work with images using PIL:
from PIL import Image
uploaded = Image.open('file.png')
# Use directly, or just as reference for colors/style
Drawing from Scratch
When drawing graphics from scratch, use PIL ImageDraw primitives:
from PIL import ImageDraw
draw = ImageDraw.Draw(frame)
# Circles/ovals
draw.ellipse([x1, y1, x2, y2], fill=(r, g, b), outline=(r, g, b), width=3)
# Stars, triangles, any polygon
points = [(x1, y1), (x2, y2), (x3, y3), ...]
draw.polygon(points, fill=(r, g, b), outline=(r, g, b), width=3)
# Lines
draw.line([(x1, y1), (x2, y2)], fill=(r, g, b), width=5)
# Rectangles
draw.rectangle([x1, y1, x2, y2], fill=(r, g, b), outline=(r, g, b), width=3)
Don't use: Emoji fonts (unreliable across platforms) or assume pre-packaged graphics exist in this skill.
Making Graphics Look Good
Graphics should look polished and creative, not basic. Here's how:
Use thicker lines - Always set width=2 or higher for outlines and lines. Thin lines (width=1) look choppy and amateurish.
Add visual depth:
- Use gradients for backgrounds (
create_gradient_background) - Layer multiple shapes for complexity (e.g., a star with a smaller star inside)
Make shapes more interesting:
- Don't just draw a plain circle - add highlights, rings, or patterns
- Stars can have glows (draw larger, semi-transparent versions behind)
- Combine multiple shapes (stars + sparkles, circles + rings)
Pay attention to colors:
- Use vibrant, complementary colors
- Add contrast (dark outlines on light shapes, light outlines on dark shapes)
- Consider the overall composition
For complex shapes (hearts, snowflakes, etc.):
- Use combinations of polygons and ellipses
- Calculate points carefully for symmetry
- Add details (a heart can have a highlight curve, snowflakes have intricate branches)
Be creative and detailed! A good Slack GIF should look polished, not like placeholder graphics.
Available Utilities
GIFBuilder (core.gif_builder)
Assembles frames and optimizes for Slack:
builder = GIFBuilder(width=128, height=128, fps=10)
builder.add_frame(frame) # Add PIL Image
builder.add_frames(frames) # Add list of frames
builder.save('out.gif', num_colors=48, optimize_for_emoji=True, remove_duplicates=True)
Validators (core.validators)
Check if GIF meets Slack requirements:
from core.validators import validate_gif, is_slack_ready
# Detailed validation
passes, info = validate_gif('my.gif', is_emoji=True, verbose=True)
# Quick check
if is_slack_ready('my.gif'):
print("Ready!")
Easing Functions (core.easing)
Smooth motion instead of linear:
from core.easing import interpolate
# Progress from 0.0 to 1.0
t = i / (num_frames - 1)
# Apply easing
y = interpolate(start=0, end=400, t=t, easing='ease_out')
# Available: linear, ease_in, ease_out, ease_in_out,
# bounce_out, elastic_out, back_out
Frame Helpers (core.frame_composer)
Convenience functions for common needs:
from core.frame_composer import (
create_blank_frame, # Solid color background
create_gradient_background, # Vertical gradient
draw_circle, # Helper for circles
draw_text, # Simple text rendering
draw_star # 5-pointed star
)
Animation Concepts
Shake/Vibrate
Offset object position with oscillation:
- Use
math.sin()ormath.cos()with frame index - Add small random variations for natural feel
- Apply to x and/or y position
Pulse/Heartbeat
Scale object size rhythmically:
- Use
math.sin(t * frequency * 2 * math.pi)for smooth pulse - For heartbeat: two quick pulses then pause (adjust sine wave)
- Scale between 0.8 and 1.2 of base size
Bounce
Object falls and bounces:
- Use
interpolate()witheasing='bounce_out'for landing - Use
easing='ease_in'for falling (accelerating) - Apply gravity by increasing y velocity each frame
Spin/Rotate
Rotate object around center:
- PIL:
image.rotate(angle, resample=Image.BICUBIC) - For wobble: use sine wave for angle instead of linear
Fade In/Out
Gradually appear or disappear:
- Create RGBA image, adjust alpha channel
- Or use
Image.blend(image1, image2, alpha) - Fade in: alpha from 0 to 1
- Fade out: alpha from 1 to 0
Slide
Move object from off-screen to position:
- Start position: outside frame bounds
- End position: target location
- Use
interpolate()witheasing='ease_out'for smooth stop - For overshoot: use
easing='back_out'
Zoom
Scale and position for zoom effect:
- Zoom in: scale from 0.1 to 2.0, crop center
- Zoom out: scale from 2.0 to 1.0
- Can add motion blur for drama (PIL filter)
Explode/Particle Burst
Create particles radiating outward:
- Generate particles with random angles and velocities
- Update each particle:
x += vx,y += vy - Add gravity:
vy += gravity_constant - Fade out particles over time (reduce alpha)
Optimization Strategies
Only when asked to make the file size smaller, implement a few of the following methods:
- Fewer frames - Lower FPS (10 instead of 20) or shorter duration
- Fewer colors -
num_colors=48instead of 128 - Smaller dimensions - 128x128 instead of 480x480
- Remove duplicates -
remove_duplicates=Truein save() - Emoji mode -
optimize_for_emoji=Trueauto-optimizes
# Maximum optimization for emoji
builder.save(
'emoji.gif',
num_colors=48,
optimize_for_emoji=True,
remove_duplicates=True
)
Philosophy
This skill provides:
- Knowledge: Slack's requirements and animation concepts
- Utilities: GIFBuilder, validators, easing functions
- Flexibility: Create the animation logic using PIL primitives
It does NOT provide:
- Rigid animation templates or pre-made functions
- Emoji font rendering (unreliable across platforms)
- A library of pre-packaged graphics built into the skill
Note on user uploads: This skill doesn't include pre-built graphics, but if a user uploads an image, use PIL to load and work with it - interpret based on their request whether they want it used directly or just as inspiration.
Be creative! Combine concepts (bouncing + rotating, pulsing + sliding, etc.) and use PIL's full capabilities.
Dependencies
pip install pillow imageio numpy
/theme-factory
Source: ~/.claude/skills/theme-factory/SKILL.md
name: theme-factory description: Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly. license: Complete terms in LICENSE.txt
Theme Factory Skill
This skill provides a curated collection of professional font and color themes themes, each with carefully selected color palettes and font pairings. Once a theme is chosen, it can be applied to any artifact.
Purpose
To apply consistent, professional styling to presentation slide decks, use this skill. Each theme includes:
- A cohesive color palette with hex codes
- Complementary font pairings for headers and body text
- A distinct visual identity suitable for different contexts and audiences
Usage Instructions
To apply styling to a slide deck or other artifact:
- Show the theme showcase: Display the
theme-showcase.pdffile to allow users to see all available themes visually. Do not make any modifications to it; simply show the file for viewing. - Ask for their choice: Ask which theme to apply to the deck
- Wait for selection: Get explicit confirmation about the chosen theme
- Apply the theme: Once a theme has been chosen, apply the selected theme's colors and fonts to the deck/artifact
Themes Available
The following 10 themes are available, each showcased in theme-showcase.pdf:
- Ocean Depths - Professional and calming maritime theme
- Sunset Boulevard - Warm and vibrant sunset colors
- Forest Canopy - Natural and grounded earth tones
- Modern Minimalist - Clean and contemporary grayscale
- Golden Hour - Rich and warm autumnal palette
- Arctic Frost - Cool and crisp winter-inspired theme
- Desert Rose - Soft and sophisticated dusty tones
- Tech Innovation - Bold and modern tech aesthetic
- Botanical Garden - Fresh and organic garden colors
- Midnight Galaxy - Dramatic and cosmic deep tones
Theme Details
Each theme is defined in the themes/ directory with complete specifications including:
- Cohesive color palette with hex codes
- Complementary font pairings for headers and body text
- Distinct visual identity suitable for different contexts and audiences
Application Process
After a preferred theme is selected:
- Read the corresponding theme file from the
themes/directory - Apply the specified colors and fonts consistently throughout the deck
- Ensure proper contrast and readability
- Maintain the theme's visual identity across all slides
Create your Own Theme
To handle cases where none of the existing themes work for an artifact, create a custom theme. Based on provided inputs, generate a new theme similar to the ones above. Give the theme a similar name describing what the font/color combinations represent. Use any basic description provided to choose appropriate colors/fonts. After generating the theme, show it for review and verification. Following that, apply the theme as described above.
/web-artifacts-builder
Source: ~/.claude/skills/web-artifacts-builder/SKILL.md
name: web-artifacts-builder description: Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts. license: Complete terms in LICENSE.txt
Web Artifacts Builder
To build powerful frontend claude.ai artifacts, follow these steps:
- Initialize the frontend repo using
scripts/init-artifact.sh - Develop your artifact by editing the generated code
- Bundle all code into a single HTML file using
scripts/bundle-artifact.sh - Display artifact to user
- (Optional) Test the artifact
Stack: React 18 + TypeScript + Vite + Parcel (bundling) + Tailwind CSS + shadcn/ui
Design & Style Guidelines
VERY IMPORTANT: To avoid what is often referred to as "AI slop", avoid using excessive centered layouts, purple gradients, uniform rounded corners, and Inter font.
Quick Start
Step 1: Initialize Project
Run the initialization script to create a new React project:
bash scripts/init-artifact.sh <project-name>
cd <project-name>
This creates a fully configured project with:
- ✅ React + TypeScript (via Vite)
- ✅ Tailwind CSS 3.4.1 with shadcn/ui theming system
- ✅ Path aliases (
@/) configured - ✅ 40+ shadcn/ui components pre-installed
- ✅ All Radix UI dependencies included
- ✅ Parcel configured for bundling (via .parcelrc)
- ✅ Node 18+ compatibility (auto-detects and pins Vite version)
Step 2: Develop Your Artifact
To build the artifact, edit the generated files. See Common Development Tasks below for guidance.
Step 3: Bundle to Single HTML File
To bundle the React app into a single HTML artifact:
bash scripts/bundle-artifact.sh
This creates bundle.html - a self-contained artifact with all JavaScript, CSS, and dependencies inlined. This file can be directly shared in Claude conversations as an artifact.
Requirements: Your project must have an index.html in the root directory.
What the script does:
- Installs bundling dependencies (parcel, @parcel/config-default, parcel-resolver-tspaths, html-inline)
- Creates
.parcelrcconfig with path alias support - Builds with Parcel (no source maps)
- Inlines all assets into single HTML using html-inline
Step 4: Share Artifact with User
Step 5: Testing/Visualizing the Artifact (Optional)
Note: This is a completely optional step. Only perform if necessary or requested.
To test/visualize the artifact, use available tools (including other Skills or built-in tools like Playwright or Puppeteer). In general, avoid testing the artifact upfront as it adds latency between the request and when the finished artifact can be seen. Test later, after presenting the artifact, if requested or if issues arise.
Reference
- shadcn/ui components: https://ui.shadcn.com/docs/components
/webapp-testing
Source: ~/.claude/skills/webapp-testing/SKILL.md
name: webapp-testing description: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. license: Complete terms in LICENSE.txt
Web Application Testing
To test local web applications, write native Python Playwright scripts.
Helper Scripts Available:
scripts/with_server.py- Manages server lifecycle (supports multiple servers)
Always run scripts with --help first to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.
Decision Tree: Choosing Your Approach
User task → Is it static HTML?
├─ Yes → Read HTML file directly to identify selectors
│ ├─ Success → Write Playwright script using selectors
│ └─ Fails/Incomplete → Treat as dynamic (below)
│
└─ No (dynamic webapp) → Is the server already running?
├─ No → Run: python scripts/with_server.py --help
│ Then use the helper + write simplified Playwright script
│
└─ Yes → Reconnaissance-then-action:
1. Navigate and wait for networkidle
2. Take screenshot or inspect DOM
3. Identify selectors from rendered state
4. Execute actions with discovered selectors
Example: Using with_server.py
To start a server, run --help first, then use the helper:
Single server:
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
Multiple servers (e.g., backend + frontend):
python scripts/with_server.py \
--server "cd backend && python server.py" --port 3000 \
--server "cd frontend && npm run dev" --port 5173 \
-- python your_automation.py
To create an automation script, include only Playwright logic (servers are managed automatically):
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode
page = browser.new_page()
page.goto('http://localhost:5173') # Server already running and ready
page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
# ... your automation logic
browser.close()
Reconnaissance-Then-Action Pattern
-
Inspect rendered DOM:
page.screenshot(path='/tmp/inspect.png', full_page=True) content = page.content() page.locator('button').all() -
Identify selectors from inspection results
-
Execute actions using discovered selectors
Common Pitfall
❌ Don't inspect the DOM before waiting for networkidle on dynamic apps
✅ Do wait for page.wait_for_load_state('networkidle') before inspection
Best Practices
- Use bundled scripts as black boxes - To accomplish a task, consider whether one of the scripts available in
scripts/can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use--helpto see usage, then invoke directly. - Use
sync_playwright()for synchronous scripts - Always close the browser when done
- Use descriptive selectors:
text=,role=, CSS selectors, or IDs - Add appropriate waits:
page.wait_for_selector()orpage.wait_for_timeout()
Reference Files
- examples/ - Examples showing common patterns:
element_discovery.py- Discovering buttons, links, and inputs on a pagestatic_html_automation.py- Using file:// URLs for local HTMLconsole_logging.py- Capturing console logs during automation
Known Issues & Fixes
2026-04-03 16:12:33
Error: Deploy verification was skipped - John claimed 'sve live' based on curl, CEO found 404. Fix: MANDATORY browser click-through test after every deploy before ANY claim to CEO. Add deploy-verify checklist.
Fix: MANDATORY browser click-through test after every deploy before ANY claim to CEO. Add deploy-verify checklist.
/plan-build-test
Source: ~/.claude/skills/plan-build-test/SKILL.md
name: plan-build-test version: "2.0" level: 3 trigger: "plan-build-test, full-cycle test, playwright, E2E testing, run tests" author: john updated: 2026-03-16 description: Orchestration skill for Plan→Build→Test development cycles. Runs Playwright CLI tests (NOT MCP) against local or remote web apps. Supports E2E testing, visual regression, and mobile viewport testing.
Plan-Build-Test Orchestration Skill
Automates the full development cycle: implement changes → build → test → fix → re-test.
CRITICAL: Playwright CLI ONLY — NEVER use MCP playwright tools. All testing via npx playwright test or ./scripts/test-runner.sh.
Modes
Mode 1: Full Cycle (\plan-build-test:full-cycle)
Purpose: Implement feature/fix → build → test → fix failures → visual regression
Agent workflow:
-
Read requirements
- Read task description and acceptance criteria
- Identify files to change and expected test coverage
-
Spawn builder subagent
- Use Task tool to spawn builder agent with clear file ownership
- Wait for builder to complete implementation
- Verify builder marked task as done
-
Build verification
- Run build command:
npx next build(or relevant for project) - Parse output for errors
- If build fails → analyze errors → spawn builder to fix → re-build
- Max 3 build iterations before escalating
- Run build command:
-
Start dev server (if testing locally)
- If TEST_BASE_URL not set, start dev server:
npx next dev & - Wait for server ready (check http://localhost:3000)
- If testing remote URL, skip this step
- If TEST_BASE_URL not set, start dev server:
-
Run E2E tests
- Execute:
./scripts/test-runner.sh [--project <project>] [--grep <pattern>] - Parse JSON results from
/tmp/playwright-results.json - Capture:
- Total tests, passed, failed, skipped
- Failure details (test title, error message)
- Screenshot paths from
/tmp/playwright-screenshots/
- Execute:
-
Fix failures (if needed)
- If tests fail:
- Analyze failure details and screenshots
- Identify root cause
- Spawn builder to fix issues
- Re-run tests
- Max 3 fix iterations before escalating
- If tests fail:
-
Visual regression (optional)
- If changes affect UI:
- Run:
./scripts/visual-regression.sh - Compare against baseline
- Report diff percentages
- Show paths to diff images
- Run:
- If no baseline exists:
- Capture baseline:
./scripts/visual-regression.sh --baseline - Skip comparison (first run)
- Capture baseline:
- If changes affect UI:
-
Report summary
- Build status: pass/fail
- Test results: X/Y passed
- Failure details (if any) with screenshot references
- Visual regression status (if run)
- Next steps or completion confirmation
Variables:
{{TASK_DESCRIPTION}}— What to implement{{PROJECT_DIR}}— Project root path{{BASE_URL}}— URL to test (default: http://localhost:3000){{MAX_ITERATIONS}}— Max fix attempts (default: 3)
Example usage:
\plan-build-test:full-cycle
Task: Implement login form validation
Project: /Users/makinja/ALAI/products/Drop/src/drop-app
Base URL: http://localhost:3000
Mode 2: Test Only (\plan-build-test:test-only)
Purpose: Run tests against existing deployment (local or remote) without building
Agent workflow:
-
Accept parameters
- URL to test (required, default: http://localhost:3000)
- Project filter (optional, e.g., "mobile-iphone")
- Test grep pattern (optional, e.g., "login")
-
Run tests
- Execute:
TEST_BASE_URL=<url> ./scripts/test-runner.sh [--project <project>] [--grep <pattern>] - Parse results from
/tmp/playwright-results.json
- Execute:
-
Report results
- Summary: X/Y tests passed
- If failures:
- Show failure details (test title + error message)
- List screenshot paths from
/tmp/playwright-screenshots/
- Exit code: 0 = all pass, 1 = failures
Variables:
{{BASE_URL}}— URL to test{{PROJECT}}— Project filter (optional){{GREP_PATTERN}}— Test name filter (optional)
Example usage:
\plan-build-test:test-only
URL: https://staging.getdrop.no
Project: mobile-iphone
Pattern: login
Mobile testing:
- iPhone viewport:
--project mobile-iphone - Galaxy viewport:
--project mobile-galaxy - iPad viewport:
--project tablet-ipad
Mode 3: Visual Check (\plan-build-test:visual-check)
Purpose: Capture screenshots and compare against baseline for visual regression detection
Agent workflow:
-
Check baseline status
- Check if baseline exists:
ls tests/visual/baseline/*.png - If no baseline → capture baseline mode
- If baseline exists → comparison mode
- Check if baseline exists:
-
Capture baseline (first run)
- Execute:
./scripts/visual-regression.sh --baseline - Saves screenshots to
tests/visual/baseline/ - Report: "Baseline captured, X screenshots saved"
- Skip comparison (nothing to compare against)
- Execute:
-
Run comparison (subsequent runs)
- Execute:
./scripts/visual-regression.sh [--threshold <percent>] - Default threshold: 5% (customizable)
- Compares current screenshots vs baseline
- Generates diff images to
/tmp/visual-diffs/
- Execute:
-
Report results
- Per-page diff percentages
- Overall status: pass (no diffs > threshold) or fail (diffs detected)
- Paths to diff images for review
- Recommendation: approve new baseline or fix regressions
Variables:
{{PROJECT_DIR}}— Project root path{{THRESHOLD}}— Max diff percentage allowed (default: 5)
Example usage:
\plan-build-test:visual-check
Threshold: 10
Workflow:
- First run: Capture baseline
- Make UI changes
- Run visual check → see diffs
- Review diff images
- If intentional → update baseline:
./scripts/visual-regression.sh --baseline - If bugs → fix issues → re-run visual check
Key Constraints
-
Playwright CLI ONLY
- NEVER use MCP playwright tools
- All tests via
npx playwright testor wrapper scripts - No browser automation except through Playwright CLI
-
URL flexibility
- Support local dev: http://localhost:3000
- Support staging: https://staging.example.com
- Support production: https://example.com
- Use TEST_BASE_URL env var to override default
-
Mobile testing
- Use
--projectflag for mobile viewports - Available projects: mobile-iphone, mobile-galaxy, tablet-ipad
- See playwright.config.ts for full project list
- Use
-
JSON results parsing
- Always parse
/tmp/playwright-results.jsonfor structured data - Extract: total, passed, failed, skipped, failures[]
- Reference screenshot paths from
/tmp/playwright-screenshots/
- Always parse
-
Screenshot evidence
- All failure screenshots saved to
/tmp/playwright-screenshots/ - Visual regression diffs saved to
/tmp/visual-diffs/ - Include paths in reports for manual review
- All failure screenshots saved to
-
Iterative fixing
- Max 3 iterations for build fixes
- Max 3 iterations for test fixes
- After max iterations → escalate to human with detailed failure analysis
-
Build before test
- Full cycle MUST run build before tests
- Test-only mode assumes build already done
- Visual check mode can run independently (screenshot capture doesn't require build)
File Locations
- Test runner:
./scripts/test-runner.sh - Visual regression:
./scripts/visual-regression.sh - Playwright config:
playwright.config.ts - Test results:
/tmp/playwright-results.json - Screenshots:
/tmp/playwright-screenshots/ - Visual diffs:
/tmp/visual-diffs/ - Visual baseline:
tests/visual/baseline/
Example Outputs
Full Cycle Success
Task #1234 COMPLETE
Build: ✓ Passed
Tests: ✓ 15/15 passed
Visual regression: ✓ No changes detected (all diffs < 5%)
Ready for deployment.
Full Cycle with Failures
Task #1234 — Test failures detected
Build: ✓ Passed
Tests: ✗ 12/15 passed (3 failures)
Failures:
1. "login with valid credentials" — Error: Element not found: button[type="submit"]
Screenshot: /tmp/playwright-screenshots/login-failure-1.png
2. "dashboard loads after login" — Error: Timeout waiting for selector: h1:has-text("Dashboard")
Screenshot: /tmp/playwright-screenshots/dashboard-timeout-2.png
Fix iteration 1/3 in progress...
Test Only (Remote)
Testing: https://staging.getdrop.no
Project: mobile-iphone
Results: ✓ 8/8 passed
All tests passed on mobile viewport.
Visual Check
Visual regression results:
✓ login.png — 0.2% diff (PASS)
✓ dashboard.png — 1.8% diff (PASS)
✗ profile.png — 12.5% diff (FAIL — exceeds 5% threshold)
Review diff: /tmp/visual-diffs/profile-diff.png
Action needed: Review profile page changes or update baseline if intentional.
⏱ Operational Limits
- MAX TURNS: 30 (build) | 20 (validate) | 10 (lookup)
- Exit cleanly after completing. On 5+ failures: escalate to John with full error context.
/sentinel
Source: ~/.claude/skills/sentinel/SKILL.md
name: sentinel version: 2.0 description: > Run full system audit using 5-agent team (BA, Architect, Developer, Tester, Validator). Use when: "audit the system", "run sentinel", "system health check", "/sentinel", "review infrastructure", "find issues", "what's broken". argument-hint: "[target] — e.g. 'tools', 'hooks', 'Drop project', 'daemons', or empty for full audit" level: 4 company: ALAI
/sentinel — System Audit Team
Purpose
5-agent parallel audit that delivers a consolidated report with prioritized action items. BA + Architect + Developer + Tester run in parallel → Validator consolidates.
Variables
| Variable | Type | Description | Default |
|---|---|---|---|
target |
string | Audit scope | full system |
model |
string | Agent model | sonnet |
depth |
string | shallow | deep | deep |
Team
| Role | Agent | Focus |
|---|---|---|
| BA | sentinel-ba.md | Business value, gaps, redundancy, ROI |
| Architect | sentinel-architect.md | Architecture, integrations, offline/online parity |
| Developer | sentinel-developer.md | Code quality, dead code, tech debt, bugs |
| Tester | sentinel-tester.md | Functional testing, daemon health, data integrity |
| Validator | sentinel-validator.md | Cross-reference, consolidate, final action plan |
Workflow
Phase 1: Pre-flight
- Read audit target/scope from $ARGUMENTS
- if no target → set target = "full system"
- if target = "quick" → set depth = shallow (skip code quality, focus on daemons + health)
Phase 2: Parallel Audit (4 agents simultaneously)
Spawn 4 sub-agents in parallel, each with:
- Role-specific prompt from
~/.claude/agents/sentinel-{role}.md - Audit target
- Key paths: ~/system/, ~/.claude/, ~/system/databases/
[Parallel]:
Task(sentinel-ba) → business audit report
Task(sentinel-architect) → architecture audit report
Task(sentinel-developer) → code quality report
Task(sentinel-tester) → health/functional report
Phase 3: Validation (after all 4 complete)
Spawn Validator with all 4 reports as input:
Task(sentinel-validator, input=[ba_report, arch_report, dev_report, test_report])
→ consolidated final report
Phase 4: Output
- Print final report from Validator
- if critical issues found → create MC tasks via delegate_task
- if minor issues → list as recommendations
Report Format
SENTINEL AUDIT REPORT
Target: [scope]
Date: [timestamp]
Model: [sonnet|opus]
CRITICAL (fix immediately):
[numbered list]
HIGH (fix this week):
[numbered list]
MEDIUM (backlog):
[numbered list]
MC Tasks Created: [list of task IDs]
Next Audit: [recommended interval]
$ARGUMENTS
/qa-doc-review
Source: ~/.claude/skills/qa-doc-review/SKILL.md
name: qa-doc-review version: "2.0" level: 3 trigger: "QA review, doc review, documentation review, qa-doc, review documentation, check docs" author: john updated: 2026-03-16
QA-Doc Review — Level 3 Supervised Skill
Sistematski pregled dokumentacije i QA artefakata. Provjerava completeness, accuracy, i linkove.
WHEN TO USE
- IF "doc review", "review docs", "check documentation", "QA doc" → activate
- IF completing a task with docs deliverable → run as post-step validation
WORKFLOW
Step 1: Classify Document Type
IF task documentation → validate against GOTCHA acceptance criteria
IF API documentation → check endpoint coverage, examples, error codes
IF runbook/ops doc → check command accuracy (run commands to verify)
IF architecture doc → check against actual system (query HiveMind)
IF changelog → check version format, completeness
Step 2: Accuracy Check
# For runbooks: verify commands actually work
# Run key commands and compare output to what doc claims
IF command in doc:
run command → compare output → flag discrepancies
Step 3: Quality Checklist
[ ] Title and metadata present (date, author, version)
[ ] All claimed commands/URLs are verified working
[ ] No localhost:XXXX references in production docs
[ ] No "TODO" or "FIXME" placeholders left
[ ] Links resolve (internal wiki + external)
[ ] Screenshots/diagrams are current (not stale)
[ ] GOTCHA acceptance criteria met (if task doc)
[ ] HiveMind post confirmed (if knowledge doc)
Step 4: BookStack Sync Check
# IF doc should be in BookStack:
cat ~/system/config/bookstack-sync-map.json | grep "[filename]"
# Verify sync status
OUTPUT FORMAT (report to John, not user)
QA-DOC REVIEW REPORT
Status: APPROVED | NEEDS_WORK | BLOCKED
Document: [title/path]
Type: [task-doc | runbook | api-doc | architecture | changelog]
❌ BLOCKERS:
- [issue]
⚠️ WARNINGS:
- [issue]
✅ CONFIRMED WORKING:
- [verified items]
BookStack: SYNCED | NOT_SYNCED | N/A
HiveMind: POSTED | NOT_POSTED | N/A
Verdict: [one sentence]
/debugging
Source: ~/.claude/skills/debugging/SKILL.md
name: debugging description: Systematic debugging workflow for finding and fixing issues. Use when user reports a bug, tests are failing, or unexpected behavior occurs. Walks through reproduce → isolate → investigate → hypothesize → test → fix → document.
Debugging
Systematic debugging workflow for finding and fixing issues.
When to Use
- When user reports a bug
- When tests are failing
- When unexpected behavior occurs
Process
Phase 1: Reproduce
- Get exact steps to reproduce
- Identify expected vs actual behavior
- Note any error messages verbatim
Phase 2: Isolate
- Find the smallest reproducible case
- Identify which component/file is involved
- Check recent changes to that area:
git log --oneline -10 -- [file] git diff HEAD~5 -- [file]
Phase 3: Investigate
- Read the relevant code
- Trace the execution path
- Add strategic logging if needed:
console.log('[DEBUG] functionName:', { input, state }); - Check for common issues:
- Null/undefined values
- Off-by-one errors
- Async timing issues
- Type mismatches
- Missing error handling
Phase 4: Hypothesize
List possible causes ranked by likelihood:
- [Most likely cause]
- [Second possibility]
- [Third possibility]
Phase 5: Test Hypothesis
For each hypothesis:
- Make minimal change to test
- Verify if it fixes the issue
- Verify it doesn't break other things
Phase 6: Fix
- Implement the fix
- Remove debug logging
- Add test to prevent regression
- Document root cause
Output Format
## Debug Report: [issue description]
### Reproduction
- Steps: [numbered steps]
- Expected: [what should happen]
- Actual: [what happens]
### Root Cause
[Explanation of why the bug occurred]
### Fix Applied
[Description of the fix]
- File: [path:line]
- Change: [what was changed]
### Prevention
- [ ] Added test: [test name]
- [ ] Related areas checked: [yes/no]
### Verification
- [ ] Bug no longer reproduces
- [ ] Existing tests pass
- [ ] No new issues introduced
Common Patterns
Async Issues
// Wrong: not awaiting
doAsyncThing();
useResult(); // result not ready
// Right: await
await doAsyncThing();
useResult();
Null Checks
// Wrong: assumes existence
user.profile.name
// Right: optional chaining
user?.profile?.name
Off-by-One
// Wrong: includes length
for (let i = 0; i <= arr.length; i++)
// Right: excludes length
for (let i = 0; i < arr.length; i++)
Mobile UAT Test
test
mobile-uat Skill
test
mobile-uat — Responsive Regression Detector
Mobile UAT — Responsive UX Regression Detector
Created: 2026-05-15 (John, after CEO caught snowit.ba landing 248px mobile overflow that source-only verification missed)
Skill path: ~/.claude/skills/mobile-uat/SKILL.md
Trigger: /mobile-uat <url>, "mob test", "responsive test", "mobile ne valja", "kreiraj mob test"
Author: Vizu/Brad Frost methodology, implemented via Playwright MCP
What it does
Drives a real Chromium browser via Playwright MCP at multiple mobile viewports (iPhone 13 390×844, iPad 768×1024, Android small 360×640) and runs deterministic hard-fail + soft-warn checks.
Hard-fail conditions (verdict = FAIL)
| Code | Check | Why it matters |
|---|---|---|
| H1 | documentElement.scrollWidth > clientWidth + 2 |
Horizontal page scroll = broken mobile layout |
| H2 | <details>:not([open]) count > 0 (opt-out for FAQs) |
Content hidden behind collapsed elements = user thinks page is empty |
| H3 | Text present on desktop but absent on mobile | Content disappeared between viewports |
| H4 | <a>, <button>, <summary> with bounding rect < 44×44px |
WCAG 2.5.5 tap target minimum, iOS HIG |
| H5 | <h1>/<h2>/<h3> with empty next-sibling chain |
Empty section = layout bug |
| H6 | <p>, <li>, <td> computed font-size < 14px |
Microscopic text on mobile = unreadable |
Soft-warn conditions (verdict = PARTIAL)
| Code | Check |
|---|---|
| S1 | Console errors > 0 |
| S2 | Network 4xx/5xx (excluding favicon, analytics) |
| S3 | Cumulative layout shift > 0.1 |
| S4 | <img> without alt attribute |
When to use
- Mandatory: after any HTML/CSS deploy to a public web app (companion to
/deploy-verify) - Reactive: whenever CEO says "doesn't look good on mobile" / "stvari nestale" / "ne valja na telefonu"
- Audit: existing site responsive sanity check
When NOT to use
- API-only services (no DOM)
- Native apps (use Paul Hudson / Skybound)
- Sites requiring login (skill is unauth-only — extend for auth scenarios later)
Example invocation
/mobile-uat https://snowit.ba/
Output: /tmp/mobile-uat-<run_id>/
SUMMARY.md— human-readable table per URL × viewportverification.json— machine-readable verdictscreenshots/— visual evidence per viewportconsole/— JS errorsnetwork/— HTTP requests
Real first run (2026-05-15)
Source-only initial run on snowit.ba legal pages reported PASS (0 hard fails). But real-browser run on the landing index.html caught:
| Metric | Value |
|---|---|
| scrollWidth | 638px (vs 390px viewport) |
| Horizontal overflow | 248px |
| Off-screen elements | 5 (hero-content, hero-badge, h1, highlight, hero-subtitle) |
| Small tap targets | 13 |
Root cause: per-page inline <style> with @media (max-width: 1024px) that set .hero-content max-width:600px without width:100% — parent grid cell was wider than viewport.
After Vizu fix (commit 37389ef): scrollWidth=390, 0 offscreen, hero readable. Same fix swept across 8 SnowIT pages (index + 7 verticals).
Lesson: source-only static checks are not enough for responsive bugs. Real Chromium with computed styles + bounding rects is mandatory.
Related skills
/deploy-verify— post-deploy gate (general, not responsive-specific)/uat-browser— generic in-session UAT via Playwright MCP (broader scope)/webapp-testing— Playwright local-app testing
Cost
Approx $0.30–$0.80 per run (Sonnet, 4 viewports × ~3 URLs). Acceptable for any site deploy.
Source
~/.claude/skills/mobile-uat/SKILL.md~/.claude/skills/mobile-uat/example-run.md