Files
kz-tob 3deb39e7b3 testing-handbook-skills: make 15 descriptions routable (#276)
* testing-handbook-skills: make 15 descriptions routable

Every description was a tool-encyclopedia blurb averaging 125 chars —
the first half defined the tool, the second half restated it as a
trigger. "Coverage-guided fuzzer built into LLVM for C/C++ projects.
Use for fuzzing C/C++ code that can be compiled with Clang." Fifteen
skills competing on wording like that lose to each other and to
siblings elsewhere in the marketplace.

Each is now three parts: what it does for the reader, task first, since
the name field already carries the tool name; what it covers, in
concrete flags and symbols; then two to four situations in the words a
user would type. The anchors are the point —  LLVMFuzzerTestOneInput,
fuzz_target!, FuzzedDataProvider, afl-clang-fast, ASAN_OPTIONS,
project.yaml, an ASan stack trace, a campaign that finds nothing.

Fix the generator too, or the next skill it emits is thin again. All
four templates prescribed the shape being removed, and their worked
examples were these same descriptions. agent-prompt.md now says why the
existing rule is not enough: "MUST include Use when" is satisfied by
"Use for fuzzing C/C++ code", which is how these got written.

Put descriptions on one quoted line rather than a folded block. Eight of
these skills already fail the plugin's own 500-line limit, and folded
blocks added 4-5 lines to each; one quoted line removes 1-2 instead.
It is also what the rest of the repo uses at this length and is exempt
from the validator's plain-scalar rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* testing-handbook-skills: ground three descriptions, gate placeholders

Three descriptions advertised anchors that appear nowhere in the skill
they route to — the worst failure for a change about routing, since the
description wins the query and the skill then has nothing to say.

harness-writing claimed "C/C++, Rust, Python, and Ruby". Grepping it for
ruby, gem, or .rb returns only that line; Python appears once, as a
Related Skills row. It would have taken "harness for my Ruby gem" from
ruzzy and delivered a file with no Ruby in it. Now C/C++ and Rust, which
is what the 12 LLVMFuzzerTestOneInput and 16 fuzz_target! sites cover.

constant-time-testing named ctgrind, whose only occurrence in the whole
plugin was that description. Replaced with Timecop and Valgrind, at 20
and 10 hits. Also leads with measuring a running implementation and adds
a "Not for" line, restoring the boundary constant-time-analysis already
documents in its own When NOT to Use.

cargo-fuzz claimed "cargo fuzz init and add"; only init, run, coverage,
and crash exist. Dropped add, added the nightly requirement and
cargo fuzz coverage, both of which the body does cover.

Gate the class rather than just these three. A description shipped with
a {placeholder} still in it passed every check, because the shortcode
pattern needs double braces — and this branch widened the templates'
slots, so the surface grew. validate-skills.py now rejects it, and
test_validate_skills.py holds each description check to a known-bad
fixture plus a positive control. Stdlib only, since CI runs these with
--no-project --with pytest, an environment without pyyaml.

Fix the pointer to a section that does not exist, drop the two-part
"what AND when" bar from testing.md's checklist since the old thin
descriptions satisfied it, correct the README's skill inventory, and
take the version to MINOR — this changes what the generator emits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop semgrep and codeql from the cross-reference graph

The prose and the summary table were updated to 14 skills, but the graph
still declared a Tools subgraph with semgrep and codeql and drew both
edges between them. Neither skill exists under skills/, so the graph
rendered 16 nodes beneath a sentence claiming 14 and promising that only
generated skills are shown.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Fail validation on frontmatter that carries no fields

extract_frontmatter returned (None, None) for an empty block, because
yaml.safe_load("") is None and that is not a parse error. validate_skill
branches on the error, so every frontmatter check was skipped and a skill
with no name and no description printed a clean tick. A bare scalar took
the same path and died on .get with an uncaught AttributeError.

Extraction now pairs both cases with an error, and validate_frontmatter
reports rather than returning silently when handed a non-mapping. Four
tests cover it, stubbing the parser so they run in CI's pyyaml-free
environment; they fail against the previous code and nothing else does.

Ground the atheris description's two API anchors in the body: rename the
harness entry point to TestOneInput, matching upstream Atheris and its
error messages, and add a FuzzedDataProvider section covering the typed
draws and the fixed-order rule. Both were advertised in the description
and appeared nowhere else in the file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Split atheris, and fix the FuzzedDataProvider method table

The structured-input section pushed atheris from 519 lines to 552, making
a file already over the plugin's 500-line error limit worse — the opposite
of what the PR body claimed. Both it and the two worked harnesses move to
sibling files, the split this plugin's own agent-prompt.md prescribes for
the band. SKILL.md is now 482 lines and passes the line-count check it has
failed since it was generated.

Three errors in the method table, all mine, corrected in the moved copy:

- remaining_bytes() returns a count and consumes nothing; it was listed as
  the way to get the remaining input. Following it hands the target an int
  where bytes is expected. The idiom is ConsumeBytes(fdp.remaining_bytes()).
- ConsumeIntList takes (count, bytes) and was shown with no arguments.
- ConsumeUnicode permits lone surrogates, not surrogate pairs. The pairs
  gloss suggests valid text; unpaired surrogates raise UnicodeEncodeError
  the moment a target encodes them, so the campaign reports its own input
  handling rather than the target's.

Every method is checked against the pybind registration in atheris.cc,
which exposes remaining_bytes despite the upstream README omitting it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 10:18:41 -04: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 14 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 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

    %% 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
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.