threat intelligence

Generating Threat Intelligence Reports

Generates structured cyber threat intelligence reports at strategic, operational, and tactical levels tailored to specific audiences including executives, security operations teams, and technical analysts. Use when producing finished intelligence products from raw collection data, creating sector threat briefings, or delivering post-incident intelligence assessments. Activates for requests involving CTI report writing, threat briefings, intelligence products, finished intelligence, or executive security reporting.

ctiintelligence-productsnist-csfpirreport-writingthreat-intelligencetlp
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

Use this skill when:

  • Producing weekly, monthly, or quarterly threat intelligence summaries for security leadership
  • Creating a rapid intelligence assessment in response to a breaking threat (e.g., new zero-day, active ransomware campaign)
  • Generating sector-specific threat briefings for executive decision-making on security investments

Do not use this skill for raw IOC distribution — use TIP/MISP for automated IOC sharing and reserve report generation for analyzed, finished intelligence.

Prerequisites

  • Completed analysis from collection and processing phase (PIRs partially or fully answered)
  • Audience profile: technical level, decision-making authority, information classification clearance
  • TLP classification decision for the product
  • Organization-specific reporting template aligned to audience expectations

Workflow

Step 1: Determine Report Type and Audience

Select the appropriate intelligence product type:

Strategic Intelligence Report: For C-suite, board, risk committee

  • Content: Threat landscape trends, adversary intent vs. capability, risk to business objectives
  • Format: 1–3 pages, minimal jargon, business impact language, recommended decisions
  • Frequency: Monthly/Quarterly

Operational Intelligence Report: For CISO, security directors, IR leads

  • Content: Active campaigns, adversary TTPs, defensive recommendations, sector peer incidents
  • Format: 3–8 pages, moderate technical detail, mitigation priority list
  • Frequency: Weekly

Tactical Intelligence Bulletin: For SOC analysts, threat hunters, vulnerability management

  • Content: Specific IOCs, YARA rules, Sigma detections, CVEs, patching guidance
  • Format: Structured tables, code blocks, 1–2 pages
  • Frequency: Daily or as-needed

Flash Report: Urgent notification for imminent or active threats

  • Content: What is happening, immediate risk, what to do right now
  • Format: 1 page maximum, distributed within 2 hours of threat identification
  • Frequency: As-needed (zero-day, active campaign targeting sector)

Step 2: Structure Report Using Intelligence Standards

Apply intelligence writing standards from government and professional practice:

Headline/Key Judgment: Lead with the most important finding in plain language.

  • Bad: "This report examines threat actor TTPs associated with Cl0p ransomware"
  • Good: "Cl0p ransomware group is actively exploiting CVE-2024-20353 in Cisco ASA devices to gain initial access; organizations using unpatched ASA appliances face imminent ransomware risk"

Confidence Qualifiers (use language from DNI ICD 203):

  • High confidence: "assess with high confidence" — strong evidence, few assumptions
  • Medium confidence: "assess" — credible sources but analytical assumptions required
  • Low confidence: "suggests" — limited sources, significant uncertainty

Evidence Attribution: Cite sources using reference numbers [1], [2]; maintain source anonymization in TLP:AMBER/RED products.

Step 3: Write Report Body

Use structured format:

Executive Summary (3–5 bullet points): Key findings, immediate business risk, top recommended action

Threat Overview: Who is the adversary? What is their objective? Why does this matter to us?

Technical Analysis: TTPs with ATT&CK technique IDs, IOCs, observed campaign behavior

Impact Assessment: Potential operational, financial, reputational impact if attack succeeds

Recommended Actions: Prioritized, time-bound defensive measures with owner assignment

Appendices: Full IOC lists, YARA rules, Sigma detections, raw source references

Step 4: Apply TLP and Distribution Controls

Select TLP based on source sensitivity and sharing agreements:

  • TLP:RED: Named recipients only; cannot be shared outside briefing room
  • TLP:AMBER+STRICT: Organization only; no sharing with subsidiaries or partners
  • TLP:AMBER: Organization and trusted partners with need-to-know
  • TLP:GREEN: Community-wide sharing (ISAC members, sector peers)
  • TLP:WHITE/CLEAR: Public distribution; no restrictions

Include TLP watermark on every page header and footer.

Step 5: Review and Quality Control

Before dissemination, apply these checks:

  • Accuracy: Are all facts sourced and cited? No unsubstantiated claims.
  • Clarity: Can the target audience understand this without additional context?
  • Actionability: Does every report section drive a decision or action?
  • Classification: Is TLP correctly applied? No source identification in AMBER/RED products?
  • Timeliness: Is this intelligence still current? Events older than 48 hours require freshness assessment.

