Skip to content

docs: add misuse-resistance and vitaminc agent skills - #494

Draft
coderdan wants to merge 4 commits into
mainfrom
docs/misuse-resistance-skills
Draft

coderdan wants to merge 4 commits into
mainfrom
docs/misuse-resistance-skills

Conversation

@coderdan

@coderdan coderdan commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR adds two Claude Code skills, plus the research notes behind them and a test-only crate that compiles every Rust block in them. Coding agents produce the same crypto misuse that the usability studies found in human developers: they copy examples, optimise for code that works, and overrate how secure it is. These skills point agents at the secure path.

.claude/skills/
├── misuse-resistance/   # designing or reviewing any security API
│   ├── SKILL.md         # footgun ladder (rungs 1–8), 11 rules, design and review procedures
│   ├── RUST.md          # Rust techniques for each rung, std-only, crypto elided with todo!()
│   └── EVIDENCE.md      # each rule mapped to the studies behind it
└── vitaminc/            # using vitaminc crates
    ├── SKILL.md         # task map (task / type / footgun it replaces), rules, behaviour to know
    └── RECIPES.md       # 9 complete recipes: Protected, Equatable, SafeRand, passwords,
                         #   derive Encrypt/Decrypt with AAD, Key::wrap, prf_with_context
docs/research/misuse-resistance.md   # sources: Holmwood LCA 2022 talk, Acar 2016/2017,
                                     #   Lazar 2014, Gorski 2018/2020, LLM-crypto studies, ...
  • misuse-resistance is generic. It covers designing APIs inside vitaminc and in other packages.
  • vitaminc is aimed at developers adopting the crates.
  • Each skill links to the other.

The .gitignore change ignores .claude/settings.local.json.

Evidence

  • Before: there were no skills. The only guidance agents had was the crate READMEs, and vitaminc-encrypt's README uses Key::from([0u8; 32]) in its examples.
  • After: every Rust block in RECIPES.md and RUST.md compiles and runs against the workspace crates, and CI now checks this. The new packages/skill-doctests crate includes each skill file as rustdoc, so every Rust block becomes a doctest. The Test job's plain cargo test runs it as a workspace member:
    cargo test -p skill-doctests
    test result: ok. 12 passed; 0 failed
    
    If a recipe stops compiling, the build fails. I checked this by renaming a method in one recipe. The crate is never published or released, the same as the derive-tests crates (publish = false, plus a release = false entry in release-plz.toml).

Gaps in vitaminc found while writing these (not fixed here):

  • The encrypt README's examples use an all-zero key.
  • Key can't be built from a Protected<[u8; 32]>.
  • Password::into_*_string is unimplemented!().
  • The README install versions are stale (0.2.0-pre.1).

Review

Both skills went through a skill-improver loop, which reviewed and fixed each one until a review found nothing critical or major. The loop used the plugin-dev skill-reviewer.

  • da5eb54 fixes 7 major findings. Most were misuse in the skills' own examples:
    • The typestate example let the caller pass in the verification check, so |_| true got past it.
    • The encrypt and key-wrap recipes made a fresh key on every call.
    • Every tenant's data key was wrapped under one hard-coded AAD.
    • SKILL.md listed a KMS as a key source, which vitaminc can't load from.
  • 2a541d1 fixes the minor findings that would mislead a reader:
    • next_below panics on zero, and the random-index recipe didn't guard against it.
    • The encrypt recipe said to share one Aes256Cipher with every call, but it is !Sync.
    • The password recipe had no minimum length.
    • The rung-1 encrypt in RUST.md couldn't fail, and nothing mentioned the random-nonce message limit.
    • Two citations in EVIDENCE.md were overstated, and one was missing from the research notes.

Every Rust block still compiles and runs against the workspace (cargo test --doc: 12 passed). The remaining open findings are style or info, such as third-person descriptions and a references/ layout. Those are plugin conventions that don't apply to project skills.

Merge Danger

Door: two-way

It changes no published crate. The only Rust addition is skill-doctests, a test-only crate that is never released.

Blast Radius: agents

The skills load automatically for anyone running Claude Code in this repo. Rules they get wrong would steer agent-written code, so review the rules and recipes as you would code.

…ability research

Most crypto failures are callers misusing correct libraries. These skills
give agents the research-backed design rules for building misuse-resistant
APIs, and the vitaminc types and recipes for using them, so agent-written
code takes the secure path by default.
…l-improver review loop

The typestate example let the caller pass the verification check, so |_| true got past it; the caller now passes a key and the library checks. The encrypt and key-wrap recipes made a fresh key per call while being labelled the production path; the cipher is now built once at startup from a data key generated once per tenant and stored wrapped under AAD naming that tenant, not one hard-coded tenant's. The Generatable recipe keeps its random secrets in Protected. SKILL.md no longer lists a KMS as a key source vitaminc can load from, says passthrough fields are unauthenticated, and stops sending password generation to a type whose value cannot be read.
…ader

The random-index recipe refused nothing although next_below panics on zero; it now returns None for an empty list. The password recipe refuses lengths under 16 characters. The encrypt recipe told readers to share one cipher with every call, but Aes256Cipher is !Sync; it now says one per thread or task. The PRF key loader takes its bytes already in Protected, the root-key loader says why it must take a plain array and to keep no other copy, and the random recipes drop an unused SeedableRng import.

In RUST.md the rung-1 encrypt returns an error rather than being infallible, as rule 9 asks, and states the random-nonce message limit; the const-generics bullet is renamed for what it shows, and the typestate example can construct an Unverified. EVIDENCE.md qualifies the Acar three-times figure and the Blessing link to rule 4, and the research notes now list Len, Grubbs and Ristenpart 2021, which rule 7 cites.
…stops compiling fails CI

The skills are Markdown, so nothing compiled their examples, and agents copy them as written. skill-doctests includes each skill file as rustdoc, which makes every Rust block a doctest against the workspace crates; the Test job's plain cargo test picks it up as a workspace member. It is never published or released, like the derive-tests crates.
@github-actions

Copy link
Copy Markdown
Contributor

🧬 Mutation testing (cargo-mutants, --in-diff)

No mutants were generated for the changed lines.

@github-actions

Copy link
Copy Markdown
Contributor

✅ No CRAP threshold violations

1288 function(s) analyzed · threshold 30

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant