Skip to content

Registration overhaul: SIWX Free endpoints, display filtering, schema detection, robustness#918

Merged
onchainlu merged 32 commits into
mainfrom
fix-schema-detection
May 22, 2026
Merged

Registration overhaul: SIWX Free endpoints, display filtering, schema detection, robustness#918
onchainlu merged 32 commits into
mainfrom
fix-schema-detection

Conversation

@onchainlu
Copy link
Copy Markdown
Contributor

@onchainlu onchainlu commented May 21, 2026

Summary

Overhaul of merchant registration — fixes schema detection, adds SIWX (Free) endpoint support, cleans up the UI, hardens robustness, fixes a payment redirection vulnerability, and recovers endpoints with oversized response headers.

Security: server-side probe cache

  • Eliminated client-trusted advisory data — probe results (including payTo addresses) previously round-tripped through the client. A malicious actor could intercept the tRPC call and substitute payment addresses.
  • Server-side Redis cache — batch test stores probe results under probe-session:{sessionId}:{url} with 5-min TTL. Registration reads from this cache; the client only passes an opaque probeSessionId.
  • Cross-origin URL validation — discovery now rejects endpoints whose resolved URL doesn't match the discovery origin, preventing malicious OpenAPI specs from injecting absolute URLs to other origins.

Oversized header fallback (HEADERS_OVERFLOW)

  • Direct HTTPS probe fallback — some merchants embed the full 402 body in a payment-required header, exceeding Node's 16 KB limit. The discovery library silently drops these as network errors.
  • Recovery via node:https — a fallback probe with maxHeaderSize: 128KB recovers the 402 body, merges OpenAPI schema data, and surfaces a HEADERS_OVERFLOW warning to the merchant.
  • Before: war-tracker.com registered 9/16 endpoints (7 skipped). After: 16/16 (7 with warnings).

Schema detection

  • Bazaar schema fallback in validateResource() — checks 402 body for bazaar extension before rejecting for missing input schema
  • Method passthrough from discovery to probe — GET endpoints probed with GET, not POST
  • Missing output schema is now a warning (not silent) with bazaar fallback check
  • Missing input schema error text updated to be accurate ("requestBody or parameter schema")

Display filtering

  • Exclude explicitly non-registrable endpoints (unprotected, apiKey) from discovery list
  • Unclassified endpoints (authMode absent) kept — may be paid but discovery didn't detect it
  • Paid endpoints probed first to avoid rate limiting before reaching unclassified ones
  • Removed Advanced dropdown (Custom Headers + Manual URLs) from register form

SIWX → Free

  • SIWX endpoints relabeled as "Free" with green styling throughout
  • SIWX DB registration via registerSiwxResource() — writes Resource with x402Version: 0, metadata: { authMode: 'siwx' }
  • SIWX-only origins now succeed registration
  • All display queries updated with OR clauses for SIWX

Probing

  • Per-endpoint progress — BATCH_SIZE=1, sequential, "Checking 3/5 endpoints..."
  • Skip SIWX/unprotected/apiKey from probing — only probe paid + unclassified
  • Sequential within chunks to avoid rate-limiting merchant servers
  • Retry on 429/503 with exponential backoff (up to 2 retries)

Warnings & prompts

  • Consolidated prompt system — one prompt combines all errors + warnings + missing schemas
  • Deduplicate warnings by code+message via shared deduplicateWarnings() utility
  • "(Not blocking)" on post-registration warnings dropdown
  • Registration error message now tells merchants about security: [] for free endpoints

Documentation

  • New "Free (Unprotected) Endpoints" section on discovery spec page

Robustness

  • P2002 race fix in registerSiwxResource — concurrent registrations caught and treated as success
  • Origin upsert inside transaction — prevents P2002 on concurrent registrations
  • Non-blocking metadata scrape in registerSiwxResource
  • Sorted resource tables — pre-registration: invalid → free → new → registered

Test results (21 origins)

