Skip to main content

Security middleware for x402 agentic payments — PII redaction, spending policy, and replay detection before blockchain commit

Project description

presidio-hardened-x402

PyPI version Python GitHub release License: MIT CI OpenSSF Scorecard

v0.8.0 — NLP screening is now a structural superset of regex (the high-accuracy engine no longer misses structured identifiers like SSN/phone/card), plus a composed-envelope screen_ref leg, action_ref byte-determinism, URL-redacted log hygiene, and OpenSSF Scorecard / SLSA Build L3 supply-chain hardening.

Security middleware for the x402 payment protocol.

Intercepts x402 payment requests before transmission to servers and facilitators to enforce:

  • PII redaction — Presidio-based detection and redaction of personal data (emails, names, SSNs, credit cards, etc.) from payment metadata fields before they are sent to the payment server or facilitator API
  • Spending policy — per-agent, per-endpoint, and per-time-window budget limits that block or throttle payments before execution
  • Replay detection — HMAC-SHA256 fingerprinting of canonical payment fields to prevent duplicate and replayed payments; case-canonicalised on pay_to and currency (v0.4.0)
  • Audit logging — HMAC-chained JSON-L audit trail for every payment attempt (including blocked ones)
  • Multi-party authorization — n-of-m approval requirement for high-value payments, via webhook or HMAC-SHA256 cryptographic countersignature modes (v0.3.0)
  • Prometheus metrics — structured telemetry for every security control activation (v0.3.0)
  • Remote screening — opt-in remote_screening=True mode offloads PII analysis to the hosted screen.presidio-group.eu service via ScreeningClient; avoids the in-process spaCy dependency (v0.4.0)
  • Per-origin wallet allowlist — per-origin pay_to allowlist blocks payments to attacker-controlled addresses when a DNS-poisoned 402 response substitutes the recipient (chain-06 mitigation) (v0.4.0)
  • Rail-agnostic core + binding layer — the screening pipeline (ScreeningPipeline) is payment-protocol-independent; everything x402-specific lives in bindings/x402. Embed on a different rail by implementing PaymentProtocolBinding — the security guarantees travel with the core (v0.5.0)
  • OEM embed kitsemver/stability guarantees, partner-runnable conformance suite (python -m presidio_x402.conformance, 7 end-to-end checks, no network), and integration quickstarts for Coinbase CDP, LangChain, and CrewAI (v0.5.0)
  • Enterprise hardening — per-tenant replay namespaces, remote audit sinks, signed evidence verification, OpenTelemetry spans, hot-reloadable policy, release provenance, and latency-SLO CI gates (v0.6.0)

Part of the presidio-hardened-* toolkit family.


Installation

pip install presidio-hardened-x402

For full NLP-based PII detection (PERSON, ORG, location, etc.):

pip install "presidio-hardened-x402[nlp]"
python -m spacy download en_core_web_sm

For production replay guard with cross-process deduplication:

pip install "presidio-hardened-x402[redis]"

For Prometheus metrics export:

pip install "presidio-hardened-x402[prometheus]"

For policy-as-code schema validation:

pip install "presidio-hardened-x402[schema]"

Quick Start

Before (bare x402 — no security controls)

import httpx

async def pay_and_fetch(url: str, signer) -> httpx.Response:
    async with httpx.AsyncClient() as client:
        resp = await client.get(url)
        if resp.status_code == 402:
            payment_details = parse_402_header(resp.headers)
            # No PII check. No policy check. No replay check.
            payment_token = await signer.sign(payment_details)
            resp = await client.get(url, headers={"X-PAYMENT": payment_token})
        return resp

After (presidio-hardened-x402)

from presidio_x402 import HardenedX402Client

async def my_signer(details):
    # Your existing signing logic (eth-account, solders, CDP SDK, etc.)
    ...

client = HardenedX402Client(
    payment_signer=my_signer,
    policy={
        "max_per_call_usd": 0.10,
        "daily_limit_usd": 5.0,
        "per_endpoint": {"https://api.example.com": 1.0},
    },
    pii_action="redact",          # redact | block | warn
    pii_entities=["EMAIL_ADDRESS", "PERSON", "US_SSN", "CREDIT_CARD"],
    replay_ttl=300,               # seconds
)

response = await client.get("https://api.example.com/resource")

What happens transparently:

  1. Client sends GET /resource → server returns 402 with payment details
  2. PIIFilter scans resource URL, description, and reason fields; redacts any PII
  3. PolicyEngine checks: amount ≤ per-call limit? daily spend within budget? endpoint limit OK?
  4. ReplayGuard checks: have we paid this exact request within the TTL window?
  5. AuditLog records the attempt (pass or block) as a tamper-evident JSON-L entry
  6. If all checks pass, signer is called → payment header sent → resource returned

