Metadata-Version: 2.4
Name: domain-attribution
Version: 0.2.0a0
Summary: Auditable organization-domain relationship verification
Author: zhangzhuoning
License: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: cryptography>=41
Provides-Extra: crypto
Requires-Dist: cryptography>=41; extra == "crypto"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: cryptography>=41; extra == "dev"

# 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

```bash
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

```bash
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.

```bash
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:

```bash
python3 -m unittest discover -s tests -v
```

Optional pytest suite:

```bash
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:

```sh
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`](docs/ARCHITECTURE.md)
- Threat model: [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md)
- Operations runbook: [`docs/OPERATIONS.md`](docs/OPERATIONS.md)
- API: [`API.md`](API.md)
- Environment template: [`.env.example`](.env.example)
- Optional multi-tenant service: [`service/README.md`](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 /health` process liveness
- `GET /ready` dependency readiness (SQLite token store)
- `GET /metrics` Prometheus text

## Offline evaluation

```bash
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).

```bash
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`.

```bash
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

```bash
./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.
