Skip to content

fix: Vercel region collides with unified region; 8 legacy fields leak invalid Cloudflare wirefilter - #275

Merged
gfargo merged 2 commits into
mainfrom
fix/cloudflare-region-collision-invalid-fields
Aug 24, 2026
Merged

fix: Vercel region collides with unified region; 8 legacy fields leak invalid Cloudflare wirefilter#275
gfargo merged 2 commits into
mainfrom
fix/cloudflare-region-collision-invalid-fields

Conversation

@gfargo

@gfargo gfargo commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Fixes #270
Fixes #271

(Also closes #273, a duplicate of #270/#271 filed independently in this same session before the duplication was noticed — see the comment on #273 for details.)

Summary

Two related, independently-verified translator correctness bugs, found and empirically confirmed (running the real vercelToUnified/unifiedToCloudflare functions against test fixtures, not just reading the code) during a rules.md doc-accuracy audit.

  • Vercel region condition silently collides with unified schema's differently-meaning region field on Cloudflare sync #270 — Vercel's region silently collides with the unified region field. Vercel's native region means the edge/deployment region a request is served from (e.g. "sfo1") — mapVercelTypeToUnified had no entry for it, so it passed through unchanged into a unified condition with field: "region". But the unified schema already has a region FieldType meaning something else entirely — the client's geographic subdivision, which Cloudflare's translator maps to ip.geoip.subdivision_1. The two silently shared one name: a Vercel region condition became a syntactically valid Cloudflare rule (zero warnings) whose condition could never actually match anything — a deny/challenge rule that never fires, or a bypass/allow rule that silently blocks everyone it was meant to exempt. Fixed by namespacing Vercel's region to vercel_region, which no other provider's field vocabulary uses; mapUnifiedTypeToVercel keeps the reverse direction in sync so the round trip stays lossless.

  • 8 legacy-only Vercel condition types produce invalid Cloudflare filter syntax with no warning #271 — 8 other legacy-only Vercel fields leaked invalid wirefilter syntax. geo_continent, protocol, target_path, environment, ja3_digest, ja4_digest, and rate_limit_api_id all pass through the same way, and mapUnifiedFieldToCloudflare's mapping[field] || field fallback then leaked each one as a bare, non-existent wirefilter identifier (e.g. geo_continent eq "NA") — again with zero warnings. Every other unsupported-field case in this codebase (mapUnifiedTypeToVercel, mapUnifiedFieldToFastly) already returns null and lets the caller drop the condition with a TranslationWarning; mapUnifiedFieldToCloudflare was the only one that fell through instead. Now it returns string | null to match, and unifiedToCloudflare filters unsupported conditions with a warning before building the expression — throwing if that leaves zero conditions, mirroring unifiedToVercel's identical existing guard (never silently sync a conditionless rule that would match all traffic).

geo_country_region (one of the 8 in #271) turned out not to need the drop-with-warning treatment: rules.md already documents it as Vercel's real client-subdivision field ("Region/state code", e.g. "CA") — the same concept as unified region, not a separate one — so it now maps there directly and translates normally.

Also fixes a threat_score (cf.threat_score) gap a pre-existing test relied on this exact same silent-fallback bug for, and corrects cloudflare.md's field table plus a claim it made that turned out to be inaccurate against actual runtime behavior (legacy-only fields "aren't reachable through a provider-tagged config" — they are; UnifiedCondition.field accepts arbitrary strings).

Test plan

  • Regression tests for both bugs, mutation-verified (reverted the source fix while keeping the new tests, confirmed all 13 new/changed assertions fail legibly against the old behavior — including one showing the literal invalid ja4_digest eq "test" string that used to leak into the expression — then restored and confirmed green)
  • pnpm tsc --noEmit && pnpm test && pnpm lint all pass (1756 tests, 0 lint errors)
  • Confirmed no regression on the legitimate unified region (client geo) path — still produces ip.geoip.subdivision_1 with zero warnings
  • Confirmed the Vercel region → unified vercel_region → Vercel region round trip stays lossless

… invalid cloudflare wirefilter

Vercel's native `region` (edge/deployment location, e.g. "sfo1") passed
through mapVercelTypeToUnified unchanged, silently colliding with the
unified schema's own `region` field (client geo subdivision, mapped to
Cloudflare's ip.geoip.subdivision_1). A Vercel region condition became a
syntactically valid but semantically inverted Cloudflare rule with zero
warnings — a deny/challenge rule that could never fire, or a bypass/allow
rule that silently blocked everyone it was meant to exempt. Fixed by
namespacing Vercel's region to `vercel_region` so it can't collide; the
reverse mapUnifiedTypeToVercel keeps the round trip lossless.

Separately, mapUnifiedFieldToCloudflare's `mapping[field] || field`
fallback leaked any unmapped field name as a bare wirefilter identifier
instead of dropping it — geo_continent, protocol, target_path,
environment, ja3_digest, ja4_digest, and rate_limit_api_id all produced
invalid expressions with zero warnings. Now returns `string | null`
(matching mapUnifiedTypeToVercel/mapUnifiedFieldToFastly's existing
pattern), and unifiedToCloudflare drops unsupported conditions with a
TranslationWarning before building the expression, throwing if that
leaves a rule with none (mirroring unifiedToVercel's identical guard —
never sync a conditionless rule that would match all traffic).

geo_country_region turned out not to need the same "unmapped, drop with
warning" treatment: rules.md already documents it as Vercel's real
client-subdivision field ("Region/state code", e.g. "CA") — the same
concept as unified region, not a separate one — so it now maps there
directly instead of passing through as its own name.

Also adds the threat_score field (Cloudflare's cf.threat_score) that a
pre-existing test relied on this same silent-fallback bug for, and updates
cloudflare.md's field table and its now-inaccurate claim that legacy-only
fields "aren't reachable through a provider-tagged config" (they are —
UnifiedCondition.field accepts arbitrary strings).

Closes #273
…-collision-invalid-fields

# Conflicts:
#	skills/doorman/references/cloudflare.md
@gfargo
gfargo merged commit e79b4eb into main Aug 24, 2026
2 checks passed
@gfargo
gfargo deleted the fix/cloudflare-region-collision-invalid-fields branch August 24, 2026 17:41
github-actions Bot pushed a commit that referenced this pull request Aug 24, 2026
## [3.15.1](v3.15.0...v3.15.1) (2026-08-24)

### Bug Fixes

* vercel region collides with unified region; 8 legacy fields leak invalid cloudflare wirefilter ([#275](#275)) ([e79b4eb](e79b4eb)), closes [#273](#273)
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 3.15.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

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