Skip to content

Addon System

Eric Fitzgerald edited this page Apr 5, 2026 · 5 revisions

Addon System

This page explains the TMI addon system, which allows administrators to register webhook-based extensions that enhance threat modeling workflows.

Overview

What are addons?

Addons are built on TMI's Webhook-Integration, extending it beyond automatic event-driven notifications. While standard webhooks fire automatically in response to threat model changes, addons can be:

  • Manually invoked by users through the Actions/Addons menus in TMI-UX — for both threat models and individual entities within them
  • Triggered automatically by threat model changes (like standard webhooks)
  • Called programmatically by external automation via the TMI API

Example addons:

  • STRIDE analysis automation
  • Compliance framework checking
  • Threat intelligence enrichment
  • Diagram validation
  • Custom report generation
  • AI-powered threat suggestions

Key characteristics:

  • Administrator-registered: Only admins can create/delete addons
  • User-invoked: Any authenticated user can trigger addons
  • Asynchronous: Long-running operations with status updates
  • Webhook-based: External services receive invocation requests
  • Rate-limited: Prevents abuse with per-user quotas

Architecture

flowchart TD
    A[User Invokes Addon<br/><i>via TMI-UX menu, API call,<br/>or automated trigger</i>] --> B[TMI Server]
    B --> C{Rate Limit Check<br/><i>Redis</i>}
    C -->|Allowed| D[Create Invocation<br/><i>Redis</i>]
    C -->|Exceeded| E[429 Too Many Requests]
    D --> F[Addon Worker<br/>HTTP POST]
    F --> G[External Service]
    G --> H[Process Asynchronously<br/><i>Security analysis,<br/>augmentation, etc.</i>]
    H --> I[POST /webhook-deliveries/&#123;id&#125;/status<br/><i>Progress updates</i>]
    H --> J[POST/PUT/PATCH<br/>/threat_model/&#123;id&#125;/...<br/><i>Write results back to TMI</i>]
    I --> K[Update Status<br/><i>Redis</i>]
    K --> L[User Polls Status]
Loading

Components:

  1. Addons Table (database): Addon registrations
  2. Invocation Store (Redis): Ephemeral invocation state (7-day TTL)
  3. Rate Limiter (Redis): Per-user quotas
  4. Addon Worker: Delivers invocation requests to webhooks
  5. Status Update Handler: Receives status updates from external services

For Users

Discovering and Invoking Addons in TMI-UX

Most users will interact with addons through the TMI-UX interface rather than the API directly.

From a threat model: Open a threat model, then use the Actions menu (or Addons submenu) to see addons available for the threat model as a whole.

From an entity: When viewing an entity within a threat model (e.g., an asset, threat, or diagram), use the entity's Actions/Addons menu to invoke addons scoped to that entity type. Only addons whose objects list includes the entity type will appear.

Each addon displays its name and icon in the menu. When invoked, TMI-UX shows the addon's description and any parameter fields the addon requires. After invocation, you receive a delivery ID to track progress.

Note: Monitoring the status of addon invocations is not currently provided in TMI-UX. Use the API to poll invocation status (see Checking Invocation Status below).

Discovering Addons via API

List available addons:

GET /addons

# Filter by threat model
GET /addons?threat_model_id={threat_model_id}

Response:

{
  "addons": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "STRIDE Analysis",
      "description": "Automated STRIDE threat analysis",
      "icon": "material-symbols:security",
      "objects": ["threat_model", "asset"],
      "threat_model_id": null,
      "created_at": "2025-01-15T10:00:00Z"
    },
    {
      "id": "7d8f6e5c-4b3a-2190-8765-fedcba987654",
      "name": "Compliance Checker",
      "description": "Check against NIST/ISO frameworks",
      "icon": "material-symbols:shield_lock",
      "objects": ["threat_model"],
      "created_at": "2025-01-15T11:00:00Z"
    }
  ],
  "total": 2,
  "limit": 50,
  "offset": 0
}

Invoking an Addon

Trigger an addon to process your threat model:

POST /addons/{addon_id}/invoke

