Files
suaveplan/packages/communication/emails/dmarc/src/parsers/forensic.md
2026-05-27 23:29:53 +01:00

3.4 KiB

forensic

Purpose

Implements parseDmarcForensic, the entry point for parsing DMARC forensic (RUF) reports. Forensic reports notify domain owners of individual authentication failures; they share the <feedback> / <report_metadata> / <policy_published> structure with aggregate reports but omit the <record> block and may include a raw MIME-formatted auth_failure_report element. This module provides the same auto-detect-decompress → parse → map → validate pipeline as the aggregate parser but targets the forensic schema.

Features

  • Auto-detects gzip input via magic-byte probe
  • Accepts raw XML strings or Uint8Array binary blobs
  • Maps snake_case XML fields to camelCase typed fields
  • Surfaces authFailureReport string when present in the source XML
  • Validates against DmarcForensicReportSchema (Zod)
  • Emits OTel counter and histogram for both success and error paths
  • Wraps all failures in typed DmarcError with stable codes

Usage: Basic Example

import { parseDmarcForensic } from "@suaveplan/dmarc/parsers/forensic";

const report = parseDmarcForensic(`
  <feedback>
    <version>1.0</version>
    <report_metadata>
      <org_name>Example Corp</org_name>
      <email>dmarc@example.com</email>
      <report_id>ruf-001</report_id>
      <date_range><begin>1609459200</begin><end>1609545600</end></date_range>
    </report_metadata>
    <policy_published>
      <domain>example.com</domain>
      <adkim>r</adkim><aspf>r</aspf><p>none</p><pct>100</pct>
    </policy_published>
  </feedback>
`);

console.log(report.feedback.reportMetadata.orgName); // "Example Corp"

Usage: Advanced Example

import { parseDmarcForensic } from "@suaveplan/dmarc/parsers/forensic";
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
import { gzipSync, strToU8 } from "fflate";

const compressed = gzipSync(strToU8(xmlString));
const report = parseDmarcForensic(compressed);

// Access optional auth failure report body
if (report.authFailureReport) {
  console.log("Failure report MIME body:", report.authFailureReport);
}

// Discriminated error handling
try {
  parseDmarcForensic("<bad>xml</bad>");
} catch (err) {
  if (err instanceof DmarcError && err.code === DMARC_CODES.INVALID_REPORT) {
    // Schema validation failed — missing required elements
  }
}

API Reference

parseDmarcForensic(input, options?): DmarcForensicReport

Parameters:

  • input: string | Uint8Array — raw XML string or binary data
  • options.decompress?: boolean — force gzip decompression

Returns: DmarcForensicReport — validated forensic report

Throws:

  • DmarcError(DMARC.PARSE_ERROR) — XML parse failure
  • DmarcError(DMARC.INVALID_REPORT) — schema validation failure or missing required elements
  • DmarcError(DMARC.UNSUPPORTED_ENCODING) — gzip decompression failure

ParseDmarcForensicOptions

{ decompress?: boolean }

Implementation Notes

The forensic parser is structurally identical to the aggregate parser except for the output schema. Both share the same str/num coercion helpers and the same two-phase (map → validate) approach. In practice, RFC 7489 forensic reports are far less common than aggregate reports and are often transmitted as raw MIME messages rather than standalone XML files; the authFailureReport field is therefore optional and surfaced as a plain string for callers to process further if needed.