You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by he...@apache.org on 2022/05/18 12:40:40 UTC

svn commit: r1901033 - in /spamassassin/trunk: UPGRADE lib/Mail/SpamAssassin/Plugin/HashBL.pm t/data/spam/hashbl t/hashbl.t

Author: hege
Date: Wed May 18 12:40:40 2022
New Revision: 1901033

URL: http://svn.apache.org/viewvc?rev=1901033&view=rev
Log:
HashBL: add check_hashbl_attachments. Improve documentation.

Modified:
    spamassassin/trunk/UPGRADE
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm
    spamassassin/trunk/t/data/spam/hashbl
    spamassassin/trunk/t/hashbl.t

Modified: spamassassin/trunk/UPGRADE
URL: http://svn.apache.org/viewvc/spamassassin/trunk/UPGRADE?rev=1901033&r1=1901032&r2=1901033&view=diff
==============================================================================
--- spamassassin/trunk/UPGRADE (original)
+++ spamassassin/trunk/UPGRADE Wed May 18 12:40:40 2022
@@ -204,6 +204,9 @@ Note for Users Upgrading to SpamAssassin
   format (e.g. 192.168.1.1-192.168.255.255) for NetSet tables
   (internal_networks, trusted_networks, msa_networks, uri_local_cidr).
 
+- HashBL: New functions check_hashbl_tag, check_hashbl_attachments.
+  New sha256 option.
+
 Note for Users Upgrading to SpamAssassin 3.4.5
 ----------------------------------------------
 
@@ -279,7 +282,7 @@ Note for Users Upgrading to SpamAssassin
 - DNSEval: add check_rbl_ns_from to check against an rbl for dns servers
 
 - HashBL: Add check_hashbl_bodyre, check_hashbl_emails, check_hashbl_uris,
-  check_hashbl_tag, hashbl_ignore. New sha256 option.
+  hashbl_ignore
 
 - ASN: Support IPv6 with asn_lookup_ipv6 (Bug 7211)
 

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm?rev=1901033&r1=1901032&r2=1901033&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm Wed May 18 12:40:40 2022
@@ -39,10 +39,14 @@ HashBL - query hashed (and unhashed) DNS
   describe HASHBL_BTC Message contains BTC address found on BTCBL
   tflags   HASHBL_BTC net
 
-  header   HASHBL_URI eval:check_hashbl_uris('rbl.example.invalid', 'sha1', '127.0.0.32')
+  header   HASHBL_URI eval:check_hashbl_uris('rbl.example.invalid', 'sha1', '^127\.0\.0\.32$')
   describe HASHBL_URI Message contains uri found on rbl
   tflags   HASHBL_URI net
 
+  body     HASHBL_ATTACHMENT eval:check_hashbl_attachments('attbl.example.invalid', 'sha256')
+  describe HASHBL_ATTACHMENT Message contains attachment found on attbl
+  tflags   HASHBL_ATTACHMENT net
+
   # Capture tag using SA 4.0 regex named capture feature
   header   __X_SOME_ID X-Some-ID =~ /^(?<XSOMEID>\d{10,20})$/
   # Query the tag value as is from a DNSBL
@@ -50,39 +54,39 @@ HashBL - query hashed (and unhashed) DNS
 
 =head1 DESCRIPTION
 
-This plugin support multiple types of hashed or unhashed DNS blocklists.
+This plugin supports multiple types of hashed or unhashed DNS blocklist queries.
+
+=over 4
 
-OPTS refers to multiple generic options:
+=item Common OPTS that apply to all functions:
 
-  raw      do not hash data, query as is
+  raw      no hashing, query as is (can break if value is not valid DNS label)
   md5      hash query with MD5
   sha1     hash query with SHA1
   sha256   hash query with Base32 encoded SHA256
   case     keep case before hashing, default is to lowercase
-  max=x	   maximum number of queries
+  max=x	   maximum number of queries (defaults to 10 if not specified)
   shuffle  if max exceeded, random shuffle queries before truncating to limit
 
-Multiple options can be separated with slash or other non-word character.
-If OPTS is empty ('') or missing, default is used.
+Multiple options can be separated with slash.
 
-HEADERS refers to slash separated list of Headers to process:
-
-  ALL           all headers
-  ALLFROM       all From headers as returned by $pms->all_from_addrs()
-  EnvelopeFrom  message envelope from (Return-Path etc)
-  HeaderName    any header as used with $pms->get()
+When rule OPTS is empty ('') or missing, default is used as documented by
+each query type.  If any options are defined, then all needed options must
+be explicitly defined.
 
