This release is a pre-release and may not be stable for production use.
domain-attribution
Auditable organization–domain relationship verification for security tooling and AI agents.
Alpha: this project verifies evidence of a relationship. It is not a phishing classifier, and
UNVERIFIEDdoes not mean malicious. Likewise,ATTRIBUTEDdoes not mean a site is safe.
Trust model
A result becomes ATTRIBUTED only when a strong first-party source explicitly declares ownership/control, such as an exact entry in /.well-known/related-domains(.json) or an explicit ownership statement on the claimed organization's domain. Ordinary mentions, DNS providers, DV certificates, and registrar data cannot establish ownership. Official warnings about a malicious/unrelated domain refute the claim.
Evidence includes a relation:
OWNERSHIP/CONTROL: eligible for attribution when supported by a strong sourceAFFILIATION: related, but not ownershipMENTION: referenced onlyADVERSE: official warning or explicit denial
Confidence values are ATTRIBUTED, CONTRADICTED, UNVERIFIED, and INSUFFICIENT.
Install and CLI
git clone https://github.com/JogJohgoeg/domain-attribution.git
cd domain-attribution
python3 -m attribution.cli cloudflare.pay --claim cloudflare.com --json
Input is normalized with IDNA and rejects URLs, paths, ports, IP literals, single-label/private-like hosts, and malformed domains.
MCP / HTTP
python3 mcp_server.py --transport stdio
QUOTA_ADMIN_KEY=change-me python3 mcp_http_server.py --port 8765
The MCP server uses newline-delimited JSON-RPC and returns structured evidence JSON. A query-quota token is required by the hosted prototype.
TOKEN=$(curl -s -X POST http://localhost:8765/mint \
-H 'content-type: application/json' \
-H 'authorization: Bearer change-me' \
-d '{"agent_id":"demo","count":10}' | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
Important: the historical X402Gate / PaymentGateway names are compatibility names. /mint grants quota and does not verify or settle a real payment. Integrators must authenticate minting and verify payment separately before granting quota.
Quota is persisted atomically in SQLite, survives restarts, binds tokens to agents, and is safe across concurrent gateway instances. Set X402_TOKEN_STORE=/path/quota.sqlite3 to choose its location.
Claim index provenance
ClaimIndex cache v2 records fetched_at, format version, and a SHA-256 hash for every page. Legacy caches remain readable but have no claimed fetch time.
Tests
Core tests require only the standard library:
python3 -m unittest discover -s tests -v
Optional pytest suite:
uv run --with pytest --with pytest-asyncio python -m pytest -q
Evaluation status
The checked-in research evaluation is small and should not be treated as a production accuracy claim. The historical report found 0 false attributions among 114 easy phishing/lookalike samples but only 6/22 genuine relationships attributed, while most CT calls were rate-limited. Future evaluation must include hard negatives such as security warnings, partners, news mentions, expired domains, and unrelated third parties.
Production gaps
Before public multi-tenant deployment, add authenticated/admin-only quota issuance, real billing if required, tenant isolation, rate limiting, metrics, and a larger independently labeled benchmark. See API.md for the current protocol.
Production observability and offline SLA benchmark
The HTTP service exposes /health (process liveness), /ready (token-store
readiness; returns 503 on dependency failure), and /metrics (Prometheus text
format). Metrics cover bounded-label request counts, server errors and latency,
signal availability, and known/unknown evidence freshness. Logs are newline
JSON events suitable for log processors.
Benchmark cached output without network access:
python3 eval/evaluate.py --corpus eval/corpus.json \
--offline-results eval/results.json --time-cutoff 2025-01-01T00:00:00Z \
--benchmark-out eval/benchmark.json
Corpus/result rows accept human_label, hard_negative_category,
labelled_at/observed_at, and explicit split. The JSON report includes
Wilson 95% intervals for precision, recall, coverage and repeated-run stability,
plus latency percentiles and hard-negative category results.
Docs
- Architecture:
docs/ARCHITECTURE.md - Threat model:
docs/THREAT_MODEL.md - Operations runbook:
docs/OPERATIONS.md - API:
API.md - Environment template:
.env.example - Optional multi-tenant service:
service/README.md
Enterprise attestation
Organizations can prove control of a claim domain with a DNS TXT challenge and publish an Ed25519-signed related-domain manifest:
- challenge record:
_domain-attribution-challenge.<claim> - public key record:
_domain-attribution-key.<claim> - manifest path:
/.well-known/related-domain-manifest.json
The signed_manifest signal verifies the signature against claim-controlled DNS keys or a local key registry. Affiliate relationships remain non-attributive.
Evidence pipeline
attribution.storage and attribution.crawler provide:
- immutable page snapshots with optional compressed bodies
- atomic crawl job leases, retries and terminal failure
- relation history
- webhook outbox for page change alerts
- ClaimIndex persistence beyond the legacy JSON cache
Multi-tenant billing service
python -m service is a separate process for tenants, hashed API keys, RBAC, Stripe PaymentIntent/Checkout creation, and webhook settlement. Creation responses never grant quota; only HMAC-verified paid webhooks credit the ledger.
Observability
HTTP endpoints:
GET /healthprocess livenessGET /readydependency readiness (SQLite token store)GET /metricsPrometheus text
Offline evaluation
python3 eval/evaluate.py --offline-results eval/results.json --benchmark-out /tmp/benchmark.json
The benchmark framework reports Wilson confidence intervals, hard-negative categories, coverage, latency and stability without network access.
Commercial closed loop
Primary settlement is USDC, not Stripe. Solana is supported (and preferred here).
export USDC_CHAIN=solana
export USDC_RECEIVE_ADDRESS=BWxcHqD1LjYTz3xanxoPmpEyy6sFtcRSk8J7Xf57w7Dr
export SOLANA_USDC_RECEIVE_ADDRESS=BWxcHqD1LjYTz3xanxoPmpEyy6sFtcRSk8J7Xf57w7Dr
export SERVICE_BOOTSTRAP_SECRET=change-me
export SERVICE_DB=./data/service.sqlite3
python3 -m service --port 8780 --db "$SERVICE_DB"
python3 examples/solana_usdc_loop_demo.py
Commercial closed loop (details)
The paid product path lives in service/ and is intentionally separate from free/local CLI use:
- Bootstrap a tenant and API key.
- Create a Stripe PaymentIntent/Checkout that includes
metadata.quota_units. - Accept only HMAC-verified terminal paid webhooks into the tenant ledger.
- Call
POST /service/v1/attributewith the API key; one unit is debited after a successful report. - Optionally convert paid ledger units into a legacy MCP agent token with
POST /service/v1/mint-agent-token.
export SERVICE_DB=./data/service.sqlite3
export SERVICE_BOOTSTRAP_SECRET=change-me
export STRIPE_WEBHOOK_SECRET=whsec_...
export STRIPE_API_KEY=sk_test_...
python3 -m service --port 8780 --db "$SERVICE_DB"
# offline proof without Stripe network:
python3 examples/commercial_loop_demo.py
Creation of a PaymentIntent never grants quota. Free /mint on the MCP HTTP prototype remains an operator-admin path and is not the commercial settlement path.
Run the commercial checkout
./scripts/run_commercial.sh
open http://127.0.0.1:8780/checkout
Plans, Solana USDC invoices, payment confirmation, auto-settle scan and paid attribution are all available from that service.
Agent auto-discovery
Agents can discover this service from a base URL via:
/.well-known/agent.json/.well-known/mcp.json/openapi.json/llms.txt/tools
See docs/AGENT_DISCOVERY.md.
Live discovery (current tunnel)
If the local tunnel is running:
- Commercial: https://neck-hunter-introduced-four.trycloudflare.com
- Agent card: https://neck-hunter-introduced-four.trycloudflare.com/.well-known/agent.json
- Catalog: https://neck-hunter-introduced-four.trycloudflare.com/catalog.json
- MCP HTTP: https://parker-prince-vitamins-respiratory.trycloudflare.com
See agent-discovery/ for registration packs and host policy snippets.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file domain_attribution-0.2.0a0.tar.gz.
File metadata
- Download URL: domain_attribution-0.2.0a0.tar.gz
- Upload date:
- Size: 92.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16b402f4ffbba79347abe2a11c78723f74e1cb613581ee6964cbc47e3bcee365
|
|
| MD5 |
621a54f7203d93a8a63d4a008be98c43
|
|
| BLAKE2b-256 |
3cedadc5f74c1ce6292d7989a76c946870a7a9338d4f88b526777ca1c78d5ba2
|
File details
Details for the file domain_attribution-0.2.0a0-py3-none-any.whl.
File metadata
- Download URL: domain_attribution-0.2.0a0-py3-none-any.whl
- Upload date:
- Size: 80.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ef5df99995fd50e17b91f1dd28e32de7436ca929449ec20b7f2f2b41a55c6f8b
|
|
| MD5 |
5496819ff48a95107f400d49d3997156
|
|
| BLAKE2b-256 |
da88b3fbd26d03dd5c6f297b8774675d70a962be3af9c8aba5709e1f0db1e364
|