XARF Python Library
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 reportstrict: bool— Returnreport=Noneon validation failures (default:False)show_missing_optional: bool— Populateresult.infowith 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 warningsinfo: list[dict[str, str]] | None— Missing optional field info (only whenshow_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 categoriestype: str— Report type within the categorysource_identifier: str— IP address or identifier of the abuse sourcereporter: dict | ContactInfo— Reporting organization detailssender: dict | ContactInfo— Sending organization detailsstrict: bool— Returnreport=Noneon validation failures (default:False)show_missing_optional: bool— Populateresult.infowith missing optional field details (default:False)**kwargs— Category-specific fields (e.g.,protocol,destination_ip,smtp_from)
Returns CreateReportResult:
report: AnyXARFReport | None— The generated reporterrors: list[ValidationError]— Structured validation errors (field,message,value)warnings: list[ValidationWarning]— Structured validation warningsinfo: list[dict[str, str]] | None— Missing optional field info (only whenshow_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 descriptionhash_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=Nonein 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.
Links
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.