Files
Dan Guido ca08fc8a91 Commit plugin lockfiles; unblock Dependabot (#213)
* Commit plugin lockfiles so Dependabot can do something useful

The uv ecosystem config added in #206 pointed at four directories that declare
PEP 621 ranges and carry no lockfile. With nothing to pin, Dependabot's only
available action is raising the lower bound of an already-open range — which
changes nothing about what installs and only drops support for older versions.
It opened five such PRs within a minute of #206 merging (#208-#212), all no-ops:
the existing ranges already resolved to exactly the versions being proposed as
new floors. The one directory that did have a lockfile, constant-time-analysis,
produced no PR at all, because there was genuinely nothing to update. That is
the whole diagnosis.

Lockfiles committed for the other four. .gitignore ignored uv.lock globally,
which is why they were missing; constant-time-analysis's was tracked only
because it predates the rule. Now scoped to the root file (ephemeral — there is
no root pyproject.toml) with plugin lockfiles explicitly allowed, matching the
pattern already used for .mcp.json.

Also fixes two bugs #206 introduced:

- The version-increment check failed all five Dependabot PRs, and Dependabot can
  neither bump a plugin version nor label its own PR, so every future dependency
  PR would have been permanently red. Exempted by actor.
- The 'no-version-bump' label was documented in AGENTS.md and wired into
  validate.yml but never created, so the escape hatch did not exist. Created.

* Re-run CI with the no-version-bump label applied

The version-increment check fired on this PR: adding uv.lock under plugins/<name>/
counts as touching those plugins. Correct behaviour — the lockfiles pin exactly
what the existing ranges already resolve to, so nothing changes for anyone
installing these plugins, which is what the label is for. First real use of the
escape hatch created in this same PR.

* Fix the three findings from this PR's review

A local uv setting leaked into all four new lockfiles. /etc/uv/uv.toml on ToB
machine images sets exclude-newer = "1 week", so every lock carried an
[options] block with exclude-newer-span = "P1W" and pinned versions resolved a
week stale — diverging from constant-time-analysis/uv.lock, which predates this
PR and has no such block. Regenerated with UV_NO_CONFIG=1. That cooldown is the
org's supply-chain posture and it belongs in dependabot.yml's
'cooldown: default-days: 7', where it already is; baking it into committed
lockfiles was my environment leaking, not a decision.

"EVERY directory here must carry a committed uv.lock" was enforced by a comment,
which is precisely the anti-pattern AGENTS.md tells people to avoid. Now a
validator check: it parses the uv ecosystem block out of dependabot.yml and
asserts a uv.lock beside each listed directory. Scoped to that block rather than
grepping for '- /plugins/...' so a future ecosystem's paths are not swept in,
and it errors if the block exists but no directories parse out — otherwise the
checker could inspect zero items and report clean, which is the exact failure it
exists to prevent. Three self-test fixtures, and verified by deleting a real
lockfile and confirming CI would go red.

The Dependabot exemption keyed on github.actor, which on a synchronize event is
whoever pushed. A human adding one commit to a Dependabot branch would re-arm
the version check and turn the PR red — making the follow-up bump mandatory
exactly where the comment says it is discretionary. Keyed on PR authorship now.
2026-07-29 20:09:36 -04:00
..
2026-01-21 07:30:20 -05:00

Testing Handbook Skills

Meta-skill that generates Claude Code skills from the Trail of Bits Application Security Testing Handbook.

Overview

This plugin provides a skill generator that:

  1. Analyzes the Testing Handbook structure
  2. Identifies skill candidates (tools, techniques, domains)
  3. Generates skills using appropriate templates
  4. Validates generated skills

Installation

Add to your Claude Code skills configuration:

# From the skills marketplace
claude skills install testing-handbook-skills

# Or manually add to .claude/settings.json
{
  "plugins": [
    "./plugins/testing-handbook-skills"
  ]
}

Usage

Generate All Skills

Generate skills from the testing handbook

This will:

  1. Locate the handbook (check common locations, ask user, or clone)
  2. Scan the handbook structure
  3. Present a plan of skills to generate
  4. On approval, generate skills as siblings to testing-handbook-generator/

Generate Specific Skill

Create a skill for the libFuzzer section of the testing handbook

Structure

plugins/testing-handbook-skills/
├── .claude-plugin/
│   └── plugin.json
├── scripts/
│   └── validate-skills.py        # Skill validation tool
├── skills/
│   ├── testing-handbook-generator/
│   │   ├── SKILL.md              # Main skill entry point
│   │   ├── discovery.md          # Handbook analysis methodology
│   │   ├── testing.md            # Validation strategy
│   │   ├── agent-prompt.md       # Agent prompt template for generation
│   │   └── templates/            # Skill generation templates
│   │       ├── tool-skill.md     # Semgrep, CodeQL
│   │       ├── fuzzer-skill.md   # libFuzzer, AFL++, cargo-fuzz
│   │       ├── technique-skill.md # Harness writing, coverage
│   │       └── domain-skill.md   # Crypto testing, web security
│   ├── [generated-skill]/        # Generated skills (siblings to generator)
│   │   └── SKILL.md
│   └── ...
└── README.md

Scripts

Script Purpose
validate-skills.py Validates generated skills (YAML, sections, line count, shortcodes, cross-refs)
# Validate all skills
uv run scripts/validate-skills.py

# Validate specific skill
uv run scripts/validate-skills.py --skill libfuzzer

# JSON output for CI
uv run scripts/validate-skills.py --json

Skill Types

Type Template Example Sources
Tool tool-skill.md Semgrep, CodeQL
Fuzzer fuzzer-skill.md libFuzzer, AFL++, cargo-fuzz
Technique technique-skill.md Harness writing, coverage analysis
Domain domain-skill.md Wycheproof, constant-time testing

Generated Skills

Generated skills are written as siblings to the generator:

skills/[skill-name]/SKILL.md

Each generated skill:

  • Follows the appropriate template structure
  • Contains content extracted from the handbook
  • Includes resource links (WebFetch summaries for non-videos)
  • Is validated with scripts/validate-skills.py before delivery

Skills Cross-Reference

This graph shows the 16 generated skills and their cross-references (from the Related Skills section of each skill). Only links between actually generated skills are shown.

graph TB
    subgraph Fuzzers
        libfuzzer[libfuzzer]
        aflpp[aflpp]
        libafl[libafl]
        cargo-fuzz[cargo-fuzz]
        atheris[atheris]
        ruzzy[ruzzy]
    end

    subgraph Techniques
        harness-writing[harness-writing]
        address-sanitizer[address-sanitizer]
        coverage-analysis[coverage-analysis]
        fuzzing-dictionary[fuzzing-dictionary]
        fuzzing-obstacles[fuzzing-obstacles]
        ossfuzz[ossfuzz]
    end

    subgraph Tools
        semgrep[semgrep]
        codeql[codeql]
    end

    subgraph Domain
        wycheproof[wycheproof]
        constant-time-testing[constant-time-testing]
    end

    %% Fuzzer → Technique references
    libfuzzer --> address-sanitizer
    libfuzzer --> coverage-analysis
    aflpp --> address-sanitizer
    cargo-fuzz --> address-sanitizer
    cargo-fuzz --> coverage-analysis
    libafl --> address-sanitizer
    libafl --> coverage-analysis
    atheris --> address-sanitizer
    atheris --> coverage-analysis
    ruzzy --> address-sanitizer

    %% Fuzzer ↔ Fuzzer alternatives
    libfuzzer -.-> aflpp
    libfuzzer -.-> libafl
    aflpp -.-> libfuzzer
    aflpp -.-> libafl
    cargo-fuzz -.-> libfuzzer
    cargo-fuzz -.-> aflpp
    cargo-fuzz -.-> libafl
    libafl -.-> libfuzzer
    libafl -.-> aflpp
    libafl -.-> cargo-fuzz
    ruzzy -.-> libfuzzer
    ruzzy -.-> aflpp

    %% Tool ↔ Tool alternatives
    semgrep -.-> codeql
    codeql -.-> semgrep

    %% Technique → Fuzzer references
    harness-writing --> libfuzzer
    harness-writing --> aflpp
    harness-writing --> cargo-fuzz
    harness-writing --> atheris
    harness-writing --> ossfuzz
    fuzzing-dictionary --> libfuzzer
    fuzzing-dictionary --> aflpp
    fuzzing-dictionary --> cargo-fuzz
    fuzzing-obstacles --> libfuzzer
    fuzzing-obstacles --> aflpp
    fuzzing-obstacles --> cargo-fuzz
    ossfuzz --> libfuzzer
    ossfuzz --> aflpp
    ossfuzz --> cargo-fuzz
    ossfuzz --> atheris

    %% Technique cross-references
    harness-writing --> address-sanitizer
    harness-writing --> coverage-analysis
    harness-writing --> fuzzing-dictionary
    harness-writing --> fuzzing-obstacles
    fuzzing-dictionary --> coverage-analysis
    fuzzing-dictionary --> harness-writing
    address-sanitizer --> coverage-analysis
    ossfuzz --> address-sanitizer
    ossfuzz --> coverage-analysis

    %% Domain → Technique references
    wycheproof --> coverage-analysis
    constant-time-testing --> coverage-analysis

Legend:

  • Solid arrows (→): Primary dependencies (techniques, tools used together)
  • Dashed arrows (-.->): Alternative suggestions (similar tools/fuzzers)

Generated Skills Summary:

Type Skills
Fuzzers (6) libfuzzer, aflpp, libafl, cargo-fuzz, atheris, ruzzy
Techniques (6) harness-writing, address-sanitizer, coverage-analysis, fuzzing-dictionary, fuzzing-obstacles, ossfuzz
Tools (2) semgrep, codeql
Domain (2) wycheproof, constant-time-testing

Note: Some skills reference planned/external skills not yet generated (e.g., honggfuzz, fuzzing-corpus, sarif-parsing). Run validate-skills.py to see the full list.

Configuration

The skill will automatically:

  1. Check common locations (./testing-handbook, ../testing-handbook, ~/testing-handbook)
  2. Ask the user for the path if not found
  3. Clone from GitHub as last resort: https://github.com/trailofbits/testing-handbook

No hardcoded paths are used - the skill adapts to your environment.

Author

Paweł Płatek

License

See repository license.