Configuration

Policy

from presidio_x402 import HardenedX402Client, PolicyConfig

policy = PolicyConfig(
    max_per_call_usd=0.05,         # block if maxAmountRequired > this
    daily_limit_usd=2.00,          # block if 24h aggregate spend would exceed this
    per_endpoint={
        "https://premium-api.io": 0.50,  # per-endpoint daily limit
    },
    window_seconds=86400,          # time window for aggregate limits (default: 24h)
    agent_id="my-agent-v1",        # tag audit events with an agent identifier
)

client = HardenedX402Client(payment_signer=signer, policy=policy)

PII Filter

# regex mode (default, zero-setup): catches structured PII
client = HardenedX402Client(
    payment_signer=signer,
    pii_mode="regex",
    pii_entities=["EMAIL_ADDRESS", "PHONE_NUMBER", "US_SSN", "CREDIT_CARD"],
    pii_action="redact",
)

# nlp mode: full spaCy NER (requires: pip install presidio-hardened-x402[nlp])
client = HardenedX402Client(
    payment_signer=signer,
    pii_mode="nlp",
    pii_entities=["EMAIL_ADDRESS", "PERSON", "LOCATION", "US_SSN"],
    pii_action="block",     # raise PIIBlockedError instead of redacting
)

Replay Guard

# In-memory (default, single-process)
client = HardenedX402Client(payment_signer=signer, replay_ttl=300)

# Redis-backed (production, cross-process)
client = HardenedX402Client(
    payment_signer=signer,
    replay_ttl=300,
    redis_url="redis://localhost:6379/0",
)

Audit Log

import sys
from presidio_x402 import HardenedX402Client
from presidio_x402.audit_log import StreamAuditWriter

client = HardenedX402Client(
    payment_signer=signer,
    audit_writer=StreamAuditWriter(sys.stdout),   # write JSON-L to stdout
)

Multi-Party Authorization (v0.3.0)

For high-value payments that require human or system oversight before execution:

from presidio_x402 import HardenedX402Client
from presidio_x402.mpa import MPAConfig, MPAApproverConfig, MPAEngine

mpa = MPAEngine(MPAConfig(
    threshold=2,               # require 2 of 3 approvals
    min_amount_usd=1.00,       # only for payments ≥ $1.00
    timeout_seconds=30,
    approvers=[
        MPAApproverConfig("alice", mode="webhook",
                          webhook_url="https://approvals.internal/alice"),
        MPAApproverConfig("bob",   mode="webhook",
                          webhook_url="https://approvals.internal/bob"),
        MPAApproverConfig("charlie", mode="webhook",
                          webhook_url="https://approvals.internal/charlie"),
    ],
))

client = HardenedX402Client(payment_signer=signer, mpa_engine=mpa)

Approval endpoints receive a POST with payment details and must return {"approved": true}. For machine-to-machine approvals, use mode="crypto" with HMAC-SHA256 countersignatures and pass them via mpa_signatures={"approver_id": "hex_sig", ...} in the request kwargs.


Prometheus Metrics (v0.3.0)

from presidio_x402 import HardenedX402Client
from presidio_x402.metrics import MetricsCollector

collector = MetricsCollector()
client = HardenedX402Client(payment_signer=signer, metrics_collector=collector)

# Expose /metrics endpoint (e.g., FastAPI)
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST

@app.get("/metrics")
def metrics():
    return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)

Available metrics: x402_payments_total, x402_payment_amount_usd (histogram), x402_pii_detections_total, x402_policy_violations_total, x402_replay_detections_total, x402_mpa_events_total.


Policy-as-Code (v0.3.0)

Define spending policy in a TOML or JSON file and validate it against the x402 policy JSON Schema:

# policy.toml
max_per_call_usd = 0.10
daily_limit_usd  = 5.00
agent_id         = "my-agent"

[per_endpoint]
"https://premium-api.io" = 0.50

[mpa]
threshold       = 2
min_amount_usd  = 1.00
timeout_seconds = 30

[[mpa.approvers]]
approver_id = "alice"
mode        = "webhook"
webhook_url = "https://approvals.internal/alice"
from presidio_x402.x402_policy_schema import load_policy_file

policy = load_policy_file("policy.toml")
client = HardenedX402Client(payment_signer=signer, policy=policy)

Remote Screening (v0.4.0)

Offload PII analysis to the hosted screen.presidio-group.eu service. Useful when you want to keep the agent process small (no in-process spaCy model) and centralise screening policy across many agents.

from presidio_x402 import HardenedX402Client

client = HardenedX402Client(
    payment_signer=signer,
    remote_screening=True,
    screening_api_key="...",   # obtain by registering at screen.presidio-group.eu
)

