Section 10

Continuous & Automated TRA

Traditional TRAs are periodic snapshots — but architectures change constantly. This section covers threat modeling as code, CI/CD pipeline integration, risk-as-code policies, and continuous risk monitoring with automated re-assessment triggers.

Related Content

See Secure SDLC Integration for embedding security into the development lifecycle. This section focuses specifically on automating the TRA process.

Threat Modeling as Code

Threat-modeling-as-code tools define system architecture in code and automatically generate threat models — making them version-controlled, reviewable, and CI/CD-integrated.

pytm (Python)

Define systems in Python, auto-generate DFDs and STRIDE threats.

  • • Pythonic DSL for system modeling
  • • Automatic STRIDE threat generation
  • • DFD diagram output (PlantUML/Graphviz)
  • • JSON/HTML report generation
  • • Open source (OWASP project)

Threagile (YAML)

YAML-based agile threat modeling with risk tracking.

  • • YAML model definition (no code required)
  • • Built-in risk rules engine
  • • PDF/Excel risk report generation
  • • Data flow and architecture diagrams
  • • Docker-based execution

MS Threat Modeling Tool

Microsoft's GUI-based tool with Azure integration.

  • • Visual DFD editor
  • • Azure/cloud-specific threat templates
  • • Built-in mitigation suggestions
  • • HTML report generation
  • • Free (Windows only)

pytm Example

payment_threat_model.py
python
"""
pytm Threat Model: Payment API Service
Generates STRIDE threats and DFD automatically.
"""
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor

# Define the threat model
tm = TM("Payment API - Threat Model")
tm.description = "TRA for payment processing microservice"
tm.isOrdered = True

# Trust boundaries
internet = Boundary("Internet")
dmz = Boundary("DMZ")
internal = Boundary("Internal Network")
db_zone = Boundary("Database Zone")

# External entities
customer = Actor("Customer")
customer.inBoundary = internet

payment_provider = Actor("Payment Provider (Stripe)")
payment_provider.inBoundary = internet

# Processes
api_gateway = Server("API Gateway")
api_gateway.inBoundary = dmz
api_gateway.protocol = "HTTPS"
api_gateway.isEncrypted = True
api_gateway.sanitizesInput = True

payment_service = Server("Payment Service")
payment_service.inBoundary = internal
payment_service.protocol = "gRPC"
payment_service.isEncrypted = True
payment_service.hasAccessControl = True
payment_service.authorizesSource = True

# Data stores
payment_db = Datastore("Payment Database")
payment_db.inBoundary = db_zone
payment_db.isEncrypted = True
payment_db.isSQL = True
payment_db.hasAccessControl = True

audit_log = Datastore("Audit Log (Immutable)")
audit_log.inBoundary = db_zone
audit_log.isEncrypted = True

# Data flows
customer_to_gw = Dataflow(customer, api_gateway, "Payment Request")
customer_to_gw.protocol = "HTTPS"
customer_to_gw.isEncrypted = True
customer_to_gw.sanitizesInput = True

gw_to_service = Dataflow(api_gateway, payment_service, "Validated Request")
gw_to_service.protocol = "gRPC + mTLS"
gw_to_service.isEncrypted = True

service_to_db = Dataflow(payment_service, payment_db, "Transaction Record")
service_to_db.protocol = "TLS"
service_to_db.isEncrypted = True

service_to_provider = Dataflow(
    payment_service, payment_provider, "Payment Authorization"
)
service_to_provider.protocol = "HTTPS"
service_to_provider.isEncrypted = True

service_to_audit = Dataflow(
    payment_service, audit_log, "Audit Event"
)
service_to_audit.protocol = "TLS"
service_to_audit.isEncrypted = True

# Generate threats and DFD
tm.process()
# Output: DFD diagram + STRIDE threat list + HTML report
"""
pytm Threat Model: Payment API Service
Generates STRIDE threats and DFD automatically.
"""
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor

# Define the threat model
tm = TM("Payment API - Threat Model")
tm.description = "TRA for payment processing microservice"
tm.isOrdered = True

# Trust boundaries
internet = Boundary("Internet")
dmz = Boundary("DMZ")
internal = Boundary("Internal Network")
db_zone = Boundary("Database Zone")

# External entities
customer = Actor("Customer")
customer.inBoundary = internet