Origin Endpoints Registered SIWX Failed Skipped Notes
stableupload.dev 11 3 7 0 1 1 unprotected skipped
stablesocial.dev 37 36 1 0 0 Clean
stablestudio.dev 30 26 4 0 0 Clean
stableemail.dev 24 13 11 0 0 Clean
rep.memoryapi.org 5 5 0 0 0 Bazaar detection works
api.northeastdealintel.com 41 1 0 0 40 36 unclassified non-x402
api.vishwalab.com 2 2 0 0 0 Clean
stableflowers.dev 6 1 2 0 3 3 unprotected skipped
stablemerch.dev 3 3 0 0 0 Clean
stabletube.dev 3 1 1 0 1 Clean
war-tracker.com 16 16 0 0 0 7 with HEADERS_OVERFLOW warning
api.carbon-cashmere.de 431 253 0 0 178 Unclassified endpoints skipped
api.strale.io 367 0 0 348 19 All v1 — merchant needs to migrate
ai.verifik.co 141 141 0 0 0 All pass (MPP warnings)
x402.quicknode.com 141 0 141 0 0 All classified as SIWX
stablecrypto.dev 105 105 0 0 0 Clean
blockrun.ai 96 79 0 13 4 13 missing input schema
chainray.online 94 94 0 0 0 Clean
apiv2.laevitas.ch 88 75 0 0 13 13 non-x402 infra endpoints
wurkapi.fun 51 0 0 0 51 Pure MPP/Tempo, not x402
0xtheplug.xyz 6 0 0 0 6 No x402 implementation

Batch-to-registration consistency: perfect across all 21 origins — every batch test failure maps to a registration skip/fail with identical error messages.

Files changed

File Change
probe-cache.ts New — Redis-backed server-side probe session cache
probe.ts Retry on 429/503, direct HTTPS fallback for oversized headers, HEADERS_OVERFLOW warning
register-origin.ts Server-side cache lookup, SIWX DB registration, method passthrough
resources.ts registerSiwxResource(), bazaar schema fallback, output schema warning
developer.ts Cache probe results with sessionId, sequential probing, warning dedup
public/resources.ts Accept probeSessionId instead of preTestedResults (security fix)
fetch-discovery.ts Cross-origin URL validation
use-batch-test.ts Per-endpoint progress, paid-first sorting, sessionId tracking
use-discovery.ts REGISTRABLE_AUTH_MODES filter, pass sessionId not advisory data
use-register-from-origin.ts Accept sessionId instead of advisory array
form.tsx SIWX count, progress button, removed Advanced section
discovery-panel.tsx Free badges, sorted tables, warnings
discovery-actions.tsx Consolidated prompt system
deduplicate-warnings.ts New shared utility
markdown.ts Free Endpoints section + classification table on spec page

The discovery package doesn't always populate advisory.inputSchema
even when the 402 body contains a full bazaar schema. Before rejecting,
check the raw paymentRequiredBody for a bazaar extension with schema
data. This prevents false rejections for merchants like rep.memoryapi.org
whose 402 bodies have complete schemas but whose OpenAPI extraction
doesn't propagate to advisory.inputSchema.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
x402scan Ready Ready Preview, Comment May 22, 2026 3:41am

Request Review

Two fixes for endpoints like api.northeastdealintel.com where 5
endpoints return 402 but only 1 registered:

1. registerResourcesFromDiscovery now passes resource.method to
   probeX402Endpoint so GET endpoints are probed with GET (not POST)
2. validateResource already falls back to bazaar schema (previous
   commit) which handles the missing inputSchema from discovery
@onchainlu onchainlu changed the title Fall back to bazaar schema when input schema is missing Fix schema detection: bazaar fallback + method passthrough May 21, 2026
Previously the batch test probed ALL discovered endpoints (including
health checks, webhooks, internal routes). For origins like
api.northeastdealintel.com (41 endpoints, only 5 paid), this
triggered rate limiting (503s) that caused even the valid paid
endpoints to fail. Now only endpoints with authMode 'paid' or
'apiKey+paid' are probed.
onchainlu added 4 commits May 21, 2026 14:37
Non-paid endpoints (health checks, webhooks, etc.) aren't probed
but should still show a red X since they won't be registered.
Sort order: failed probes → passed → non-probed at bottom.
When registerResourcesFromDiscovery registers multiple resources
from the same origin concurrently, the connectOrCreate on the
origin record can race — both try to create, second hits P2002
unique constraint. Fix: upsert the origin outside the transaction
first, then connect (not connectOrCreate) inside the transaction.
- Failed (red X): probed but failed validation
- Warning (yellow triangle): registered but has audit warnings
- Verified (green check): passed with no warnings
- SIWX (blue check): identity-gated, no payment
- Skipped (grey dash, strikethrough): not x402, not probed