Key Concepts

Term Definition
Finished Intelligence Analyzed, contextualized intelligence product ready for consumption by decision-makers; distinct from raw collected data
Key Judgment Primary analytical conclusion of a report; clearly stated in opening paragraph
TLP Traffic Light Protocol — FIRST-standard classification system for controlling intelligence sharing scope
ICD 203 Intelligence Community Directive 203 — US government standard for analytic standards including confidence language
Flash Report Urgent, time-sensitive intelligence notification for imminent threats; prioritizes speed over depth
Intelligence Gap Area where collection is insufficient to answer a PIR; should be explicitly documented in reports

Tools & Systems

  • ThreatConnect Reports: Built-in report templates with ATT&CK mapping, IOC tables, and stakeholder distribution controls
  • Recorded Future: Pre-built intelligence report templates with automated sourcing from proprietary datasets
  • OpenCTI Reports: STIX-based report objects with linked entities for structured finished intelligence
  • Microsoft Word/Confluence: Common report delivery formats; use organization-approved templates with TLP headers

Common Pitfalls

  • Writing for analysts instead of the audience: Technical detail appropriate for SOC analysts overwhelms executives. Maintain strict audience segmentation.
  • Omitting confidence levels: Statements presented without confidence qualifiers appear as established facts when they may be low-confidence assessments.
  • Intelligence without recommendations: Reports that describe threats without prescribing actions leave stakeholders without direction.
  • Stale intelligence: Publishing a report on a threat campaign that was resolved 2 weeks ago creates alarm without utility. Include freshness dating on all claims.
  • Over-classification: Applying TLP:RED to information that could be TLP:GREEN impedes community sharing and limits defensive value across the sector.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md2.8 KB

API Reference: Threat Intelligence Report Generator Agent

Dependencies

Library Version Purpose
jinja2 >=3.1 Template rendering for report generation

CLI Usage

python scripts/agent.py \
  --type operational \
  --data /cases/intel_data.json \
  --output-dir /cases/reports/ \
  --output report_meta.json

Report Types

Type Audience Length Frequency
strategic C-suite, board, risk committee 1-3 pages Monthly/Quarterly
operational CISO, security directors, IR leads 3-8 pages Weekly
tactical SOC analysts, threat hunters 1-2 pages Daily/as-needed
flash All security staff 1 page max Urgent/as-needed

Functions

confidence_label(level) -> str

Maps confidence levels to ICD 203 language: "high" -> "We assess with high confidence", "medium" -> "We assess", "low" -> "Evidence suggests".

render_report(report_type, data) -> str

Renders a Jinja2 template with the provided data dict. Sets defaults for date, org, tlp.

validate_report_data(report_type, data) -> list

Validates required fields per report type. Returns list of error strings.

quality_check(rendered) -> list

Checks rendered report for: minimum length, TLP marker presence, unqualified confidence statements.

generate_report(report_type, data_path, output_dir) -> dict

Full pipeline: load JSON data, validate, render template, run quality checks, save Markdown output.

TLP Levels

Level Sharing Scope
RED Named recipients only
AMBER+STRICT Organization only
AMBER Organization and trusted partners
GREEN Community-wide (ISAC, sector peers)
CLEAR Public distribution

Input Data Schema (Operational Example)

{
  "title": "APT29 Campaign Targeting Financial Sector",
  "tlp": "AMBER",
  "org": "Security Operations Center",
  "executive_summary": ["APT29 actively targeting financial institutions..."],
  "adversary": {
    "name": "APT29 / Cozy Bear",
    "motivation": "Espionage",
    "sophistication": "Advanced",
    "target_sectors": ["Financial", "Government"]
  },
  "ttps": [{"tactic": "Initial Access", "technique_id": "T1566.001", "name": "Spearphishing", "observed": "2025-03-01"}],
  "key_judgments": [{"confidence": "high", "statement": "APT29 will continue targeting...", "evidence": "..."}],
  "recommendations": [{"priority": "Critical", "description": "...", "owner": "SOC", "timeframe": "24h", "details": "..."}],
  "iocs": [{"type": "domain", "value": "evil[.]com", "context": "C2", "confidence": "high"}]
}

Output

The agent produces two files:

  1. {type}_report_{date}.md - Rendered Markdown report with TLP headers
  2. report_meta.json - Metadata including validation errors and quality issues

Scripts 1