payment_provider = Actor("Payment Provider (Stripe)")
payment_provider.inBoundary = internet

# Processes
api_gateway = Server("API Gateway")
api_gateway.inBoundary = dmz
api_gateway.protocol = "HTTPS"
api_gateway.isEncrypted = True
api_gateway.sanitizesInput = True

payment_service = Server("Payment Service")
payment_service.inBoundary = internal
payment_service.protocol = "gRPC"
payment_service.isEncrypted = True
payment_service.hasAccessControl = True
payment_service.authorizesSource = True

# Data stores
payment_db = Datastore("Payment Database")
payment_db.inBoundary = db_zone
payment_db.isEncrypted = True
payment_db.isSQL = True
payment_db.hasAccessControl = True

audit_log = Datastore("Audit Log (Immutable)")
audit_log.inBoundary = db_zone
audit_log.isEncrypted = True

# Data flows
customer_to_gw = Dataflow(customer, api_gateway, "Payment Request")
customer_to_gw.protocol = "HTTPS"
customer_to_gw.isEncrypted = True
customer_to_gw.sanitizesInput = True

gw_to_service = Dataflow(api_gateway, payment_service, "Validated Request")
gw_to_service.protocol = "gRPC + mTLS"
gw_to_service.isEncrypted = True

service_to_db = Dataflow(payment_service, payment_db, "Transaction Record")
service_to_db.protocol = "TLS"
service_to_db.isEncrypted = True

service_to_provider = Dataflow(
    payment_service, payment_provider, "Payment Authorization"
)
service_to_provider.protocol = "HTTPS"
service_to_provider.isEncrypted = True

service_to_audit = Dataflow(
    payment_service, audit_log, "Audit Event"
)
service_to_audit.protocol = "TLS"
service_to_audit.isEncrypted = True

# Generate threats and DFD
tm.process()
# Output: DFD diagram + STRIDE threat list + HTML report

Threagile YAML Model

threagile-model.yaml
yaml
# Threagile YAML Model: Payment Microservice
# Run: docker run --rm -v "$(pwd)":/app/work threagile/threagile -model /app/work/model.yaml

threagile_version: 1.0.0

title: Payment Service TRA
date: 2025-01-15

business_criticality: critical

technical_assets:
  api-gateway:
    type: process
    usage: business
    technology: kong
    internet: true
    encryption: transparent
    communication_links:
      payment-service-link:
        target: payment-service
        protocol: grpc
        authentication: certificate
        encryption: transparent

  payment-service:
    type: process
    usage: business
    technology: go
    internet: false
    encryption: transparent
    data_assets_processed:
      - cardholder-data
      - transaction-records
    communication_links:
      db-link:
        target: payment-db
        protocol: postgresql
        authentication: credentials
        encryption: transparent
      stripe-link:
        target: stripe-api
        protocol: https
        authentication: token
        encryption: transparent

  payment-db:
    type: datastore
    usage: business
    technology: postgresql
    internet: false
    encryption: data-with-symmetric-shared-key
    data_assets_stored:
      - cardholder-data
      - transaction-records

data_assets:
  cardholder-data:
    usage: business
    quantity: many
    confidentiality: strictly-confidential
    integrity: critical
    availability: critical

  transaction-records:
    usage: business
    quantity: many
    confidentiality: confidential
    integrity: critical
    availability: critical

trust_boundaries:
  dmz:
    type: network-cloud-security-group
    technical_assets_inside:
      - api-gateway

  internal:
    type: network-cloud-security-group
    technical_assets_inside:
      - payment-service

  data-zone:
    type: network-cloud-security-group
    technical_assets_inside:
      - payment-db
# Threagile YAML Model: Payment Microservice
# Run: docker run --rm -v "$(pwd)":/app/work threagile/threagile -model /app/work/model.yaml

threagile_version: 1.0.0

title: Payment Service TRA
date: 2025-01-15

business_criticality: critical