Sort order: failed → warning → verified/SIWX → skipped
onchainlu added 2 commits May 21, 2026 14:58
- Sort: errors → warnings → SIWX (blue) → verified (green) → skipped
- Bazaar fallback: type-check paymentRequiredBody + try-catch
- Origin upsert moved inside transaction to prevent race conditions
- nonPaidUrls: only classify with evidence from authModeMap, not absence
- Remove paidModes useMemo code smell (inline static set)
…king

- Filter discovery to only show paid + SIWX endpoints (hide apiKey, unprotected)
- Relabel SIWX badges as "Free" with green styling
- Register SIWX endpoints to DB via registerSiwxResource() (x402Version: 0, no Accepts)
- Update displayableResourceWhere, listOrigins, listOriginsWithResources, searchResources
  with OR clauses for SIWX — respects chain/address filters (SIWX excluded when filtering)
- Allow SIWX-only origins to succeed registration
- Replace SIWX warning with missing output schema warning in validateResource()
- Add batch test progress tracking (Checking N/M endpoints...)
- Include SIWX count in registrableResourceCount after batch test
@onchainlu onchainlu changed the title Fix schema detection: bazaar fallback + method passthrough Fix registration: schema detection, SIWX as Free, display filtering May 21, 2026
onchainlu added 2 commits May 21, 2026 16:54
…d endpoints

- Remove Advanced section (Custom Headers + Manual URLs) from register form
- Clean up dead state/handlers/imports
- Fix discovery filter: only include endpoints with a known registrable authMode
  (previously unclassified endpoints passed through, showing 36 non-paid endpoints
  for origins like api.northeastdealintel.com)
- Discovery filter now excludes only explicitly non-registrable (unprotected,
  apiKey). Unclassified endpoints (authMode absent) are kept — they may be paid
  but the discovery package didn't detect it (e.g. bazaar-only on rep.memoryapi.org).
- Server-side batch test filter updated to match: probe unclassified + paid,
  skip SIWX/unprotected/apiKey.
- stableflowers.dev: 3 unprotected excluded, 2 SIWX + 1 paid shown correctly.
- northeastdealintel: 41 shown pre-probe, 5 pass post-probe (36 unclassified fail).
- rep.memoryapi.org: 5 unclassified shown and probed (all should pass).
- Add "(Not blocking)" to post-registration warnings dropdown.
Discovery package warns about missing schemas in the raw 402 body (bazaar
extension), but the advisory may already have schemas from the OpenAPI spec.
Drop SCHEMA_INPUT_MISSING when advisory.inputSchema exists and
SCHEMA_OUTPUT_MISSING when advisory.outputSchema exists. Applied in both
batch test (developer.ts) and registration (register-origin.ts) paths.
Server probes sequentially anyway, so 1 endpoint per request gives real-time
"Checking 3/5 endpoints..." updates without changing total probe time.
- Replace 5 separate prompts (generic, error, warning, v1, schema) with one
  consolidated prompt that lists all errors AND warnings together. The setup
  prompt (no discovery) remains separate.
- Consolidated prompt includes all failing URLs + their errors, all warnings,
  and missing schema URLs in a single copy-to-clipboard block.
- Fix instructions now cover paid endpoints (402), free endpoints (security: []),
  request validation ordering, required params, and schemas.
- Simplify DiscoveryFixHint: remove v1Migration/needsSetup/missingSchema props
- Fix "Missing input schema" error: say "requestBody or parameter schema"
  instead of misleading "request/response schemas"