agent.py8.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Threat intelligence report generation agent using jinja2 for template-based reporting."""

import argparse
import json
import logging
import os
import sys
from datetime import datetime
from typing import List

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

try:
    from jinja2 import Environment, BaseLoader
except ImportError:
    sys.exit("jinja2 required: pip install jinja2")

TLP_LEVELS = {
    "RED": "Named recipients only; do not share outside the briefing room",
    "AMBER+STRICT": "Organization only; no sharing with partners or subsidiaries",
    "AMBER": "Organization and trusted partners with need-to-know",
    "GREEN": "Community-wide sharing (ISAC members, sector peers)",
    "CLEAR": "Public distribution; no restrictions",
}

CONFIDENCE_MAP = {
    "high": "We assess with high confidence",
    "medium": "We assess",
    "low": "Evidence suggests",
}

REPORT_TEMPLATES = {
    "strategic": """
# {{ title }}
**TLP:{{ tlp }}** | {{ date }} | {{ org }}

## Executive Summary
{% for point in executive_summary %}
- {{ point }}
{% endfor %}

## Threat Landscape Overview
{{ threat_overview }}

## Business Impact Assessment
{{ impact_assessment }}

## Key Judgments
{% for judgment in key_judgments %}
{{ loop.index }}. {{ confidence_label(judgment.confidence) }} that {{ judgment.statement }}
   - Evidence: {{ judgment.evidence }}
{% endfor %}

## Recommended Strategic Actions
{% for action in recommendations %}
- **{{ action.priority }}** ({{ action.timeframe }}): {{ action.description }}
{% endfor %}

## Intelligence Gaps
{% for gap in intelligence_gaps %}
- {{ gap }}
{% endfor %}

---
Classification: TLP:{{ tlp }} - {{ tlp_description }}
""",
    "operational": """
# {{ title }}
**TLP:{{ tlp }}** | {{ date }} | {{ org }}

## Executive Summary
{% for point in executive_summary %}
- {{ point }}
{% endfor %}

## Active Campaign Analysis
### Adversary Profile
- **Name**: {{ adversary.name }}
- **Motivation**: {{ adversary.motivation }}
- **Sophistication**: {{ adversary.sophistication }}
- **Target Sectors**: {{ adversary.target_sectors | join(', ') }}

### TTPs (MITRE ATT&CK)
| Tactic | Technique ID | Technique Name | Observed |
|--------|-------------|----------------|----------|
{% for ttp in ttps %}
| {{ ttp.tactic }} | {{ ttp.technique_id }} | {{ ttp.name }} | {{ ttp.observed }} |
{% endfor %}

## Key Judgments
{% for judgment in key_judgments %}
{{ loop.index }}. {{ confidence_label(judgment.confidence) }} that {{ judgment.statement }}
{% endfor %}

## Defensive Recommendations
{% for action in recommendations %}
### {{ action.priority }}: {{ action.description }}
- **Owner**: {{ action.owner }}
- **Timeframe**: {{ action.timeframe }}
- **Details**: {{ action.details }}
{% endfor %}

## IOC Summary
| Type | Value | Context | Confidence |
|------|-------|---------|------------|
{% for ioc in iocs %}
| {{ ioc.type }} | {{ ioc.value }} | {{ ioc.context }} | {{ ioc.confidence }} |
{% endfor %}

---
Classification: TLP:{{ tlp }} - {{ tlp_description }}
""",
    "tactical": """
# {{ title }}
**TLP:{{ tlp }}** | {{ date }} | {{ org }}

## Summary
{{ summary }}

## Indicators of Compromise
| Type | Value | Context | Confidence |
|------|-------|---------|------------|
{% for ioc in iocs %}
| {{ ioc.type }} | `{{ ioc.value }}` | {{ ioc.context }} | {{ ioc.confidence }} |
{% endfor %}

## Detection Rules
{% for rule in detection_rules %}
### {{ rule.name }} ({{ rule.format }})
```
{{ rule.content }}
```
{% endfor %}

## MITRE ATT&CK Mapping
{% for ttp in ttps %}
- **{{ ttp.technique_id }}** - {{ ttp.name }}: {{ ttp.description }}
{% endfor %}

## Patching Guidance
{% for patch in patches %}
- **{{ patch.cve }}**: {{ patch.description }} ({{ patch.severity }})
{% endfor %}

---
Classification: TLP:{{ tlp }} - {{ tlp_description }}
""",
    "flash": """
# FLASH: {{ title }}
**TLP:{{ tlp }}** | {{ date }} | IMMEDIATE ACTION REQUIRED

## What Is Happening
{{ what_is_happening }}

## Immediate Risk
{{ immediate_risk }}

## What To Do Right Now
{% for action in immediate_actions %}
{{ loop.index }}. {{ action }}
{% endfor %}