The free tier serves the regex-mode screening backend at 100 screenings/day per key. Falls back to local regex / NLP modes if the network is unhealthy, so the client never hard-fails on a screening-service outage.

Per-origin wallet allowlist (v0.4.0)

Defence against DNS-poisoning attacks that substitute the recipient wallet in a 402 response. Configure the trusted pay_to addresses per resource origin:

client = HardenedX402Client(
    payment_signer=signer,
    trusted_wallets={
        "https://api.example.com": {"0xAbC...123"},
    },
)

Payments to a pay_to not in the allowlist for the response's origin are blocked before signing.


Kubernetes Deployment (v0.3.0)

Deploy as a sidecar using the bundled Helm chart:

helm install x402 ./helm \
  --set x402.agentId=my-agent \
  --set x402.maxPerCallUsd=0.10 \
  --set x402.dailyLimitUsd=5.00 \
  --set serviceMonitor.enabled=true

See docs/soc2-reference-architecture.md for SOC 2 TSC mapping, GDPR obligations, and deployment patterns.


SLO Payment Broker (v0.7.0)

Market-based SLO enforcement: when infrastructure degrades, the agent autonomously pays for a capacity upgrade via x402 micropayments instead of relying on pre-provisioned autoscaling. The broker acts only on a verified degradation signal — a signed evidence-ref@1 from a trusted presidio-hardened-arch-translucency signer — so a spoofed or misconfigured signal cannot trigger a payment (authorization, not metric).

from presidio_x402 import (
    HardenedX402Client, SLOPaymentBroker, SLOPaymentPolicy,
    X402CapacityProvider, ArchTranslucencyAdapter,
)

# arch-translucency's signed degradation feed → verified trigger (fail-closed).
adapter = ArchTranslucencyAdapter(
    {"presidio-hardened-arch-translucency": {"alg": "ed25519", "public_key": ARCH_PUB}},
    expected_signers=["presidio-hardened-arch-translucency"],
)
client = HardenedX402Client(payment_signer=signer)
broker = SLOPaymentBroker(
    client=client,
    slo_policy=SLOPaymentPolicy(
        cooldown_seconds=300,            # anti-drain
        max_per_slo_event_usd=0.50,
        max_daily_slo_usd=10.00,
        tier_escalation_rules=(1.0, 2.0, 4.0),  # step-up pricing for repeated degradation
    ),
    provider=X402CapacityProvider("compute", "https://compute.example/v1/capacity", client),
    base_event_usd=0.10,
)

for trigger in adapter.build_triggers(signed_envelope):  # verified or skipped
    await broker.handle_trigger(trigger)   # → paid / blocked / skipped, with audit event

The signing key lives in a separate bridge sidecar (arch-translucency stays key-less); x402 holds only the public key. The wire contract is pinned by the evidence-ref@1 golden-vector tests shared with arch-translucency.

Provisioning Metadata Redaction

Capacity-upgrade requests can reveal workload type, data classification, or access pattern. Those heuristics are intentionally opt-in so ordinary payment metadata keeps the v0.6 false-positive profile.

from presidio_x402 import PIIFilter, PROVISIONING_ENTITIES

filt = PIIFilter(
    entities=list(PROVISIONING_ENTITIES),
    redaction_template="<{entity_type}>",
)

clean, entities = filt.scan_and_redact(
    "ml training workload over CONFIDENTIAL data: SELECT email FROM customers"
)
# clean:
# "<WORKLOAD_CLASS> workload over <DATA_CLASSIFICATION> data: <QUERY_PATTERN>"

Exceptions

Exception Raised when
PIIBlockedError PII detected in metadata and pii_action="block"
PolicyViolationError Payment amount or aggregate spend exceeds configured limit
ReplayDetectedError Payment fingerprint matches a recent transaction
X402PaymentError Upstream payment signing or network error
MPADeniedError Multi-party authorization required but not enough approvals received
MPATimeoutError Multi-party authorization webhook approval timed out

All exceptions are importable from presidio_x402.


Research Artifacts

Artifact Location Description
Synthetic corpus vstantch/x402-pii-corpus on Hugging Face · corpus/ 2,000 labelled x402 metadata triples; generator (generate.py, seed=42) + metadata (corpus_meta.json); raw JSONL reproducible from seed
Precision/recall sweep experiments/ 42-configuration grid search (run_sweep.py); latency benchmark (run_latency.py)
Dune Analytics queries dune/ 6 Trino SQL queries used to characterise the live x402 ecosystem (20 projects, 96 wallets, 11 chains, ≥79M transactions); see dune/README.md

Roadmap

