Skip to main content
Pre-release

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 UNVERIFIED does not mean malicious. Likewise, ATTRIBUTED does 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 source
  • AFFILIATION: related, but not ownership
  • MENTION: referenced only
  • ADVERSE: 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

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 /health process liveness
  • GET /ready dependency readiness (SQLite token store)
  • GET /metrics Prometheus 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:

  1. Bootstrap a tenant and API key.
  2. Create a Stripe PaymentIntent/Checkout that includes metadata.quota_units.
  3. Accept only HMAC-verified terminal paid webhooks into the tenant ledger.
  4. Call POST /service/v1/attribute with the API key; one unit is debited after a successful report.
  5. 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:

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

domain_attribution-0.2.0a0.tar.gz (92.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

domain_attribution-0.2.0a0-py3-none-any.whl (80.3 kB view details)

Uploaded Python 3

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

Hashes for domain_attribution-0.2.0a0.tar.gz
Algorithm Hash digest
SHA256 16b402f4ffbba79347abe2a11c78723f74e1cb613581ee6964cbc47e3bcee365
MD5 621a54f7203d93a8a63d4a008be98c43
BLAKE2b-256 3cedadc5f74c1ce6292d7989a76c946870a7a9338d4f88b526777ca1c78d5ba2

See more details on using hashes here.

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

Hashes for domain_attribution-0.2.0a0-py3-none-any.whl
Algorithm Hash digest
SHA256 ef5df99995fd50e17b91f1dd28e32de7436ca929449ec20b7f2f2b41a55c6f8b
MD5 5496819ff48a95107f400d49d3997156
BLAKE2b-256 da88b3fbd26d03dd5c6f297b8774675d70a962be3af9c8aba5709e1f0db1e364

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page