dmarcator 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50fb6579de6a2c481e5154bd28500cdb8c9800ba6897cd6aa76952c985531f22
4
- data.tar.gz: d36d360f6d6cfe6e022cca51af4eec27c03349951c6259b486e178d31d2e32ad
3
+ metadata.gz: ab57f0c6599df86ed3331199b84f0abdd9aae8876405e4205f2070b9e457b5ca
4
+ data.tar.gz: 5466098000ff91848f6cb8208606ed8b3312eb9b476763074d917b3f7f377aca
5
5
  SHA512:
6
- metadata.gz: 8aaea7f0723b6a2eb070345ba516e09b0b6a0391d35c19349eab9791b72ddc06b90f149706c8bf988f3c80784cfe47319577f3c10d190592c482c84320e4962d
7
- data.tar.gz: bafee6506fdd045ff918bcbb89a7553fd761bcbbdd91eb274d737dc02d87b14014a5011566799f570197dbc0ffd71cc37872f3cf915eadeda8022c591e594b97
6
+ metadata.gz: 71c83a39fc17926997e43465ef07210a7a062a67b922462623e4d6c0daa617acefc4ec2546194ffa2f6755bf62321504134140b5e35813ac9e34b8ed43abbf89
7
+ data.tar.gz: ebdc8623cdba9d7632188a52fbdc203d7c1f85efed580f72ce5ceebeafd8a7c5eae4f5f0797d8960b1a6fb5e1b2af9aeb9d5484ffc74a5f8adc4942af4de286a
data/ChangeLog.md CHANGED
@@ -1,3 +1,11 @@
1
+ ### 0.6.1 / 2026-10-29
2
+
3
+ * Remove a redundant `attr_reader :sp` that `Record#sp` already overrode, fixing a `method redefined; discarding old sp` warning emitted under `-w` (as CI enables).
4
+ * Add tests covering `Record#sp` falling back to `p` when `sp` is omitted.
5
+ * Document every report accessor, constant, and public class that YARD previously omitted; documentation coverage is now 100% with no warnings.
6
+ * Note the encrypted-transport guarantees in the README security section.
7
+ * No functional changes.
8
+
1
9
  ### 0.6.0 / 2026-10-28
2
10
 
3
11
  * Add DNS over HTTPS (RFC 8484) and DNS over TLS (RFC 7858) transports, selected with `DMARCATOR_DNS` (`doh` or `dot`). No provider is bundled and plaintext remains the default.
data/README.md CHANGED
@@ -177,6 +177,11 @@ DMARC records and XML reports are treated as untrusted input:
177
177
  CVE-2026-80212/CVE-2026-80213.
178
178
  * Domain names are validated before resolution (labels ≤ 63 octets, total ≤
179
179
  248 octets, no leading/trailing hyphens).
