Sign in

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:

Zero-Knowledge Design
Encrypted report content remains encrypted in your SIEM. Decryption occurs only in your secure environment using your private key.
Pull-Based Sync
Your systems initiate all API calls. No webhooks or push notifications that could expose your internal infrastructure.
Metadata Enrichment
Rich metadata including severity, status, timestamps for correlation and alerting.

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

High Volume Programs
Poll every 1-2 minutes during business hours, every 5 minutes off-hours
Medium Volume Programs
Poll every 5 minutes during business hours, every 15 minutes off-hours
Low Volume Programs
Poll every 15 minutes during business hours, hourly off-hours

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
Sp

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
El

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
    }
  }
}
Ji

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}")
SN

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:

API Health
  • Response time (target: <2s)
  • Error rate (target: <1%)
  • Rate limit utilization
  • Authentication failures
Data Sync
  • 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.