Version Milestone
v0.1.0 PII redaction + spending policy + replay detection
v0.2.0 Synthetic corpus + 42-configuration precision/recall sweep, LangChain/CrewAI adapters, compliance report · arXiv:2604.11430
v0.2.1 Live ecosystem characterisation via Dune Analytics (20 projects, 96 wallets, 11 chains, ≥79M transactions); IEEE S&P magazine article submitted; IEEE TIFS paper under review · Corpus: v0.2.1/dataport/, Hugging Face, IEEE DataPort (doi:10.21227/kpsz-nq73)
v0.3.0 Multi-party authorization (mpa.py: n-of-m, webhook + crypto modes) · Policy-as-code JSON Schema (IETF draft candidate) · Prometheus metrics exporter · Kubernetes Helm chart + Docker image · SOC2 reference architecture
v0.4.0 Screening API launch — hosted screen.presidio-group.eu free tier (regex mode, 100 req/day) · ScreeningClient + remote_screening=True mode · per-origin pay_to allowlist (chain-06 mitigation) · audit-cycle hardening (F-A/B 2026-05-03, F-C/D/E 2026-05-10, F1/F2/F3 2026-05-17); see CHANGELOG.md
v0.5.0 OEM embed kit — rail-agnostic ScreeningPipeline core + bindings/x402 layer (PaymentProtocolBinding protocol, hedge against rail fragmentation: ACP/Tempo, AP2) · SEMVER.md stability guarantees · partner conformance suite (python -m presidio_x402.conformance) · CDP/LangChain/CrewAI quickstarts · SBOM in CI · freshness-bound MPA countersignatures (F-8)
v0.6.0 Production hardening + evidence substrate — multi-tenant replay namespaces · enterprise audit sinks (S3 / Splunk / Datadog) · signed evidence-ref@1 verification · SLSA/OIDC release provenance · digest-pinned Docker base + hash-pinned spaCy model · latency SLO and all-matrix coverage CI gates · OTel spans · policy hot-reload · startup key gates · human-pentest retest closure
v0.7.0 SLO payment broker (library) — x402 micropayments as runtime infrastructure bids: SLOPaymentBroker + SLOPaymentPolicy + evidence-anchored ArchTranslucencyAdapter + provisioning PII entities; cross-repo validated against presidio-hardened-arch-translucency. cs.DC preprint + empirical eval tracked separately
v0.8.0 PII completeness + supply-chain hardening (library)nlp mode is now a structural superset of regex (no structured-identifier misses) · composed-envelope screen_ref leg · action_ref byte-determinism (NFC + non-empty entity sets) · URL-redacted log hygiene · OpenSSF Scorecard workflow/badge + SLSA Build L3 provenance · independent multi-round security audit cleared — latest release
Unreleased on main Deferred #23 prompt-injection / agent-bound response scanning threat-model work

See PRESIDIO-REQ.md for full deliberation and rationale.


License

MIT


SDLC

This repository is developed under the Presidio hardened-family SDLC: https://github.com/presidio-v/presidio-hardened-docs/blob/main/sdlc/sdlc-report.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

presidio_hardened_x402-0.8.1.tar.gz (430.3 kB view details)

Uploaded Source

Built Distribution

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

presidio_hardened_x402-0.8.1-py3-none-any.whl (107.0 kB view details)

Uploaded Python 3

File details

Details for the file presidio_hardened_x402-0.8.1.tar.gz.

File metadata

  • Download URL: presidio_hardened_x402-0.8.1.tar.gz
  • Upload date:
  • Size: 430.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for presidio_hardened_x402-0.8.1.tar.gz
Algorithm Hash digest
SHA256 d9602c1e705bd079487b3dc97f796abd197920b3db4940493e17ca8f2883c573
MD5 9d79034195cc18595dff0d9b929ff2f3
BLAKE2b-256 fc87308371baae56b70e772a2b863d831602c3d2966656c2ed96205bf9471867

See more details on using hashes here.

Provenance

The following attestation bundles were made for presidio_hardened_x402-0.8.1.tar.gz:

Publisher: publish.yml on presidio-v/presidio-hardened-x402

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file presidio_hardened_x402-0.8.1-py3-none-any.whl.

File metadata

File hashes

Hashes for presidio_hardened_x402-0.8.1-py3-none-any.whl
Algorithm Hash digest
SHA256 bb0d07b7e2af47ca212b9579e7faca04a99535788ad70bcd480fa234e9980c74
MD5 9ce1ad9293d9ace1464d910357f1c4bf
BLAKE2b-256 0b4446b5947a84680db0b58688af78c57edc5e2da0b750faf5018dced2a29e2a

See more details on using hashes here.

Provenance

The following attestation bundles were made for presidio_hardened_x402-0.8.1-py3-none-any.whl:

Publisher: publish.yml on presidio-v/presidio-hardened-x402

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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