sendermap 0.1.2__tar.gz → 0.1.3__tar.gz
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.
- {sendermap-0.1.2 → sendermap-0.1.3}/ARCHITECTURE.md +15 -10
- {sendermap-0.1.2 → sendermap-0.1.3}/CHANGELOG.md +18 -0
- sendermap-0.1.3/CLAUDE.md +38 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/PKG-INFO +1 -1
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/data/vendors.json +509 -7
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/data/vendors_pending.json +29 -38
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/engine.py +61 -11
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/mcp_server.py +7 -5
- sendermap-0.1.3/sendermap/render/__init__.py +32 -0
- sendermap-0.1.3/sendermap/render/html.py +238 -0
- sendermap-0.1.3/sendermap/render/markdown.py +112 -0
- sendermap-0.1.3/sendermap/render/text.py +164 -0
- sendermap-0.1.3/sendermap/render/view.py +691 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/setup.py +16 -12
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/dkim.py +30 -12
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/mx.py +15 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/subdomains.py +15 -1
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/story.py +10 -3
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/web/app.py +16 -3
- sendermap-0.1.3/sendermap/web/static/app.js +258 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/web/static/index.html +3 -14
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/web/static/llms.txt +2 -1
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/web/static/style.css +19 -15
- {sendermap-0.1.2 → sendermap-0.1.3}/server.json +3 -3
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/conftest.py +6 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/allbirds.com/dns.json +29 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/allbirds.com/expected.json +98 -21
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/casper.com/dns.json +29 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/casper.com/expected.json +72 -19
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/example.com/dns.json +2 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/example.com/expected.json +3 -1
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/glossier.com/dns.json +29 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/glossier.com/expected.json +40 -11
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/mills-reeve.com/dns.json +29 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/mills-reeve.com/expected.json +38 -9
- sendermap-0.1.3/tests/snapshots/allbirds.com/markdown-evidence.md +260 -0
- sendermap-0.1.3/tests/snapshots/allbirds.com/markdown.md +78 -0
- sendermap-0.1.3/tests/snapshots/allbirds.com/page.html +520 -0
- sendermap-0.1.3/tests/snapshots/allbirds.com/text-evidence.txt +443 -0
- sendermap-0.1.3/tests/snapshots/allbirds.com/text.txt +152 -0
- sendermap-0.1.3/tests/snapshots/casper.com/markdown-evidence.md +200 -0
- sendermap-0.1.3/tests/snapshots/casper.com/markdown.md +73 -0
- sendermap-0.1.3/tests/snapshots/casper.com/page.html +470 -0
- sendermap-0.1.3/tests/snapshots/casper.com/text-evidence.txt +287 -0
- sendermap-0.1.3/tests/snapshots/casper.com/text.txt +142 -0
- sendermap-0.1.3/tests/snapshots/example.com/markdown-evidence.md +26 -0
- sendermap-0.1.3/tests/snapshots/example.com/markdown.md +18 -0
- sendermap-0.1.3/tests/snapshots/example.com/page.html +45 -0
- sendermap-0.1.3/tests/snapshots/example.com/text-evidence.txt +28 -0
- sendermap-0.1.3/tests/snapshots/example.com/text.txt +20 -0
- sendermap-0.1.3/tests/snapshots/glossier.com/markdown-evidence.md +151 -0
- sendermap-0.1.3/tests/snapshots/glossier.com/markdown.md +54 -0
- sendermap-0.1.3/tests/snapshots/glossier.com/page.html +338 -0
- sendermap-0.1.3/tests/snapshots/glossier.com/text-evidence.txt +244 -0
- sendermap-0.1.3/tests/snapshots/glossier.com/text.txt +102 -0
- sendermap-0.1.3/tests/snapshots/mills-reeve.com/markdown-evidence.md +119 -0
- sendermap-0.1.3/tests/snapshots/mills-reeve.com/markdown.md +58 -0
- sendermap-0.1.3/tests/snapshots/mills-reeve.com/page.html +245 -0
- sendermap-0.1.3/tests/snapshots/mills-reeve.com/text-evidence.txt +156 -0
- sendermap-0.1.3/tests/snapshots/mills-reeve.com/text.txt +98 -0
- sendermap-0.1.3/tests/test_alias_subdomains.py +48 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_backed_only.py +11 -1
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_checked.py +7 -3
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_dkim_denial.py +24 -11
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_engine.py +19 -1
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_gaps.py +3 -1
- sendermap-0.1.3/tests/test_html.py +133 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_labels_say_what_was_seen.py +11 -9
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_mcp.py +19 -1
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_reconcile.py +5 -5
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_resolver.py +3 -5
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_setup.py +32 -17
- sendermap-0.1.3/tests/test_snapshots.py +54 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_spf_checks.py +2 -1
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_story.py +33 -10
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_supporting_evidence.py +4 -2
- sendermap-0.1.3/tests/test_view.py +150 -0
- sendermap-0.1.3/tests/test_web.py +170 -0
- sendermap-0.1.2/sendermap/render/__init__.py +0 -181
- sendermap-0.1.2/sendermap/render/markdown.py +0 -97
- sendermap-0.1.2/sendermap/render/text.py +0 -152
- sendermap-0.1.2/sendermap/web/static/app.js +0 -500
- sendermap-0.1.2/tests/test_web.py +0 -55
- {sendermap-0.1.2 → sendermap-0.1.3}/.github/workflows/publish.yml +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/.github/workflows/recheck-corpus.yml +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/.github/workflows/test.yml +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/.github/workflows/verify-proofs.yml +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/.gitignore +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/LICENSE +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/README.md +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/data/hunt_domains.txt +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/pyproject.toml +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/__init__.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/catalog.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/cli.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/data/schema/sendermap-1.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/dns.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/infer.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/model.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/proofs.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/py.typed +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/reconcile.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/selectors.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/__init__.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/bimi.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/ct.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/labels.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/mta_sts.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/networks.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/reports.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/spf.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/spf_checks.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/spf_eval.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/tokens.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/sources/web.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/testing.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/sendermap/web/__init__.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/skills/sendermap/SKILL.md +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/README.md +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/allbirds.com/case.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/allbirds.com/http.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/casper.com/case.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/casper.com/http.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/example.com/case.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/example.com/http.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/glossier.com/case.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/glossier.com/http.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/mills-reeve.com/case.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/corpus/mills-reeve.com/http.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/fixtures/crtsh_allbirds.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/fixtures/mcp-server-2025-12-11.schema.json +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_caps.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_catalog.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_cli.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_corpus.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_ct.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_delegation.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_determinism.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_dkim.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_evidence.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_gap_kinds.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_hunt_proofs.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_listing.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_networks.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_one_selector_list.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_package.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_pending_vendors.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_publish.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_recheck_corpus.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_record_replay.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_schema.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_selectors.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_skill.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_sources.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_spf.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_spf_eval.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_subprocessor_names.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_vendor_proofs.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_verify_proofs.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_web_guard.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_web_mcp.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/test_wildcard_subdomains.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/vectors/rfc7208-tests.LICENSE +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tests/vectors/rfc7208-tests.yml +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tools/fill_corpus.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tools/hunt_proofs.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tools/record_corpus.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tools/release_notes.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/tools/verify_proofs.py +0 -0
- {sendermap-0.1.2 → sendermap-0.1.3}/uv.lock +0 -0
|
@@ -137,7 +137,8 @@ Assembly
|
|
|
137
137
|
-> SenderMap + Run
|
|
138
138
|
story.write: the paragraph from the map; no query
|
|
139
139
|
reconcile.with_claimed: the claimed names against the services; no query
|
|
140
|
-
-> render.
|
|
140
|
+
-> render.view: one View, findings first, each thing said once; no query
|
|
141
|
+
-> render.text | render.markdown (both from the View) | to_dict JSON | web stream
|
|
141
142
|
```
|
|
142
143
|
|
|
143
144
|
Each source returns an `Observed`: claims, unknowns, findings, gaps and what it checked. Sources never raise
|
|
@@ -208,7 +209,8 @@ for the check and a private one a moment later (DNS rebinding) is not caught.
|
|
|
208
209
|
| `sources/networks.py` | Naming SPF IP ranges by the ranges services publish, then by network owner |
|
|
209
210
|
| `sources/ct.py` | Certificate transparency: the only reader of certificate logs. Reads them once per map through a provider list (Cert Spotter always, crt.sh with `--ct`), each with its own tries, timeout and one-day disk cache, into one set of names; with `--ct`, each logged name resolved and matched by the same catalog matchers as the DNS sources |
|
|
210
211
|
| `sources/subdomains.py` | Finding sending subdomains from a wordlist and the names `ct.py` read |
|
|
211
|
-
| `render/
|
|
212
|
+
| `render/view.py` | The View every view renders: findings first, then services by tier with one evidence line per kind and the setup in one clause, then what was not recognised, checked and asked, counted unless evidence is set; and the helpers only views use (unknowns grouped by network, the claimed parts, the CT lines, the resolver words) |
|
|
213
|
+
| `render/text.py`, `render/markdown.py` | The terminal and Markdown views of the View; they hold no logic about the map. `tests/snapshots` holds both, for every corpus case, with evidence off and on |
|
|
212
214
|
| `cli.py` | The `sendermap` command, and `sendermap mcp`, which starts the MCP server |
|
|
213
215
|
| `mcp_server.py` | The MCP server: its two tools, two resources and instructions, and the summary of a map |
|
|
214
216
|
| `testing.py` | Recording and replaying DNS answers and pages, and replaying a recorded case |
|
|
@@ -222,7 +224,7 @@ These live in `catalog.py` and the sources, and each has a test.
|
|
|
222
224
|
longest pattern wins: `u161779.wl030.sendgrid.net` is Klaviyo, not SendGrid.
|
|
223
225
|
- A DKIM key belongs to a service by CNAME first. `selector1` and `selector2` name nobody
|
|
224
226
|
without the Microsoft CNAME. Next, a backed only selector (`rules.backed_only_selectors`, each
|
|
225
|
-
with a proof: `s1` and `s2` for Klaviyo) credits its one vendor only when SPF, MX or an NS
|
|
227
|
+
with a proof: `s1` and `s2` for Klaviyo, `mailing` for Optimizely Campaign) credits its one vendor only when SPF, MX or an NS
|
|
226
228
|
delegation already shows that vendor, and nobody otherwise. Next, a selector on the list of a
|
|
227
229
|
service that SPF, MX or an NS delegation already shows is that service's. Last, the name table, except for generic names (`default`, `mail`,
|
|
228
230
|
any two-letter name not explicitly allowed).
|
|
@@ -233,7 +235,7 @@ These live in `catalog.py` and the sources, and each has a test.
|
|
|
233
235
|
A redirect is ignored when the record has `all`. Inside a service's own record nothing more
|
|
234
236
|
is credited, except inside a flattening service's record (EasyDMARC), which copies the
|
|
235
237
|
domain's own senders.
|
|
236
|
-
- A hosted SPF service (Valimail, OnDMARC, EasyDMARC) or a macro include is reported as a note,
|
|
238
|
+
- A hosted SPF service (Valimail, OnDMARC, EasyDMARC, DmarcDuty) or a macro include is reported as a note,
|
|
237
239
|
since it can authorize services without naming them.
|
|
238
240
|
- A subdomain delegated by NS is the service's when its NS records name the service's
|
|
239
241
|
nameservers (`delegation_ns`), as Klaviyo's recommended setup delegates `send.example.com` to
|
|
@@ -352,8 +354,8 @@ Checked's class as `denial`.
|
|
|
352
354
|
set of networks, an owner name and a list of publishers are one line, "2 ranges and 1 address
|
|
353
355
|
in AS16509 (Amazon Web Services)", with the publishers once after it, then the ranges, the first
|
|
354
356
|
record and "+ N more" (every record with `--evidence`). `render.grouped_unknowns` does this
|
|
355
|
-
for
|
|
356
|
-
|
|
357
|
+
for every view by reading the value networks.py writes, with the pattern
|
|
358
|
+
`render.NETWORK_VALUE`. A range with no network, one alone in its
|
|
357
359
|
network, one whose networks have different names, and any unknown that is not an IP range stay
|
|
358
360
|
single lines.
|
|
359
361
|
- Every provider's answer becomes rows of (name, not before, not after) that go through one
|
|
@@ -459,8 +461,8 @@ DKIM or SPF and a note for a bounce or tracking CNAME. A service whose only clai
|
|
|
459
461
|
only is one note asking whether the account is still used, and nothing about what its setup
|
|
460
462
|
lacks. Broken raises nothing new: the key and SPF findings already exist. Every view shows one
|
|
461
463
|
line per setup under its service, "Setup at <where>: SPF present, DKIM missing (selector1 and
|
|
462
|
-
selector2 no such name), bounce CNAME not needed.", built by `setup.line
|
|
463
|
-
|
|
464
|
+
selector2 no such name), bounce CNAME not needed.", built by `setup.line`; the web page shows
|
|
465
|
+
the same words, rendered on the server. An optional item that is missing reads "not used (optional, <what
|
|
464
466
|
was asked>)" in every view, so a reader does not take an optional record for a fault; its state
|
|
465
467
|
in JSON stays `missing`, with its detail and evidence.
|
|
466
468
|
|
|
@@ -532,7 +534,7 @@ claimed in. Blank names are left out and a name written twice the same way count
|
|
|
532
534
|
claimed is no reconciliation: `null` in JSON and no section in any view. Every view shows a
|
|
533
535
|
CLAIMED SENDERS section with all four parts, "none" for an empty one, each found service with
|
|
534
536
|
its tier and, when the client wrote another name, "claimed as" with their words. The text and
|
|
535
|
-
Markdown views build it with `render.claimed_parts
|
|
537
|
+
Markdown and HTML views build it with `render.claimed_parts`.
|
|
536
538
|
|
|
537
539
|
The web site caches maps by domain only; the claimed names a request carries are applied to the
|
|
538
540
|
cached map for that response and kept nowhere. The page passes its own `?claimed=` on to the API,
|
|
@@ -690,7 +692,10 @@ recorder keeps the answers, not those requests.
|
|
|
690
692
|
`tests/test_listing.py` validates `server.json` against the registry's 2025-12-11 schema, kept
|
|
691
693
|
in `tests/fixtures/`, and holds the README line and `llms.txt`.
|
|
692
694
|
- **Web site**: a FastAPI app. `GET /api/map?d=` returns the map as JSON, `GET /api/map.md?d=`
|
|
693
|
-
as Markdown,
|
|
695
|
+
as Markdown, `GET /api/map.html?d=` as the HTML fragment the page shows, and
|
|
696
|
+
`GET /api/map/stream?d=` streams progress then one `map` event carrying the map and that
|
|
697
|
+
fragment. The page's HTML comes from `render/html.py`, the same View the command line shows;
|
|
698
|
+
app.js sets it as it came and draws the ring map, and holds no rendering logic of its own.
|
|
694
699
|
Each takes `claimed`, a comma separated list, for the map's reconciliation.
|
|
695
700
|
Uvicorn's access log is turned off when the app starts, so no query string is logged; a test
|
|
696
701
|
runs a real server and checks that a request logs nothing. Visitor addresses are held in
|
|
@@ -11,6 +11,24 @@ fails before anything is published.
|
|
|
11
11
|
|
|
12
12
|
## Unreleased
|
|
13
13
|
|
|
14
|
+
## 0.1.3 (2026-10-23)
|
|
15
|
+
|
|
16
|
+
- The terminal and Markdown views render one view model (`render/view.py`) and say each thing once: findings come first, each service shows one evidence line per kind ("DKIM keys: selector1, selector2 (both 2048 bit, by CNAME)") and its setup in one clause ("setup complete", or only what is missing), an item a finding names is said in the setup by its state alone, and What was asked gives counts. Full records and the names asked print with `--evidence`, the names in Markdown as a last section, Appendix: names asked. The Markdown senders table gains a Setup column, and its Setups section goes. Allbirds' Markdown view falls from 111 lines to 76.
|
|
17
|
+
- The site renders the same view as the command line. The page's HTML is rendered on the server from the one View (`render/html.py`), every string escaped there, and app.js keeps only the form, the stream, the ring map and the buttons, so the page holds no third copy of the rendering logic and no longer shows the old layout (services before findings, a setup line per record). Each service's records in full and the names asked sit inside details elements. The stream's `map` event now carries `{"map": ..., "html": ...}`, and `GET /api/map.html` returns the fragment with the same limits and cache as `/api/map`.
|
|
18
|
+
- The terminal view is easier to read. Each service is a block: its name and category, then one line per kind of record with the labels in a column, then its setup, coloured by whether it is complete. Wrapped lines keep their indent everywhere, so a long finding, gap or record no longer runs back to the left margin. The story prints one sentence per line.
|
|
19
|
+
- Short DKIM keys at one name are said once: "Six DKIM keys at goop.com are 1024-bit: selector1, s1, s2, smtpapi, mandrill and key1", not six findings with the same advice. The JSON still has one finding per key.
|
|
20
|
+
- Checks that are opt-in or turned off (crt.sh without `--ct`, `--no-web`, `--no-subdomains`) move out of Not checked into a short Not run section at the end, without the request they would have made. Not checked now holds only questions that were asked and got no usable answer. The story still names them.
|
|
21
|
+
- The absence for a zone with no DKIM selectors says it in one sentence: "_domainkey.bounce.allbirds.com does not exist, so the 27-name sweep was skipped; the 8 names its MX, SPF and delegations point to were asked as a check, and none exists".
|
|
22
|
+
- A subdomain that is a CNAME to the domain or to another sending subdomain is no longer mapped as a sending subdomain of its own. www.github.com, gist.github.com and shop.github.com are aliases of github.com and answer with its SPF and MX, so Microsoft 365 and Google Workspace showed at each with a missing DKIM key there: nine findings for setups no one made. An alias to a name outside the domain still counts, since its records are nowhere else in the map.
|
|
23
|
+
- A zone that answers no such name for `_domainkey.<domain>` while a key sits under it is now a note, not a gap. Every selector is still asked there, so nothing went unchecked, but the story read "one DKIM selector that could not be read". The note says what the DNS host does wrong: under RFC 8020 that answer covers every name below it. functionalmedicinecoaching.org, on Bluehost's nameservers, answers this way.
|
|
24
|
+
- A record that a likely service's setup lacks is a note, not a warning, and says to check the service still sends first. goop.com showed "Cloudflare Email Routing at goop.com has no SPF" as a warning from a DKIM key name alone, while its mail goes to Google.
|
|
25
|
+
- A domain that does not exist in DNS says so: a finding, "The domain does not exist in DNS", and a story that leads with it, instead of "No sending service shows in DNS" as for a real domain with no mail set up. The map stops there: one lookup, not about 210.
|
|
26
|
+
- A pasted value is refused unless it is a hostname: an IP address (`192.0.2.1` was mapped as a domain), a wildcard, characters a hostname cannot hold, a label of more than 63 characters or a numeric top-level domain now reads "not a domain" on the command line, the site and the MCP server alike, and a malformed name no longer raises an IDNA error.
|
|
27
|
+
- The MCP tool's text reply lists findings most severe first, with short keys at one name said once, as the other views do. It listed them in the order the sources raised them.
|
|
28
|
+
- 16 services added and 15 patterns for known ones, each with a live proof, from a run over 50 domains not mapped before (DTC brands, B2B software, universities, nonprofits, media). Marketing: Conversica, Maropost, Engaging Networks, ActionKit, Extole, Communicator, ClickDimensions, SPIRAL, FreshMail, Oracle Responsys. Transactional: SocketLabs (email-od.com). Business apps: Braintree, Tipalti, Impartner, and Wix's website host. Hosted SPF: DmarcDuty, read like Valimail. Known services: Qualtrics's qemailserver.com include and MX, Amazon SES's us-west-2 inbound, Postmark's bounce MX, Campaign Monitor's createsend.com include and inbound MX, Klaviyo's klaviyomail.com and Customer.io's customeriomail.com includes, GetResponse's _spf.getresponse.com, Freshdesk's fwdkim1 return path, Epsilon's epsl1.com, 250ok's report address under Validity Everest, Mailgun's report address and Stripe's custom email return path. Across the 50 domains, unknowns fall from 700 to 560.
|
|
29
|
+
- Intercom's and Atlassian's return path CNAMEs are read: `outbound.intercom` and `atlassian-bounces` join the labels every map asks. Klaviyo gains km1 and km2, IONOS s2-ionos, and Optimizely Campaign `mailing` as a backed only name, credited and asked only where SPF or MX already shows Optimizely.
|
|
30
|
+
- Listrak added (marketing): its `esp1.co` SPF include, bounce MX and tracking CNAME, and its network AS394396, each with a live proof. goop.com's bounce. and email. subdomains read as Listrak, not as seven unknowns.
|
|
31
|
+
|
|
14
32
|
## 0.1.2 (2026-10-15)
|
|
15
33
|
|
|
16
34
|
- 31 services added and 10 patterns for known ones, each with a live proof, from a run over 50 large domains (retail, software, universities, charities, news, government). Marketing: Oracle Eloqua, Epsilon, Adobe Campaign, Microsoft Dynamics 365 Customer Insights, Blackbaud Luminate Online, Salsa Labs, Alida, Netigate, Hivebrite. Security gateway: Trend Micro Email Security, with an MX setup from its docs. Support, alerts and business apps: Khoros, Everbridge, Greenhouse, Red Points, Trustpilot, Cvent, Kibo Commerce, Docebo, ADP, SAP SuccessFactors, Fundraise Up, Q4, Madgex, Clinch. SPF flattening: AutoSPF, read like EasyDMARC. DMARC reports: DMARCLY, Report URI, Netcraft, Inbox Monster, Validity Everest, SendForensics. Known services: Sailthru's aspmx.sailthru.com and MX, Campaign Monitor's createsend.com tracking, Mandrill's inbound MX, HubSpot's MX hosts, Amazon SES's eu-west-1 inbound and Mail Manager MX, Squarespace's squarespace-mail.com, and Oracle's spf_c.oraclecloud.com. Across the 50 domains, unknowns fall from 1,008 to 727.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Sendermap
|
|
2
|
+
|
|
3
|
+
## Architecture
|
|
4
|
+
|
|
5
|
+
Sendermap is a library first. All DNS lookups, SPF and DMARC parsing,
|
|
6
|
+
MX and DKIM evidence gathering, and the reasoning that maps evidence
|
|
7
|
+
to sending services live in the sendermap package and nowhere else.
|
|
8
|
+
|
|
9
|
+
The CLI, the MCP server, and any web front end are thin callers. They
|
|
10
|
+
accept a domain, call the package, and present the result. They hold
|
|
11
|
+
no DNS or parsing logic of their own.
|
|
12
|
+
|
|
13
|
+
## Do not repeat yourself
|
|
14
|
+
|
|
15
|
+
Before writing any new lookup or parsing code, check whether the
|
|
16
|
+
package already has it. If it does, reuse it. If dns-audit has it,
|
|
17
|
+
port it into the package once rather than copying it.
|
|
18
|
+
|
|
19
|
+
Design the package so dns-audit and the APRF reader can import it
|
|
20
|
+
later and drop their own DNS code. One engine per job.
|
|
21
|
+
|
|
22
|
+
## Code quality
|
|
23
|
+
|
|
24
|
+
This is a portfolio project. Readable names, short functions,
|
|
25
|
+
docstrings on public functions, type hints throughout, tests for
|
|
26
|
+
every parser. Keep public functions small and pure where possible.
|
|
27
|
+
|
|
28
|
+
## Working style
|
|
29
|
+
|
|
30
|
+
Show the package layout, one line per module, before writing code.
|
|
31
|
+
Finish one thing before starting the next. Ask when a design choice
|
|
32
|
+
is genuinely Neil's call; otherwise make the call and say what you
|
|
33
|
+
chose. Prefer the repeatable solution over the one-off.
|
|
34
|
+
|
|
35
|
+
## Writing
|
|
36
|
+
|
|
37
|
+
No em dashes anywhere. Plain directives, not rules phrased as never.
|
|
38
|
+
No emoji in README or docs. Lead with the point and say it once.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sendermap
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Type a domain, see every service that sends email as it, with the public evidence for each.
|
|
5
5
|
Project-URL: Homepage, https://github.com/marmot7775/sendermap
|
|
6
6
|
Author: Neil Anuskiewicz
|