-
Notifications
You must be signed in to change notification settings - Fork 2
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.
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:
-
issue_urifield: A dedicated URL field on both threat models and individual threats. Users can store the URL of a corresponding issue in any external tracker. -
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_urifield 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_urivia the TMI API -
Traceability: The
issue_urifield 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 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.
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 }
]'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" }
]'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"In the TMI UI:
- Both the threat model edit page and the threat editor dialog include an
issue_uriinput 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)
The simplest approach. No integration code required.
Flow:
- User creates a threat in TMI
- User creates a corresponding ticket in their issue tracker
- User pastes the ticket URL into the
issue_urifield on the threat (or threat model) - 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
Use TMI webhooks to trigger external automation that creates issues and writes the URL back.
Flow:
- An admin creates a webhook subscription in TMI
- When a threat is created or updated, TMI sends an HTTP POST to the subscriber URL
- The subscriber (your middleware) creates an issue in the external tracker
- The subscriber calls the TMI API to set
issue_urion 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.createdthreat.updatedthreat.deletedthreat_model.createdthreat_model.updatedthreat_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.
Periodically query the TMI API for threats and sync to your issue tracker.
Flow:
- Poll
GET /threat_modelsand iterate through threat models and their threats - For threats without an
issue_uri, create a ticket in your tracker - Write the ticket URL back to the threat's
issue_urivia PATCH
Advantages:
- Simpler to implement than webhooks
- No publicly accessible endpoint needed
- Works behind firewalls
See REST-API-Reference for API details.
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)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 |
-
Always verify webhook signatures using the
X-Webhook-Signatureheader and HMAC-SHA256 - Use HTTPS for both the webhook endpoint and TMI API calls
- Store API tokens securely in a secrets manager, not in source code
- Mark security issues as confidential in your issue tracker
- Use minimal permission scopes for issue tracker API tokens
- Handle duplicate events -- webhook deliveries may be retried; use idempotency checks
- Respect rate limits on both TMI and your issue tracker's API
- Log all integration actions with request IDs for traceability
- Handle failures gracefully -- if ticket creation fails, log the error and retry or alert
When creating tickets from TMI threats, consider mapping:
- Threat
nameto ticket title (e.g., prefixed with[Threat]) - Threat
descriptionandmitigationto ticket body - Threat
severityandpriorityto tracker priority/severity fields - Threat
threat_typeto ticket labels or tags - Threat
cwe_idto ticket labels for vulnerability classification
- 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
- 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
- Ensure you are using the correct nested endpoint:
/threat_models/{threat_model_id}/threats/{threat_id} - Threats are not available at a top-level
/threatsendpoint - Verify the Bearer token has write permissions to the threat model
- Webhook-Integration -- Webhook setup, delivery, and signature verification
- REST-API-Reference -- Full TMI API documentation
- API-Integration -- General API integration guide
- Using TMI for Threat Modeling
- Accessing TMI
- Authentication
- Creating Your First Threat Model
- Understanding the User Interface
- Working with Data Flow Diagrams
- Managing Threats
- Collaborative Threat Modeling
- Using Notes and Documentation
- Timmy AI Assistant
- Metadata and Extensions
- Planning Your Deployment
- Terraform Deployment (AWS, OCI, GCP, Azure)
- Deploying TMI Server
- OCI Container Deployment
- Certificate Automation
- Deploying TMI Web Application
- Setting Up Authentication
- Database Setup
- Component Integration
- Post-Deployment
- Branding and Customization
- Monitoring and Health
- Cloud Logging
- Database Operations
- Security Operations
- Performance and Scaling
- Maintenance Tasks
- Getting Started with Development
- Architecture and Design
- API Integration
- Testing
- Contributing
- Extending TMI
- Dependency Upgrade Plans
- DFD Graphing Library Reference
- Migration Instructions