Trail of Bits’ taint-trace skill adds the source and sink models your taint analyzer is missing

2 days ago

Trail of Bits' Trailmark plugin 0.13.0 adds taint-trace, a skill that writes the missing source, sink and sanitizer models into your code so an existing taint analyzer such as Psalm, Pysa, Joern, Semgrep or CodeQL can follow input it used to lose. Merged November 12, 2026, for Claude Code and Codex. https://github.com/trailofbits/skills/pull/502 https://github.com/trailofbits/skills/blob/main/plugins/trailmark/skills/taint-trace/SKILL.md https://github.com/trailofbits/skills/blob/main/plugins/trailmark/README.md

Ask about this presentation

Chapters

  1. 0:00Trail of Bits’ taint-trace skill adds the source and sink models your taint analyzer is missing
  2. 0:17Taint analyzers miss flows through code they have no model for
  3. 0:34Trailmark’s graph picks where to annotate, and the analyzer finds the flow
  4. 0:46On URLify, two docblock lines closed the gap
  5. 1:04Psalm now flags the wrapped shell calls and leaves the safe ones clean
  6. 1:18A sanitizer model removes only the threat it actually prevents
  7. 1:33Every model is checked against a run from before it was added
  8. 1:48Psalm leads for PHP, and Joern or Semgrep covers most other languages
  9. 2:04taint-trace ships in Trailmark plugin 0.13.0 for Claude Code and Codex
Show transcript
  1. Trail of Bits' Trailmark plugin gained a taint-trace skill on November 12th. It writes the source and sink annotations your taint analyzer, like Psalm, is missing, then reruns it. The analyzer can then follow user input through your own wrapper functions.

    Slide text
    github.com/trailofbits/skills merged Nov 12, 2026
    transliterate($input) · clean TaintedShell
    trailofbits/skills · PR #502, merged Nov 12, 2026
  2. Psalm, CodeQL and Semgrep follow untrusted input to dangerous calls. When your own sources, sinks, sanitizers and wrappers have no models, the input drops out at that boundary. Marking them is what makes Psalm's taint analysis work.

    Slide text
    github.com/trailofbits/skills trailmark 0.13.0
    sources unmodeled
    sinks unmodeled
    sanitizers unmodeled
    wrappers unmodeled

    Psalm’s taint analysis works far better when the codebase marks its sources and sinks.

    trailofbits/skills · PR #502 description
  3. Trailmark maps callers, callees and unresolved calls around each dangerous sink. That graph only shows what can call what. So every taint claim needs a finding from the analyzer's own run.

    Slide text
    github.com/trailofbits/skills taint-trace/SKILL.md
    Trailmark
    call graph callers, callees and unresolved calls around each sink
    your analyzer
    taint findings every reported flow comes from its own run
    taint-trace/SKILL.md · Required Graph Evidence, Rationalizations to Reject
  4. Psalm lost track of user input passed through transliterate, a function in the PHP library URLify. The skill added one annotation saying input flows to the return value, and one keeping each call site separate.

    Slide text
    github.com/trailofbits/skills reviewer walkthrough · Psalm 7.1.0
    URLify::transliterate() docblock
    + @psalm-flow ($string) -> return
    + @psalm-taint-specialize

    Neither line sanitizes data or declares the method pure.

    PR #502 · reviewer walkthrough on voku/urlify
  5. Psalm then caught two shell calls it had missed, including one escaped for HTML instead of the shell. The literal string and the shell-escaped call stayed clean, and every earlier warning was still there.

    Slide text
    github.com/trailofbits/skills reviewer walkthrough · Psalm 7.1.0
    shell_exec( … ) before after
    transliterate($input) missed TaintedShell
    transliterate('literal') clean clean
    $input TaintedShell TaintedShell
    escapeshellarg(transliterate($input)) clean clean
    htmlspecialchars(transliterate($input)) missed TaintedShell
    PR #502 · reviewer walkthrough, report: 2 new warnings, 0 removed
  6. Naming a function sanitize doesn't make it a sanitizer, and neither does a boolean check. If the analyzer can't tell an HTML context from a shell context, the skill leaves the sanitizer out and says so.

    Slide text
    github.com/trailofbits/skills taint-trace/SKILL.md
    HTML escaping does not sanitize SQL or shell commands.

    When the analyzer can’t express the difference, the skill leaves the sanitizer out and records the limitation.

    taint-trace/SKILL.md · Model Contracts
  7. First the skill proves the analyzer flags a known vulnerable case, ignores a literal string, and records a baseline. Then it runs again with the new models. Without that comparison, the benefit is reported as unmeasured.

    Slide text
    github.com/trailofbits/skills taint-trace/SKILL.md
    1 vulnerable fixture is flagged
    2 literal fixture stays clean
    3 baseline run
    4 run with the new models

    With no before-and-after comparison, a model’s benefit is reported as unmeasured.

    taint-trace/SKILL.md · Native Execution and Artifacts
  8. Each language gets an order of analyzers to try: Psalm for PHP, Pysa for Python, Clang for C. Rust and Dart get only Semgrep's single-function analysis. Psalm 5, 6 and 7 pass the skill's CI tests.

    Slide text
    github.com/trailofbits/skills references/catalog.md
    PHP Psalm → Joern → Semgrep CE
    Python Pysa → Joern → Semgrep CE
    C, C++ Clang → Infer Pulse → Joern → Semgrep CE
    Java, JS, TS, Go, Ruby Joern → Semgrep CE
    Rust, Dart Semgrep CE, single function only
    CI ✓ Psalm 5.26.1 · 6.20.10 · 7.0.0 · 7.1.0 on PHP 8.3
    taint-trace/references/catalog.md · PR #502 validation
  9. It's in Trailmark plugin 0.13.0 for Claude Code and Codex, with the Trailmark Python package, version 0.8. Analyzers are separate installs. I'd start with Psalm on a PHP project, the most tested route. Setup is in the plugin's readme.

    Slide text
    github.com/trailofbits/skills trailmark 0.13.0
    0.13.0 Trailmark plugin
    trailmark 0.8 Python package, from the index
    your analyzer installed separately

    Psalm, Pysa, Joern and the other analyzers are separate installs.

    plugins/trailmark/README.md · PR #502

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built with ❤️ in New York