technical_assets:
  api-gateway:
    type: process
    usage: business
    technology: kong
    internet: true
    encryption: transparent
    communication_links:
      payment-service-link:
        target: payment-service
        protocol: grpc
        authentication: certificate
        encryption: transparent

  payment-service:
    type: process
    usage: business
    technology: go
    internet: false
    encryption: transparent
    data_assets_processed:
      - cardholder-data
      - transaction-records
    communication_links:
      db-link:
        target: payment-db
        protocol: postgresql
        authentication: credentials
        encryption: transparent
      stripe-link:
        target: stripe-api
        protocol: https
        authentication: token
        encryption: transparent

  payment-db:
    type: datastore
    usage: business
    technology: postgresql
    internet: false
    encryption: data-with-symmetric-shared-key
    data_assets_stored:
      - cardholder-data
      - transaction-records

data_assets:
  cardholder-data:
    usage: business
    quantity: many
    confidentiality: strictly-confidential
    integrity: critical
    availability: critical

  transaction-records:
    usage: business
    quantity: many
    confidentiality: confidential
    integrity: critical
    availability: critical

trust_boundaries:
  dmz:
    type: network-cloud-security-group
    technical_assets_inside:
      - api-gateway

  internal:
    type: network-cloud-security-group
    technical_assets_inside:
      - payment-service

  data-zone:
    type: network-cloud-security-group
    technical_assets_inside:
      - payment-db

CI/CD Integration

Integrate threat modeling into your pipeline to catch architectural risk regressions before they reach production.

.github/workflows/threat-model.yml
yaml
# GitHub Actions Workflow: Automated Threat Model Check
name: Threat Model Validation
on:
  pull_request:
    paths:
      - 'architecture/**'
      - 'threat-model/**'
      - 'infrastructure/**'

jobs:
  threat-model:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write

    steps:
      - uses: actions/checkout@v4

      - name: Run Threagile Analysis
        run: |
          docker run --rm \
            -v "${{ github.workspace }}/threat-model:/app/work" \
            threagile/threagile \
            -model /app/work/model.yaml \
            -output /app/work/output

      - name: Check for High/Critical Risks
        run: |
          # Parse Threagile JSON output for risk severity
          HIGH_RISKS=$(jq '[.identified_risks[] |
            select(.severity == "high" or
                   .severity == "critical")] |
            length' threat-model/output/risks.json)

          if [ "$HIGH_RISKS" -gt 0 ]; then
            echo "::error::Found $HIGH_RISKS high/critical risks"
            echo "## Threat Model: $HIGH_RISKS Risks Found" >> $GITHUB_STEP_SUMMARY
            jq -r '.identified_risks[] |
              select(.severity == "high" or
                     .severity == "critical") |
              "- **(.severity)**: (.title)"' \
              threat-model/output/risks.json >> $GITHUB_STEP_SUMMARY
            exit 1
          fi

      - name: Upload Threat Report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: threat-model-report
          path: threat-model/output/
# GitHub Actions Workflow: Automated Threat Model Check
name: Threat Model Validation
on:
  pull_request:
    paths:
      - 'architecture/**'
      - 'threat-model/**'
      - 'infrastructure/**'

jobs:
  threat-model:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write

    steps:
      - uses: actions/checkout@v4

      - name: Run Threagile Analysis
        run: |
          docker run --rm \
            -v "${{ github.workspace }}/threat-model:/app/work" \
            threagile/threagile \
            -model /app/work/model.yaml \
            -output /app/work/output

      - name: Check for High/Critical Risks
        run: |
          # Parse Threagile JSON output for risk severity
          HIGH_RISKS=$(jq '[.identified_risks[] |
            select(.severity == "high" or
                   .severity == "critical")] |
            length' threat-model/output/risks.json)

          if [ "$HIGH_RISKS" -gt 0 ]; then
            echo "::error::Found $HIGH_RISKS high/critical risks"
            echo "## Threat Model: $HIGH_RISKS Risks Found" >> $GITHUB_STEP_SUMMARY
            jq -r '.identified_risks[] |
              select(.severity == "high" or
                     .severity == "critical") |
              "- **(.severity)**: (.title)"' \
              threat-model/output/risks.json >> $GITHUB_STEP_SUMMARY
            exit 1
          fi

      - name: Upload Threat Report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: threat-model-report
          path: threat-model/output/

Risk-as-Code Policies

Codify risk policies so they are version-controlled, testable, and automatically enforced. Use Open Policy Agent (OPA) or custom scripts to evaluate risk conditions.

risk_policies.rego
rego
# Risk-as-Code Policy (Rego / Open Policy Agent)
# Evaluates architecture decisions against risk policies