{
  "threat_model_id": "threat-model-uuid",
  "object_type": "asset",          # Optional
  "object_id": "asset-uuid",       # Optional
  "data": {                        # Custom data, max 1KB JSON-serialized
    "analysis_type": "full",
    "include_recommendations": true
  }
}

Response (202 Accepted):

{
  "delivery_id": "abc-123-def-456",
  "status": "pending",
  "created_at": "2025-01-15T12:00:00Z"
}

Checking Invocation Status

Poll for status updates:

GET /webhook-deliveries/{delivery_id}

Response:

{
  "id": "abc-123-def-456",
  "subscription_id": "webhook-subscription-uuid",
  "event_type": "addon.invoked",
  "addon_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "in_progress",
  "status_percent": 75,
  "status_message": "Analyzing assets...",
  "created_at": "2025-01-15T12:00:00Z",
  "last_activity_at": "2025-01-15T12:02:30Z",
  "attempts": 1
}

Status values:

  • pending: Queued for processing
  • in_progress: Currently processing
  • delivered: Successfully delivered (auto-complete mode)
  • completed: Successfully finished (set via async callback)
  • failed: Processing failed

Note: There is no public endpoint for listing all deliveries. Administrators can list all deliveries via GET /admin/webhooks/deliveries (see Monitoring Invocations below).

Rate Limits

Default quotas per user:

  • Active invocations: 3 concurrent (pending or in_progress)
  • Hourly rate: 10 invocations per hour

Rate limit error (429 Too Many Requests):

When you exceed the active invocation limit, the response includes details about blocking invocations:

{
  "error": "rate_limit_exceeded",
  "error_description": "Active invocation limit reached: 3/3 concurrent invocations.",
  "details": {
    "context": {
      "limit": 3,
      "current": 3,
      "retry_after": 542,
      "blocking_invocations": [
        {
          "delivery_id": "abc-123",
          "addon_id": "def-456",
          "status": "in_progress",
          "created_at": "2025-01-15T12:00:00Z",
          "expires_at": "2025-01-15T12:15:00Z",
          "seconds_remaining": 542
        }
      ]
    },
    "suggestion": "Wait for an existing invocation to complete, or retry after 542 seconds when the oldest will timeout."
  }
}

The response also includes a Retry-After HTTP header with the recommended wait time in seconds.

Contact your administrator if you need higher limits.

For Administrators

Prerequisites

Before creating addons, you need:

  1. Administrator privileges (configured in YAML):

    administrators:
      # User admin by provider ID (preferred)
      - provider: "google"
        provider_id: "101155414856250184779"
        email: "admin@example.com"
        subject_type: "user"
      # Group-based admin
      - provider: "microsoft"
        group_name: "security-team"
        subject_type: "group"
  2. Active webhook subscription (admin endpoint):

    POST /admin/webhooks/subscriptions
    {
      "name": "STRIDE Analyzer Service",
      "url": "https://analyzer.example.com/webhooks/tmi",
      "events": ["addon.invoked"],  # At least one event required
      "secret": "your-hmac-secret"
    }

Registering an Addon via TMI-UX

Administrators can register addons through the TMI-UX admin interface at /admin/addons. The form includes the following fields:

Field Purpose
name Displayed in the Actions/Addons invocation menu in TMI-UX
webhook_id Links the addon to an existing webhook subscription
description Explains what the addon does; shown to users before invocation
icon Displayed next to the name in the menu. Must be a Material Symbols reference in the format material-symbols:<icon_name> (e.g., material-symbols:security)
objects Entity types the addon applies to (e.g., threat_model, asset). Controls which menus the addon appears in
threat_model_id Optional. Scopes the addon to a specific threat model
parameters Optional. Typed parameter declarations that TMI-UX renders as form fields when users invoke the addon

Registering an Addon via API

Create addon registration:

POST /addons

{
  "name": "STRIDE Analysis",
  "webhook_id": "webhook-uuid",
  "description": "Automated STRIDE threat analysis",
  "icon": "material-symbols:security",
  "objects": ["threat_model", "asset"],
  "threat_model_id": null,         # Optional: scope to specific threat model
  "parameters": [                  # Optional: typed parameter declarations
    {
      "name": "analysis_type",
      "type": "enum",
      "description": "Type of analysis to perform",
      "required": true,
      "enum_values": ["full", "quick", "targeted"],
      "default_value": "full"
    }
  ]
}

