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.
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
wyrm analyzetls/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.
Getting started
-
Install the CLI
cargo install --git https://github.com/1ARdotNO/wyrm wyrm-cli
-
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)
-
Analyze & diagram
wyrm validate # structural checks wyrm analyze # STRIDE findings (exits non-zero on HIGH+) wyrm diagram # Mermaid data-flow diagram
-
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โฆ?
| Editing | Storage format | Model source | In-repo / CI gate | Engine | |
|---|---|---|---|---|---|
| Threat Dragon | GUI canvas | tool-specific JSON | hand-drawn | no | manual |
| pytm | Python program | imperative .py | hand-coded | partial | Python threat lib |
| Threagile | text (YAML) | own YAML schema | hand-written | yes | Go rules |
| wyrm | your editor (text) | OTM (standard) | auto-detected + reconciled | yes | Rust, 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
- shipped OTM core engine โ parse, validate, STRIDE rules, Mermaid render
- shipped
wyrmCLI + CI gate - shipped
wyrm-lsplanguage server - shipped Zed extension
- shipped Obsidian plugin โ render
otmblocks as diagram + findings - shipped
wyrm initโ baseline model from docker-compose - next Obsidian JSON Canvas โ OTM (visual authoring)
- later Browser sidecar editor
Attribution
- Open Threat Model (OTM) โ the file format wyrm reads and writes.
- OWASP pytm (MIT) โ the STRIDE threat library adapted into wyrm's rule catalogue.
- OWASP Threat Dragon โ the mission and inspiration.
- Mermaid โ diagram rendering on this page.
- lsp-server / lsp-types โ the language-server plumbing.
- CI/security philosophy modeled on the author's jync project โ CI as the safety net, aggressive Renovate automerge.
Wyrm is MIT licensed.