Threat modeling that lives with your code.

The dragon, evolved โ€” threat models as code.

Wyrm takes OWASP Threat Dragon's mission and moves it into your codebase. A threat model is a plain Open Threat Model file in the repo โ€” versioned, diff-reviewable, and enforced in CI. Wyrm parses it, validates it, runs a STRIDE rule engine over it, and renders a data-flow diagram from it.

See it work

This model โ€” three trust zones, a cleartext login flow โ€” is a real .threatmodel/example.otm.yaml. Wyrm renders the diagram and finds the flaws.

Rendered from the model  ยท  wyrm diagram
flowchart LR
  subgraph tz_internet["Internet"]
    browser["User Browser"]
  end
  subgraph tz_dmz["DMZ"]
    web_app["Web Application"]
  end
  subgraph tz_private["Private Network"]
    auth_db["Auth Database"]
  end
  browser -->|"Submit login"| web_app
  web_app -->|"Verify credentials"| auth_db
      
STRIDE findings  ยท  wyrm analyze
CRIT InformationDisclosure Sensitive data crosses a trust boundary in cleartext (Submit login) WYRM-T001 โ€” confidential data leaves one trust zone for another without transport encryption. โ†ณ Enforce TLS (or mTLS) on this flow; tag it tls/https once encrypted. HIGH Tampering Unencrypted flow across a trust boundary (Submit login) WYRM-T002 โ€” traffic leaving a trust zone unencrypted can be read or modified in transit. MED Spoofing External entity flow lacks authentication (Submit login) MED Spoofing External entity flow lacks authentication (Verify credentials) 4 finding(s). โ€” exits non-zero on HIGH+, failing the build.

In your editor

Editors run the same engine as a language server: open a *.otm.yaml and findings appear inline as you type. Same diagnostics in the editor and in CI โ€” they never disagree. Integrations for Zed, VS Code, IntelliJ, and Obsidian โ€” see the editor setup guide.

Wyrm diagnostics inline in the Zed editor
Inline STRIDE diagnostics in Zed. (screenshot pending โ€” see the repo's SCREENSHOTS.md)

Getting started

  1. Install the CLI

    cargo install --git https://github.com/1ARdotNO/wyrm wyrm-cli
  2. Add a model โ€” or generate one

    Drop a model in .threatmodel/ at your repo root so it lives with the code:

    mkdir -p .threatmodel
    $EDITOR .threatmodel/system.otm.yaml

    Already have a docker-compose.yml? Generate a baseline from your topology, then annotate it:

    wyrm init            # โ†’ .threatmodel/<project>.otm.yaml (services, zones, flows)
  3. Analyze & diagram

    wyrm validate     # structural checks
    wyrm analyze      # STRIDE findings (exits non-zero on HIGH+)
    wyrm diagram      # Mermaid data-flow diagram
  4. Gate it in CI

    - run: cargo install --git https://github.com/1ARdotNO/wyrm wyrm-cli
    - run: wyrm analyze            # fails the build on a real design flaw

Writing a model

A model is OTM โ€” trust zones, assets, components, and dataflows. Tag a flow tls and the encryption findings clear.

otmVersion: 0.2.0
project: { id: shop, name: Shop }

trustZones:
  - { id: tz-internet, name: Internet,  risk: { trustRating: 10 } }
  - { id: tz-private,  name: Private,   risk: { trustRating: 80 } }

assets:
  - id: pii
    name: Customer PII
    risk: { confidentiality: 100, integrity: 80, availability: 50 }

components:
  - { id: api, name: API, type: web-service, parent: { trustZone: tz-internet },
      assets: { processed: [pii] } }
  - { id: db,  name: DB,  type: database,    parent: { trustZone: tz-private } }

dataflows:
  - { id: save, name: Save profile, source: api, destination: db,
      assets: [pii], tags: [tls] }

Run it in CI

Pull a prebuilt binary from a release and wire wyrm into your PRs. Each push regenerates the model from your infra, merges in any mitigations a reviewer added (they're never clobbered), commits the refresh back, and fails the check if a high/critical finding is unresolved.

name: Threat model
on: pull_request

permissions:
  contents: write        # to commit the refreshed model back to the PR

jobs:
  wyrm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Install wyrm
        run: |
          curl -sSL https://github.com/1ARdotNO/wyrm/releases/latest/download/wyrm-x86_64-unknown-linux-gnu.tar.gz | tar -xz
          mkdir -p "$HOME/.local/bin" && mv wyrm "$HOME/.local/bin/"
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Refresh the threat model (keeps your mitigations)
        run: wyrm init --from k8s/ --output .threatmodel/app.otm.yaml

      - name: Commit model updates
        run: |
          git config user.name  "wyrm[bot]"
          git config user.email "wyrm@users.noreply.github.com"
          git add .threatmodel/
          git diff --cached --quiet || { git commit -m "chore: refresh threat model"; git push; }

      - name: Gate on findings
        run: wyrm analyze .threatmodel/app.otm.yaml --fail-on high

Make Gate on findings a required status check. The severity is configurable (--fail-on medium|high|critical). When a finding blocks, a reviewer pulls the branch, adds the mitigation (a tls tag, an auth control, a top-level mitigation), and pushes โ€” the next run merges their edit, the finding clears, and the check goes green. No drawing, no separate tool: the threat model rides along with the change.

Why not just useโ€ฆ?

EditingStorage formatModel sourceIn-repo / CI gateEngine
Threat DragonGUI canvastool-specific JSONhand-drawnnomanual
pytmPython programimperative .pyhand-codedpartialPython threat lib
Threagiletext (YAML)own YAML schemahand-writtenyesGo rules
wyrmyour editor (text)OTM (standard)auto-detected + reconciledyesRust, data-driven

OTM is a published, platform-independent standard, so a wyrm model isn't locked to wyrm. The STRIDE catalogue is data โ€” grow it without recompiling.

Roadmap

Attribution

Wyrm is MIT licensed.