## Indicators of Compromise
{% for ioc in iocs %}
- {{ ioc.type }}: `{{ ioc.value }}`
{% endfor %}

## Additional Context
{{ context }}

---
Classification: TLP:{{ tlp }} - {{ tlp_description }}
Disseminated: {{ date }}
""",
}


def confidence_label(level: str) -> str:
    """Map confidence level to ICD 203 language."""
    return CONFIDENCE_MAP.get(level.lower(), "Evidence suggests")


def render_report(report_type: str, data: dict) -> str:
    """Render a threat intelligence report from template and data."""
    template_str = REPORT_TEMPLATES.get(report_type)
    if not template_str:
        raise ValueError(f"Unknown report type: {report_type}. Available: {list(REPORT_TEMPLATES.keys())}")

    data.setdefault("date", datetime.utcnow().strftime("%Y-%m-%d"))
    data.setdefault("org", "Security Operations")
    data.setdefault("tlp", "AMBER")
    data["tlp_description"] = TLP_LEVELS.get(data["tlp"], "")

    env = Environment(loader=BaseLoader())
    env.globals["confidence_label"] = confidence_label
    template = env.from_string(template_str)
    return template.render(**data)


def validate_report_data(report_type: str, data: dict) -> List[str]:
    """Validate that required fields are present for the report type."""
    errors = []
    required_all = ["title", "tlp"]
    for field in required_all:
        if field not in data:
            errors.append(f"Missing required field: {field}")
    if data.get("tlp") and data["tlp"] not in TLP_LEVELS:
        errors.append(f"Invalid TLP level: {data['tlp']}. Valid: {list(TLP_LEVELS.keys())}")

    type_required = {
        "strategic": ["executive_summary", "threat_overview", "key_judgments", "recommendations"],
        "operational": ["executive_summary", "adversary", "ttps", "recommendations"],
        "tactical": ["summary", "iocs"],
        "flash": ["what_is_happening", "immediate_risk", "immediate_actions"],
    }
    for field in type_required.get(report_type, []):
        if field not in data:
            errors.append(f"Missing field for {report_type} report: {field}")
    return errors


def quality_check(rendered: str) -> List[str]:
    """Run quality checks on rendered report."""
    issues = []
    if len(rendered) < 200:
        issues.append("Report is very short; may lack sufficient detail")
    if "TLP:" not in rendered:
        issues.append("Missing TLP classification marker")
    unqualified = 0
    for keyword in ["will", "is certain", "definitely", "undoubtedly"]:
        if keyword in rendered.lower():
            unqualified += 1
    if unqualified > 0:
        issues.append(f"Found {unqualified} statements that may need confidence qualifiers")
    return issues


def generate_report(report_type: str, data_path: str, output_dir: str) -> dict:
    """Load data, validate, render, and save the report."""
    with open(data_path, "r") as f:
        data = json.load(f)

    validation_errors = validate_report_data(report_type, data)
    if validation_errors:
        logger.warning("Validation issues: %s", validation_errors)

    rendered = render_report(report_type, data)
    quality_issues = quality_check(rendered)
    if quality_issues:
        logger.warning("Quality issues: %s", quality_issues)

    report_filename = f"{report_type}_report_{datetime.utcnow().strftime('%Y%m%d')}.md"
    report_path = os.path.join(output_dir, report_filename)
    with open(report_path, "w", encoding="utf-8") as f:
        f.write(rendered)
    logger.info("Report saved to %s", report_path)

    return {
        "report_type": report_type,
        "output_path": report_path,
        "tlp": data.get("tlp", "AMBER"),
        "validation_errors": validation_errors,
        "quality_issues": quality_issues,
        "rendered_length": len(rendered),
    }


def main():
    parser = argparse.ArgumentParser(description="Threat Intelligence Report Generator")
    parser.add_argument("--type", required=True, choices=list(REPORT_TEMPLATES.keys()),
                        help="Report type: strategic, operational, tactical, flash")
    parser.add_argument("--data", required=True, help="Path to JSON data file with report content")
    parser.add_argument("--output-dir", default=".", help="Output directory")
    parser.add_argument("--output", default="report_meta.json")
    args = parser.parse_args()

    os.makedirs(args.output_dir, exist_ok=True)
    result = generate_report(args.type, args.data, args.output_dir)
    out_path = os.path.join(args.output_dir, args.output)
    with open(out_path, "w") as f:
        json.dump(result, f, indent=2)
    logger.info("Metadata saved to %s", out_path)
    print(json.dumps(result, indent=2))


if __name__ == "__main__":
    main()
Keep exploring