package tra.risk_policies

# Deny unencrypted data stores containing sensitive data
deny[msg] {
    some store in input.data_stores
    store.classification in ["confidential", "restricted"]
    not store.encryption_at_rest
    msg := sprintf(
        "RISK: Data store '%s' contains %s data without encryption at rest",
        [store.name, store.classification]
    )
}

# Deny internet-facing services without authentication
deny[msg] {
    some service in input.services
    service.internet_facing
    not service.authentication_required
    msg := sprintf(
        "RISK: Internet-facing service '%s' has no authentication",
        [service.name]
    )
}

# Warn on single-tenant database without backup encryption
warn[msg] {
    some store in input.data_stores
    store.business_criticality == "critical"
    not store.backup_encryption
    msg := sprintf(
        "WARN: Critical data store '%s' backups are not encrypted",
        [store.name]
    )
}

# Deny services processing PII without audit logging
deny[msg] {
    some service in input.services
    service.processes_pii
    not service.audit_logging_enabled
    msg := sprintf(
        "RISK: Service '%s' processes PII without audit logging",
        [service.name]
    )
}
# Risk-as-Code Policy (Rego / Open Policy Agent)
# Evaluates architecture decisions against risk policies

package tra.risk_policies

# Deny unencrypted data stores containing sensitive data
deny[msg] {
    some store in input.data_stores
    store.classification in ["confidential", "restricted"]
    not store.encryption_at_rest
    msg := sprintf(
        "RISK: Data store '%s' contains %s data without encryption at rest",
        [store.name, store.classification]
    )
}

# Deny internet-facing services without authentication
deny[msg] {
    some service in input.services
    service.internet_facing
    not service.authentication_required
    msg := sprintf(
        "RISK: Internet-facing service '%s' has no authentication",
        [service.name]
    )
}

# Warn on single-tenant database without backup encryption
warn[msg] {
    some store in input.data_stores
    store.business_criticality == "critical"
    not store.backup_encryption
    msg := sprintf(
        "WARN: Critical data store '%s' backups are not encrypted",
        [store.name]
    )
}

# Deny services processing PII without audit logging
deny[msg] {
    some service in input.services
    service.processes_pii
    not service.audit_logging_enabled
    msg := sprintf(
        "RISK: Service '%s' processes PII without audit logging",
        [service.name]
    )
}

Continuous Risk Monitoring

Define Key Risk Indicators (KRIs) that trigger automated re-assessment when thresholds are exceeded.

KRI Source Threshold Action
Architecture changes Git commits to architecture/ folder Any merge to main Trigger threat model diff
New critical CVEs Dependency scanning (Dependabot/Snyk) CVSS ≥ 9.0 on in-scope component Trigger vulnerability reassessment
Failed security scans SAST/DAST/IaC scanning pipeline > 5 high findings in sprint Flag for risk review
Cloud posture drift CSPM (Prowler, Prisma, Defender) Critical finding on production Escalate to risk owner
Threat intelligence CTI feeds, ISAC alerts Active exploitation of relevant TTP Trigger threat landscape update
Vendor incidents Vendor security advisories Tier 1 vendor security incident Trigger supply chain reassessment

Continuous TRA Feedback Loop

flowchart LR DEV["Architecture\nChanges"] --> CI["CI/CD\nPipeline"] CI --> TMC["Threat Model\nas Code"] TMC --> RISK["Risk\nEvaluation"] RISK --> REG["Risk\nRegister"] REG --> MON["Continuous\nMonitoring"] MON -->|"KRI breach"| DEV MON -->|"Threat intel"| TMC style DEV fill:#ff8800,stroke:#000,color:#000 style CI fill:#22d3ee,stroke:#000,color:#000 style TMC fill:#a855f7,stroke:#000,color:#000 style RISK fill:#ec4899,stroke:#000,color:#000 style REG fill:#ff8800,stroke:#000,color:#000 style MON fill:#22d3ee,stroke:#000,color:#000

Section Summary

Key Takeaways

  • • pytm and Threagile enable version-controlled threat models
  • • CI/CD integration catches architectural risk regressions automatically
  • • Risk-as-code with OPA/Rego policies enables automated enforcement
  • • KRIs trigger re-assessment when risk conditions change
  • • Continuous TRA creates a feedback loop, not a one-time document