Threat Model

Web App

Browser, API, database: the shape most systems actually are.

Threat Model — Web App: rendered example
Rendered by Gnomon from the source below. No edits.

The source

63 lines of PlantUML, and pulls in C4-PlantUML and Threat modelling. Copy it, or open the template inside Gnomon and render it as it is.

@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
!include <threat/ThreatModel>

LAYOUT_TOP_DOWN()
hide stereotype
skinparam LegendFontColor #334155
skinparam LegendBackgroundColor #F8FAFC
skinparam LegendBorderColor #CBD5E1

AddBoundaryTag("zone_saas", $bgColor="#F3E5F5", $fontColor="#4A148C", $borderColor="#6A1B9A", $borderStyle="dashed", $legendText="Contracted SaaS — outside our perimeter")
AddBoundaryTag("zone_app", $bgColor="#DBEAFE", $fontColor="#1E3A8A", $borderColor="#2563EB", $borderStyle="solid", $legendText="Our cloud account — application")
AddBoundaryTag("zone_data", $bgColor="#FEF3C7", $fontColor="#78350F", $borderColor="#D97706", $borderStyle="solid", $legendText="Our cloud account — restricted services")

title Customer portal — trust boundaries (C2)

Person(user, "Customer", "Zone 1 — public internet")

TrustBoundary(edge, "Zone 2 — Contracted SaaS", "low", $tags="zone_saas") {
  Container_Ext(waf, "CDN / WAF", "Cloudflare", "Edge filtering, TLS termination and rate limiting")
  System_Ext(mail, "Email Provider", "Delivers transactional notifications")
}

TrustBoundary(dmz, "Zone 3 — Product application", "medium", $tags="zone_app") {
  Container(web, "Web App", "Next.js", "Server-rendered customer portal", $tags="internet_facing+authn_oauth2")
  Container(api, "API", "Node.js", "Tenant authorisation and business logic", $tags="authn_oauth2+multi_tenant")
}

TrustBoundary(core, "Zone 4 — Restricted services", "high", $tags="zone_data") {
  ContainerDb(db, "App Database", "PostgreSQL", "Customer records; encrypted at rest and audited", $tags="pii+encrypted_at_rest+audit_logged")
  ContainerQueue(jobs, "Job Queue", "RabbitMQ", "Background work; transport control TBC")
  Container(admin, "Admin Console", "React", "Privileged support access; OIDC and MFA", $tags="admin+mfa+authn_oauth2")
}

' Every cross-zone flow names its control, or the control still to confirm.
Rel_R(user, waf, "Browses the customer portal", "HTTPS — Z1→Z2", $tags="encrypted")
Rel_R(waf, web, "Forwards filtered traffic", "HTTPS — Z2→Z3; origin ACL TBC", $tags="encrypted")
Rel_D(web, api, "Calls with customer token", "HTTPS / OAuth 2.0 — Z3 internal", $tags="encrypted")
Rel(api, db, "Reads/writes tenant records", "SQL — Z3→Z4; transport TBC")
Rel(api, jobs, "Enqueues background work", "AMQP — Z3→Z4; transport TBC")
Rel_R(admin, db, "Queries via read-only role", "SQL — Z4 internal; transport TBC")
Rel_L(api, mail, "Sends notifications", "HTTPS — Z3→Z2; scoped API key", $tags="encrypted")

Lay_D(waf, mail)
Lay_R(admin, db)
Lay_R(db, jobs)

legend bottom left
|= Notation |= Meaning |
| <back:#F3E5F5> Zone 2 </back> | Contracted SaaS; low trust |
| <back:#DBEAFE> Zone 3 </back> | Our application; medium trust |
| <back:#FEF3C7> Zone 4 </back> | Our restricted services; high trust |
| <back:#FEF3C7> Amber data </back> | Contains PII |
| <color:#DC2626>Red element outline</color> | Internet-facing |
| <color:#16A34A>Green flow</color> / grey flow | Encrypted / transport unverified |
endlegend

footer
Dashed zone: contracted SaaS. Solid zones: services in our cloud account. Zone 1 (public internet) is implicit.
TBC = a control requiring confirmation. Database and queue transport are unverified; no encryption is assumed.
Scope: customer requests, background jobs and support access. Identity provider and job workers are omitted.
end footer
@enduml

Render this offline

This template ships in Gnomon and renders on your machine, with no account and no diagram uploads. The browser editor is free and needs no install.

Get GnomonOpen the browser editor

Others in Threat model diagrams

Analytics cookies (optional)

May we use Google Analytics to understand visits, product links and feature use? If you accept, Google receives usage and device information, including your IP address, and sets analytics cookies. We do not send your documents, filenames or account details. Advertising features are disabled.

Analytics is off until you accept. Your choice is saved on this browser for 180 days using necessary local storage. You can change it at any time in Analytics preferences. Closing this message rejects analytics. Read our privacy policy.