Skip to content

Fix/redirect safety hardening - #20

Merged
botnick merged 3 commits into
botnick:mainfrom
tmxnova:fix/redirect-safety-hardening
Sep 8, 2026
Merged

botnick merged 3 commits into
botnick:mainfrom
tmxnova:fix/redirect-safety-hardening

Conversation

@botnick

@botnick botnick commented Sep 8, 2026

Copy link
Copy Markdown
Owner

No description provided.

…d custom host

- Enforce the destination blocklist on iosUrl/androidUrl/desktopUrl (create +
  update). Only destination and geoRules were checked, yet per-OS deep links
  route straight from the cached payload (cache.ts routeDestination), so one
  could point a device-specific target at a blocked domain and bypass the block.
- Normalize hosts in isBlockedDestination: strip trailing FQDN dot(s) from the
  destination host and blocklist entries ("evil.com." bypass), and punycode-
  encode IDN entries so a Unicode blocklist entry matches new URL()'s ASCII
  destination host (and vice versa).
- Fail closed when a custom host's domain lookup fails (KV miss + DB error): a
  new LOOKUP_FAILED sentinel distinguishes a hard failure from a definitive "no
  such domain", and every /:slug consumer of resolveScope — the redirect, POST
  /api/unlock/:slug, GET /api/qr/:slug and GET /qr/:file.svg — now 404s instead
  of resolving the host against the unrelated default bucket (which could, e.g.,
  serve/unlock an unrelated default-host link at the same slug).
- Add scripts/seed-api-key-d1.mjs to mint the first API key on a D1 deploy
  without the dashboard (resolves database_id like d1-migrate.mjs for --remote).
- Add tests/blocklist.ts (+ test:blocklist), including real IDN conversion.
Strip trailing dot(s) AFTER the IDNA conversion of a blocklist entry, not only
before. Unicode dot separators (U+3002 。/ U+FF0E ./ U+FF61 。) only become ASCII
dots inside new URL(), so a pre-conversion strip left the converted entry with a
trailing dot (e.g. "пример.example。" -> "xn--…example.") that no longer matched
the dot-stripped destination host. Add regression cases for all three separators.
The U+FF0E case used an interior fullwidth dot (a label separator), not a
trailing one, so it didn't exercise the after-IDNA trailing-strip it was meant
to. Add a trailing-U+FF0E case and keep the interior one (relabeled).
@botnick
botnick merged commit 1f57765 into botnick:main Sep 8, 2026
1 check 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.

2 participants