Skip to content

Addon System

Eric Fitzgerald edited this page Nov 12, 2025 · 1 revision

Addon System

This page explains TMI's addon system, which allows administrators to register webhook-based extensions that users can invoke on-demand to enhance threat modeling workflows.

Overview

What are addons?

Addons are webhook-based integrations that extend TMI's functionality. Unlike standard webhooks that react to events, addons are user-invoked and can process threat model data on-demand.

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

User Invokes Addon
       ↓
   TMI Server
       ↓
 Rate Limit Check (Redis)
       ↓
Create Invocation (Redis)
       ↓
 Addon Worker → HTTP POST → External Service
                                    ↓
                          Process Asynchronously
                                    ↓
                          POST /invocations/{id}/status
                                    ↓
                          Update Status (Redis)
                                    ↓
                          User Polls Status

Components:

  1. Addons Table (PostgreSQL): 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 Addons

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
}

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
  "payload": {                     # Custom data, max 1KB
    "analysis_type": "full",
    "include_recommendations": true
  }
}

Response (202 Accepted):

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

Checking Invocation Status

Poll for status updates:

GET /invocations/{invocation_id}

Response:

{
  "id": "abc-123-def-456",
  "addon_id": "550e8400-e29b-41d4-a716-446655440000",
  "threat_model_id": "threat-model-uuid",
  "status": "in_progress",
  "status_percent": 75,
  "status_message": "Analyzing assets...",
  "created_at": "2025-01-15T12:00:00Z",
  "status_updated_at": "2025-01-15T12:02:30Z"
}

Status values:

  • pending: Queued for processing
  • in_progress: Currently processing
  • completed: Successfully finished
  • failed: Processing failed

Listing Your Invocations

View your invocation history:

GET /invocations

# Filter by status
GET /invocations?status=completed

# Filter by addon
GET /invocations?addon_id={addon_id}

Response:

{
  "invocations": [
    {
      "id": "abc-123",
      "addon_id": "550e8400-...",
      "status": "completed",
      "status_percent": 100,
      "status_message": "Analysis complete",
      "created_at": "2025-01-15T12:00:00Z"
    }
  ],
  "total": 10,
  "limit": 50,
  "offset": 0
}

Rate Limits

Default quotas per user:

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

Rate limit error (429 Too Many Requests):

{
  "error": "rate_limit_exceeded",
  "message": "Maximum of 10 invocations per hour exceeded",
  "retry_after": 1234
}

Contact your administrator if you need higher limits.

For Administrators

Prerequisites

Before creating addons, you need:

  1. Administrator privileges (configured in YAML):

    administrators:
      - subject: "[email protected]"
        subject_type: "user"
      - subject: "security-team"
        subject_type: "group"
  2. Active webhook subscription:

    POST /webhook/subscriptions
    {
      "name": "STRIDE Analyzer Service",
      "url": "https://analyzer.example.com/webhooks/tmi",
      "events": [],  # Addons don't need event subscriptions
      "secret": "your-hmac-secret"
    }

Registering an Addon

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
}

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"],
  "created_at": "2025-01-15T10:00:00Z"
}

Field validation:

icon (optional):

  • Material Symbols: material-symbols:icon_name
  • FontAwesome: fa-solid fa-icon-name
  • Max length: 60 characters

objects (optional):

  • Valid types: threat_model, diagram, asset, threat, document, note, repository, metadata
  • Used as UI hint (not enforced on invocation)

threat_model_id (optional):

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

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:

Database update (no API yet):

INSERT INTO addon_invocation_quotas
  (owner_id, max_active_invocations, max_invocations_per_hour)
VALUES
  ('user-uuid', 5, 50)
ON CONFLICT (owner_id) DO UPDATE
SET max_active_invocations = EXCLUDED.max_active_invocations,
    max_invocations_per_hour = EXCLUDED.max_invocations_per_hour;

Check quota usage:

-- View custom quotas
SELECT owner_id, max_active_invocations, max_invocations_per_hour
FROM addon_invocation_quotas;

-- Check current usage
SELECT owner_id, COUNT(*) as active_invocations
FROM addon_invocations
WHERE status IN ('pending', 'in_progress')
GROUP BY owner_id;

Monitoring Invocations

Administrators can view all invocations:

GET /invocations
# Returns all users' invocations for admins

Useful queries:

# Active invocations
GET /invocations?status=in_progress

# Failed invocations (last hour)
GET /invocations?status=failed

# By addon
GET /invocations?addon_id={addon_id}

Database queries:

-- Invocation statistics
SELECT addon_id, status, COUNT(*)
FROM addon_invocations
WHERE created_at > NOW() - INTERVAL '24 hours'
GROUP BY addon_id, status;

-- Top users by invocations
SELECT invoked_by, COUNT(*) as count
FROM addon_invocations
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY invoked_by
ORDER BY count DESC
LIMIT 10;

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. Call back with status updates

Receiving Invocations

When a user invokes your addon, you receive:

POST /webhooks/tmi
Content-Type: application/json
X-Webhook-Event: addon_invocation
X-Invocation-Id: abc-123-def-456
X-Addon-Id: 550e8400-e29b-41d4-a716-446655440000
X-Webhook-Signature: sha256=abc123...
User-Agent: TMI-Addon-Worker/1.0

{
  "event_type": "addon_invocation",
  "invocation_id": "abc-123-def-456",
  "addon_id": "550e8400-e29b-41d4-a716-446655440000",
  "threat_model_id": "threat-model-uuid",
  "object_type": "asset",
  "object_id": "asset-uuid",
  "timestamp": "2025-01-15T12:00:00Z",
  "payload": {
    "analysis_type": "full",
    "include_recommendations": true
  },
  "callback_url": "https://api.tmi.dev/invocations/abc-123-def-456/status"
}

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
    invocation = request.json
    queue.enqueue(process_invocation, invocation)

    return '', 200

Updating Status

Call back to TMI to update status:

import requests
import hmac
import hashlib
import json

def update_status(invocation_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/invocations/{invocation_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(invocation_id, 'in_progress', 10, 'Starting analysis...')
# ... do work ...
update_status(invocation_id, 'in_progress', 50, 'Analyzing assets...')
# ... do more work ...
update_status(invocation_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:

  • pendingin_progresscompleted
  • pendingin_progressfailed
  • Cannot transition from completed or failed back to in_progress

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
    invocation = request.json
    queue.enqueue(process_invocation, invocation)

    return '', 200

def process_invocation(invocation):
    invocation_id = invocation['invocation_id']
    threat_model_id = invocation['threat_model_id']
    user_payload = invocation['payload']

    try:
        # Start processing
        update_status(invocation_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(invocation_id, 'in_progress', 25, 'Analyzing threats')
        threats = analyze_stride(threat_model)

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

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

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

    except Exception as e:
        # Report failure
        update_status(invocation_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
    curl -X POST https://api.tmi.dev/webhook/subscriptions \
      -H "Authorization: Bearer $TOKEN" \
      -d '{"name":"Test","url":"https://abc.ngrok.io/webhooks/tmi","events":[],"secret":"test"}'
    
    # 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","payload":{}}'
    
    # Check status
    curl https://api.tmi.dev/invocations/{invocation_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
GET /webhook/subscriptions/{webhook_id}

# Check invocation
GET /invocations/{invocation_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/invocations/{id}/status \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: sha256=..." \
  -d '{"status":"completed","status_percent":100}'

High Failure Rate

Investigate:

# Check failed invocations
GET /invocations?status=failed

# Review error messages

Common causes:

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

Related Documentation

Clone this wiki locally