- SETUP_PROMPT now mentions both paid (x402) and free (identity-gated) endpoints
Extract shared deduplicateWarnings() to discovery/utils. Applied at both
merge points: batch test (developer.ts) and registration (resources.ts).
Prevents duplicate warnings when probe and validation emit the same code.
When an origin has the same URL with different HTTP methods (e.g. POST and
DELETE on /api/site/domain), concurrent registration hits a unique constraint
race on the resources table. Catch P2002, look up the existing record, and
return success. Found via stableupload.dev testing.
Sort resources before chunking: paid/apiKey+paid first, unclassified last.
For origins like api.northeastdealintel.com (5 paid + 36 unclassified),
paid endpoints are probed before rate limiting kicks in from probing
non-paid endpoints that return 503.
@onchainlu onchainlu changed the title Fix registration: schema detection, SIWX as Free, display filtering Registration overhaul: SIWX Free endpoints, display filtering, schema detection, robustness May 21, 2026
- Add "Free (Unprotected) Endpoints" section to discovery spec page explaining
  security: [] as the OpenAPI standard for declaring no-auth-required
- Add endpoint classification summary table (paid, SIWX, free, unclassified)
- Add "No valid x402 response" to Common Failure Reasons with fix guidance
- Update registration error message to tell merchants about security: [] when
  endpoints fail probing — "If these endpoints are free, add security: [] to
  their OpenAPI definition to exclude them from probing"
- registerSiwxResource P2002 catch: always return success even if findUnique
  returns null (record deleted between race and lookup). The constraint
  violation proves the resource existed.
- Consolidated prompt: early return with spec link when all issue arrays are
  empty (shouldn't be reachable but avoids misleading generic message).
Previously only errors had a collapsible section — warnings showed as yellow
triangle icons with no explanation. Now warnings get their own "(Not blocking)"
dropdown listing each endpoint and its warning messages, plus a fix prompt.
When a merchant server rate-limits a probe (429 or 503), retry up to 2 times
with exponential backoff (1.5s, 3s). Fixes transient probe failures for
origins like tiresapi.com where sequential probing of 13 endpoints triggers
rate limiting on later endpoints.
When the user clicks "Register All", pass the batch test results (URL →
advisory map) to the server. Endpoints with pre-tested advisories skip
probing and use the advisory directly for registration. Endpoints not in
the pre-tested set are probed as before.

This avoids redundant probing and rate limiting — the batch test already
validated these endpoints moments ago.

Chain: handleRegisterAll → register(origin, preTestedResults) →
  registerFromOrigin mutation → registerResourcesFromDiscovery(preTestedAdvisories)
Matches the existing fire-and-forget pattern used for ownership verification.
The origin row is already created inside upsertResource's transaction — the
metadata upsert just enriches it with scraped title/favicon/OG. When multiple
resources from the same origin register concurrently (especially with
pre-tested advisories skipping probes), the metadata upsert can P2002 race.
Safe to swallow since another concurrent call will succeed.
…data

The registerFromOrigin endpoint previously accepted pre-tested advisory
data (including payTo addresses) from the client. A malicious actor could
intercept the tRPC call and substitute payTo with their own address,
redirecting merchant payments.

Fix: probe results now stay server-side in Redis under a session ID.
The batch test caches results under probe-session:{sessionId}:{url}
with 5-min TTL. Registration reads from this cache — the client only
passes an opaque probeSessionId, never advisory data.

Also adds cross-origin URL validation in discovery: endpoints whose
resolved URL doesn't match the discovery origin are rejected, preventing
malicious OpenAPI specs from injecting absolute URLs to other origins.
Some merchants embed the full 402 payment body in a payment-required
header, exceeding Node's default 16 KB header size limit. The discovery
library silently drops these as network errors.

Adds a direct HTTPS fallback probe (node:https with maxHeaderSize 128KB)
that fires when the library's probe fails. Recovers the 402 body, merges
OpenAPI schema data, and surfaces a HEADERS_OVERFLOW warning to the
merchant advising them to reduce their header size.

Before: war-tracker.com registered 9/16 endpoints (7 skipped)
After: war-tracker.com registers 16/16 endpoints (7 with warnings)
…dation

- Remove unsafe `as unknown as` double cast on payment options in
  directProbe402 fallback; filter + validate accepts before mapping
- Extract duplicated SIWX registration logic into registerAsSiwx helper
- Validate cached probe result structure before casting from Redis
- Reuse URL object in fetch-discovery cross-origin check
@onchainlu onchainlu merged commit c82549f into main May 22, 2026
3 checks passed
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