180
+ * Optional [DNS over HTTPS and DNS over TLS](#dns-transport) transports verify
181
+ TLS certificates and fail closed: a transport failure raises
182
+ `DMARCator::ResolutionError` rather than being reported as a domain without a
183
+ DMARC record, so a blocked or downgraded lookup can never silently enforce no
184
+ policy.
180
185
 
181
186
  ## Requirements
182
187
 
@@ -3,6 +3,11 @@
3
3
  require 'parslet'
4
4
 
5
5
  module DMARCator
6
+ #
7
+ # Raised when a DMARC record cannot be parsed.
8
+ #
9
+ # @since 0.2.0
10
+ #
6
11
  class InvalidRecord < Parslet::ParseFailed
7
12
  end
8
13
 
@@ -50,6 +50,21 @@ module DMARCator
50
50
  unknown_tag
51
51
  end
52
52
 
53
+ #
54
+ # Defines a rule for a `tag=value` DMARC tag, capturing values that do not
55
+ # match the rule as `:invalid_tag` so strict mode can reject them.
56
+ #
57
+ # @param [Symbol] name
58
+ # The rule name, used as `dmarc_<name>`.
59
+ #
60
+ # @param [String] tag
61
+ # The DMARC tag name.
62
+ #
63
+ # @yieldreturn [Parslet::Atoms::Base]
64
+ # The parslet matching the tag's value.
65
+ #
66
+ # @api private
67
+ #
53
68
  def self.tag_rule(name,tag,&block)
54
69
  rule(:"dmarc_#{name}") do
55
70
  (
@@ -123,6 +138,11 @@ module DMARCator
123
138
  rule(:wsp) { str(' ') | str("\t") }
124
139
  rule(:wsp?) { wsp.repeat }
125
140
 
141
+ #
142
+ # Converts the raw parse tree into tag attributes.
143
+ #
144
+ # @api private
145
+ #
126
146
  class Transform < Parslet::Transform
127
147
 
128
148
  rule(fo_opt: simple(:fo_opt)) { fo_opt }
@@ -155,8 +175,21 @@ module DMARCator
155
175
  rule(rua: subtree(:uris)) { {rua: Array(uris)} }
156
176
  rule(ruf: subtree(:uris)) { {ruf: Array(uris)} }
157
177
 
178
+ # Maximum length of error messages echoed from untrusted input.
158
179
  MAX_ERROR_MESSAGE_LENGTH = 300
159
180
 
181
+ #
182
+ # Parses a `mailto:` URI from a parse tree slice.
183
+ #
184
+ # @param [Parslet::Slice] value
185
+ #
186
+ # @return [URI::MailTo]
187
+ #
188
+ # @raise [InvalidRecord]
189
+ # The URI is not a valid `mailto:` URI.
190
+ #
191
+ # @api private
192
+ #
160
193
  def self.parse_uri(value)
161
194
  uri = value.to_s
162
195
 
@@ -173,6 +206,15 @@ module DMARCator
173
206
  parsed
174
207
  end
175
208
 
209
+ #
210
+ # Truncates an error message to a bounded length.
211
+ #
212
+ # @param [String] message
213
+ #
214
+ # @return [String]
215
+ #
216
+ # @api private
217
+ #
176
218
  def self.truncate(message)
177
219
  message = message.to_s
178
220
 
@@ -7,10 +7,15 @@ require 'dmarcator/exceptions'
7
7
  require 'resolv'
8
8
 
9
9
  module DMARCator
10
+ #
11
+ # A parsed DMARC record, as published in a `_dmarc.<domain>` TXT record.
12
+ #
13
+ # @see https://www.rfc-editor.org/rfc/rfc7489
14
+ #
10
15
  class Record
11
16
 
12
17
  # `p` field.
13
- #
18
+ #
14
19
  # @return [:none, :quarantine, :reject]
15
20
  attr_reader :p
16
21
 
@@ -24,11 +29,6 @@ module DMARCator
24
29
  # @return [Array<Uri>]
25
30
  attr_reader :ruf
26
31
 
27
- # `sp` field.
28
- #
29
- # @return [:none, :quarantine, :reject]
30
- attr_reader :sp
31
-
32
32
  # `v` field.
33
33
  #
34
34
  # @return [:DMARC1]
@@ -10,9 +10,20 @@ module DMARCator
10
10
  class AuthResult
11
11
  include XmlHelper
12
12
 
13
+ # Attribute names exposed by this class.
13
14
  ATTRIBUTES = [:type, :domain, :result, :selector, :scope]
14
15
 
15
- attr_accessor(*ATTRIBUTES)
16
+ # @!attribute [rw] type
17
+ # @return [String] the entry name (`dkim` or `spf`).
18
+ # @!attribute [rw] domain
19
+ # @return [String, nil] the authenticated domain.
20
+ # @!attribute [rw] result
21
+ # @return [String, nil] the authentication result.
22
+ # @!attribute [rw] selector
23
+ # @return [String, nil] the DKIM selector.
24
+ # @!attribute [rw] scope
25
+ # @return [String, nil] the SPF scope.
26
+ ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
16
27
 
17
28
  def initialize(node)
18
29
  @node = node
@@ -10,12 +10,27 @@ module DMARCator
10
10
  class Metadata
11
11
  include XmlHelper
12
12
 
13
+ # Attribute names exposed by this class.
13
14
  ATTRIBUTES = [
14
15
  :org_name, :email, :extra_contact_info, :report_id,
15
16
  :begin_at, :end_at, :errors
16
17
  ]
17
18
 
18
- attr_accessor(*ATTRIBUTES)
19
+ # @!attribute [rw] org_name
20
+ # @return [String, nil] the reporting organization.
21
+ # @!attribute [rw] email
22
+ # @return [String, nil] the reporting organization's contact address.
23
+ # @!attribute [rw] extra_contact_info
24
+ # @return [String, nil] additional contact information.
25
+ # @!attribute [rw] report_id
26
+ # @return [String, nil] the unique report identifier.
27
+ # @!attribute [rw] begin_at
28
+ # @return [Time, nil] the start of the reporting interval.
29
+ # @!attribute [rw] end_at
30
+ # @return [Time, nil] the end of the reporting interval.
31
+ # @!attribute [rw] errors
32
+ # @return [Array<String>] errors reported by the reporting organization.
33
+ ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
19
34
 
20
35
  def initialize(node)
21
36
  @node = node
@@ -10,9 +10,24 @@ module DMARCator
10
10
  class Policy
11
11
  include XmlHelper
12
12
 
13
+ # Attribute names exposed by this class.
13
14
  ATTRIBUTES = [:domain, :adkim, :aspf, :p, :sp, :pct, :fo]
14
15
 
15
- attr_accessor(*ATTRIBUTES)
16
+ # @!attribute [rw] domain
17
+ # @return [String, nil] the domain the policy applies to.
18
+ # @!attribute [rw] adkim
19
+ # @return [String, nil] the DKIM alignment mode (`r` or `s`).
20
+ # @!attribute [rw] aspf
21
+ # @return [String, nil] the SPF alignment mode (`r` or `s`).
22
+ # @!attribute [rw] p
23
+ # @return [String, nil] the requested policy.
24
+ # @!attribute [rw] sp
25
+ # @return [String, nil] the requested subdomain policy.
26
+ # @!attribute [rw] pct
27
+ # @return [Integer, nil] the percentage of messages the policy applied to.
28
+ # @!attribute [rw] fo
29
+ # @return [String, nil] the requested failure-reporting options.
30
+ ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
16
31
 
17
32
  def initialize(node)
18
33
  @node = node
@@ -10,9 +10,14 @@ module DMARCator
10
10
  class Reason
11
11
  include XmlHelper
12
12
 
13
+ # Attribute names exposed by this class.
13
14
  ATTRIBUTES = [:type, :comment]
14
15
 
15
- attr_accessor(*ATTRIBUTES)
16
+ # @!attribute [rw] type
17
+ # @return [String, nil] the reason type.
18
+ # @!attribute [rw] comment
19
+ # @return [String, nil] an optional explanatory comment.
20
+ ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
16
21
 
17
22
  def initialize(node)
18
23
  @node = node
@@ -12,12 +12,29 @@ module DMARCator
12
12
  class Record
13
13
  include XmlHelper
14
14
 
15
+ # Attribute names exposed by this class.
15
16
  ATTRIBUTES = [
16
17
  :source_ip, :count, :disposition, :dkim, :spf,
17
18
  :envelope_to, :envelope_from, :header_from
18
19
  ]
19
20
 
20
- attr_accessor(*ATTRIBUTES)
21
+ # @!attribute [rw] source_ip
22
+ # @return [String, nil] the sending IP address.
23
+ # @!attribute [rw] count
24
+ # @return [Integer, nil] the number of messages from this source.
25
+ # @!attribute [rw] disposition
26
+ # @return [String, nil] the action applied to the messages.
27
+ # @!attribute [rw] dkim
28
+ # @return [String, nil] the DKIM evaluation result.
29
+ # @!attribute [rw] spf
30
+ # @return [String, nil] the SPF evaluation result.
31
+ # @!attribute [rw] envelope_to
32
+ # @return [String, nil] the envelope recipient domain.
33
+ # @!attribute [rw] envelope_from
34
+ # @return [String, nil] the envelope sender domain.
35
+ # @!attribute [rw] header_from
36
+ # @return [String, nil] the `From` header domain.
37
+ ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
21
38
  attr_writer :auth_results, :reasons
22
39
 
23
40
  def initialize(node)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DMARCator
4
4
  # dmarcator version
5
- VERSION = '0.6.0'
5
+ VERSION = '0.6.1'
6
6
  end
data/lib/dmarcator.rb CHANGED
@@ -6,3 +6,15 @@ require 'dmarcator/record'
6
6
  require 'dmarcator/parser'
7
7
  require 'dmarcator/report'
8
8
  require 'dmarcator/version'
9
+
10
+ #
11
+ # Parses, queries, and validates DMARC records and aggregates.
12
+ #
13
+ # Use {DMARCator::Record} for `_dmarc.<domain>` TXT records, {DMARCator::Report}
14
+ # for aggregate/forensic XML reports, and {DMARCator::Resolver} to choose the
15
+ # DNS transport (plaintext, DNS over HTTPS, or DNS over TLS).
16
+ #
17
+ # @see https://www.rfc-editor.org/rfc/rfc7489
18
+ #
19
+ module DMARCator
20
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dmarcator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Berman