Skip to content

feat(ci): add a safe production link checker - #202

Merged
truthixify merged 5 commits into
wraith-protocol:developfrom
aratass:feat/188-safe-link-checker
Oct 18, 2026
Merged

truthixify merged 5 commits into
wraith-protocol:developfrom
aratass:feat/188-safe-link-checker

Conversation

@aratass

@aratass aratass commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Closes #188

Carries over #160 and addresses every point raised on #183.

Checker

pnpm check:links (scripts/check-links.ts) seeds from the sitemap, / and /feed.xml, renders each public route in Chromium and fails on:

  • routes that return an error status or render the 404 page (NotFound now carries data-page="not-found")
  • internal links to missing routes, files or #fragments
  • canonical URLs that are missing, duplicated, off-site or not the page itself, and hreflang or feed alternates that point off-site or at missing routes
  • feeds that are not served as XML, and broken feed links
  • external links to hosts that are not listed in scripts/link-check/external-links.json, or that fail

By default it checks the build through scripts/preview-server.ts. --base https://usewraith.xyz checks production, --report <file> writes a JSON report and --no-external skips external requests. Allowlist entries that accept a status (www.npmjs.com answers bots with a Cloudflare 403) or skip the request (x.com, twitter.com) must give a reason.

SSRF and DNS rebinding

External requests go through scripts/link-check/safe-request.ts:

  • http(s) only, no credentials, ports 80 and 443
  • the hostname is resolved once and refused if any address is not globally routable: loopback, private, link-local, CGNAT, documentation, benchmarking, multicast, reserved, IPv6 outside 2000::/3, IPv4-compatible and Teredo
  • IPv4-mapped (::ffff:127.0.0.1, ::ffff:7f00:1), NAT64 and 6to4 addresses are judged by the IPv4 address they embed
  • the socket connects to the validated address through a pinned lookup, so a second DNS answer is never used. TLS still verifies the certificate for the hostname
  • redirects are followed manually and every hop is validated and pinned again
  • the Chromium context can only load the site under test, so page content cannot make the browser fetch other hosts

Concurrency and retries

4 pages render at once and 6 external requests run at once (--concurrency, --external-concurrency). External checks retry up to 2 times on network errors and on 408, 425, 429, 500, 502, 503 and 504, with exponential backoff (1 s base, 10 s cap) that honors Retry-After. Refused destinations are never retried.

Link fixes from the #183 review

  • status links use /status until the status host exists
  • aquarius.fi is replaced with https://aqua.network
  • chain docs: Stellar and Solana link to /sdk/chains/stellar and /sdk/chains/solana, Nervos to /sdk/chains/ckb, EVM chains and Horizen to /sdk/chains/evm
  • the stale Wave 1 CTA now points at https://www.drips.network/wave/stellar
  • audits, bug bounty and disclosures link to anchored sections on /security (#audits, #bug-bounty, #disclosures)
  • Spectre CTAs link to the roadmap milestone (/roadmap#phase-2). Milestones have ids and the page scrolls to the hash
  • Terms is a real page at /terms (in the sitemap), not Privacy. /press is routed
  • canonicals: the SPA shell no longer ships a static canonical (React 19 hoists Helmet tags without removing static ones, so every route also declared / as canonical). Terms, Press and the Stellar locale pages declare their own, and hreflang es/pt is only emitted for routes that have localized pages
  • placeholder team profiles whose GitHub accounts return 404 no longer link out, and the placeholder PGP key is hidden
  • the footer changelog links to the SDK CHANGELOG and API to https://docs.usewraith.xyz/api-reference/endpoints

CI

  • ci.yml: new link-check job on every pull request. It runs the checker's unit tests, builds, runs pnpm check:links against the build and uploads the JSON report on failure
  • production-links.yml: daily at 06:23 UTC and on demand against https://usewraith.xyz

Verification

  • pnpm test:link-check: 96 tests, including DNS rebinding (the resolver's second answer is private and is never used), a redirect to [::ffff:127.0.0.1], a redirect to a host that resolves to a private address, mixed public and private answers, NAT64 and 6to4, redirect limits, timeouts and retries
  • pnpm build && pnpm check:links --no-external: 69 routes, 8 feeds, 188 fragments and 6 files, no failures
  • pnpm format:check, lint:i18n, validate:content, test:a11y, validate:jsonld and test:smoke pass
  • external links were not requested locally (restricted network). The new CI job reports them

src/__tests__/seo.test.tsx (7) and src/__tests__/jsonld.test.ts (1) already fail on develop and are untouched here.

zemishyrius-sketch and others added 4 commits October 17, 2026 09:13
scripts/check-links.ts renders every public route from the sitemap in Chromium
and checks routes, internal links and #fragments, files, canonical and hreflang
URLs, RSS feeds and approved external links. It runs against the build (served
by scripts/preview-server.ts) or production with --base.

External links must use a host from scripts/link-check/external-links.json and
are requested through safe-request.ts: http(s) on ports 80 and 443 only, every
resolved address must be public (IPv4-mapped, NAT64 and 6to4 forms are judged
by the IPv4 address they carry), each hop connects to the validated address
through a pinned lookup so DNS rebinding cannot redirect it, and redirects are
re-validated. The browser is locked to the site under test. Requests run with
bounded concurrency and retry transient failures.

The 404 page gets a data-page marker so the checker can detect it.
- Register /press and add a real /terms page instead of linking to 404s.
- Localized links and hreflang alternates only point at routes that exist
  under /es and /pt; og.ts no longer emits them for other routes, and the
  SPA shell no longer carries the homepage canonical onto every fallback
  route (React 19 hoists Helmet tags next to static ones).
- Stellar and Press render their own canonical URLs.
- The footer Compare link goes to /#compare, and pages scroll to hash
  targets once lazy sections render.
- Status links use /status until the status host exists.
- Chain docs links use /sdk/chains/{evm,stellar,solana,ckb}, Aquarius moved
  to aqua.network, the grants CTA points at the live Stellar Wave, and
  footer docs links point at pages that exist.
- Audit, bounty and disclosure links go to anchored sections on /security,
  Spectre goes to its roadmap milestone, and the placeholder PGP key is
  hidden.
- Team GitHub profiles that return 404 are removed.
@aratass
aratass requested a review from truthixify as a code owner October 17, 2026 09:28
@vercel

vercel Bot commented Oct 17, 2026

Copy link
Copy Markdown

@aratass is attempting to deploy a commit to the truthixify's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@aratass Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@truthixify truthixify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link checker pins validated DNS answers, revalidates redirects, blocks private address ranges, and passes all 96 focused tests. Approved.

@aratass

aratass commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Merged cleanly with develop now: the Stellar page head takes develop's seo values (#201), which already include the canonical and hreflang links this PR added. On the merge with develop: tsc -b, pnpm build, all 597 unit tests, format:check, lint:i18n, validate:content, validate:jsonld and test:link-check pass.

@truthixify truthixify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rebased link checker is ready. The focused security and routing suite passes 96/96, the production build succeeds, and the rendered-site crawl checks 69 routes, 8 feeds, 188 fragments, and 6 files with no failures. The only red status is the external Vercel authorization check.

@truthixify
truthixify merged commit ca6a45d into wraith-protocol:develop Oct 18, 2026
6 of 7 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.

[Wave 10][Carryover] Add a safe production link checker

3 participants