Response (201 Created):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "STRIDE Analysis",
  "webhook_id": "webhook-uuid",
  "description": "Automated STRIDE threat analysis",
  "icon": "material-symbols:security",
  "objects": ["threat_model", "asset"],
  "parameters": [
    {
      "name": "analysis_type",
      "type": "enum",
      "description": "Type of analysis to perform",
      "required": true,
      "enum_values": ["full", "quick", "targeted"],
      "default_value": "full"
    }
  ],
  "created_at": "2025-01-15T10:00:00Z"
}

Field validation:

icon (optional):

  • Must reference an actual Material Symbols icon in the format material-symbols:icon_name (e.g., material-symbols:security, material-symbols:shield_lock)
  • Max length: 60 characters

objects (optional):

  • Valid types: threat_model, diagram, asset, threat, document, note, repository, metadata, survey, survey_response
  • When set, the object type is validated at invocation time (the invocation request object_type must be in the addon's objects list)

threat_model_id (optional):

  • Scope addon to specific threat model
  • Only visible/invocable within that threat model

parameters (optional):

  • Up to 20 typed parameter declarations for client UI generation
  • Each parameter has a name, type, and type-specific configuration
  • Supported types: enum, boolean, string, number, metadata_key
  • Parameters can be marked required; required parameters are validated at invocation time
  • Enum parameters must include enum_values; metadata_key parameters must include metadata_key
  • String parameters support string_max_length and string_validation_regex constraints
  • Number parameters support number_min and number_max constraints

Deleting an Addon

DELETE /addons/{addon_id}

Response: 204 No Content (success)

Deletion rules:

  • Blocked if active invocations exist (status: pending or in_progress)
  • Error (409 Conflict): "Cannot delete addon - X active invocations exist"
  • Allowed once all invocations complete or fail
  • Cascaded when webhook is deleted (ON DELETE CASCADE)

Managing Quotas

Set custom quotas for specific users via the admin API:

List all custom quotas:

GET /admin/quotas/addons

Get quota for a specific user:

GET /admin/quotas/addons/{user_id}

Set or update quota for a user:

PUT /admin/quotas/addons/{user_id}

{
  "max_active_invocations": 5,
  "max_invocations_per_hour": 50
}

Delete custom quota (reverts to defaults):

DELETE /admin/quotas/addons/{user_id}

For detailed operator configuration including database schema, Redis monitoring, and backup procedures, see the Addon Configuration Guide.

Monitoring Invocations

Administrators can view all webhook deliveries (including addon invocations):

GET /admin/webhooks/deliveries
# Returns all deliveries across all subscriptions

# Filter by subscription
GET /admin/webhooks/deliveries?subscription_id={subscription_id}

Individual delivery details:

GET /admin/webhooks/deliveries/{delivery_id}

Note: Addon invocations are stored as webhook delivery records in Redis with a 7-day TTL. They can be identified by their event_type of addon.invoked and the presence of an addon_id field.

For Addon Developers

Webhook Service Requirements

Your webhook service must:

  1. Accept POST requests at the registered URL
  2. Verify HMAC signatures for security
  3. Respond with 200 OK within 30 seconds
  4. Process asynchronously (don't block response)
  5. Choose a callback mode (auto-complete or async callbacks)

Callback Modes

TMI supports two callback modes, controlled by the X-TMI-Callback response header:

Auto-Complete Mode (Default)

When your webhook returns a 2xx response without the X-TMI-Callback header, TMI automatically marks the invocation as delivered. Use this when:

  • Your webhook handles work synchronously
  • You don't need to report progress
  • The invocation is "fire and forget"
# Auto-complete mode - invocation marked complete immediately
return '', 200

Async Callback Mode

When your webhook returns X-TMI-Callback: async, TMI marks the invocation as in_progress and expects callbacks. Use this when:

  • Processing takes significant time
  • You want to report progress percentages
  • You need to report success/failure after processing
# Async mode - you must call back with status updates
return '', 200, {'X-TMI-Callback': 'async'}

Important: If using async mode without calling back, the invocation times out after 15 minutes and is marked failed.

Receiving Invocations

When a user invokes your addon, you receive:

POST /webhooks/tmi
Content-Type: application/json
X-Webhook-Event: addon.invoked
X-Webhook-Delivery-Id: abc-123-def-456
X-Webhook-Subscription-Id: subscription-uuid
X-Webhook-Signature: sha256=abc123...
User-Agent: TMI-Webhook/1.0

{
  "event_type": "addon.invoked",
  "threat_model_id": "threat-model-uuid",
  "object_type": "asset",
  "object_id": "asset-uuid",
  "timestamp": "2025-01-15T12:00:00Z",
  "data": {
    "addon_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_data": {
      "analysis_type": "full",
      "include_recommendations": true
    }
  }
}

Verifying Signatures

Always verify HMAC signatures:

import hmac
import hashlib

def verify_signature(payload_bytes, signature, secret):
    expected = hmac.new(
        secret.encode('utf-8'),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

@app.route('/webhooks/tmi', methods=['POST'])
def handle_invocation():
    signature = request.headers.get('X-Webhook-Signature')
    payload_bytes = request.get_data()

    if not verify_signature(payload_bytes, signature, WEBHOOK_SECRET):
        return 'Unauthorized', 401

    # Process invocation
    delivery_id = request.headers.get('X-Webhook-Delivery-Id')
    invocation = request.json
    queue.enqueue(process_invocation, delivery_id, invocation)

    return '', 200

Updating Status

Call back to TMI to update status:

import requests
import hmac
import hashlib
import json

def update_status(delivery_id, status, percent, message=''):
    payload = {
        'status': status,
        'status_percent': percent,
        'status_message': message
    }

    payload_str = json.dumps(payload)

    # Generate HMAC signature
    signature = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        payload_str.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    response = requests.post(
        f'https://api.tmi.dev/webhook-deliveries/{delivery_id}/status',
        data=payload_str,
        headers={
            'Content-Type': 'application/json',
            'X-Webhook-Signature': f'sha256={signature}'
        }
    )
    return response.status_code == 200

# Usage during processing
update_status(delivery_id, 'in_progress', 10, 'Starting analysis...')
# ... do work ...
update_status(delivery_id, 'in_progress', 50, 'Analyzing assets...')
# ... do more work ...
update_status(delivery_id, 'completed', 100, 'Analysis complete')

Status update validation:

  • status: Must be in_progress, completed, or failed
  • status_percent: Must be 0-100
  • status_message: Optional, max 255 characters

Valid transitions:

  • pendingdelivered (auto-complete mode: webhook returns 2xx without X-TMI-Callback header)
  • pendingin_progresscompleted (async callback mode)
  • pendingin_progressfailed (async callback mode)
  • Cannot transition from delivered, completed, or failed to any other status

Processing Flow Example

Complete Python example:

from flask import Flask, request
import hmac
import hashlib
import json
import requests

app = Flask(__name__)
WEBHOOK_SECRET = 'your-webhook-secret'

@app.route('/webhooks/tmi', methods=['POST'])
def handle_invocation():
    # Verify signature
    signature = request.headers.get('X-Webhook-Signature')
    payload_bytes = request.get_data()

    if not verify_signature(payload_bytes, signature, WEBHOOK_SECRET):
        return 'Unauthorized', 401

    # Queue for async processing
    delivery_id = request.headers.get('X-Webhook-Delivery-Id')
    invocation = request.json
    queue.enqueue(process_invocation, delivery_id, invocation)

    return '', 200

def process_invocation(delivery_id, invocation):
    threat_model_id = invocation['threat_model_id']
    user_data = invocation['data'].get('user_data', {})

    try:
        # Start processing
        update_status(delivery_id, 'in_progress', 0, 'Starting STRIDE analysis')

        # Fetch threat model data from TMI API
        threat_model = fetch_threat_model(threat_model_id)

        # Perform analysis
        update_status(delivery_id, 'in_progress', 25, 'Analyzing threats')
        threats = analyze_stride(threat_model)

        update_status(delivery_id, 'in_progress', 50, 'Analyzing assets')
        asset_risks = analyze_assets(threat_model)

        update_status(delivery_id, 'in_progress', 75, 'Generating report')
        report = generate_report(threats, asset_risks)

        # Complete
        update_status(delivery_id, 'completed', 100, 'Analysis complete')

    except Exception as e:
        # Report failure
        update_status(delivery_id, 'failed', 0, f'Error: {str(e)}')

def fetch_threat_model(threat_model_id):
    response = requests.get(
        f'https://api.tmi.dev/api/v1/threat-models/{threat_model_id}',
        headers={'Authorization': f'Bearer {TMI_TOKEN}'}
    )
    response.raise_for_status()
    return response.json()

if __name__ == '__main__':
    app.run(port=5000)

Testing Your Addon

  1. Local development:

    # Expose local service with ngrok
    ngrok http 5000
    
    # Use ngrok URL for webhook registration
  2. Register webhook and addon:

    # Create webhook (admin token required)
    curl -X POST https://api.tmi.dev/admin/webhooks/subscriptions \
      -H "Authorization: Bearer $ADMIN_TOKEN" \
      -d '{"name":"Test","url":"https://abc.ngrok.io/webhooks/tmi","events":["addon.invoked"],"secret":"test-secret-min16"}'
    
    # Create addon (admin token required)
    curl -X POST https://api.tmi.dev/addons \
      -H "Authorization: Bearer $ADMIN_TOKEN" \
      -d '{"name":"Test Addon","webhook_id":"webhook-uuid"}'
  3. Invoke and monitor:

    # Invoke addon
    curl -X POST https://api.tmi.dev/addons/{addon_id}/invoke \
      -H "Authorization: Bearer $TOKEN" \
      -d '{"threat_model_id":"tm-uuid","data":{}}'
    
    # Check status
    curl https://api.tmi.dev/webhook-deliveries/{delivery_id} \
      -H "Authorization: Bearer $TOKEN"

Best Practices

For Users

  1. Check status regularly: Poll invocations endpoint for updates
  2. Handle rate limits: Don't invoke excessively
  3. Provide context: Use payload to give addon context
  4. Monitor failures: Check failed invocations for errors

For Administrators

  1. Vet addon services: Only register trusted webhook services
  2. Set appropriate quotas: Balance usage and system load
  3. Monitor performance: Track invocation success rates
  4. Clean up unused addons: Delete addons no longer needed
  5. Document addons: Provide users with addon documentation

For Developers

  1. Respond quickly: Return 200 OK within 30 seconds
  2. Process asynchronously: Don't block webhook response
  3. Update status frequently: Keep users informed of progress
  4. Handle errors gracefully: Report failures with helpful messages
  5. Implement timeouts: Don't let processing run indefinitely
  6. Verify signatures: Always validate HMAC signatures
  7. Use idempotency: Handle duplicate invocations gracefully

Troubleshooting

Invocations Not Starting

Check:

  1. Addon exists and is accessible
  2. Rate limits not exceeded
  3. Webhook subscription is active
  4. Webhook service is reachable

Debug:

# Check addon
GET /addons/{addon_id}

# Check webhook status (admin only)
GET /admin/webhooks/subscriptions/{webhook_id}

# Check invocation
GET /webhook-deliveries/{delivery_id}

Status Not Updating

Causes:

  1. Invalid HMAC signature on status update
  2. Invalid status transition
  3. Invocation expired (7-day TTL)
  4. Network issues from addon service

Debug:

# Check server logs
grep "invocation" /var/log/tmi/server.log

# Verify signature generation
# Test status update manually
curl -X POST https://api.tmi.dev/webhook-deliveries/{id}/status \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: sha256=..." \
  -d '{"status":"completed","status_percent":100}'

High Failure Rate

Investigate:

# Check failed deliveries (admin only)
GET /admin/webhooks/deliveries

# Review individual delivery error messages
GET /admin/webhooks/deliveries/{delivery_id}

Common causes:

  1. Webhook service errors
  2. Timeout processing
  3. Invalid data from TMI API
  4. External dependency failures

Related Pages

Clone this wiki locally