-if HEADERS is empty ('') or missing, default is used.
+=back 
 
 =over 4
 
-=item header RULE check_hashbl_emails('bl.example.invalid/A', 'OPTS', 'HEADERS/body', '^127\.')
+=item header RULE check_hashbl_emails('bl.example.invalid/A', 'OPTS', 'HEADERS', '^127\.')
+
+Check email addresses from DNS list.  Note that "body" can be specified
+along with headers to search message body for emails.  Rule type must always
+be "header".
 
-Check email addresses from DNS list, "body" can be specified along with
-headers to search body for emails.  Optional subtest regexp to match DNS
-answer.  Note that eval rule type must always be "header".
+Optional DNS query type can be appended to list with /A (default) or /TXT.
 
-DNS query type can be appended to list with /A (default) or /TXT.
+Default OPTS: sha1/notag/noquote/max=10/shuffle
 
 Additional supported OPTS:
 
@@ -91,13 +95,23 @@ Additional supported OPTS:
   nouri    ignore emails inside uris
   noquote  ignore emails inside < > or possible quotings
 
-Default OPTS: sha1/notag/noquote/max=10/shuffle
-
 Default HEADERS: ALLFROM/Reply-To/body
 
+HEADERS refers to slash separated list of Headers to process:
+
+  ALL           all headers
+  ALLFROM       all From headers as returned by $pms->all_from_addrs()
+  EnvelopeFrom  message envelope from (Return-Path etc)
+  <HeaderName>  any header as used with header rules or $pms->get()
+  body          all emails found in message body
+
+If HEADERS is empty ('') or missing, default is used.
+
+Optional subtest regexp to match DNS answer (default: '^127\.').
+
 For existing public email blocklist, see: http://msbl.org/ebl.html
 
-  # Working example, see http://msbl.org/ebl.html before usage
+  # Working example, see https://msbl.org/ebl.html before usage
   header   HASHBL_EMAIL eval:check_hashbl_emails('ebl.msbl.org')
   describe HASHBL_EMAIL Message contains email address found on EBL
   tflags   HASHBL_EMAIL net
@@ -105,7 +119,7 @@ For existing public email blocklist, see
 Default regex for matching and capturing emails can be overridden with
 C<hashbl_email_regex>.  Likewise, the default welcomelist can be changed with
 C<hashbl_email_welcomelist>.  Only change if you know what you are doing, see
-module source for the defaults.  Example: hashbl_email_regex \S+@\S+.com
+plugin source code for the defaults.  Example: hashbl_email_regex \S+@\S+.com
 
 =back
 
@@ -113,22 +127,29 @@ module source for the defaults.  Example
 
 =item header RULE check_hashbl_uris('bl.example.invalid/A', 'OPTS', '^127\.')
 
-Check uris from DNS list, optional subtest regexp to match DNS
-answer.
+Check all URIs parsed from message from DNS list.
 
-DNS query type can be appended to list with /A (default) or /TXT.
+Optional DNS query type can be appended to list with /A (default) or /TXT.
 
 Default OPTS: sha1/max=10/shuffle
 
+Optional subtest regexp to match DNS answer (default: '^127\.').
+
 =back
 
 =over 4
 
-=item body RULE check_hashbl_bodyre('bl.example.invalid/A', 'OPTS', '\b(match)\b', '^127\.')
+=item [raw]body RULE check_hashbl_bodyre('bl.example.invalid/A', 'OPTS', '\b(match)\b', '^127\.')
 
 Search body for matching regexp and query the string captured.  Regexp must
-have a single capture ( ) for the string ($1).  Optional subtest regexp to
-match DNS answer.  Note that eval rule type must be "body" or "rawbody".
+have a single capture ( ) for the string ($1).  Rule type must be "body" or
+"rawbody".
+
+Optional DNS query type can be appended to list with /A (default) or /TXT.
+
+Default OPTS: sha1/max=10/shuffle
+
+Optional subtest regexp to match DNS answer (default: '^127\.').
 
 =back
 
@@ -136,10 +157,9 @@ match DNS answer.  Note that eval rule t
 
 =item header RULE check_hashbl_tag('bl.example.invalid/A', 'OPTS', 'TAGNAME', '^127\.')
 
-Lookup value of SpamAssassin tag _TAGNAME_ from DNS list, optional subtest
-regexp to match DNS answer.
+Query value of SpamAssassin tag _TAGNAME_ from DNS list.
 
-DNS query type can be appended to list with /A (default) or /TXT.
+Optional DNS query type can be appended to list with /A (default) or /TXT.
 
 Default OPTS: sha1/max=10/shuffle
 
@@ -153,9 +173,46 @@ Additional supported OPTS:
   tld       only query if value has valid TLD (is_domain_valid)
   trim      trim name from hostname to domain (trim_domain)
 
-If both ip/ipv4/ipv6 and fqdn/tld are enabled, only either of them is
-required to match.  Both fqdn and tld are needed for complete FQDN+TLD
-check.
+  If both ip/ipv4/ipv6 and fqdn/tld are enabled, only either of them is
+  required to match.  Both fqdn and tld are needed for complete FQDN+TLD
+  check.
+
+Optional subtest regexp to match DNS answer (default: '^127\.').
+
+=back
+
+=over 4
+
+=item header RULE check_hashbl_attachments('bl.example.invalid/A', 'OPTS', '^127\.')
+
+Check all all message attachments (mimeparts) from DNS list.
+
+Optional DNS query type can be appended to list with /A (default) or /TXT.
+
+Default OPTS: sha1/max=10/shuffle
+
+Additional supported OPTS:
+
+  minsize=x  skip any parts smaller than x bytes
+  maxsize=x  skip any parts larger than x bytes
+
+Optional subtest regexp to match DNS answer (default: '^127\.').
+
+Specific attachment filenames can be skipped with C<hashbl_ignore>.  For
+example "hashbl_ignore safe.pdf".
+
+Specific mime types can be skipped with C<hashbl_ignore>.  For example
+"hashbl_ignore text/plain".
+
+=back
+
+=over 4
+
+=item hashbl_ignore value [value...]
+
+Skip any type of query, if either the hash or original value (email for
+example) matches.  Multiple values can be defined, separated by whitespace. 
+Matching is case-insensitive.
 
 =back
 
