Skip to main content

XARF Python Library

XARF Spec PyPI version License: MIT CI

A Python library for parsing, validating, and generating XARF v4 (eXtended Abuse Reporting Format) reports.

Features

  • Parse XARF reports from JSON with validation and typed results
  • Generate XARF-compliant reports with auto-generated metadata (UUIDs, timestamps)
  • Validate reports against the official JSON schemas with detailed errors and warnings
  • Full type support with Pydantic v2 discriminated union models for all 7 categories
  • v3 backward compatibility with automatic detection and conversion
  • Schema-driven — validation rules derived from the official xarf-spec schemas, not hardcoded

Installation

pip install xarf

Quick Start

Parsing a Report

from xarf import parse

# Missing first_seen and source_port produce validation warnings.
result = parse({
    "xarf_version": "4.2.0",
    "report_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timestamp": "2024-01-15T10:30:00Z",
    # "first_seen": "2024-01-15T10:00:00Z",
    "reporter": {
        "org": "Security Team",
        "contact": "abuse@example.com",
        "domain": "example.com",
    },
    "sender": {
        "org": "Security Team",
        "contact": "abuse@example.com",
        "domain": "example.com",
    },
    "source_identifier": "192.0.2.100",
    # "source_port": 1234,
    "category": "connection",
    "type": "ddos",
    "evidence_source": "honeypot",
    "destination_ip": "203.0.113.10",
    "protocol": "tcp",
})

if not result.errors:
    print(result.report.category)  # 'connection'
else:
    for e in result.errors:
        print(f"{e.field}: {e.message}")

Creating a Report

from xarf import create_report, create_evidence

# Returns XARFEvidence with content_type, payload (base64), hash, size, description
evidence = create_evidence(
    "message/rfc822",
    raw_email_bytes,
    description="Original spam email",
    hash_algorithm="sha256",
)

# xarf_version, report_id, and timestamp are auto-generated
result = create_report(
    category="messaging",
    type="spam",
    source_identifier="192.0.2.100",
    reporter={
        "org": "Example Security",
        "contact": "abuse@example.com",
        "domain": "example.com",
    },
    sender={
        "org": "Example Security",
        "contact": "abuse@example.com",
        "domain": "example.com",
    },
    evidence_source="spamtrap",
    description="Spam email detected from source",
    protocol="smtp",
    smtp_from="spammer@evil.example.com",
    evidence=[evidence],
)

import json
print(json.dumps(result.report.model_dump(by_alias=True, exclude_none=True), indent=2))

API Reference

parse(json_data, strict=False, show_missing_optional=False)

Parse and validate a XARF report from JSON. Supports both v4 and v3 (legacy) formats — v3 reports are automatically converted to v4 with deprecation warnings.

from xarf import parse

result = parse(json_data, strict=False, show_missing_optional=False)

Parameters:

  • json_data: str | dict — JSON string or dict containing a XARF report
  • strict: bool — Return report=None on validation failures (default: False)
  • show_missing_optional: bool — Populate result.info with missing optional field details (default: False)

Returns ParseResult:

  • report: AnyXARFReport | None — The parsed report, typed by category (e.g., DdosReport, SpamReport)
  • errors: list[ValidationError] — Structured validation errors (each has .field, .message, .value)
  • warnings: list[ValidationWarning] — Structured validation warnings
  • info: list[dict[str, str]] | None — Missing optional field info (only when show_missing_optional=True)

create_report(*, category, type, source_identifier, reporter, sender, **kwargs)

Create a validated XARF report with auto-generated metadata. Automatically fills xarf_version, report_id (UUID v4), and timestamp (ISO 8601 UTC).

from xarf import create_report

result = create_report(
    category="messaging",
    type="spam",
    source_identifier="192.0.2.100",
    reporter={"org": "...", "contact": "...", "domain": "..."},
    sender={"org": "...", "contact": "...", "domain": "..."},
    # category-specific fields as keyword arguments
    protocol="smtp",
)

Parameters:

  • category: str — One of the 7 XARF categories
  • type: str — Report type within the category
  • source_identifier: str — IP address or identifier of the abuse source
  • reporter: dict | ContactInfo — Reporting organization details
  • sender: dict | ContactInfo — Sending organization details
  • strict: bool — Return report=None on validation failures (default: False)
  • show_missing_optional: bool — Populate result.info with missing optional field details (default: False)
  • **kwargs — Category-specific fields (e.g., protocol, destination_ip, smtp_from)

Returns CreateReportResult:

  • report: AnyXARFReport | None — The generated report
  • errors: list[ValidationError] — Structured validation errors (field, message, value)
  • warnings: list[ValidationWarning] — Structured validation warnings
  • info: list[dict[str, str]] | None — Missing optional field info (only when show_missing_optional=True)

create_evidence(content_type, payload, *, description=None, hash_algorithm="sha256")

Create an evidence object with automatic base64 encoding, hashing, and size calculation.

