Skip to content

fix(x402): refuse to sign when the 402 resource is unreachable - #396

Open
konstantinbozukov wants to merge 2 commits into
apify:mainfrom
konstantinbozukov:fix/x402-refuse-unreachable-resource
Open

konstantinbozukov wants to merge 2 commits into
apify:mainfrom
konstantinbozukov:fix/x402-refuse-unreachable-resource

Conversation

@konstantinbozukov

@konstantinbozukov konstantinbozukov commented Sep 13, 2026 •

Copy link
Copy Markdown

Summary

HTTP 402 is not proof the paid host is alive. Ghost bazaar URLs still return a PAYMENT-REQUIRED (or a cached one) after the origin is gone, and mcpc signed anyway.

This probes resource.url before signPayment:

  • timeout / DNS / connection failure → fail closed (pass the 402 through, do not sign, do not settle)
  • any HTTP status, including 402 → a host answered, payment may proceed
  • missing / non-http resource.url, or the same target we already got the 402 from → skip the extra round-trip

Same guard on the tool-result retry path in the bridge.

No new flags. No vendor default. Useful even if you strip the probe later.

The probe uses GET (body cancelled), not HEAD: live hosts that omit HEAD can hang until timeout, which would fail-closed on a reachable resource.

Test plan

  • vitest run test/unit/lib/x402 — 53 passed (probe skip/reachable/unreachable/timeout + middleware refuses to sign on ghost URL, still signs when the probe answers). Re-run locally after the follow-up commit, same 53.
  • pnpm run test:unit on CI (first-time contributor — Actions is waiting for workflow approval)
  • Confirm a live paid tool (e.g. mcp.apify.com with --x402) still settles
  • Confirm a 402 whose resource.url does not resolve does not produce a PAYMENT-SIGNATURE

konstantinbozukov and others added 2 commits September 12, 2026 20:40
HTTP 402 is not proof the paid host is alive. Probe resource.url before signing and fail closed on timeout, DNS, or connection errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
Changelog cites apify#396. Import the fetch-only probe statically in the bridge (no viem). Comment why the probe uses GET instead of HEAD.

Co-authored-by: Cursor <cursoragent@cursor.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant