How it works
wyrm init # auto-detect compose in the current dir wyrm init --from k8s/ # a directory of Kubernetes manifests wyrm init --from main.tf -o - # print to stdout wyrm analyze # then find the threats in the baseline
It writes .threatmodel/<project>.otm.yaml. Two principles guide
every rule:
- Defaults favor exposure. If a load balancer's
internalflag is absent, it's treated as internet-facing β because that's the provider default. wyrm errs toward flagging exposure, not hiding it. - A WAF is a mitigation, not a wall. Detecting Cloudflare, Cloud Armor,
or a Next-Gen WAF attaches a control to the ingress flow and downgrades the
finding β it never makes it disappear. Detect-only rules (
action = "log") don't count.
What it detects
Status: shipped building planned
Docker Compose shipped
| Signal | Becomes |
|---|---|
| service | component (type inferred from image) |
published ports | internet ingress dataflow |
depends_on | dataflow between services |
| image is postgres/mysql/redis/β¦ | database / data-store in a data tier |
Kubernetes & Istio shipped
| Signal | Becomes |
|---|---|
Service type LoadBalancer / NodePort | internet-facing component |
Service type ClusterIP | internal component |
Ingress (+ backends) | edge entrypoint + dataflows; tls β encrypted |
Istio Gateway / VirtualService | mesh edge + routing dataflows |
loadBalancerSourceRanges | mitigation (source-IP allowlist) |
NetworkPolicy, Istio AuthorizationPolicy / mTLS | trust boundary / mitigation |
| CNI: Flannel vs Calico/Cilium | whether NetworkPolicy is actually enforced |
Terraform β AWS / GCP / Azure shipped
| Signal | Becomes |
|---|---|
AWS aws_lb.internal = false, API Gateway | internet-facing LB / API |
AWS aws_security_group ingress 0.0.0.0/0 | world-open port |
GCP forwarding_rule EXTERNAL*, public GKE | internet exposure |
GCP iap {}, master_authorized_networks | mitigation (auth / allowlist) |
Azure public frontend_ip_configuration, azurerm_public_ip | internet-facing frontend |
| VPC / VNet / subnets | trust zones |
WAF, DDoS, reverse proxies & access control planned
| Signal | Becomes |
|---|---|
| Cloudflare managed/custom WAF, rate limiting, bot management | mitigation (mode: block vs log) |
| Fastly WAF / Next-Gen WAF (Signal Sciences) | mitigation (+ coverage %) |
| AWS WAFv2 & Shield, GCP Cloud Armor, Azure WAF & DDoS | mitigation on the ingress flow |
Pangolin, Cloudflare Tunnel (cloudflared), ngrok | tunneled edge β no direct internet port + auth mitigation |
Authelia, Authentik, oauth2-proxy, Keycloak, Pomerium; Traefik forwardAuth | mitigation (authentication / SSO) |
Istio oauth2 ext-authz / JWT RequestAuthentication | mitigation (auth at the gateway) |
fail2ban, CrowdSec; IP allowlists (Traefik ipAllowList, nginx allow/deny) | mitigation (intrusion prevention / source allowlist) |
| Tailscale tailnet / subnet router | private trust zone (not internet-exposed) |
tailscale funnel | deliberate internet exposure |
The full signalβOTM mapping, with exact resource/attribute names, lives in DETECTION.md. Want a source wyrm doesn't cover yet? Issue #12 tracks the roadmap.
What it can't detect (you add these)
A generated baseline captures structure. It cannot know sensitivity β
which flows carry PII, credentials, or regulated data. That's the review step: open the
generated model, add assets with confidentiality ratings and attach them to the
right components and dataflows, then re-run wyrm analyze. The topology is the
90% wyrm does for you; the sensitivity is the 10% only you know.