@@ -200,6 +257,7 @@ sub new {
     'check_hashbl_uris' => $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS,
     'check_hashbl_bodyre' => $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS,
     'check_hashbl_tag' => $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS,
+    'check_hashbl_attachments' => $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS,
   };
   while (my ($func, $type) = each %{$self->{evalfuncs}}) {
     $self->register_eval_rule($func, $type);
@@ -803,6 +861,87 @@ sub _check_hashbl_tag {
   return;
 }
 
+sub check_hashbl_attachments {
+  my ($self, $pms, undef, $list, $opts, $subtest) = @_;
+
+  return 0 if !$self->{hashbl_available};
+  return 0 if !$pms->is_dns_available();
+
+  my $rulename = $pms->get_current_eval_rule_name();
+
+  if (!defined $list) {
+    warn "HashBL: $rulename blocklist argument missing\n";
+    return 0;
+  }
+
+  if ($subtest) {
+    my ($rec, $err) = compile_regexp($subtest, 0);
+    if (!$rec) {
+      warn "HashBL: $rulename invalid subtest regex: $@\n";
+      return 0;
+    }
+    $subtest = $rec;
+  }
+
+  # Parse opts, defaults
+  $opts = _parse_opts($opts || 'sha1/max=10/shuffle');
+
+  if ($opts->{raw}) {
+    warn "HashBL: $rulename raw option invalid\n";
+    return 0;
+  }
+
+  my %seen;
+  my @hashes;
+  foreach my $part ($pms->{msg}->find_parts(qr/./, 1, 1)) {
+    my $body = $part->decode();
+    next if !defined $body || $body eq '';
+    my $type = lc $part->{'type'} || '';
+    my $name = $part->{'name'} || '';
+    my $len = length($body);
+    dbg("found attachment, type: $type, length: $len, name: $name");
+    if (exists $pms->{conf}->{hashbl_ignore}->{$type}) {
+      dbg("query skipped, ignored type: $type");
+      next;
+    }
+    if (exists $pms->{conf}->{hashbl_ignore}->{lc $name}) {
+      dbg("query skipped, ignored filename: $name");
+      next;
+    }
+    if ($opts->{minsize} && $len < $opts->{minsize}) {
+      dbg("query skipped, size smaller than $opts->{minsize}");
+      next;
+    }
+    if ($opts->{maxsize} && $len > $opts->{minsize}) {
+      dbg("query skipped, size larger than $opts->{maxsize}");
+      next;
+    }
+    my $hash = $self->_hash($opts, $body);
+    next if $seen{$hash}++;
+    push @hashes, $hash;
+  }
+
+  return 0 unless @hashes;
+
+  # Randomize order
+  if ($opts->{shuffle}) {
+    Mail::SpamAssassin::Util::fisher_yates_shuffle(\@hashes);
+  }
+
+  # Truncate list
+  my $max = $opts->{max} || 10;
+  $#hashes = $max-1 if scalar @hashes > $max;
+
+  my $queries;
+  foreach my $hash (@hashes) {
+    my $ret = $self->_submit_query($pms, $rulename, $hash, $list, $opts, $subtest, 1);
+    $queries++ if defined $ret;
+  }
+
+  return 0 if !$queries; # no query started
+  return; # return undef for async status
+}
+
 sub _hash {
   my ($self, $opts, $value) = @_;
 
@@ -821,21 +960,21 @@ sub _hash {
 }
 
 sub _submit_query {
-  my ($self, $pms, $rulename, $value, $list, $opts, $subtest) = @_;
+  my ($self, $pms, $rulename, $value, $list, $opts, $subtest, $already_hashed) = @_;
 
-  if (exists $pms->{conf}->{hashbl_ignore}->{lc $value}) {
+  if (!$already_hashed && exists $pms->{conf}->{hashbl_ignore}->{lc $value}) {
     dbg("query skipped, ignored string: $value");
     return 0;
   }
 
-  my $hash = $self->_hash($opts, $value);
-  dbg("querying $value ($hash) from $list");
-
-  if (exists $pms->{conf}->{hashbl_ignore}->{$hash}) {
+  my $hash = $already_hashed ? $value : $self->_hash($opts, $value);
+  if (exists $pms->{conf}->{hashbl_ignore}->{lc $hash}) {
     dbg("query skipped, ignored hash: $value");
     return 0;
   }
 
+  dbg("querying $value ($hash) from $list");
+
   my $type = $list =~ s,/(A|TXT)$,,i ? uc($1) : 'A';
   my $lookup = "$hash.$list";
 
@@ -888,5 +1027,6 @@ sub has_hashbl_email_welcomelist { 1 }
 sub has_hashbl_email_whitelist { 1 }
 sub has_hashbl_tag { 1 }
 sub has_hashbl_sha256 { 1 }
+sub has_hashbl_attachments { 1 }
 
 1;

Modified: spamassassin/trunk/t/data/spam/hashbl
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/spam/hashbl?rev=1901033&r1=1901032&r2=1901033&view=diff
==============================================================================
--- spamassassin/trunk/t/data/spam/hashbl (original)
+++ spamassassin/trunk/t/data/spam/hashbl Wed May 18 12:40:40 2022
@@ -15,6 +15,13 @@ Subject: There yours for FREE!
 X-Original-Sender: hustl.er@gmail.com
 X-Some-ID: 1234567890
 To: undisclosed-recipients:;
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="ETDFsshmzrOmOVdZ"
+Content-Disposition: inline
+
+--ETDFsshmzrOmOVdZ
+Content-Type: text/plain; charset=utf-8
+Content-Disposition: inline
 
 Hello
 
@@ -26,3 +33,248 @@ Some uris spammer.com https://spammer2.c
 
 btc 1JaSs2bTZYVbj6jaqZ5Mjfs8gSLY9vYCrK
 
+--ETDFsshmzrOmOVdZ
+Content-Type: application/octet-stream
+Content-Disposition: attachment; filename="macro.xlsm"
+Content-Transfer-Encoding: base64
+
+UEsDBBQABgAIAAAAIQDxGuVmhQEAAE8FAAATANkBW0NvbnRlbnRfVHlwZXNdLnhtbCCi1QEo
+oAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzFTLbsIwELxX6j9EvlbYQKWq
+qhI4tPTYcqAfYJwNuDi25TU0/H034SEhpSmUSy+J8tiZ2ZlJ0nFVmmQDAbWzGRvwPkvAKpdr
+u8jYx+y198gSjNLm0jgLGdsCsvHo9iadbT1gQtMWM7aM0T8JgWoJpUTuPFh6UrhQykiXYSG8
+VCu5ADHs9x+EcjaCjb1YY7BR+gKFXJuYTCq6vVMy15Ylz7v3aqqMSe+NVjKSULGxOS+x54pC
+K+CbuZwG9wkqMjFK32mboHNIpjLEN1nSqKiMiCQNdscBJ3G/oJ+usGfKnVqXJJw3YHc1ys+E
+GLcG8Goq9AFkjkuAWBq+Az0wt/gWwOBlq+2T4TTZmItL7bGDodu7bk++XFjNnVud4QrFC5UC
+WrlenZdSBTexcm6AopfaHhS2xU05USE8CurMGVTdWUNdyhzynidICFHD0Z02bqpavWWjGkVz
+Gl6t4bQER/wuD1p03P8THdd/f3/w49gJ5QJcbsThK6mnW5ogmt/h6BsAAP//AwBQSwMEFAAG
+AAgAAAAhALVVMCP1AAAATAIAAAsAzgFfcmVscy8ucmVscyCiygEooAACAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAIySz07DMAzG70i8Q+T76m5ICKGlu0xIuyFUHsAk7h+1jaMkQPf2hAOCSmPb
+0fbnzz9b3u7maVQfHGIvTsO6KEGxM2J712p4rZ9WD6BiImdpFMcajhxhV93ebF94pJSbYtf7
+qLKLixq6lPwjYjQdTxQL8exypZEwUcphaNGTGahl3JTlPYa/HlAtPNXBaggHeweqPvo8+bK3
+NE1veC/mfWKXToxAnhM7y3blQ2YLqc/bqJpCy0mDFfOc0xHJ+yJjA54m2lxP9P+2OHEiS4nQ
+SODzPN+Kc0Dr64Eun2ip+L3OPOKnhOFNZPhhwcUPVF8AAAD//wMAUEsDBBQABgAIAAAAIQCc
+fziWGAEAAMEDAAAaAAgBeGwvX3JlbHMvd29ya2Jvb2sueG1sLnJlbHMgogQBKKAAAQAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+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
+AwAAsi4AAAAA
+
+--ETDFsshmzrOmOVdZ--
+

Modified: spamassassin/trunk/t/hashbl.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/hashbl.t?rev=1901033&r1=1901032&r2=1901033&view=diff
==============================================================================
--- spamassassin/trunk/t/hashbl.t (original)
+++ spamassassin/trunk/t/hashbl.t Wed May 18 12:40:40 2022
@@ -39,6 +39,7 @@ jykf2a5v6asavfel3stymlmieh4e66jeroxuw52m
 6a42acf4133289d595e3875a9d677f810e80b7b4.hashbltest4.spamassassin.org
 5c6205960a65b1f9078f0e12dcac970aab0015eb.hashbltest4.spamassassin.org
 1234567890.hashbltest5.spamassassin.org
+w3hcrlct6yshq5vq6gjv2hf3pzk3jvsk6ilj5iaks4qwewudrr6q.hashbltest6.spamassassin.org
 );
 
 sub check_queries {
@@ -59,11 +60,11 @@ sub check_queries {
     }
   }
   close WL;
+  diag("Invalid query launched: $_") foreach (keys %invalid);
   unless (keys %found == @valid_queries) {
-    diag("Not all queries launched");
+    diag("Incorrect amount of queries launched");
     return 0;
   }
-  diag("Invalid query launched: $_") foreach (keys %invalid);
   return !%invalid;
 }
 
@@ -90,6 +91,12 @@ tstlocalrules(q{
   header   __X_SOME_ID X-Some-ID =~ /^(?<XSOMEID>\d{10,20})$/
   header   X_HASHBL_TAG eval:check_hashbl_tag('hashbltest5.spamassassin.org/A', 'raw', 'XSOMEID', '^127\.')
 
+  # Not supposed to hit, @valid_queries just checks that they are launched
+  hashbl_ignore text/plain
+  body     X_HASHBL_ATT eval:check_hashbl_attachments('hashbltest6.spamassassin.org/A', 'sha256')
+  describe X_HASHBL_ATT Message contains attachment found on attbl
+  tflags   X_HASHBL_ATT net
+
   # Bug 7897 - test that meta rules depending on net rules hit
   meta META_HASHBL_EMAIL X_HASHBL_EMAIL
   # It also needs to hit even if priority is lower than dnsbl (-100)