sendermap 0.1.0__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.0/.github/workflows/publish.yml +96 -0
- sendermap-0.1.0/.github/workflows/recheck-corpus.yml +49 -0
- sendermap-0.1.0/.github/workflows/test.yml +40 -0
- sendermap-0.1.0/.github/workflows/verify-proofs.yml +62 -0
- sendermap-0.1.0/.gitignore +5 -0
- sendermap-0.1.0/ARCHITECTURE.md +811 -0
- sendermap-0.1.0/CHANGELOG.md +36 -0
- sendermap-0.1.0/LICENSE +21 -0
- sendermap-0.1.0/PKG-INFO +215 -0
- sendermap-0.1.0/README.md +187 -0
- sendermap-0.1.0/data/hunt_domains.txt +2004 -0
- sendermap-0.1.0/pyproject.toml +75 -0
- sendermap-0.1.0/sendermap/__init__.py +23 -0
- sendermap-0.1.0/sendermap/catalog.py +510 -0
- sendermap-0.1.0/sendermap/cli.py +265 -0
- sendermap-0.1.0/sendermap/data/schema/sendermap-1.json +743 -0
- sendermap-0.1.0/sendermap/data/vendors.json +5553 -0
- sendermap-0.1.0/sendermap/data/vendors_pending.json +1405 -0
- sendermap-0.1.0/sendermap/dns.py +252 -0
- sendermap-0.1.0/sendermap/engine.py +365 -0
- sendermap-0.1.0/sendermap/infer.py +58 -0
- sendermap-0.1.0/sendermap/mcp_server.py +257 -0
- sendermap-0.1.0/sendermap/model.py +504 -0
- sendermap-0.1.0/sendermap/proofs.py +89 -0
- sendermap-0.1.0/sendermap/py.typed +0 -0
- sendermap-0.1.0/sendermap/reconcile.py +65 -0
- sendermap-0.1.0/sendermap/render/__init__.py +165 -0
- sendermap-0.1.0/sendermap/render/markdown.py +89 -0
- sendermap-0.1.0/sendermap/render/text.py +144 -0
- sendermap-0.1.0/sendermap/selectors.py +251 -0
- sendermap-0.1.0/sendermap/setup.py +403 -0
- sendermap-0.1.0/sendermap/sources/__init__.py +38 -0
- sendermap-0.1.0/sendermap/sources/bimi.py +16 -0
- sendermap-0.1.0/sendermap/sources/ct.py +366 -0
- sendermap-0.1.0/sendermap/sources/dkim.py +529 -0
- sendermap-0.1.0/sendermap/sources/labels.py +80 -0
- sendermap-0.1.0/sendermap/sources/mta_sts.py +43 -0
- sendermap-0.1.0/sendermap/sources/mx.py +72 -0
- sendermap-0.1.0/sendermap/sources/networks.py +267 -0
- sendermap-0.1.0/sendermap/sources/reports.py +52 -0
- sendermap-0.1.0/sendermap/sources/spf.py +393 -0
- sendermap-0.1.0/sendermap/sources/spf_checks.py +125 -0
- sendermap-0.1.0/sendermap/sources/spf_eval.py +565 -0
- sendermap-0.1.0/sendermap/sources/subdomains.py +104 -0
- sendermap-0.1.0/sendermap/sources/tokens.py +27 -0
- sendermap-0.1.0/sendermap/sources/web.py +184 -0
- sendermap-0.1.0/sendermap/story.py +448 -0
- sendermap-0.1.0/sendermap/testing.py +274 -0
- sendermap-0.1.0/sendermap/web/__init__.py +0 -0
- sendermap-0.1.0/sendermap/web/app.py +279 -0
- sendermap-0.1.0/sendermap/web/static/app.js +488 -0
- sendermap-0.1.0/sendermap/web/static/index.html +113 -0
- sendermap-0.1.0/sendermap/web/static/llms.txt +28 -0
- sendermap-0.1.0/sendermap/web/static/style.css +185 -0
- sendermap-0.1.0/server.json +38 -0
- sendermap-0.1.0/skills/sendermap/SKILL.md +230 -0
- sendermap-0.1.0/tests/conftest.py +116 -0
- sendermap-0.1.0/tests/corpus/README.md +24 -0
- sendermap-0.1.0/tests/corpus/allbirds.com/case.json +8 -0
- sendermap-0.1.0/tests/corpus/allbirds.com/dns.json +719 -0
- sendermap-0.1.0/tests/corpus/allbirds.com/expected.json +4271 -0
- sendermap-0.1.0/tests/corpus/allbirds.com/http.json +3 -0
- sendermap-0.1.0/tests/corpus/casper.com/case.json +8 -0
- sendermap-0.1.0/tests/corpus/casper.com/dns.json +739 -0
- sendermap-0.1.0/tests/corpus/casper.com/expected.json +3399 -0
- sendermap-0.1.0/tests/corpus/casper.com/http.json +3 -0
- sendermap-0.1.0/tests/corpus/example.com/case.json +8 -0
- sendermap-0.1.0/tests/corpus/example.com/dns.json +207 -0
- sendermap-0.1.0/tests/corpus/example.com/expected.json +106 -0
- sendermap-0.1.0/tests/corpus/example.com/http.json +4 -0
- sendermap-0.1.0/tests/corpus/glossier.com/case.json +8 -0
- sendermap-0.1.0/tests/corpus/glossier.com/dns.json +692 -0
- sendermap-0.1.0/tests/corpus/glossier.com/expected.json +2100 -0
- sendermap-0.1.0/tests/corpus/glossier.com/http.json +3 -0
- sendermap-0.1.0/tests/corpus/mills-reeve.com/case.json +8 -0
- sendermap-0.1.0/tests/corpus/mills-reeve.com/dns.json +583 -0
- sendermap-0.1.0/tests/corpus/mills-reeve.com/expected.json +1450 -0
- sendermap-0.1.0/tests/corpus/mills-reeve.com/http.json +3 -0
- sendermap-0.1.0/tests/fixtures/crtsh_allbirds.json +123 -0
- sendermap-0.1.0/tests/fixtures/mcp-server-2025-12-11.schema.json +574 -0
- sendermap-0.1.0/tests/test_backed_only.py +139 -0
- sendermap-0.1.0/tests/test_caps.py +133 -0
- sendermap-0.1.0/tests/test_catalog.py +74 -0
- sendermap-0.1.0/tests/test_checked.py +88 -0
- sendermap-0.1.0/tests/test_cli.py +112 -0
- sendermap-0.1.0/tests/test_corpus.py +30 -0
- sendermap-0.1.0/tests/test_ct.py +346 -0
- sendermap-0.1.0/tests/test_delegation.py +142 -0
- sendermap-0.1.0/tests/test_determinism.py +172 -0
- sendermap-0.1.0/tests/test_dkim.py +228 -0
- sendermap-0.1.0/tests/test_dkim_denial.py +156 -0
- sendermap-0.1.0/tests/test_engine.py +160 -0
- sendermap-0.1.0/tests/test_evidence.py +121 -0
- sendermap-0.1.0/tests/test_gap_kinds.py +143 -0
- sendermap-0.1.0/tests/test_gaps.py +178 -0
- sendermap-0.1.0/tests/test_hunt_proofs.py +278 -0
- sendermap-0.1.0/tests/test_labels_say_what_was_seen.py +236 -0
- sendermap-0.1.0/tests/test_listing.py +68 -0
- sendermap-0.1.0/tests/test_mcp.py +230 -0
- sendermap-0.1.0/tests/test_networks.py +209 -0
- sendermap-0.1.0/tests/test_one_selector_list.py +64 -0
- sendermap-0.1.0/tests/test_package.py +33 -0
- sendermap-0.1.0/tests/test_pending_vendors.py +137 -0
- sendermap-0.1.0/tests/test_publish.py +97 -0
- sendermap-0.1.0/tests/test_recheck_corpus.py +100 -0
- sendermap-0.1.0/tests/test_reconcile.py +380 -0
- sendermap-0.1.0/tests/test_record_replay.py +135 -0
- sendermap-0.1.0/tests/test_resolver.py +121 -0
- sendermap-0.1.0/tests/test_schema.py +97 -0
- sendermap-0.1.0/tests/test_selectors.py +249 -0
- sendermap-0.1.0/tests/test_setup.py +443 -0
- sendermap-0.1.0/tests/test_skill.py +53 -0
- sendermap-0.1.0/tests/test_sources.py +134 -0
- sendermap-0.1.0/tests/test_spf.py +157 -0
- sendermap-0.1.0/tests/test_spf_checks.py +181 -0
- sendermap-0.1.0/tests/test_spf_eval.py +244 -0
- sendermap-0.1.0/tests/test_story.py +378 -0
- sendermap-0.1.0/tests/test_subprocessor_names.py +63 -0
- sendermap-0.1.0/tests/test_supporting_evidence.py +92 -0
- sendermap-0.1.0/tests/test_vendor_proofs.py +100 -0
- sendermap-0.1.0/tests/test_verify_proofs.py +282 -0
- sendermap-0.1.0/tests/test_web.py +55 -0
- sendermap-0.1.0/tests/test_web_guard.py +181 -0
- sendermap-0.1.0/tests/test_web_mcp.py +105 -0
- sendermap-0.1.0/tests/vectors/rfc7208-tests.LICENSE +27 -0
- sendermap-0.1.0/tests/vectors/rfc7208-tests.yml +2660 -0
- sendermap-0.1.0/tools/hunt_proofs.py +751 -0
- sendermap-0.1.0/tools/record_corpus.py +202 -0
- sendermap-0.1.0/tools/release_notes.py +47 -0
- sendermap-0.1.0/tools/verify_proofs.py +423 -0
- sendermap-0.1.0/uv.lock +1890 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Publish a release to PyPI when a tag v* is pushed, then make the GitHub release.
|
|
2
|
+
#
|
|
3
|
+
# PYPI_READY: publishing is held until the repository variable PYPI_READY is true. It is set
|
|
4
|
+
# under Settings, Secrets and variables, Actions, Variables, once the PyPI trusted publisher
|
|
5
|
+
# exists. Until then a v* tag still builds the sdist and wheel and uploads them as the workflow
|
|
6
|
+
# artifact "dist", and the publish and release jobs are skipped, not failed.
|
|
7
|
+
#
|
|
8
|
+
# build: the sdist and wheel with python -m build, from a full clone so hatch-vcs reads the tag,
|
|
9
|
+
# checked to carry the tag's version; the tag's section of CHANGELOG.md becomes the release
|
|
10
|
+
# notes, and a tag without one fails here, before anything is published.
|
|
11
|
+
# publish: uploads to PyPI by trusted publishing (no token: the pypi environment and the
|
|
12
|
+
# id-token permission), with attestations.
|
|
13
|
+
# release: the GitHub release for the tag, with the notes and the built files.
|
|
14
|
+
|
|
15
|
+
name: Publish
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
push:
|
|
19
|
+
tags: ["v*"]
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
build:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
timeout-minutes: 10
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v5
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0 # hatch-vcs takes the version from the tag
|
|
32
|
+
- uses: actions/setup-python@v6
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.12"
|
|
35
|
+
- name: Build
|
|
36
|
+
run: |
|
|
37
|
+
python -m pip install build
|
|
38
|
+
python -m build
|
|
39
|
+
- name: Check the version is the tag's
|
|
40
|
+
run: |
|
|
41
|
+
version="${GITHUB_REF_NAME#v}"
|
|
42
|
+
ls dist
|
|
43
|
+
test -f "dist/sendermap-${version}.tar.gz"
|
|
44
|
+
test -f "dist/sendermap-${version}-py3-none-any.whl"
|
|
45
|
+
- name: Release notes from CHANGELOG.md
|
|
46
|
+
run: python tools/release_notes.py "$GITHUB_REF_NAME" > release-notes.md
|
|
47
|
+
- uses: actions/upload-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: dist
|
|
50
|
+
path: dist/
|
|
51
|
+
- uses: actions/upload-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: release-notes
|
|
54
|
+
path: release-notes.md
|
|
55
|
+
|
|
56
|
+
publish:
|
|
57
|
+
needs: build
|
|
58
|
+
if: vars.PYPI_READY == 'true'
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
timeout-minutes: 10
|
|
61
|
+
environment:
|
|
62
|
+
name: pypi
|
|
63
|
+
url: https://pypi.org/p/sendermap
|
|
64
|
+
permissions:
|
|
65
|
+
id-token: write # trusted publishing
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/download-artifact@v4
|
|
68
|
+
with:
|
|
69
|
+
name: dist
|
|
70
|
+
path: dist/
|
|
71
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
72
|
+
with:
|
|
73
|
+
attestations: true
|
|
74
|
+
|
|
75
|
+
release:
|
|
76
|
+
needs: publish
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
timeout-minutes: 5
|
|
79
|
+
permissions:
|
|
80
|
+
contents: write # create the release
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/download-artifact@v4
|
|
83
|
+
with:
|
|
84
|
+
name: dist
|
|
85
|
+
path: dist/
|
|
86
|
+
- uses: actions/download-artifact@v4
|
|
87
|
+
with:
|
|
88
|
+
name: release-notes
|
|
89
|
+
- name: Create the GitHub release
|
|
90
|
+
env:
|
|
91
|
+
GH_TOKEN: ${{ github.token }}
|
|
92
|
+
run: >
|
|
93
|
+
gh release create "$GITHUB_REF_NAME" dist/*
|
|
94
|
+
--repo "$GITHUB_REPOSITORY"
|
|
95
|
+
--title "Sendermap ${GITHUB_REF_NAME#v}"
|
|
96
|
+
--notes-file release-notes.md
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Map every case in tests/corpus live again, weekly (an hour after the proofs job) and by hand,
|
|
2
|
+
# and report how each live map differs from its expected.json, leaving out checked_at and the
|
|
3
|
+
# two run fields. Drift is reported in the job summary and fails nothing: the world changing is
|
|
4
|
+
# not a bug, and a drifted case is re-recorded by hand after reading the diff. The fresh
|
|
5
|
+
# recordings are uploaded for 30 days; nothing is committed. The job fails only when a live map
|
|
6
|
+
# could not be built at all.
|
|
7
|
+
|
|
8
|
+
name: Recheck corpus
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
schedule:
|
|
12
|
+
- cron: "0 10 * * 1" # Monday 10:00 UTC
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
recheck:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 20
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v5
|
|
24
|
+
- uses: astral-sh/setup-uv@v6
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.12"
|
|
27
|
+
- name: Install
|
|
28
|
+
run: uv sync --locked --no-dev
|
|
29
|
+
- name: Map each case live and compare
|
|
30
|
+
run: |
|
|
31
|
+
failed=()
|
|
32
|
+
for case in tests/corpus/*/case.json; do
|
|
33
|
+
domain=$(basename "$(dirname "$case")")
|
|
34
|
+
if ! uv run --no-sync python tools/record_corpus.py "$domain" --recheck recheck >>"$GITHUB_STEP_SUMMARY"; then
|
|
35
|
+
failed+=("$domain")
|
|
36
|
+
printf '### %s\n\nThe live map could not be built.\n\n' "$domain" >>"$GITHUB_STEP_SUMMARY"
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
if (( ${#failed[@]} )); then
|
|
40
|
+
echo "::error::no live map for: ${failed[*]}"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
- uses: actions/upload-artifact@v4
|
|
44
|
+
if: always()
|
|
45
|
+
with:
|
|
46
|
+
name: corpus-recheck-${{ github.run_id }}
|
|
47
|
+
path: recheck/
|
|
48
|
+
retention-days: 30
|
|
49
|
+
if-no-files-found: ignore
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Run the suite on every push to main, on every pull request and by hand: ruff check, ruff
|
|
2
|
+
# format --check, mypy and pytest, on each supported Python, installed from uv.lock exactly.
|
|
3
|
+
# pytest takes its addopts from pyproject.toml, so the network is blocked here the same way it
|
|
4
|
+
# is locally. Any of the four failing fails the job.
|
|
5
|
+
|
|
6
|
+
name: Test
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [main]
|
|
11
|
+
pull_request:
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
test:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
timeout-minutes: 10
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
python-version: ["3.12", "3.13"]
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v5
|
|
27
|
+
- uses: astral-sh/setup-uv@v6
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
enable-cache: true
|
|
31
|
+
- name: Install
|
|
32
|
+
run: uv sync --locked --extra web --extra mcp # mypy and the tests read fastapi and fastmcp
|
|
33
|
+
- name: ruff check
|
|
34
|
+
run: uv run --no-sync ruff check
|
|
35
|
+
- name: ruff format --check
|
|
36
|
+
run: uv run --no-sync ruff format --check
|
|
37
|
+
- name: mypy
|
|
38
|
+
run: uv run --no-sync mypy
|
|
39
|
+
- name: pytest
|
|
40
|
+
run: uv run --no-sync pytest
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Recheck every proof in vendors.json against live DNS and the web: on any pull request that
|
|
2
|
+
# touches the file or the checker, weekly, and by hand. A proof not found fails the job, which
|
|
3
|
+
# blocks a pull request; on the weekly run it also opens an issue, or comments on the open one. A
|
|
4
|
+
# proof that could not be checked (timeout, lookup failure, a page that refuses the request) is
|
|
5
|
+
# listed in the output and fails nothing.
|
|
6
|
+
|
|
7
|
+
name: Verify proofs
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
pull_request:
|
|
11
|
+
paths:
|
|
12
|
+
- sendermap/data/vendors.json
|
|
13
|
+
- tools/verify_proofs.py
|
|
14
|
+
schedule:
|
|
15
|
+
- cron: "0 9 * * 1" # Monday 09:00 UTC
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
issues: write
|
|
21
|
+
|
|
22
|
+
defaults:
|
|
23
|
+
run:
|
|
24
|
+
shell: bash # -eo pipefail, so the checker's exit code survives the tee
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
verify:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
timeout-minutes: 10
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v5
|
|
32
|
+
- uses: astral-sh/setup-uv@v6
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.12"
|
|
35
|
+
- name: Install
|
|
36
|
+
run: uv sync --locked --no-dev
|
|
37
|
+
- name: Check every proof
|
|
38
|
+
id: verify
|
|
39
|
+
run: uv run --no-sync python tools/verify_proofs.py --json | tee results.jsonl
|
|
40
|
+
- name: Open or update an issue per failing proof
|
|
41
|
+
if: failure() && steps.verify.outcome == 'failure' && github.event_name == 'schedule'
|
|
42
|
+
env:
|
|
43
|
+
GH_TOKEN: ${{ github.token }}
|
|
44
|
+
run: |
|
|
45
|
+
today=$(date -u +%F)
|
|
46
|
+
open=$(gh issue list --state open --limit 1000 --json number,title)
|
|
47
|
+
jq -c 'select(.status == "not found")' results.jsonl | while read -r row; do
|
|
48
|
+
title=$(jq -r '"Proof failed: \(.vendor) \(.field) \(.pattern)"' <<<"$row")
|
|
49
|
+
body=$(jq -r --arg today "$today" '[
|
|
50
|
+
"Proof domain: \(.domain)",
|
|
51
|
+
"Record expected: \(.record), a \(.expected)",
|
|
52
|
+
"Published now: \(.observed)",
|
|
53
|
+
"Checked: \($today)"
|
|
54
|
+
] | join("\n\n")' <<<"$row")
|
|
55
|
+
number=$(jq -r --arg title "$title" \
|
|
56
|
+
'first(.[] | select(.title == $title) | .number) // empty' <<<"$open")
|
|
57
|
+
if [[ -n $number ]]; then
|
|
58
|
+
gh issue comment "$number" --body "$body" </dev/null
|
|
59
|
+
else
|
|
60
|
+
gh issue create --title "$title" --body "$body" </dev/null
|
|
61
|
+
fi
|
|
62
|
+
done
|