Email-Abuse-Investigator

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    ADDRESS is omitted, the copy goes to the --from address, which is the
    common case for keeping a personal record of what was sent.  The BCC
    is implemented as a second SMTP RCPT TO envelope recipient with no
    Bcc: header in the message, so the primary abuse contact never sees
    the monitoring address.  In --dry-run mode the BCC address is shown
    in the output header but no mail is sent.

  When the analysis finds domains or URL hosts in the message but cannot determine
    an abuse contact for them, they are now listed so the user knows where
    to look for manual follow-up.  The list appears in three places: after
    the "No abuse contacts could be determined" message, at the end of
    --dry-run output, and at the end of a live run summary.  Domains whose
    only source is a spoofable sending header (From:, Return-Path:,
    Sender:) are excluded -- these are innocent victims of address forgery
    rather than parties to investigate.  Domains already covered by
    abuse_contacts() or form_contacts() are also excluded to avoid
    redundancy.  Discovered via a spam from nced.edu.kw (spoofed) that
    contained mailto:gyomu@tolde.co.jp and http://www.toolde.co.jp in the
    body -- both genuine spam contact points that previously produced no
    output at all.

    - Added cross-message CHI cache to avoid redundant network lookups across
    multiple messages processed in the same run.  A shared in-memory CHI
    instance (TTL 1 hour) is initialised on first call to new() when CHI
    is installed.  IP WHOIS results are cached under "whois_ip:$ip",
    domain analysis under "dom:$domain", and DNS resolution under
    "resolve:$host".  Failed DNS lookups are cached as an empty string
    and returned as undef on subsequent calls so the resolver is not
    retried.  Gracefully degrades to the existing per-message cache when
    CHI is not installed.

  - Added IPv6 support throughout.  The @PRIVATE_RANGES table now covers
    fe80::/10 (link-local), 2001:db8::/32 (RFC 3849 documentation range),
    and 64:ff9b::/96 (NAT64 well-known prefix), in addition to the
    loopback and ULA ranges already present.  @RECEIVED_IP_RE now includes
    a bracketed IPv6 pattern so IPv6 addresses are extracted from
    Received: headers.  _extract_ip_from_received() accepts colon-
    containing addresses without IPv4 validation.  _resolve_host() tries
    an AAAA query after a failed A query when Net::DNS is available.
    _raw_whois() uses IO::Socket::IP (dual-stack) in preference to
    IO::Socket::INET when that module is installed.

  - Added multipart recursion guard.  _decode_multipart() now accepts a
    $depth parameter (starting at 0 from _split_message()).  When depth
    reaches MAX_MULTIPART_DEPTH (Readonly constant, value 20) the method
    carps and returns immediately rather than recursing further, preventing
    stack exhaustion on pathological crafted messages with deeply nested
    MIME structures.

  - Added Domain::PublicSuffix support to _registrable().  When
    Domain::PublicSuffix is installed, get_root_domain() is used for
    accurate eTLD+1 normalisation covering the full Public Suffix List.
    The existing heuristic (handling co.uk, com.au, and similar common
    two-label ccTLD second-levels) is retained as a fallback when the
    module is absent.

  - Added parallel DNS resolution via AnyEvent::DNS.  When AnyEvent::DNS
    is installed and a message contains more than one unique URL hostname,
    _extract_and_resolve_urls() fires all A queries concurrently via a
    condvar and pre-populates the host cache before the sequential
    enrichment loop runs.  Falls back transparently to sequential
    resolution when AnyEvent::DNS is not installed or the host list
    contains only one entry.

  - Added input sanitisation to parse_email().  The raw message text is
    stripped of characters outside [\x09\x0A\x0D\x20-\x7E\x80-\xFF]
    (i.e. C0 controls other than tab, LF, and CR, and the DEL character)
    before storage in _raw and header parsing.  High bytes (0x80-0xFF)
    are preserved to avoid corrupting valid UTF-8 content in headers and
    bodies.

  - Added _sanitise_output() private function.  Strips C0 control
    characters (0x01-0x08, 0x0B, 0x0C, 0x0E-0x1F) and DEL (0x7F) from
    any user-derived string before it is written to report() or
    abuse_report_text() output.  Tabs, LF, and CR are preserved.  High
    bytes (0x80-0xFF) are preserved for UTF-8 content.  Applied to all
    user-derived fields: IP info, organisation names, registrar names,
    flag detail strings, and header values.

  - Added Object::Configure integration to new().  The constructor now
    calls Object::Configure::configure($class, $params) after parameter
    validation, allowing per-class defaults to be loaded from a
    configuration file.  The returned hashref overlays the caller-supplied
    parameters before the object is blessed.

  - Added new Readonly constants: $MAX_MULTIPART_DEPTH (20),
    $CACHE_TTL_SECS (3600), $DEFAULT_TIMEOUT (10), $WHOIS_PORT (43),
    $WHOIS_READ_CHUNK (4096), $WHOIS_RAW_MAX (2048), $RECENT_REG_DAYS
    (180), $EXPIRY_WARN_DAYS (30), $SECS_PER_DAY (86400),
    $DATE_SKEW_DAYS (7), $TZ_MAX_POS_MINS (840), $TZ_MAX_NEG_MINS (720),
    $SCORE_HIGH (9), $SCORE_MEDIUM (5), $SCORE_LOW (2), %FLAG_WEIGHT,
    $ROLE_MAX_LEN (80), $ROLE_WRAP_LEN (66).  All previously magic
    numbers have been removed from the code body.

  [Bug Fixes]

  - Fixed NumericBoundary mutator in SchemaExtractor incorrectly treating
    the < operator in file open (open $fh, '<', $path) and readline
    (<$fh>) expressions as numeric comparisons, generating spurious
    mutant variants for those operators.

  - Wrap in eval to catch 'Connection reset by peer' thrown by Fatal/autodie

0.07	Mon Mar 30 08:45:04 EDT 2026

  Bug fixes

  - Added Route 7 to abuse_contacts(): reply addresses found in the message
    body are now looked up in the provider table and generate abuse contacts.
    This catches a common advance-fee and investment scam pattern where the
    From: and Return-Path: headers use a spoofed or compromised address (the
    innocent victim of the fraud), while the real contact address -- a free
    webmail account chosen by the spammer -- is mentioned explicitly in the
    body text, e.g. "contact profcindyinvestments@hotmail.com for details".
    The %TRUSTED_DOMAINS filter is intentionally bypassed for this route:
    being hosted on a trusted provider (Hotmail, Gmail) is what makes these
    addresses actionable, since the spammer chose free webmail precisely
    because it is accessible and anonymous.  Recipient domains (To:, Cc:)
    are still excluded.  Only domains present in %PROVIDER_ABUSE generate
    contacts via this route; unknown domains in the body are ignored.
    The role string includes the specific address found, e.g.



( run in 4.168 seconds using v1.01-cache-2.11-cpan-7fcb06a456a )