DKIM2#848
Conversation
✅ Deploy Preview for support-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 86efa82. Configure here.
|
I'll restructure the page into sub-pages. |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
||
| The following are known gaps or operational considerations to be aware of: | ||
|
|
||
| * **§10.5/§10.6 Lower-hop signatures not cryptographically verified**: |
There was a problem hiding this comment.
Suggested rewrite:
Lower-hop signatures not cryptographically verified (§9.1 / §9.2 / §10.5–10.6): Momentum runs the full cryptographic procedure — key fetch (§10.5) and EVP signature verification (§10.6) — only on the highest-i
signature, which §9.1 makes a SHOULD. Earlier hops (i < max_i) get no key lookup and no crypto (status="chain_verified"); their integrity rests on the §8.3/§10.4 chain-of-custody check and the §9.2/§10 recipe
reconstruction, which reverse-applies each hop's recipe to rebuild the original message and confirms the reconstructed instance-1 hashes match MI[1]'s h=. This proves end-to-end content integrity but does not
authenticate each lower hop's signing key, so earlier-hop signer identities should not be used for Reviser reputation. The spec does not yet clearly mandate per-hop crypto (§9.1 is SHOULD; §9.3 implies it for
reputation purposes but defers to a TBA doc, and §15 is TBA). Full per-hop verification — reverse-applying subsequent recipes to rebuild each hop's state and EVP-verifying each signature — would close this and
is deferred to a future release.
| significant architectural change that can be planned in a future | ||
| release if desirable. | ||
|
|
||
| * **§9.1 / §11 DSN**: The spec requires that when DKIM2 verification |
There was a problem hiding this comment.
Suggested rewrite:
- §9.1 / §11 DSN: Per §9.1, after a failed DKIM2 verification the
MTA MUST NOT generate a DSN; the spec recommends rejecting with a 5xx
during the SMTP conversation as the best alternative. This is not
automatically enforced — verify() only reports a verdict, so policy
must explicitly reject rather than bounce on verify failure. On the
generation side (§11), Momentum does not yet address a DSN to the
mf= of the highest-numbered DKIM2-Signature of the original message,
nor suppress DSN generation when that highest-numbered mf= is <>
(null sender). Inbound DSN authentication (§11.1.2, a SHOULD) is also
not implemented: the reject/propagate decision is scriptable via the
inbound hooks, but verifying the embedded returned message's
signatures — and checking signing-domain alignment against its
highest-i= rt= — has no exposed API, since verify() operates
only on the live message.
| correctly. See the [Forwarder and modifier signing](/momentum/4/dkim2/sign#forwarder-and-modifier-signing) section for how to | ||
| do this. | ||
|
|
||
| * **Content modifier recipe composition**: When a Momentum pipeline |
There was a problem hiding this comment.
Suggested rewrite:
- Content modifier recipe composition: When an upstream-signed
message passes through a Momentum stage that modifies it — the
engagement tracker rewriting URLs, a footer filter, a list processor
changing headers — sign() automatically detects the change (its
freshly computed header/body hashes no longer match the prior
Message-Instance) and requires a recipe= describing how to reverse
the hop; without one the sign call fails. When the full diff isn't
available, the workaround is a null recipe declaring the change
irreversible: recipe='{"b":null}' for a body change,
recipe='{"h":null}' for header changes (a precise recipe when the
diff is available). This lets signing succeed and this hop's
signature verifies downstream — but earlier signatures' content can
no longer be reconstructed past this hop, so the inner chain is
broken for that field and acceptance depends on the verifier's
policy toward a broken chain. (Originated mail needs no recipe —
there's no prior instance to diff against.) Automatic change-recording
by pipeline stages is not yet built; a planned Recipe Accumulator API
would let sign() assemble the recipe without operator involvement.
| If you already publish DKIM1 keys at a selector, you can reuse the same | ||
| selector for DKIM2 without any DNS change. To generate fresh keys for | ||
| DKIM2 specifically, follow the standard openssl recipe in | ||
| [Generating DKIM Keys](/momentum/4/using-dkim#using_dkim.generating). |
There was a problem hiding this comment.
The link is wrong; it should be: /momentum/4/using-dkim#generating-dkim-keys
| | `keybuf` | yes (single) | PEM-encoded private key as a string in memory. Alternative to `keyfile` for cases where the key is held in a secrets manager or generated at runtime. When `sig_sets` is used, set per entry inside `sig_sets` instead. | | ||
| | `algorithm` | no | `"rsa-sha256"` (default) or `"ed25519-sha256"`. When `sig_sets` is used, set per entry inside `sig_sets` instead. | | ||
| | `sig_sets` | no | Array of `{selector, keyfile, keybuf, algorithm}` tables for multi-algorithm signing (§7.8). When `sig_sets` is used, `selector`/`keyfile`/`keybuf`/`algorithm` are set per entry inside `sig_sets` — do not set them at the top level. All other options (`domain`, `mailfrom`, `rcptto`, `flags`, `recipe`, etc.) remain at the top level and apply to all entries. | | ||
| | `mailfrom` | no | **Normally omitted** — Momentum reads the live envelope MAIL FROM automatically. Two production exceptions: (1) null-sender DSN/bounce messages where `mailfrom=""` is required since the envelope API returns nil for `MAIL FROM:<>`; (2) testing/simulation of specific envelope conditions without real SMTP transit. | |
There was a problem hiding this comment.
(Lack of consistency) In the corresponding table in verify.md, the "test/simulation" is not considered a production exception.
There was a problem hiding this comment.
iiuc, you're suggesting to move the second bullet out of "production exceptions", which sounds good to me.
| promoted to `dkim2=temperror` or `dkim2=permerror` for specific reason codes | ||
| (noted in the table below); `status="chain_verified"` and `status="none"` are excluded from AR output. | ||
|
|
||
| The full set. Unless otherwise noted, each reason code below pairs with `status="fail"` in `result.signatures[i].status`. |
There was a problem hiding this comment.
Didn't get it: "the full set".
There was a problem hiding this comment.
Seems like that can be removed.
| | `info` | Adds one DNS resolution line per verified signature plus verification failures with their cause (`bh_mismatch` with expected vs. actual hash; `sig_invalid` with selector, algorithm, signed-input length, and OpenSSL detail). | | ||
| | `debug` | Adds raw TXT-record bytes from the resolver, a per-crypto-check trace line, and the raw signed-input bytes on failure. Too noisy for steady-state production; useful when chasing a specific sign/verify mismatch. | | ||
|
|
||
| ### Per-signature reason codes |
There was a problem hiding this comment.
I think this whole section ("Per-signature reason codes") should reside in the verify.md page. It is not just debugging; it is about what can be found in the validation results.
Same for "recipe_chain detail strings" below.
| | `signature_expired` | The `t=` timestamp is older than `max_sig_age_days` (default 14). §10.3 says verifiers SHOULD reject such signatures; Momentum's implementation choice is to treat this as PERMERROR (permanently unverifiable — no cryptographic verification is attempted). Maps to `dkim2=permerror` in AR output. | | ||
| | `signature_future` | The `t=` timestamp is more than `max_sig_future_secs` (default 300 s) in the future. Treated as a soft policy failure (`dkim2=fail`): the timestamp was evaluated and rejected, but it is not a permanent infrastructure error — the spec (§7.4 MAY) does not define a verdict for this case. | | ||
| | `nonce_too_long` | The `n=` nonce exceeded the 64-character ceiling (§7.3 SHOULD). Treated as `dkim2=fail` — the constraint is a SHOULD, not a structural permanent error. | | ||
| | `mailfrom_mismatch` | The signed `mf=` doesn't match the actual envelope MAIL FROM — replay-to-different-sender. | |
There was a problem hiding this comment.
nit: "replay-from-different-sender"
There was a problem hiding this comment.
I find replay-to-different-sender. and the replay-to-different-recipient in next bullet confusing. Maybe just remove those phrases, since rest of the description is clear enough?

What Changed
How To Test or Verify
PR Checklist
Below are some checklists to follow for the correct procedure in different circumstance. The first list ("All PRs Checklist") should be followed for ALL PRs. The next 2 are additive to this list depending on what type of PR you are using.
For example: If you are submitting a content change to one of the support documents, your checklist would include the:
If you are submitting a feature addition, enhancement, or bug fix, your checklist would include the:
All PRs Checklist
team-FEorteam-SAZ)Content Changes Checklist
examples/article.mdin the root of the project directory and on the momentum doc's preface articleDevelopment Changes Checklist (some checks are automatic github actions and will not be listed here. ie. "all tests pass")
cypress/directory in the root of the projectNote
Low Risk
Documentation-only changes with no runtime or configuration behavior; main risk is inaccurate operator guidance if the draft spec or product behavior diverges from the text.
Overview
Adds a full DKIM2 documentation set for Momentum 4, targeting draft
ietf-dkim-dkim2-spec-02, placed next to the existing DKIM1 articles.The new
/momentum/4/dkim2overview explains DKIM2 vs DKIM1, thedkim2 {}config stanza, Lua-driven signing/verification viavalidate_data_spool_each_rcpt, key reuse, coexistence with DKIM1/ARC, and documented implementation gaps (lower-hop crypto, DSN, forwarder/recipe automation).Child pages document
msys.validate.dkim2.sign()(hooks, options, forwarder bridges, recipes),verify()(§10.4 per-recipient checks, result table, §9.4 SMTP guidance),ar_clauses()for combinedAuthentication-Results, and a debug reference (debug_level, reason codes, recipe-chain paniclog strings,dkim2_*context keys).content/momentum/navigation.ymlgains a “Using DKIM2” section with links to signing and verifying anchors under Configuring Momentum.Reviewed by Cursor Bugbot for commit 129960a. Bugbot is set up for automated code reviews on this repo. Configure here.