mirror of
https://github.com/trailofbits/skills.git
synced 2026-09-14 14:28:48 +08:00
main
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9e3fd2f9e8 |
constant-time-analysis: pair ilspycmd's TFM with a matching runtime (#283)
* constant-time-analysis: pair ilspycmd's TFM with a matching runtime The tool-store fallback in _get_il_output globbed for `*/ilspycmd/*/tools/net8.0/any/ilspycmd.dll`. ilspycmd 9.x installs under `tools/net9.0/`, so on any newer install the glob matched nothing, the loop body never ran, and C# IL analysis fell through to monodis and then to "IL disassembly tools not available" — while `dotnet tool install -g ilspycmd`, the command that error recommends, produces exactly the layout the glob could not see. Widening the glob to net* alone would have been wrong. The next lines run the assembly under a Homebrew dotnet@8 runtime, and that pairing is deliberate: ilspycmd installs framework-dependent and .NET does not roll forward across a major version by default, so a net9.0 assembly will not start on an 8.0 runtime. A wide glob with a pinned runtime turns a silent no-match into a silent failed exec. So the TFM found on disk now selects the runtime. Candidates are tried newest-first, sorting the parsed (major, minor) tuple rather than the moniker string, since lexically "net10.0" sorts below "net8.0"; a TFM whose runtime is absent falls back to an older one that has one. Store entries that do not name a .NET runtime major (netstandard2.0, net48) are skipped rather than producing a dotnet@netstandard2 path. The arbitrary `break` after the first matching dll is gone. Extracted to _il_via_versioned_runtime so _get_il_output stays flat. TestCSharpILRuntimePairing covers it with a fake store and fake runtime paths, needing no dotnet install. The suite was mutation-tested rather than trusted green: against the old pinned glob two tests fail, and against the naive widening four fail, including the one asserting a net9.0 assembly is never handed to dotnet@8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * constant-time-analysis: don't depend on one Homebrew keg existing Review P2 on #283: pairing the store TFM with /opt/homebrew/opt/dotnet@N assumes a keg for that major exists, and if it does not the motivating failure just moves from the glob to the exists() check. `brew info dotnet@9` says the keg does exist — stable 9.0.120 — so the ilspycmd 9.x case the PR was written for does work as shipped. The general concern holds anyway: homebrew-core carries dotnet@6, dotnet@8 and dotnet@9 but dropped dotnet@7 at end of life, and no Linux distribution ships a versioned keg at all. So the exact-major keg is now preferred rather than required. After it come the unversioned Homebrew prefixes, the official installer paths for macOS and Linux, and the PATH dotnet, each tried with DOTNET_ROLL_FORWARD=Major, which is what permits a net9.0 assembly to start on a 10.x runtime. Roll-forward is set only on those candidates: setting it on the exact-major keg would mask a genuinely mismatched pair instead of letting it fail, which is the property the existing test pins. The PATH entry resolves through shutil.which, since dotnet_path defaults to the bare name "dotnet" and exists() on a bare name tests the cwd. The except clause widens from FileNotFoundError to OSError. This is P3, included because the change makes it reachable: adding the Intel prefixes means an Intel keg on an Apple Silicon box without Rosetta is now a live candidate, and it raises OSError("Bad CPU type in executable") rather than FileNotFoundError, which escaped the helper and aborted the whole analysis. Review P2 on references/vm-compiled.md: the only user-facing remedy still said `brew install dotnet@8` and claimed the analyzer detects dotnet@8. It now tells the reader to read the TFM out of the tool store, names the keg gap, and explains that the roll-forward path means no install is strictly required. The store lookup uses find rather than ls on a glob, for the reason #282 documents. Five tests added, each mutation-checked: dropping the generic tier, setting roll-forward unconditionally, narrowing OSError back, and deleting the DOTNET_ROOT line each fail exactly the test that covers them. DOTNET_ROOT had no assertion before, so that line could be deleted with the suite green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4b1b74b181 |
Give differential-review a trigger, and name every component in its README (#278)
* Give differential-review a trigger, and name every component in its README differential-review's description listed what it does and never named a situation, so it competed on capability wording alone. It now closes with the triggers its own README already documents — reviewing a PR, commit, or diff; checking whether a change re-introduces a fixed bug; asking what else a change could break; finding modified code with no test. The same plugin's README never mentioned adversarial-modeler, which is what Phase 5 dispatches for HIGH RISK changes. Checking whether that was isolated turned up more of it, and the sweep found three kinds of gap: zeroize-audit's agent table was missing three of its eleven agents — 0-preflight, which gates the entire run, plus 5b-poc-validator and 5c-poc-verifier. All three appear in the phase diagram directly above the table, which is why they read as present. constant-time-analysis documents the ct-analyzer CLI end to end and never says the plugin also ships a skill and a command. entry-point-analyzer lists phrases that trigger its skill but never names the skill or its command. Three more READMEs describe their skill without naming it. That matters most where the skill name is not the plugin name and a user cannot guess it: chrome-mcp-troubleshooting and interpreting-culture-index. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix review findings and make the README sweep a gate The two PoC rows I added to zeroize-audit said Phase 4. The diagram three lines above them, SKILL.md, and workflows/phase-5-poc-validation.md all say Phase 5, steps 5a and 5b. "Wave 5a" is a label that exists nowhere. A debugger consulting the table — the artifact this branch designates as what runs when — would have opened phase-4-poc-generation.md and found no validation in it. Also corrected the sentence introducing that table, which still said 10 agents across 8 phases against 11 across 9, and the Phase 0 diagram line, which still credited the orchestrator for a gate the new row credits to 0-preflight. differential-review's README claimed the agent is "dispatched", and named it bare in a column whose other rows are namespaced. Nothing dispatches it: the only instruction is prose in SKILL.md, and a bare subagent_type fails at runtime. Namespaced both, and corrected the five stale line counts in the same file — reporting.md is 369 lines, not the ~120 the token-efficiency section budgets for. Drop the dead `name: trailofbits:<cmd>` key from five command files. The three newest command files carry no name: at all, #275 namespaced 22 bare invocations, and this branch documents the `/<plugin>:<cmd>` form — so the key contradicts the docs it sits next to. Then make the sweep repeatable. Doing this by hand three times found eight gaps and missed two more, both of the same shape: a workflow ships under meta.name, not its filename, so a README citing the filename never writes the name a reader types. The validator now checks that a README names every skill, agent, command, and workflow its plugin ships, reading meta.name for workflows. It refuses a run that inspected zero components, and six self-test assertions hold it to known-bad fixtures. It found git-cleanup on its first run: ships as /git-cleanup:git-cleanup-analysis, README cites workflows/analyze-branches.js four times and that name never. static-analysis had the same gap for codeql-build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix both P2s: the gate was a substring test, and the dispatch was still bare The README gate ran `name not in text`. That reads as thorough and could not fail for a large share of what it counted: `draw` was satisfied by "(draw cards instead)", `semgrep-rule` by the plugin's own name in the install line, `burp-search` by a `scripts/burp-search.sh` path that is a different thing, and `audit` by the prose "shared-state struct audit". Match by kind instead. Commands and workflows are reachable only as `/<plugin>:<name>`, so require that literal — it is the only string a user can type. Agents are dispatched by identifier and never typed as prose, so require an identifier-shaped mention. Skills are genuinely referred to by bare name, so require only a delimited occurrence, which is what stops "draws" counting as `draw`. That surfaced seven real gaps, the four above plus insecure-defaults' audit-pipeline workflow, mutation-testing's skill, and trailmark's code-slice-worker. All seven fixed. adversarial-modeler was still bare at SKILL.md:96. Line 77 was the decision-tree mention; line 96 is the "Delegate to this agent" instruction a model actually acts on, so the runtime failure the last commit claimed to fix survived it. Namespaced, and it now says why. Also from the review: a per-kind floor, since a single total stays healthy while skill_files() — 63% of coverage — silently stops matching; workflow_names anchored to the meta block, because a bare search takes any earlier `name:` in a comment, and .mjs was invisible; and AGENTS.md documents the new hard failure. Self-test 88 -> 96, each new rule with a negative control. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8c1b2670bf |
constant-time-analysis: make the VLD version lookup portable (#277)
Both copies of the PECL install snippet ran VLD_VERSION=$(curl -s ... | grep -oP 'vld-\K[0-9.]+(?=\.tgz)' | head -1) PCRE mode is a GNU extension. Under stock macOS grep this exits 2, the command substitution yields an empty string, and the next line installs a package named `vld-` with no version at all: grep: invalid option -- P VLD_VERSION=[] would run: pecl install channel://pecl.php.net/vld- Three things let that happen, so fix all three. POSIX ERE instead of PCRE, keeping the -o semantics through a sed strip. curl -fsS so an HTTP or DNS failure is reported rather than feeding empty input downstream. And a guard, so the install runs only when a version was actually found and otherwise hands the reader the URL to check by hand. Verified against the live PECL page: old and new both return 0.19.1, so behaviour is unchanged where the old form worked at all. Both snippets were then executed with PATH=/usr/bin:/bin to force BSD tools, on the success path and with PECL unreachable. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9e06dc67a3 |
Make every documented command runnable under our own python shims (#258)
* Make every documented command runnable under our own python shims
The modern-python plugin ships PATH shims that refuse `python <script>`,
`pip install`, `python -m pip` and `uv pip install`. Twelve other plugins
in this marketplace issued exactly those forms, so installing our own
plugin broke our own skills — and CI was green throughout.
The worst case was not theoretical. c-review and rust-review both call
their Phase 4 planner as `python3 "${PLUGIN_ROOT}/scripts/build_run_plan.py"`,
so with the shim installed every run died before spawning a worker.
Verified both directions: the new form exits 0 with the shim on PATH, the
old form exits 1.
Phase 1's reading pass named 16 skills. A mechanical sweep found 96
candidate lines across 44 files, and scanning shell scripts as well as
markdown found 10 more the docs sweep had missed. That gap is the reason
the check below exists.
The fix is not one substitution. Four classes needed different treatment:
- Our own scripts become `uv run --no-project <script>`. Not bare `uv run`,
because these execute inside the *target* repo, which may be a Python
project that cannot resolve; verified against a broken pyproject.toml and
against validate_artifacts.py's sibling import of generate_sarif.
- Package installs become `uv add` for a dependency, `uv tool install` for a
CLI, `uv sync` for a project's own editable install.
- Third-party CLIs we merely document — OSS-Fuzz's infra/helper.py, yarGen —
become `uv run --no-project python <script>`, which keeps upstream's exact
semantics rather than handing their script an environment we manage.
- atheris's instrumented build keeps its source build, as
`uv add --no-binary-package cbor2`. Dropping that flag would silently
produce an uninstrumented fuzzer, which is worse than a visible failure.
Its prose was updated to name the flag it now uses.
Two factual corrections fell out. `pip install caracal` was wrong twice
over: caracal is a Rust tool (Cargo.toml at its root), so it is now
upstream's own `cargo install --git`, not a uv equivalent that would fetch
an unrelated PyPI package. And `pip install uv` cannot bootstrap uv under
a shim that intercepts pip, so culture-index now points at the official
installer.
Thirteen lines stay as they are, each deliberately: Dockerfile `RUN` lines
and oss-fuzz's build.sh run in containers where our shims are absent;
codeql's pip calls install the *analysed* project's dependencies, and that
project is arbitrary; trailmark's dispatch skills must keep saying "Do NOT
run `pip install`"; and modern-python documents what it intercepts.
`make shell-suites` passes again as a result — exit 0 with the 1.6.0 shim,
where AGENTS.md previously recorded it as broken by variant-analysis.
The guardrail: check_python_invocations scans 698 markdown and shell files
and fails on the four refused forms, with structural exemptions for
dockerfile fences and an `allow-legacy-python: <reason>` marker that scopes
to its code block. Eleven self-test fixtures cover it, four asserting it
fires and seven asserting it stays quiet on the compliant forms. It was
mutation-tested in both languages, and it caught its own worst bug during
development: unanchored patterns first flagged `uv run --no-project python
fuzz.py`, the very form the advice recommends. Self-test goes 45 -> 56.
* Review pass: fix the atheris flow, drop a stray exemption, trim comments
Three corrections from reviewing the branch diff:
- atheris's install now opens with `uv init --bare`, without which the
documented `uv add atheris` errors in a bare harness directory. The old
pip form assumed an activated venv, so setup was always implicit; now
it is one explicit line.
- ossfuzz carried an allow-legacy-python marker on a C++ build block that
contains no python at all — yesterday's insertion matched the first of
three "Build in build.sh" headings instead of the python one. The
exemption now sits only on the block that needs it.
- The anti-vacuity message said "read no markdown" for a scan that also
covers shell scripts.
The rest is weight: the new check's comment blocks, the hardcoded-path
constants' commentary, the AGENTS.md bullets and the three exemption
markers all said the same things at two to three times the length. Each
keeps its one-line why; the narratives are gone. No behavioural change —
self-test still passes 56 assertions and the full scan is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Address the review: fix where packages land, widen the check to match the shim
The review's core insight was right twice over. Several substitutions had
changed WHERE a package lands, breaking the documented next step, and the
checker enforced a narrower invariant than the shim it exists to mirror.
Where packages land:
- trailmark is imported as a library from five skills, and a `uv tool
install` environment is not importable — the retry loop at
trailmark/SKILL.md:47-51 would have spun forever on the exact error it
names. The CLI install stays `uv tool install`; the import snippets now
run under `uv run --with trailmark python -`.
- `uv add` writes to the manifest of whatever project you are standing
in, which for sarif-parsing is the audited repo. Its scripting rows,
ijson comment and jsonschema example now use `uv run --with <pkg>`,
which leaves no trace. atheris keeps `uv add` deliberately: the fuzzing
harness is the user's own project, made explicit by `uv init --bare`.
- `uv sync` leaves ct-analyzer in .venv/bin, so the README's very next
line failed with command not found. Now `uv tool install .`, verified
end to end: the console script lands on PATH and --help runs.
- yarGen needs pefile/lxml/yara-python, which `--no-project` had detached;
now `uv run --with-requirements requirements.txt`.
- The cbor2 source-build preference now persists via
`no-binary-package = ["cbor2"]` under [tool.uv] (field verified against
uv's accepted-settings list), so a later `uv sync` cannot silently swap
in an uninstrumented wheel.
The checker, widened to the shim's actual behaviour:
- `python3 --version` and `python3 -u foo.py` are refused by the shim but
passed the old patterns; one live instance (constant-time-analysis
README) proved it. Both forms are now caught.
- Every `uv pip` subcommand is refused, not just install; `-t` joins the
allowed tool-managed flags.
- .py files are scanned too: usage strings and error messages told users
to run refused commands from ten scripts, including the --help of the
very planner this PR fixed. All rewritten.
- The evals/tests exemption now tests path parts relative to plugins/, so
a checkout under a directory named tests no longer exempts every file.
- An allow-marker's scope ends at a blank line as well as a fence, so one
marker cannot blanket a whole file; quality-assessment.md gains the
second marker that scoping made necessary.
Also from the review: zeroize's preflight gets `which python3` back (a
helper script still needs the binary; the shim never required removing
it), the Makefile's shell-suites note no longer describes an interception
that is gone, and the cairo CI example warns that it rebuilds caracal
from source each run.
Self-test 56 -> 63; every new pattern and exemption is fixture-covered
and was mutation-probed against the real tree. Full scan: 0 findings over
773 files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Address the second review: prerequisite probe, checker parity, package placement
The review's P2 was a regression this PR introduced for a population the
first fix ignored: c-review and rust-review now require uv, and a box
with python3 but no uv would die at Phase 4 exactly the way shimmed boxes
died before. Phase 1 (Prerequisites) in both skills now probes
`command -v uv` and aborts with install guidance. zeroize-audit's
preflight already checked uv. The four converted shell suites gain the
same guard with a clear message instead of a bare 127 mid-run.
Checker parity with the shims, second pass:
- pipx and the non-install pip subcommands are refused by catch-all shim
arms and passed the checker; both get named-subcommand patterns.
- A script named by variable or path (`python3 "$MERGE"`) has no `.py`
token; a new pattern covers it and immediately caught one live
instance — a codeql test stub that fakes uv itself, now carrying an
allow-marker with its reason.
- finditer everywhere: a compliant `uv run` earlier on a line no longer
masks a refused command later on it, which was exactly the table-cell
case the unanchored design exists for.
- Prohibition phrases now test the text BEFORE the match, so
"Use `pip install semgrep` instead of the tarball" is flagged while
"Do NOT run `pip install`" stays exempt.
- The uv-pip allowance matches whole flags after the command, so
`--target-dir` no longer counts as `--target` and a trailing `-t /tmp`
does; `uv pip` precedes `pip` in the pattern order so its lines get
the right advice; a pip match directly after `uv ` defers to the
uv-pip verdict instead of double-reporting.
Package placement, continued from the same insight as round one:
- yarGen regains --no-project alongside --with-requirements, plus a cd
into the checkout so requirements.txt resolves where it lives.
- sarif-parsing's jsonschema example no longer names a script that does
not exist, and the table's run-forms show a concrete script.py.
- culture-index's two messages now agree and name the actual remedy
(`uv run --project` on the scripts directory) instead of re-adding a
dependency its pyproject already declares.
- merge_sarif's usage line gains --no-project; the generator plugin's
install section stops prescribing a venv its own runner never uses.
- generate_poc declared requires-python >=3.9 while using `str | None`
in a signature, a TypeError on 3.9 that uv's interpreter selection
made reachable; now >=3.10.
- The GitLab CI example exports ~/.local/bin onto PATH, without which
`uv tool install` warns and the next line dies command-not-found.
Self-test 63 -> 71; the masking, prohibition-direction, flag-position
and pipx cases are all fixtures, and each new pattern was probed live
against the tree (plant, error, remove, clean — 0 findings over 773
files).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Address the third review: importable trailmark, honest probes, sturdier scan
The P2 was the residue of round two's own fix, applied to the siblings
but not the flagship: trailmark/SKILL.md told the model to cure an import
error with `uv tool install`, which cannot cure it — a tool env is not
importable — while forbidding every fallback. The install block now says
what each remedy is for: `uv tool install` for the CLI, `uv run --with
trailmark python -` for the snippets, and the other five library-first
docs carry the same one-line annotation next to their install command.
Empirically settled rather than taken from the review: `uv run python3
<script>` works fine under the shims — uv prepends its environment's bin
directory, so python3 resolves to a real interpreter, not the shim. The
review's claim to the contrary would have meant rewriting the Makefile
and a bats suite; a two-minute transcript said no. Also declined: a
zeroize uv-prerequisite (its preflight already lists uv and uvx; the
C/C++ `which` line now names uv too).
Real and fixed:
- ct-analyzer's availability probe ran `python3 --version` by subprocess
— the one refused form — so under the shims it reported "Python is not
available" on machines where it plainly is. It now probes
sys.executable, the interpreter the analyzer itself runs under.
Verified under the shim: probe returns True.
- The flag step-over in both script patterns handles long and
value-taking flags (`python3 -W ignore harness.py`, `--verbose
tool.py`), matching the shim's two-slot consumption.
- A bare `allow-legacy-python:` with no reason no longer exempts
anything; the reason the docs demand is now enforced.
- `uv run {baseDir}/...` gets --no-project at the ten semgrep and
culture-index call sites that round two missed, and the culture-index
remediation strings now name that same runnable command instead of a
--project mechanism nothing uses.
- pip gains cache/config; the pattern comment now says the subcommand
list is deliberately a subset.
- Both filesystem scans skip .venv/node_modules-style directories, after
a stray local .venv (left by this session's own uv probe, and invisible
to CI) turned the path scan red.
Smaller review items: the uv-probe prose says "Phase 4 onward" rather
than a wrong phase range, run_fixtures' comment stops claiming PEP 723
headers its stdlib-only helpers do not have, the yarGen one-liners say
to run from the checkout, `uv tool install` sites note or export the
tool bin dir the way a fresh container needs, and sarif-parsing's table
column says Install / run and stops naming a file that does not exist.
Self-test 71 -> 74. Full scan: 0 findings over 773 files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
9ea55c5987 |
Fix constant-time analyzer backends and trim the skill (#220)
* Add per-language triage fixtures for the constant-time analyzer One fixture per supported language, each pairing operations the analyzer reports identically but that triage must separate: a division on a private-key coefficient beside one on a public buffer length, and, where the backend detects them, a weak RNG seeding a nonce beside the same call jittering a retry delay. Expected verdicts are deliberately absent from the fixture source. These files double as evaluation input, and a verdict written next to the code is a verdict the reviewer reads instead of deriving. A later commit adds the manifest that records them. Fixture shapes are dictated by what each toolchain emits: the Go helpers are //go:noinline so each stays a distinct symbol, its main() takes input from argv so the arithmetic is not constant-folded away, and the Swift entry points use @_cdecl to keep symbol names readable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Configure ty and fix the typing weaknesses it reports `ty check` reported 50 diagnostics, all pre-existing. Most were noise from module resolution: the tests reach the modules via `sys.path.insert` and import `analyzer` rather than `ct_analyzer.analyzer`, which ty cannot know without being told. `tool.ty.environment extra-paths` tells it, dropping 32 unresolved-import reports and leaving the substantive ones visible. The rest were real, if not yet bugs: - Six compilers and several entry points annotated `list[str]`/`str` while defaulting to None. Now spelled `| None`. - `get_compiler` was annotated to require a compiler name, but its body auto-detects from the language when the name is falsy, which is how every caller uses it. - Each parser tracked functions as a bare dict, typing the values `str | int`, so the `functions[-1]["instructions"] += 1` counter present in all eight parsers read as string addition. A `ParsedFunction` TypedDict names the shape instead. Three unresolved-import reports remain by construction: analyzer.py is documented to run as a script as well as import as a package, so both the relative and top-level spellings are needed and only one can resolve. Those carry an inline ignore with the reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix analyzer backends that reported PASSED on vulnerable code Auditing each backend against real toolchain output found six of them skipping unparseable lines and reporting success. Every one was covered by tests that replay hand-written listings, which is why the drift went unnoticed; the tests added here drive the actual toolchains. Python, on 3.11 and later: - The BINARY_OP oparg map read 12 as `//`, but 12 is `^`. Floor division on a secret was missed and every constant-time XOR was reported as a division. Keyed on the operator symbol dis prints, which cannot silently renumber. - The instruction regex required a byte offset that 3.13 no longer prints, so all but the first instruction of each source line was skipped. On 3.13 `key_coef // (2 * gamma2)` came back PASSED. Every column ahead of the opcode is now optional, and the source line carries forward. PHP: `_parse_opcache_output` delegated to the VLD parser on the claim the formats were "similar enough"; they share nothing structural. `analyze()` also called the VLD parser unconditionally, so the function was dead either way. VLD is an unbundled PECL build, so this is the path most users hit: a file with intdiv, mt_rand and base64_encode reported zero findings. Adds a real parser, plus intdiv/fdiv, which perform division through a call and so emit no DIV opcode. JavaScript and TypeScript: V8 prints `67 E> 0x… @ 14 : 3e 03 04 Div a0, [4]`, not `offset : Mnemonic`, so every file parsed as zero instructions. `--no-lazy` covers functions a module exports without calling. Bytecode blocks are filtered to names the file declares, since V8 dumps node's internals identically. Source positions are byte offsets, now converted to lines except for transpiled TypeScript, where they index generated output. Go: `go build` links the runtime in, so 23 of 24 findings came from the allocator and garbage collector while the caller's code was silent. Objdump headers name the source file, so the filter is exact. Go also writes arm64 in Plan 9 syntax, where a 32-bit divide is SDIVW — absent from the table, so `int32` division, the shape of every polynomial coefficient divide, read as clean. Rust: compiled as a bin crate, so any library file failed E0601. Crypto lives in libraries. Swift: target triples were hardcoded to Apple platforms, so swiftc rejected them on Linux and no Swift file could be analyzed there. Java: the weak-RNG pattern matched only `new Random(`, missing the fully-qualified form that needs no import. All six source-level scanners treated comments as code, so `/** a / b */` counted as a division; comment bodies are now blanked while preserving offsets. Finally, the text report labelled every scripting-language error `[WARN]` while the summary counted it as an error: analyzer.py runs as `__main__` while script_analyzers imports it as `analyzer`, giving two Severity enums, so identity comparison failed. Severity is compared by value now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Record triage verdicts for every fixture and assert the pairs hold expectations.json gives each fixture's cases a verdict and the reasoning behind it: 53 cases over 13 languages, covering arithmetic everywhere, conditional branches on the native backends, weak RNG wherever a backend detects it, and PHP's encoding functions. TestTriageMatrix asserts the mechanical half — that the analyzer still reports both members of every true/false-positive pair, since the premise of the skill's triage step is that the tool cannot tell them apart. Four of its checks need no toolchain, so the matrix stays guarded on machines that cannot compile most of these languages: every supported extension has a fixture, every fixture pairs a true with a false positive, every line-based locator resolves exactly once, and no fixture leaks its verdict into a comment. The run itself fails rather than skipping when no fixture could be exercised, and names the languages it skipped. Two cases record measured behaviour rather than the intent: Rust claims no branch true positive, because rustc emits no flagged conditional branch in the tag comparison at O0 on arm64 and asserting one would encode a fixture artefact; and Go's branch false positive is its stack-growth check, which appears in every Go function and depends on nothing the caller passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Trim the constant-time-analysis skill and state its real coverage SKILL.md goes from 219 to 165 lines by removing what was duplicated or inert: a decision tree that restated the trigger bullets below it, five near-identical invocation blocks, and JVM/.NET setup copied from references/vm-compiled.md. It gains the frontmatter it was missing — allowed-tools, which it never declared and so inherited everything, and an explicit effort level. The additions come from evaluating the skill rather than from review. Runs at three effort levels all recommended fixing a secret-dependent divide by handing the compiler a constant divisor; measuring that fix showed it still emits a real divide on gcc riscv64 at every optimization level, on gcc arm64 and x86_64 at Os and Oz, and on clang arm64 at O0 and Oz. The sweep section now says so, with the table. Coverage is not uniform across backends, and a clean report means different things depending on the language, so there is a table for that too. Triage guidance now also covers the weak-RNG and encoding findings, where no operand is secret and the question is what the result is used for — seeding a nonce or jittering a retry delay. "When NOT to Use" names the constant-time-testing skill and draws the line between them: that one measures a running binary, this one reads compiler output and never executes the code. Both READMEs listed nine languages when thirteen are supported, and the skill's file tree was missing four reference files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix warning-level detectors that no source could ever match Extending the triage matrix to the comparison, lookup and encoding families found that several of those detectors were unreachable. The tables listed them, so the coverage looked complete, but no input could trigger them. - Ruby built every warning pattern as `\.<name>`, which is right for method calls like `.start_with?(` but turns the module-qualified keys into `\.base64\.encode64`. Five entries — both base64 functions, json.parse and both marshal functions — could not match any Ruby source. - JavaScript did the same to its non-method entries, so `btoa(`, `atob(`, `decodeURIComponent(` and `JSON.parse(` never matched. - The JS keyed-property mnemonics were V8 9.x spellings. Node 22 emits `GetKeyedProperty`, so secret-indexed array access — the whole point of that family — was undetectable. The named-property entry is removed rather than updated: a named access has a constant property name and cannot be indexed by a secret, so flagging it reports every `Math.trunc` call. - Java, Kotlin and C# selected patterns with if/elif chains covering three or four keys and skipping the rest, leaving the base64 and convert entries unreachable. They now share `qualified_call_pattern`, which distinguishes the two shapes the keys use: `string.equals` is a method on any receiver, while `arrays.equals` and `base64.getencoder` are members of a named type and must match only the qualified form. Case-insensitivity is embedded in the pattern because the keys are lowercase while the source spells them `Base64.getEncoder` and `.Equals`. Ruby's warning mnemonics also kept the dots from their keys, unlike every other severity and backend, so they read `BASE64.ENCODE64`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Cover the comparison, lookup and encoding families in the triage matrix The matrix covered two of the four families the analyzer implements. It now covers all four, taking it from 53 cases to 99. Each of the eight bytecode and scripting fixtures gains three pairs: an early-exit comparison of a secret tag against the same comparison on a public header, a table lookup indexed by a key byte against one indexed by a fixed offset into a public header, and a base64 encode of a decrypted session key against one of the public algorithm identifier. PHP already had the encoding pair. Native fixtures are unchanged — their instruction tables carry no comparison or load entries, and C's tag comparison is already covered as a branch. All three families are warning severity, so their cases run with `include_warnings`. That is worth noting on its own: the invocation the skill documents does not pass `--warnings`, so a reviewer following it sees none of this — including early-exit comparison, which is the most common timing bug in practice. Every case was measured against the real toolchain before being written down. Python's fixture avoids a `list[bytes]` annotation in a signature because the subscript in the annotation is itself reported at module scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Refuse an architecture the compiler cannot target Every compiler looked its architecture up with `.get()` and simply omitted the target flag when the lookup missed, while `analyze_source` still labelled the report with the architecture that was requested and applied that architecture's instruction table. So `--arch riscv64` on Swift, whose Linux map holds two entries, compiled for the host and reported `architecture: riscv64` with no findings; `--arch mips` on gcc reported `passed: true` the same way. "PASSED for riscv64" is what a reviewer writes down, so producing host output under the requested label is worse than failing. All five compilers now refuse and name what they do support. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Stop the Go symbol filter readmitting the runtime by basename The filter compares the objdump block's source path against the file under analysis, but when the realpath comparison failed it fell back to comparing basenames — and Go's runtime ships map.go, slice.go, string.go, time.go and select.go. Analyzing a user file named map.go therefore matched every `TEXT runtime.…(SB) /usr/lib/go/src/runtime/map.go` block, reported runtime.makeBucketArray among the findings, and parsed 14 functions instead of 5. That is the behaviour the filter exists to prevent, and the existing test missed it only because its fixture is named triage_go.go. objdump prints an absolute path, so the exact comparison is sufficient there; the basename fallback now applies only when the printed path is relative. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Make comment blanking string-aware Blanking comments before the source-level scans stopped doc comments counting as code, but the regexes knew nothing about string literals, so a comment marker inside one blanked live code: - `const u = "http://example.com"; const k = a / b;` lost the division, and `String u = "http://x"; Random r = new Random();` reported clean — for Java, Kotlin and C# the source scan is the only detector for `new Random()`. - Ruby's `puts "tag #{a / b}"` lost everything from the interpolation on. - Worst, a lone `const marker = "/*";` plus any later `*/` anywhere in the file blanked every line in between, with no diagnostic. String and template literals are now matched first and preserved. This is still a regex rather than a lexer — unterminated literals, JavaScript regex literals, Ruby heredocs and `%w[]` are not modelled — and the docstring says so; the failure mode is a blanked or unblanked span, never a crash. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Test the empty-parse guards and close smaller robustness gaps The test named for PHP's "nothing parsed" refusal asserted only that the parser returned empty lists, which the pre-fix code did too, so deleting the raise in `analyze()` left the suite green. It now asserts the RuntimeError, and the Go equivalent — the symbol filter keeping nothing — is covered as well. Both were confirmed to fail with their guard removed. Four gaps that each degrade quietly: - The OPcache offset group was `\d{4}` exactly, so an op_array past 9999 opcodes stopped parsing mid-function. `functions` is non-empty by then, so the empty-parse guard cannot fire and the report reads as a partial pass. - The V8 name filter treated "could not read the compiled file" as "filtering disabled", listing hundreds of node-internal functions with nothing to distinguish that from a genuinely noisy file. Unreadable input is now an explicit None with a note on stderr. - `last_line_num` was not reset per code object, so the first instructions of a function inherited the previous function's line when dis omits one. - The triage matrix printed its skip list to stdout, where CI summaries do not show it, so an image that lost a toolchain would keep passing while the claim that all backends are exercised quietly stopped being true. Skips are now emitted per language and appear in the count. Two test-quality fixes: the matrix completeness check compared set sizes, so renaming one language while dropping another stayed green — it compares sets now, through an explicit display-name to `detect_language()` mapping. And `_have` uses `shutil.which` rather than running `--version`, because javap exits non-zero for it, which would have skipped Java and Kotlin everywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pass --warnings in the documented invocation The Quick Start ran the analyzer with no flags, which reports only error-severity findings: division, modulo and weak RNG. Four families are warning severity and stayed silent — secret-dependent branches, early-exit comparison, secret-indexed table lookups, and variable-time encoding. A reviewer following the skill exactly never saw the early-exit comparison of an authentication tag, which is the most common timing bug in practice and what Lucky Thirteen was. `--warnings` is now in the documented command, the directory sweep and the flag table, with the two consequences of turning it on stated alongside. Warnings do not affect the PASSED line, so `Result: PASSED` beside `Warnings: 6` is normal and is not a clean result. And comparison and lookup findings need their own triage question: for a lookup it is the index that must be secret, not the contents. Confirmed comparison findings have a per-language constant-time primitive, so those are tabulated rather than left as "write it branch-free"; a secret-indexed lookup gets the honest answer that no drop-in exists. Also names the plugin that owns constant-time-testing, since a reader cannot assume it is installed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix the fixture path and the sdist include list Two defects that both make something unreachable: The pointer to the triage fixtures was written as a bare relative path, while every other path in SKILL.md is `{baseDir}`-prefixed. The skill runs against the user's repository, so a model following it looked for `ct_analyzer/tests/triage_samples/` inside the project being audited. The validator resolves markdown links rather than inline code, so this passed 359 reference checks while being unusable at runtime. The sdist include list named three globs covering C, Go and Rust, so a packaged install shipped none of the Python, Ruby, PHP, JS, TS, Java, C#, Kotlin or Swift samples, nor the triage fixtures and their manifest, and the tests that read them could not run from an sdist. A built sdist now carries all 27. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Test that cross-compilation really targets the requested architecture Nothing covered cross-compilation. The pre-existing cross-architecture test asks for arm64, which is the host on an aarch64 machine, so it passes there without ever crossing — and a silent fall back to the host is exactly the failure `reject_arch` was added to prevent. These assert instruction names unique to the target: x86 `DIVL`/`DIVQ`/`IDIVL` and RISC-V `DIVU`/`DIVW`/`REMU` must appear, and arm64 `SDIV` must not. Dropping clang's `--target` makes both fail, which a report-shape assertion would not have caught. Go gets the same treatment for amd64, where GOARCH cross-builds because CGO is disabled. Only the paths that work are covered: on this host clang crosses to both targets and Go to amd64, while gcc, rustc and Go/riscv64 cannot cross here at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Drive an explicit compiler with its own flags, and name it in the report Cross-compiling with gcc was impossible through the analyzer. `--arch x86_64` passed gcc's native ISA switch `-m64`, which the gcc on an arm64 host rejects, and the escape hatch that should have worked — handing over the cross binary — was broken: `get_compiler` treated every unrecognized `--compiler` value as clang, so `--compiler x86_64-linux-gnu-gcc` was driven with `--target=` and failed on "unrecognized command-line option". An explicit compiler is now dispatched on which compiler it actually is: by filename first, falling back to its `--version` banner, so `cc` is recognized as whatever it points at. `--compiler x86_64-linux-gnu-gcc --arch x86_64` and `--compiler riscv64-linux-gnu-gcc --arch riscv64` both work and emit that target's division instructions. Nothing is substituted on the caller's behalf. The analyzer could look for `<triple>-gcc` and use it automatically, but a Debian cross gcc is frequently a different major version than the host's, so its codegen is not the host gcc's; silently swapping binaries would put one compiler's output under another's name — the same mislabelling this commit removes. Instead, when gcc is asked for an architecture it cannot build, the error names the binary to pass, and the report records the binary that ran rather than the family, so `compiler:` is no longer "gcc" when a cross build produced the assembly. SKILL.md now states how each toolchain crosses, since "sweep more than one --arch" was not actionable for gcc users, and advises comparing against the toolchain that builds the product rather than a packaged cross build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Detect software division, and fix the armhf flags that hid it Installing the remaining cross toolchains turned up two problems that are not about missing dependencies. The arm flags were self-contradictory: `-march=armv7-a -mfloat-abi=hard` is rejected with "selected architecture lacks an FPU", because armv7-a alone specifies no FPU. Adding `-mfpu=vfpv3-d16`, Debian's armhf baseline, makes the build work. With it building, arm reported no findings at all on a fixture with three divisions. armv7-a has no hardware divider, so gcc emits `bl __aeabi_idiv` rather than a division instruction, and the analyzer matches mnemonics: a branch-with-link is not in any table, so the file read as PASSED. Software division loops over its operands, making it more operand-dependent than the instruction it replaces, so this was the worst kind of false negative. Calls to the libgcc and Arm EABI division routines are now reported by call target, which also covers __divti3 — used on x86_64 for __int128 division, which crypto code does perform. Verified against every cross target now installed: x86_64 DIVQ/IDIVL, i386 DIVL/IDIVL, arm AEABI_IDIV, riscv64 DIVU/DIVW/REMU, ppc64le DIVDU/DIVW, s390x DLGR/DSGFR, arm64 SDIV/UDIV. The ppc64le and s390x instruction tables had never been exercised before. Also stops the new "pass a cross build" hint from telling the caller to do what they just did: it is suppressed when the compiler already is a cross build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Cross-compile a freestanding probe, not a fixture that needs a cross libc CI failed on `test_clang_targets_riscv64` with "bits/libc-header-start.h file not found". The test compiled the C triage fixture, which includes <stdint.h>, so cross-compiling it needs the target's C library headers as well as the compiler. The x86_64 case passed only because the runner is x86_64, making that target native; my dev box passed riscv64 only because installing gcc-riscv64-linux-gnu had pulled in libc6-dev-riscv64-cross. So the test was asserting an environment property, not the analyzer's behaviour. Nothing about checking that a division instruction reaches the assembly needs a libc. The cross tests now use a freestanding sample with no includes, verified to compile for x86_64, riscv64 and s390x with `-nostdinc` — that is, with no headers reachable at all — while still yielding that target's division mnemonics. This also corrects the claim in SKILL.md that clang needs nothing installed to cross-compile: it needs no second compiler, but a source including libc headers still needs the target's headers. When that is what failed, the error now names the package that supplies them, which is the analyzer's own advice to sweep `--arch` being made actionable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Scott Arciszewski <147527775+tob-scott-a@users.noreply.github.com> Co-authored-by: kz-tob <kara.zaffarano@trailofbits.com> |
||
|
|
d5fe2e6a78 |
feat(codex): add UI metadata for skills (#175)
* feat(codex): add skill UI metadata * Use official Trail of Bits logo * fix: resolve code review findings for PR #175 Codex silently drops the icons as authored: its loader (codex-rs/core-skills resolve_asset_path) requires icon paths containing '..' to resolve under <plugin_root>/assets/, and the repo-root .codex/assets location fails that containment check. Verified empirically via codex app-server plugin/read: every iconSmall/iconLarge came back null; only brand_color applied. P1 fixed: - Vendor trail-of-bits-mark.svg into plugins/<name>/assets/ for all 38 plugins with skills and point every openai.yaml at ../../assets/trail-of-bits-mark.svg (the supported plugin-level shared asset pattern). Icons now resolve for marketplace installs too, since nothing escapes the plugin root. - Drop the .codex/ additions: .codex/skills/gh-cli/agents/ openai.yaml resolved nowhere (.codex/skills is not a Codex discovery root) and PR #173 removes the whole .codex/ tree P2 fixed: - Patch-bump all 38 touched plugins in plugin.json and marketplace.json so installed clients pick up the metadata Verified: - Static check replicating Codex's resolution algorithm: all 73 yaml files resolve under their plugin assets/ and exist - Live codex app-server probe: 71/72 loadable skills report resolved iconSmall/iconLarge and brand_color #D83A34 (claude-in-chrome-troubleshooting fails to load on main due to a pre-existing 64-char qualified-name limit, fixed by #173's rename; zeroize-audit's manifest mcpServers object is likewise a pre-existing Codex incompatibility fixed by #173) - validate_codex_skills.py, validate_plugin_metadata.py, prek all pass Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(codex): use skill-local icon assets --------- Co-authored-by: Dan Guido <dan@trailofbits.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
debfb29c8e |
Fix allowed-tools to use spec-compliant space-delimited strings (#139)
* Fix `allowed-tools` to use spec-compliant space-delimited strings Per the agentskills.io specification, `allowed-tools` must be a single string of space-delimited patterns, not a YAML list. Converted all 23 SKILL.md files from the `- Item` list format to the correct `"Item1 Item2"` string format. Also updated the frontmatter examples in CLAUDE.md and the workflow-skill-design skill template to match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix remaining allowed-tools format in firebase-apk-scanner and workflow-skill-design docs - Convert firebase-apk-scanner from comma-separated to space-delimited - Update anti-patterns.md and tool-assignment-guide.md examples from YAML lists to space-delimited strings - Remove unnecessary quotes from SKILL.md template placeholder Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Cover commands, new SKILL.md files, and fix template placeholder Extends the previous spec-compliance fixes: * Convert command frontmatter (commands/*.md) — per Claude Code docs, command files use the same frontmatter as skills, so the same space-delimited rule applies. * Convert three SKILL.md files added since the original PR: mutation-testing, trailmark-structural, trailmark-summary. * Fix the placeholder in the workflow-skill-design template. The previous "[minimum tools needed, space-delimited]" was YAML flow-sequence syntax, which parses as a list — the opposite of what the placeholder claims. Replaced with a concrete-looking space-delimited example plus a comment. Zeroize-audit agent files still use `allowed-tools:` in YAML list form. They are intentionally excluded: per the project's own docs (workflow-skill-design references), agents declare tools with `tools:` (not `allowed-tools:`). Fixing those requires changing the field name as well as the format and is out of scope for this PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * zeroize-audit agents: switch allowed-tools to tools Subagents declare their tool allowlist via `tools:` (comma-separated), not `allowed-tools:` — see Claude Code's subagent docs and this repo's own designing-workflow-skills/SKILL.md:47: > Skills use `allowed-tools:` in frontmatter. Agents use `tools:` > in frontmatter. Before this change, the zeroize-audit agents declared their tool list under `allowed-tools:`, which Claude Code does not read for subagents. The field was effectively a no-op; the spawned agents had no tool restriction enforced. Renames the field on all 11 agents to `tools:` and reformats the YAML list as comma-separated to match the documented format and existing agents elsewhere in the repo (e.g. function-analyzer.md, spec-compliance-checker.md). Tool sets are unchanged. Behavior change: tools now actually constrain what each spawned agent can call. The lists are the ones the original author intended. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill-improver: convert command allowed-tools to space-delimited The two command files in plugins/skill-improver/commands/ still used the JSON flow-array format (`allowed-tools: ["..."]`), which the rest of this PR converted everywhere else. Convert them to the spec-compliant space-delimited string form for consistency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Dan Guido <dan@trailofbits.com> |
||
|
|
52314112b9 |
fix(ct_analyzer): escape XML special chars in _compile_csharp() (#106)
* fix(ct_analyzer): escape XML special chars in _compile_csharp()
source_path and output_dir were embedded into the .csproj f-string
without escaping. On Linux, filenames may contain XML-special chars
(", <, >, &), allowing a crafted filename to inject arbitrary MSBuild
XML (CWE-91 -> CWE-78).
Fix: wrap both values with xml.sax.saxutils.escape() before
interpolation. The extra {chr(34): '"'} arg covers double-quotes
inside the attribute value (chr(34) avoids an f-string quote conflict
on Python < 3.12).
* Update script_analyzers.py
---------
Co-authored-by: Scott Arciszewski <147527775+tob-scott-a@users.noreply.github.com>
|
||
|
|
45e2ed25bc |
Fix Windows compatibility: remove colons from command filenames (#52)
Rename 10 command files that contained `:` in their filenames, which is invalid on Windows filesystems (reserved for drive letters). The `trailofbits:` namespace is preserved in the frontmatter `name` field, so slash commands like `/trailofbits:audit-context` continue to work. Fixes #51 Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
a6ed466329 |
Add trailofbits: prefixed slash commands to 9 plugins (#49)
Add slash commands that wrap existing skills for easier invocation: - /trailofbits:scan-apk - Firebase APK security scanner - /trailofbits:burp-search - Burp Suite project file search - /trailofbits:entry-points - Smart contract entry point analyzer - /trailofbits:variants - Vulnerability variant analysis - /trailofbits:semgrep-rule - Semgrep rule creator - /trailofbits:diff-review - Differential security review - /trailofbits:ct-check - Constant-time analysis - /trailofbits:spec-compliance - Spec-to-code compliance checker - /trailofbits:audit-context - Audit context builder Also rename existing fix-review command to trailofbits:fix-review for consistency with the naming convention. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
fd367ad81b |
ci: add lint enforcement with ruff, shellcheck, and shfmt (#10)
* ci: add lint enforcement with ruff, shellcheck, and shfmt Add pre-commit hooks (prek) and GitHub Actions CI to enforce Python and shell linting across the repository. - Add root pyproject.toml with ruff configuration (line-length=100, py311) - Add .pre-commit-config.yaml with ruff, shellcheck, shfmt, and standard hooks - Add .github/workflows/lint.yml with SHA-pinned actions - Update .github/dependabot.yml with root pip ecosystem entry - Fix existing lint violations: - Auto-fix imports and formatting with ruff - Fix duplicate dict key in ct_analyzer (contentequals -> arrays.contentequals) - Add noqa comment for required sys.path manipulation in extract_pdf.py - Format shell scripts with shfmt (case statement indentation) - Add per-file-ignores for ct_analyzer's long opcode tables and style patterns Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use 2-space indentation for shell scripts Change shfmt configuration from 4-space to 2-space indentation to match CLAUDE.md standards. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
695119c312 |
Initial release of Trail of Bits Skills Marketplace
16 plugins for security analysis, smart contract auditing, and verification: Smart Contract Security: - building-secure-contracts - entry-point-analyzer Code Auditing: - audit-context-building - burpsuite-project-parser - differential-review - semgrep-rule-creator - sharp-edges - testing-handbook-skills - variant-analysis Verification: - constant-time-analysis - property-based-testing - spec-to-code-compliance Audit Lifecycle: - fix-review Reverse Engineering: - dwarf-expert Development: - ask-questions-if-underspecified Team Management: - culture-index Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |