Skip to content

Issue Tracker Integration

Eric Fitzgerald edited this page Apr 8, 2026 · 2 revisions

Issue Tracker Integration

This page explains how TMI supports linking threats and threat models to external issue-tracking systems using the issue_uri field and webhooks.

Overview

TMI does not include built-in integrations with any specific issue tracker (Jira, GitHub Issues, GitLab, Azure DevOps, etc.). Instead, TMI provides two mechanisms that enable you to connect threats and threat models to any issue tracking system:

  1. issue_uri field: A dedicated URL field on both threat models and individual threats. Users can store the URL of a corresponding issue in any external tracker.
  2. Webhook notifications: TMI emits events (e.g., threat.created, threat.updated) via webhook subscriptions. External middleware can consume these events and create or update issues in a tracker of your choice.

Together, these mechanisms support:

  • Manual linking: Paste or drag an issue URL into the issue_uri field on a threat or threat model
  • Automated ticket creation: Use webhooks to trigger issue creation in an external system, then write the issue URL back to issue_uri via the TMI API
  • Traceability: The issue_uri field is rendered as a clickable hyperlink in the TMI UI, providing one-click navigation from a threat to its tracking ticket

The issue_uri Field

Where it exists

The issue_uri field is a first-class, optional string field on both:

  • Threat models (/threat_models/{threat_model_id})
  • Threats (/threat_models/{threat_model_id}/threats/{threat_id})

It is not stored in the metadata key-value pairs. It is a dedicated column in the database schema and a named property in the API schema.

Setting issue_uri on a threat model

PUT (full update):

curl -X PUT "https://your-tmi-instance/threat_models/{threat_model_id}" \
  -H "Authorization: Bearer $TMI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Threat Model",
    "issue_uri": "https://github.com/example/project/issues/42"
  }'

PATCH (partial update using JSON Patch):

curl -X PATCH "https://your-tmi-instance/threat_models/{threat_model_id}" \
  -H "Authorization: Bearer $TMI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "op": "replace", "path": "/issue_uri", "value": "https://github.com/example/project/issues/42" }
  ]'

To clear the field, send null as the value:

curl -X PATCH "https://your-tmi-instance/threat_models/{threat_model_id}" \
  -H "Authorization: Bearer $TMI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "op": "replace", "path": "/issue_uri", "value": null }
  ]'

Setting issue_uri on a threat

Threats are sub-resources of threat models. The endpoint pattern is:

/threat_models/{threat_model_id}/threats/{threat_id}

PATCH example:

curl -X PATCH "https://your-tmi-instance/threat_models/{threat_model_id}/threats/{threat_id}" \
  -H "Authorization: Bearer $TMI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "op": "replace", "path": "/issue_uri", "value": "https://jira.example.com/browse/SEC-123" }
  ]'

Filtering by issue_uri

You can filter threat models by issue_uri using a query parameter (partial, case-insensitive match):

curl "https://your-tmi-instance/threat_models?issue_uri=SEC-100" \
  -H "Authorization: Bearer $TMI_TOKEN"

UI behavior

In the TMI UI:

  • Both the threat model edit page and the threat editor dialog include an issue_uri input field
  • The field accepts any valid URL and validates it as a URL format
  • Users can type a URL or drag-and-drop a URL into the field
  • When a valid URL is present, the UI renders it as a clickable hyperlink that opens in a new tab
  • In view-only mode, the URI is displayed as a link (not an editable input)

Integration Patterns

Pattern 1: Manual Linking

The simplest approach. No integration code required.

Flow:

  1. User creates a threat in TMI
  2. User creates a corresponding ticket in their issue tracker
  3. User pastes the ticket URL into the issue_uri field on the threat (or threat model)
  4. TMI UI displays the link for one-click navigation

Advantages:

  • Works with any issue tracker that has web URLs
  • No infrastructure or middleware needed
  • Full control over when and how tickets are created

Pattern 2: Webhook-Driven Automation

Use TMI webhooks to trigger external automation that creates issues and writes the URL back.

Flow:

  1. An admin creates a webhook subscription in TMI
  2. When a threat is created or updated, TMI sends an HTTP POST to the subscriber URL
  3. The subscriber (your middleware) creates an issue in the external tracker
  4. The subscriber calls the TMI API to set issue_uri on the threat

Webhook subscription creation (admin-only endpoint):

curl -X POST "https://your-tmi-instance/admin/webhooks/subscriptions" \
  -H "Authorization: Bearer $TMI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Issue Tracker Integration",
    "url": "https://your-middleware.example.com/webhooks/tmi",
    "events": ["threat.created", "threat.updated"],
    "secret": "your-webhook-secret"
  }'

Available threat-related event types:

  • threat.created
  • threat.updated
  • threat.deleted
  • threat_model.created
  • threat_model.updated
  • threat_model.deleted

Webhook delivery format:

TMI sends an HTTP POST with a JSON body containing the event type and object data. If a secret was provided during subscription creation, TMI includes an HMAC-SHA256 signature in the X-Webhook-Signature header:

X-Webhook-Signature: sha256={hmac_hex_digest}

See Webhook-Integration for full details on webhook setup, delivery, retry behavior, and signature verification.

Pattern 3: API Polling

Periodically query the TMI API for threats and sync to your issue tracker.

Flow:

  1. Poll GET /threat_models and iterate through threat models and their threats
  2. For threats without an issue_uri, create a ticket in your tracker
  3. Write the ticket URL back to the threat's issue_uri via PATCH

Advantages:

  • Simpler to implement than webhooks
  • No publicly accessible endpoint needed
  • Works behind firewalls

See REST-API-Reference for API details.

Example: Webhook Middleware Skeleton

Below is a minimal example of a webhook receiver that creates issues in an external system. This is not a built-in TMI feature -- it is custom middleware you would build and host yourself.

# example_webhook_receiver.py
from flask import Flask, request
import hmac
import hashlib
import requests

app = Flask(__name__)

WEBHOOK_SECRET = 'your-webhook-secret'
TMI_API_URL = 'https://your-tmi-instance'
TMI_TOKEN = 'your-tmi-jwt-token'

def verify_signature(payload_bytes, signature_header):
    """Verify TMI webhook HMAC-SHA256 signature."""
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature_header or '')

@app.route('/webhooks/tmi', methods=['POST'])
def handle_tmi_webhook():
    # Verify signature
    signature = request.headers.get('X-Webhook-Signature')
    if not verify_signature(request.data, signature):
        return 'Unauthorized', 401

    event = request.json
    event_type = event.get('event_type')

    if event_type == 'threat.created':
        threat_data = event.get('data', {})
        threat_model_id = event.get('threat_model_id', '')
        threat_id = threat_data.get('id', '')

        # --- Replace this section with your issue tracker's API ---
        # Example: create a ticket and get back a URL
        ticket_url = create_ticket_in_your_tracker(
            name=threat_data.get('name', ''),
            description=threat_data.get('description', ''),
            severity=threat_data.get('severity', ''),
            threat_type=threat_data.get('threat_type', []),
        )
        # --- End tracker-specific section ---

        # Write the issue URL back to the threat's issue_uri field
        if ticket_url and threat_model_id and threat_id:
            requests.patch(
                f'{TMI_API_URL}/threat_models/{threat_model_id}/threats/{threat_id}',
                headers={
                    'Authorization': f'Bearer {TMI_TOKEN}',
                    'Content-Type': 'application/json',
                },
                json=[
                    {'op': 'replace', 'path': '/issue_uri', 'value': ticket_url}
                ],
            )

        return 'Created', 201

    return 'Event ignored', 200

def create_ticket_in_your_tracker(name, description, severity, threat_type):
    """
    Replace this with calls to your issue tracker's API.
    Return the URL of the created ticket.
    """
    # Example placeholder:
    # response = jira.create_issue(...)
    # return response.permalink()
    return None

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

Threat Fields Available for Mapping

When building an integration, the following fields are available on a Threat object:

Field Type Description
id UUID Unique threat identifier
name string Threat name
description string (nullable) Threat description
threat_type string[] Threat categories (e.g., STRIDE types)
severity string (nullable) Severity level
priority string (nullable) Priority level
score number (nullable) Numeric risk/impact score
status string (nullable) Current threat status
mitigated boolean Whether the threat is mitigated
mitigation string (nullable) Mitigation description
issue_uri string (nullable) URL to external issue tracker ticket
cwe_id string[] CWE identifiers
cvss object[] CVSS scoring information
include_in_report boolean Whether to include in reports
metadata object[] Key-value metadata pairs
threat_model_id UUID Parent threat model identifier
asset_id UUID (nullable) Associated asset
cell_id UUID (nullable) Associated diagram cell
diagram_id UUID (nullable) Associated diagram
created_at datetime Creation timestamp
modified_at datetime Last modification timestamp

Best Practices

Security

  1. Always verify webhook signatures using the X-Webhook-Signature header and HMAC-SHA256
  2. Use HTTPS for both the webhook endpoint and TMI API calls
  3. Store API tokens securely in a secrets manager, not in source code
  4. Mark security issues as confidential in your issue tracker
  5. Use minimal permission scopes for issue tracker API tokens

Reliability

  1. Handle duplicate events -- webhook deliveries may be retried; use idempotency checks
  2. Respect rate limits on both TMI and your issue tracker's API
  3. Log all integration actions with request IDs for traceability
  4. Handle failures gracefully -- if ticket creation fails, log the error and retry or alert

Data Mapping

When creating tickets from TMI threats, consider mapping:

  • Threat name to ticket title (e.g., prefixed with [Threat])
  • Threat description and mitigation to ticket body
  • Threat severity and priority to tracker priority/severity fields
  • Threat threat_type to ticket labels or tags
  • Threat cwe_id to ticket labels for vulnerability classification

Troubleshooting

issue_uri not appearing in the UI

  • Verify the field value is a valid URL (the UI validates URL format)
  • Check that the value is not "n/a" or an empty string -- the UI hides these

Webhook not receiving events

  • Check webhook subscription status via the admin API
  • Verify the endpoint is publicly accessible from the TMI server
  • Verify HMAC signature computation matches X-Webhook-Signature
  • Review TMI server logs for delivery failures

Writing issue_uri back via API fails

  • Ensure you are using the correct nested endpoint: /threat_models/{threat_model_id}/threats/{threat_id}
  • Threats are not available at a top-level /threats endpoint
  • Verify the Bearer token has write permissions to the threat model

Related Documentation

Clone this wiki locally