from xarf import create_evidence

evidence = create_evidence(
    "message/rfc822",
    raw_bytes,
    description="Original email",
    hash_algorithm="sha256",
)

Parameters:

  • content_type: str — MIME type of the evidence (e.g., 'message/rfc822')
  • payload: bytes | str — The evidence data (strings are UTF-8 encoded)
  • description: str | None — Human-readable description
  • hash_algorithm: Literal["sha256", "sha512", "sha1", "md5"] — Hash algorithm (default: "sha256")

Returns XARFEvidence with computed hash, size, and base64-encoded payload.

schema_registry

Access schema-derived validation rules and metadata programmatically.

from xarf import schema_registry

# Get all valid categories
schema_registry.get_categories()
# {'messaging', 'connection', 'content', 'infrastructure', 'copyright', 'vulnerability', 'reputation'}

# Get valid types for a category
schema_registry.get_types_for_category("connection")
# {'ddos', 'port_scan', 'login_attack', ...}

# Check if a category/type combination is valid
schema_registry.is_valid_type("connection", "ddos")  # True

# Get field metadata including descriptions
schema_registry.get_field_metadata("confidence")
# FieldMetadata(description='...', required=False, recommended=True, ...)

Validation Details

Both parse() and create_report() run validation internally. Additional behaviors:

  • Unknown fields trigger warnings (or cause report=None in strict mode)
  • Missing optional fields can be discovered with show_missing_optional=True:
result = parse(report, show_missing_optional=True)

if result.info:
    for item in result.info:
        print(f"{item['field']}: {item['message']}")
        # e.g., "description: OPTIONAL - Human-readable description of the abuse"
        # e.g., "confidence: RECOMMENDED - Confidence score between 0.0 and 1.0"

Type narrowing after parsing — use isinstance or check .category/.type:

from xarf import parse, DdosReport

result = parse(json_data)
if isinstance(result.report, DdosReport):
    print(result.report.destination_ip)

# or check attributes directly
if result.report and result.report.category == "connection":
    print(result.report.type)

v3 Backward Compatibility

The library automatically detects XARF v3 reports (by the Version field) and converts them to v4 during parsing. Converted reports include legacy_version: '3' and deprecation warnings.

from xarf import parse

result = parse(v3_report)

print(result.report.xarf_version)   # '4.2.0'
print(result.report.category)       # mapped category (e.g., 'messaging')
print(result.report.legacy_version) # '3'
# result.warnings includes deprecation notice + conversion details

You can also use the low-level utilities directly:

from xarf import is_v3_report, convert_v3_to_v4, get_v3_deprecation_warning

if is_v3_report(json_data):
    v4_data = convert_v3_to_v4(json_data)
    print(get_v3_deprecation_warning())

Unknown v3 report types cause a parse error listing the supported types. See MIGRATION_V3_TO_V4.md for the full type mapping and migration strategies.

Schema Management

This library validates against the official xarf-spec JSON schemas. Schemas are bundled with the package and pinned to the spec version configured in pyproject.toml:

[tool.xarf]
spec_version = "v4.2.0"
# Re-fetch schemas (e.g., to pick up a newer spec version)
python -m xarf fetch-schemas

# Check whether a newer spec version is available
python -m xarf check-schema-updates

To update to a newer spec version, change spec_version in pyproject.toml and run python -m xarf fetch-schemas.

Development

pytest                        # Run tests
pytest --cov=xarf             # Run tests with coverage
ruff check xarf/              # Lint
ruff format --check xarf/     # Check formatting
mypy --strict xarf/           # Type-check

See CONTRIBUTING.md for development guidelines.

Metadata

Release files for xarf 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xarf 1.0.0
File Size Uploaded Provenance Actions
xarf-1.0.0.tar.gz 96.1 kB With provenance Details for xarf-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for xarf 1.0.0
File Interpreter ABI Platform Actions
xarf-1.0.0-py3-none-any.whl Python 3 none any Details for xarf-1.0.0-py3-none-any.whl

Total release size: 196.4 kB

Release files / xarf-1.0.0.tar.gz

Download URL xarf-1.0.0.tar.gz
Size 96.1 kB
Tags Source
SHA-256 checksum
How to use checksums
446e6772057a570daf990edf62225ef2c4e64e63cd0903ce18464a8bc078ac06
BLAKE2b-256 checksum
How to use checksums
96fa7e004d9335bee8dcc15f4be74af60e1aea4a94634df3e19ec7acab18228f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.

Transparency log

Release files / xarf-1.0.0-py3-none-any.whl

Download URL xarf-1.0.0-py3-none-any.whl
Size 100.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bc73ff873177eea30f708ea8ed9646b247cb35bfa57efa0abef26cafaf8c2b0c
BLAKE2b-256 checksum
How to use checksums
b38421d72f1b37ba262df64706a3873ac3586ca85b78396d0c68472d122619d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.

Transparency log

Security for xarf 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for xarf-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page