SIEM and Ticketing Integration Guide
Integrate vulnerability reports into your existing security operations workflow with robust data synchronization and monitoring.
Integration Architecture
ResponsibleDisclosure.io provides a RESTful API that enables pull-based integration with your security infrastructure. The integration follows these principles:
Authentication and Authorization
API access uses token-based authentication with program-scoped permissions. Each program receives a unique API token that provides read-only access to that program's reports.
Obtaining API Tokens
API tokens are automatically generated for each program and can be viewed in the program settings page after enabling API access. Each token is a UUID that uniquely identifies the program.
Request Authentication
curl -H "Authorization: Token <api_token>" \ -H "Accept: application/json" \ "https://responsibledisclosure.io/reports/api/programs/<program_uuid>/reports/"
Security Note: Store API tokens in your secrets management system. Tokens cannot be regenerated, so treat them as sensitive credentials.
Report Data Model
Understanding the report structure is crucial for effective field mapping in your SIEM or ticketing system.
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"program_id": "f1e2d3c4-b5a6-7890-1234-567890abcdef",
"status": "pending",
"severity": "medium",
"created_at": "2024-01-15T14:30:00Z",
"updated_at": "2024-01-15T14:30:00Z",
"acknowledged_at": null,
"resolved_at": null,
"encrypted_title": "base64_encrypted_data...",
"encrypted_description": "base64_encrypted_data...",
"reporter_email": "[email protected]",
"reporter_name": "Security Researcher",
"attachments": [
{
"id": "b1c2d3e4-f5a6-7890-1234-567890abcdef",
"original_filename": "screenshot.png",
"file_size": 245760,
"mime_type": "image/png"
}
],
"comments": [
{
"id": "c1d2e3f4-a5b6-7890-1234-567890abcdef",
"encrypted_content": "base64_encrypted_data...",
"is_internal": false,
"author_name": "admin",
"created_at": "2024-01-15T15:00:00Z"
}
]
}
Key Fields for SIEM Integration
id
UUID identifier for correlation across systems
status
Workflow state (pending, acknowledged, in_progress, resolved, closed, duplicate, invalid)
severity
Risk level (critical, high, medium, low)
created_at
Submission timestamp for SLA tracking
encrypted_*
Base64-encoded encrypted content (requires program's private key to decrypt)
Polling Strategy and Rate Limits
Pagination and Filtering
The API supports pagination and filtering to efficiently retrieve reports:
# Get all reports with pagination curl "https://responsibledisclosure.io/reports/api/programs/<program_uuid>/reports/?page_size=100" \ -H "Authorization: Token <api_token>" # Filter by status and severity curl "https://responsibledisclosure.io/reports/api/programs/<program_uuid>/reports/?status=pending&severity=high" \ -H "Authorization: Token <api_token>"
Supported Query Parameters:
page
Page number (default: 1)
page_size
Results per page (max: 100, default: 50)
status
Filter by status (pending, acknowledged, in_progress, resolved, closed, duplicate, invalid)
severity
Filter by severity (critical, high, medium, low)
Polling Frequency Guidelines
Rate Limiting
API rate limits are enforced at the nginx layer:
- 10 requests per second for all
/api/endpoints (burst of 10 additional requests) - This equates to approximately 600 requests per minute sustained, with burst capacity for temporary spikes
- Rate-limited requests return HTTP 429 status
Splunk Integration
Custom Data Input Script
#!/usr/bin/env python3
import json, requests, sys
from datetime import datetime
API_TOKEN = "your_api_token_here"
PROGRAM_UUID = "your_program_uuid_here"
BASE_URL = "https://responsibledisclosure.io"
def fetch_reports():
url = f"{BASE_URL}/reports/api/programs/{PROGRAM_UUID}/reports/"
params = {"page_size": 100}
headers = {"Authorization": f"Token {API_TOKEN}"}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
return response.json()
else:
print(f"ERROR: API returned {response.status_code}", file=sys.stderr)
return None
def format_for_splunk(report):
event = {
"time": report["created_at"],
"sourcetype": "responsible_disclosure:report",
"source": "rd_api",
"event": {
"report_id": report["id"],
"program_id": report["program_id"],
"status": report["status"],
"severity": report.get("severity", ""),
"reporter_email": report.get("reporter_email", ""),
"reporter_name": report.get("reporter_name", ""),
"acknowledged_at": report.get("acknowledged_at"),
"resolved_at": report.get("resolved_at"),
"has_attachments": len(report.get("attachments", [])) > 0,
"attachment_count": len(report.get("attachments", []))
}
}
return json.dumps(event)
if __name__ == "__main__":
data = fetch_reports()
if data:
for report in data.get("results", []):
print(format_for_splunk(report))
Splunk Configuration
# inputs.conf [script://./bin/rd_collector.py] disabled = false interval = 300 sourcetype = responsible_disclosure:report source = rd_api # props.conf [responsible_disclosure:report] SHOULD_LINEMERGE = false KV_MODE = json TIME_PREFIX = "time": TIME_FORMAT = %Y-%m-%dT%H:%M:%SZ TRUNCATE = 0
Elastic Stack Integration
Logstash Configuration
input {
http_poller {
urls => {
reports => {
method => get
url => "https://responsibledisclosure.io/reports/api/programs/YOUR_PROGRAM_UUID/reports/"
headers => {
"Authorization" => "Token YOUR_API_TOKEN"
"Accept" => "application/json"
}
query => {
"page_size" => "100"
}
}
}
request_timeout => 60
interval => 300
codec => "json"
}
}
filter {
if [results] {
split {
field => "results"
}
mutate {
add_field => {
"[@metadata][report_id]" => "%{[results][id]}"
"[@metadata][severity]" => "%{[results][severity]}"
"[@metadata][status]" => "%{[results][status]}"
}
}
date {
match => [ "[results][created_at]", "ISO8601" ]
target => "@timestamp"
}
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "responsible-disclosure-%{+YYYY.MM.dd}"
document_id => "%{[@metadata][report_id]}"
}
}
Elasticsearch Index Template
PUT _index_template/responsible-disclosure
{
"index_patterns": ["responsible-disclosure-*"],
"template": {
"mappings": {
"properties": {
"@timestamp": {"type": "date"},
"results.id": {"type": "keyword"},
"results.program_id": {"type": "keyword"},
"results.severity": {"type": "keyword"},
"results.status": {"type": "keyword"},
"results.reporter_email": {"type": "keyword"},
"results.reporter_name": {"type": "keyword"},
"results.created_at": {"type": "date"},
"results.updated_at": {"type": "date"}
}
},
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}
}
Jira Integration
Automated Ticket Creation
Create Jira tickets automatically from new vulnerability reports:
import requests from jira import JIRA RD_API_TOKEN = "your_rd_api_token" RD_PROGRAM_UUID = "your_program_uuid" JIRA_SERVER = "https://your-company.atlassian.net" JIRA_USERNAME = "[email protected]" JIRA_API_TOKEN = "your_jira_api_token" PROJECT_KEY = "SEC" def create_jira_client(): return JIRA( server=JIRA_SERVER, basic_auth=(JIRA_USERNAME, JIRA_API_TOKEN) ) def fetch_new_reports(): url = f"https://responsibledisclosure.io/reports/api/programs/{RD_PROGRAM_UUID}/reports/" headers = {"Authorization": f"Token {RD_API_TOKEN}"} params = {"status": "pending", "page_size": 50} response = requests.get(url, headers=headers, params=params) return response.json().get("results", []) def create_ticket(jira_client, report): severity_map = { "critical": "Highest", "high": "High", "medium": "Medium", "low": "Low" } issue_dict = { "project": {"key": PROJECT_KEY}, "summary": f"Security Report {report['id'][:8]}", "description": f"""New security report received via ResponsibleDisclosure.io Report ID: {report['id']} Severity: {report.get('severity', 'N/A')} Reporter: {report.get('reporter_name', 'Anonymous')} ({report.get('reporter_email', 'N/A')}) Submitted: {report['created_at']} Status: {report['status']} Encrypted content requires decryption with private key. View full report: https://responsibledisclosure.io/reports/view/{report['id']}/ """.strip(), "issuetype": {"name": "Bug"}, "priority": {"name": severity_map.get(report.get('severity', 'medium'), 'Medium')}, "labels": ["security", "responsible-disclosure"], "customfield_10001": report['id'] } return jira_client.create_issue(fields=issue_dict) if __name__ == "__main__": jira = create_jira_client() reports = fetch_new_reports() for report in reports: try: ticket = create_ticket(jira, report) print(f"Created ticket {ticket.key} for report {report['id']}") except Exception as e: print(f"Failed to create ticket for {report['id']}: {e}")
ServiceNow Integration
Incident Creation via REST API
Automatically create ServiceNow incidents from vulnerability reports:
import requests
import base64
SERVICENOW_INSTANCE = "your-instance.service-now.com"
SERVICENOW_USER = "integration_user"
SERVICENOW_PASSWORD = "integration_password"
def create_servicenow_incident(report):
url = f"https://{SERVICENOW_INSTANCE}/api/now/table/incident"
auth = base64.b64encode(f"{SERVICENOW_USER}:{SERVICENOW_PASSWORD}".encode()).decode()
headers = {
"Authorization": f"Basic {auth}",
"Content-Type": "application/json",
"Accept": "application/json"
}
severity_map = {
"critical": {"impact": "1", "urgency": "1"},
"high": {"impact": "2", "urgency": "2"},
"medium": {"impact": "3", "urgency": "3"},
"low": {"impact": "3", "urgency": "3"}
}
mapping = severity_map.get(report.get('severity', 'medium'), {"impact": "3", "urgency": "3"})
data = {
"short_description": f"Security Vulnerability Report {report['id'][:8]}",
"description": f"""Security vulnerability report received via ResponsibleDisclosure.io
Report Details:
- ID: {report['id']}
- Severity: {report.get('severity', 'N/A')}
- Status: {report['status']}
- Reporter: {report.get('reporter_name', 'Anonymous')} ({report.get('reporter_email', 'N/A')})
- Submitted: {report['created_at']}
The report content is encrypted and requires decryption with the program's private key.
Access the full report at: https://responsibledisclosure.io/reports/view/{report['id']}/
""".strip(),
"category": "Security",
"subcategory": "Vulnerability",
"impact": mapping["impact"],
"urgency": mapping["urgency"],
"assignment_group": "Security Team",
"u_external_reference": report['id'],
"work_notes": "Automated incident created from ResponsibleDisclosure.io integration"
}
response = requests.post(url, headers=headers, json=data)
if response.status_code == 201:
incident = response.json()["result"]
return incident["number"]
else:
raise Exception(f"ServiceNow API error: {response.status_code} - {response.text}")
Integration Monitoring
Health Check Metrics
Monitor these key metrics to ensure your integration remains healthy:
- Response time (target: <2s)
- Error rate (target: <1%)
- Rate limit utilization
- Authentication failures
- Sync lag (time between report creation and ingestion)
- Missing reports (compare API count vs SIEM count)
- Duplicate detection
- Field mapping accuracy
Alerting Rules Example
# Prometheus alerting rules
groups:
- name: responsible_disclosure_integration
rules:
- alert: RDAPIDown
expr: up{job="rd_integration"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "ResponsibleDisclosure.io API integration is down"
- alert: RDSyncLag
expr: time() - rd_last_successful_sync > 900
for: 5m
labels:
severity: warning
annotations:
summary: "ResponsibleDisclosure.io sync lag exceeds 15 minutes"
- alert: RDHighErrorRate
expr: rate(rd_api_errors_total[5m]) > 0.1
for: 2m
labels:
severity: warning
annotations:
summary: "High error rate in ResponsibleDisclosure.io integration"
Need Help?
For assistance with SIEM integration or questions about data mapping and deployment patterns, contact us.