-
Notifications
You must be signed in to change notification settings - Fork 0
Issue Tracker Integration
This page explains how to integrate TMI with popular issue tracking systems to automatically create tickets from identified threats, sync threat status, and maintain traceability between threats and issues.
TMI can integrate with issue tracking systems through:
- Webhook Notifications: Receive events when threats are created/updated
- API Integration: Use TMI's REST API to sync threat data
- Custom Integration: Build custom connectors using the API
- Metadata Links: Store issue tracker URLs in threat metadata
Integration enables:
- Automatic ticket creation when threats are identified
- Bidirectional sync of threat status and issue status
- Traceability from threat models to remediation tickets
- Workflow automation for security teams
Use TMI webhooks to push threat events to your issue tracker.
Flow:
- Subscribe to threat events via webhook
- Receive notification when threat created/updated
- Create or update ticket in issue tracker
- Store ticket URL in threat metadata
Advantages:
- Real-time notifications
- Event-driven architecture
- Scalable for high-volume environments
See: Webhook-Integration for webhook setup details.
Periodically poll TMI API for threat changes and sync to issue tracker.
Flow:
- Query TMI API for threats modified since last sync
- For each new/updated threat, create or update ticket
- Store sync state (last sync timestamp)
- Update threat metadata with ticket URL
Advantages:
- Simpler to implement
- No webhook infrastructure needed
- Works with read-only API access
See: REST-API-Reference for API details.
Store issue tracker links in threat metadata for manual traceability.
Flow:
- User creates threat in TMI
- User creates ticket in issue tracker
- User adds ticket URL to threat metadata
- TMI displays linked tickets in UI
Advantages:
- No integration code required
- Full control over ticket creation
- Works with any issue tracker
See: Metadata-and-Extensions for metadata usage.
Create tickets in Jira Cloud when threats are identified.
Prerequisites:
- Jira Cloud instance
- API token with project permissions
- Jira webhook endpoint or middleware service
Integration Steps:
- Create Jira webhook service (example):
# jira_webhook.py
from flask import Flask, request
from jira import JIRA
import hmac
import hashlib
app = Flask(__name__)
# Jira configuration
jira = JIRA(
server='https://your-domain.atlassian.net',
basic_auth=('[email protected]', 'api_token')
)
WEBHOOK_SECRET = 'your-webhook-secret'
def verify_signature(payload, signature):
"""Verify TMI webhook HMAC signature"""
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
@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
# Parse event
event = request.json
event_type = event.get('event_type')
if event_type == 'threat.created':
# Create Jira issue
threat_data = event.get('data', {})
issue_dict = {
'project': {'key': 'SEC'},
'summary': f"Threat: {threat_data.get('title')}",
'description': format_threat_description(threat_data),
'issuetype': {'name': 'Security Issue'},
'labels': ['threat-model', 'security'],
'priority': {'name': map_severity(threat_data.get('severity'))}
}
issue = jira.create_issue(fields=issue_dict)
# Update threat metadata with Jira issue URL
update_threat_metadata(
event.get('resource_id'),
{'jira_issue': issue.key, 'jira_url': issue.permalink()}
)
return {'issue_key': issue.key}, 201
elif event_type == 'threat.updated':
# Sync threat updates to Jira
threat_data = event.get('data', {})
issue_key = threat_data.get('metadata', {}).get('jira_issue')
if issue_key:
issue = jira.issue(issue_key)
issue.update(
summary=f"Threat: {threat_data.get('title')}",
description=format_threat_description(threat_data)
)
return 'OK', 200
return 'Event ignored', 200
def format_threat_description(threat_data):
"""Format threat data for Jira description"""
return f"""
h2. Threat Details
*Threat Model:* {threat_data.get('threat_model_name')}
*Category:* {threat_data.get('category')}
*Severity:* {threat_data.get('severity')}
*Likelihood:* {threat_data.get('likelihood')}
h3. Description
{threat_data.get('description', 'No description provided')}
h3. Affected Assets
{format_assets(threat_data.get('assets', []))}
h3. Mitigation
{threat_data.get('mitigation', 'No mitigation specified')}
*Source:* [View in TMI|{threat_data.get('url')}]
"""
def map_severity(severity):
"""Map TMI severity to Jira priority"""
mapping = {
'critical': 'Highest',
'high': 'High',
'medium': 'Medium',
'low': 'Low'
}
return mapping.get(severity, 'Medium')
if __name__ == '__main__':
app.run(port=5000)- Deploy webhook service:
# Install dependencies
pip install flask jira requests
# Run service
python jira_webhook.py- Register webhook in TMI:
curl -X POST https://api.tmi.dev/webhook/subscriptions \
-H "Authorization: Bearer $TMI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Jira Integration",
"url": "https://your-webhook-service.com/webhooks/tmi",
"events": ["threat.created", "threat.updated"],
"secret": "your-webhook-secret"
}'Poll TMI API and sync threats to Jira Server.
Integration script (example):
# sync_jira.py
import requests
from jira import JIRA
import json
from datetime import datetime, timedelta
# Configuration
TMI_API_URL = 'https://api.tmi.dev'
TMI_TOKEN = 'your-tmi-jwt-token'
JIRA_SERVER = 'https://jira.example.com'
JIRA_USER = 'serviceaccount'
JIRA_PASSWORD = 'password'
SYNC_STATE_FILE = 'sync_state.json'
def get_last_sync_time():
"""Load last sync timestamp from state file"""
try:
with open(SYNC_STATE_FILE, 'r') as f:
state = json.load(f)
return datetime.fromisoformat(state['last_sync'])
except FileNotFoundError:
return datetime.now() - timedelta(days=7)
def save_sync_time(timestamp):
"""Save sync timestamp to state file"""
with open(SYNC_STATE_FILE, 'w') as f:
json.dump({'last_sync': timestamp.isoformat()}, f)
def get_updated_threats(since):
"""Get threats modified since timestamp"""
response = requests.get(
f'{TMI_API_URL}/api/v1/threats',
headers={'Authorization': f'Bearer {TMI_TOKEN}'},
params={
'modified_since': since.isoformat(),
'limit': 100
}
)
response.raise_for_status()
return response.json()
def sync_threat_to_jira(jira, threat):
"""Sync a threat to Jira"""
# Check if threat already has Jira issue
jira_key = threat.get('metadata', {}).get('jira_issue')
if jira_key:
# Update existing issue
try:
issue = jira.issue(jira_key)
issue.update(
summary=f"Threat: {threat['title']}",
description=format_threat_description(threat)
)
print(f"Updated Jira issue {jira_key} for threat {threat['id']}")
except Exception as e:
print(f"Error updating Jira issue {jira_key}: {e}")
else:
# Create new issue
issue_dict = {
'project': {'key': 'SEC'},
'summary': f"Threat: {threat['title']}",
'description': format_threat_description(threat),
'issuetype': {'name': 'Bug'},
'labels': ['threat-model']
}
try:
issue = jira.create_issue(fields=issue_dict)
print(f"Created Jira issue {issue.key} for threat {threat['id']}")
# Update threat metadata with Jira link
update_threat_metadata(threat['id'], {
'jira_issue': issue.key,
'jira_url': f"{JIRA_SERVER}/browse/{issue.key}"
})
except Exception as e:
print(f"Error creating Jira issue for threat {threat['id']}: {e}")
def update_threat_metadata(threat_id, metadata):
"""Update threat metadata in TMI"""
response = requests.patch(
f'{TMI_API_URL}/api/v1/threats/{threat_id}',
headers={
'Authorization': f'Bearer {TMI_TOKEN}',
'Content-Type': 'application/json'
},
json={'metadata': metadata}
)
response.raise_for_status()
def main():
# Initialize Jira client
jira = JIRA(server=JIRA_SERVER, basic_auth=(JIRA_USER, JIRA_PASSWORD))
# Get threats modified since last sync
last_sync = get_last_sync_time()
threats = get_updated_threats(last_sync)
print(f"Found {len(threats)} threats modified since {last_sync}")
# Sync each threat to Jira
for threat in threats:
sync_threat_to_jira(jira, threat)
# Save sync timestamp
save_sync_time(datetime.now())
print("Sync completed")
if __name__ == '__main__':
main()Schedule sync:
# Crontab entry (run every 15 minutes)
*/15 * * * * /usr/bin/python3 /path/to/sync_jira.py >> /var/log/tmi-jira-sync.log 2>&1Use GitHub API to create issues when threats are identified.
Example webhook handler:
// github-webhook.js
const express = require('express');
const crypto = require('crypto');
const { Octokit } = require('@octokit/rest');
const app = express();
app.use(express.json());
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const GITHUB_OWNER = 'your-org';
const GITHUB_REPO = 'security-issues';
function verifySignature(payload, signature) {
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(JSON.stringify(payload));
const expectedSig = `sha256=${hmac.digest('hex')}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
}
app.post('/webhooks/tmi', async (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Unauthorized');
}
const event = req.body;
const eventType = event.event_type;
if (eventType === 'threat.created') {
const threat = event.data;
// Create GitHub issue
const issue = await octokit.issues.create({
owner: GITHUB_OWNER,
repo: GITHUB_REPO,
title: `[Threat] ${threat.title}`,
body: formatThreatBody(threat),
labels: ['security', 'threat-model', `severity:${threat.severity}`]
});
console.log(`Created GitHub issue #${issue.data.number} for threat ${threat.id}`);
// Update threat metadata
await updateThreatMetadata(threat.id, {
github_issue: issue.data.number,
github_url: issue.data.html_url
});
return res.json({ issue_number: issue.data.number });
}
res.send('OK');
});
function formatThreatBody(threat) {
return `
## Threat Details
**Threat Model**: ${threat.threat_model_name}
**Category**: ${threat.category}
**Severity**: ${threat.severity}
**Likelihood**: ${threat.likelihood}
### Description
${threat.description}
### Affected Assets
${threat.assets ? threat.assets.map(a => `- ${a.name}`).join('\n') : 'None specified'}
### Mitigation
${threat.mitigation || 'No mitigation specified'}
---
**Source**: [View in TMI](${threat.url})
`;
}
async function updateThreatMetadata(threatId, metadata) {
// Update threat via TMI API
const response = await fetch(`https://api.tmi.dev/api/v1/threats/${threatId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${process.env.TMI_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ metadata })
});
return response.json();
}
app.listen(3000, () => {
console.log('GitHub webhook handler listening on port 3000');
});Deploy and register:
# Install dependencies
npm install express @octokit/rest
# Set environment variables
export GITHUB_TOKEN=your_github_token
export WEBHOOK_SECRET=your_webhook_secret
export TMI_TOKEN=your_tmi_token
# Run handler
node github-webhook.js
# Register webhook with TMI
curl -X POST https://api.tmi.dev/webhook/subscriptions \
-H "Authorization: Bearer $TMI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Issues",
"url": "https://your-service.com/webhooks/tmi",
"events": ["threat.created"],
"secret": "your_webhook_secret"
}'Similar pattern to GitHub, using GitLab API.
Key differences:
- Use GitLab API:
https://gitlab.com/api/v4 - Authentication via Personal Access Token or OAuth
- Project-specific issue creation
Example:
import requests
GITLAB_URL = 'https://gitlab.com'
GITLAB_TOKEN = 'your_gitlab_token'
PROJECT_ID = '12345'
def create_gitlab_issue(threat):
"""Create GitLab issue from threat"""
response = requests.post(
f'{GITLAB_URL}/api/v4/projects/{PROJECT_ID}/issues',
headers={'PRIVATE-TOKEN': GITLAB_TOKEN},
json={
'title': f"[Threat] {threat['title']}",
'description': format_threat_description(threat),
'labels': 'security,threat-model',
'confidential': True # Mark as confidential
}
)
response.raise_for_status()
return response.json()Create work items in Azure DevOps from threats.
Prerequisites:
- Azure DevOps organization and project
- Personal Access Token (PAT) with work item permissions
Example:
from azure.devops.connection import Connection
from azure.devops.v7_0.work_item_tracking.models import JsonPatchOperation
from msrest.authentication import BasicAuthentication
# Configuration
ORG_URL = 'https://dev.azure.com/your-org'
PAT = 'your_personal_access_token'
PROJECT = 'YourProject'
# Authenticate
credentials = BasicAuthentication('', PAT)
connection = Connection(base_url=ORG_URL, creds=credentials)
# Get work item tracking client
wit_client = connection.clients.get_work_item_tracking_client()
def create_work_item_from_threat(threat):
"""Create Azure DevOps work item from threat"""
# Build patch document
patch_document = [
JsonPatchOperation(
op='add',
path='/fields/System.Title',
value=f"[Threat] {threat['title']}"
),
JsonPatchOperation(
op='add',
path='/fields/System.Description',
value=format_threat_description(threat)
),
JsonPatchOperation(
op='add',
path='/fields/Microsoft.VSTS.Common.Severity',
value=map_severity(threat['severity'])
),
JsonPatchOperation(
op='add',
path='/fields/System.Tags',
value='security; threat-model'
)
]
# Create work item
work_item = wit_client.create_work_item(
document=patch_document,
project=PROJECT,
type='Bug'
)
return work_item
def map_severity(tmi_severity):
"""Map TMI severity to Azure DevOps severity"""
mapping = {
'critical': '1 - Critical',
'high': '2 - High',
'medium': '3 - Medium',
'low': '4 - Low'
}
return mapping.get(tmi_severity, '3 - Medium')- Idempotency: Handle duplicate events gracefully
- Error Handling: Retry on transient failures, log errors
- Rate Limiting: Respect issue tracker rate limits
- Authentication: Store credentials securely (not in code)
- Monitoring: Log all integration events and errors
Threat to Issue Fields:
- Title:
[Threat] {threat.title} - Description: Formatted threat details
- Priority/Severity: Map TMI severity to tracker severity
- Labels/Tags: Include "threat-model", "security", severity level
- Assignee: Optionally map to security team member
Suggested workflows:
-
Automatic Ticket Creation:
- New threat identified → Create issue
- Issue URL stored in threat metadata
- Issue assigned to security team
-
Status Sync:
- Threat status updated → Update issue status
- Issue closed → Update threat to "mitigated"
-
Notification:
- Critical threat identified → Create issue + notify team
- Use issue tracker notifications for alerts
-
Secrets Management:
- Store API tokens in secure vaults
- Rotate tokens regularly
- Use minimal permission scopes
-
Data Sensitivity:
- Mark security issues as confidential/private
- Restrict issue visibility to security team
- Don't include sensitive data in public fields
-
Webhook Security:
- Always verify HMAC signatures
- Use HTTPS for webhook endpoints
- Validate all input data
-
Audit Logging:
- Log all integration actions
- Track ticket creation and updates
- Monitor for failures and anomalies
Webhook not receiving events:
- Check webhook status in TMI
- Verify endpoint is accessible
- Check HMAC signature verification
- Review server logs
Tickets not created:
- Verify API credentials
- Check issue tracker permissions
- Review integration logs
- Test API connectivity manually
Duplicate tickets:
- Implement deduplication logic
- Check threat metadata before creating ticket
- Use unique identifiers
- Webhook-Integration - Webhook setup and configuration
- REST-API-Reference - TMI API documentation
- Metadata-and-Extensions - Using metadata for linking
- API-Integration - General API integration guide
- Using TMI for Threat Modeling
- Accessing TMI
- Creating Your First Threat Model
- Understanding the User Interface
- Working with Data Flow Diagrams
- Managing Threats
- Collaborative Threat Modeling
- Using Notes and Documentation
- Metadata and Extensions
- Planning Your Deployment
- Deploying TMI Server
- Deploying TMI Web Application
- Setting Up Authentication
- Database Setup
- Component Integration
- Post-Deployment
- Monitoring and Health
- Database Operations
- Security Operations
- Performance and Scaling
- Maintenance Tasks