Skip to content

Block unverifiable free-mode countries#551

Merged
jahooma merged 2 commits intomainfrom
jahooma/block-vpn-country
Apr 26, 2026
Merged

Block unverifiable free-mode countries#551
jahooma merged 2 commits intomainfrom
jahooma/block-vpn-country

Conversation

@jahooma
Copy link
Copy Markdown
Contributor

@jahooma jahooma commented Apr 26, 2026

Summary

Require free-mode requests to resolve to an allowlisted country before entering the Freebuff queue or using chat completions. Treat Cloudflare anonymized/unknown country codes, missing client IPs, and unresolved GeoIP lookups as blocked, and show clearer CLI copy when location cannot be verified. Add focused coverage for allowed, disallowed, unknown, and anonymized-location cases.

Validation

  • ANTHROPIC_API_KEY=test FIREWORKS_API_KEY=test GRAVITY_API_KEY=test STRIPE_SUBSCRIPTION_100_PRICE_ID=test STRIPE_SUBSCRIPTION_200_PRICE_ID=test STRIPE_SUBSCRIPTION_500_PRICE_ID=test DATABASE_URL=postgres://test:test@localhost:5432/test bun test web/src/server/tests/free-mode-country.test.ts web/src/app/api/v1/freebuff/session/tests/session.test.ts web/src/app/api/v1/chat/completions/tests/completions.test.ts
  • bun run --cwd web typecheck
  • bun run --cwd cli typecheck
  • bun run --cwd common typecheck

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR hardens the free-mode country gate by switching from fail-open (unknown/unresolvable location → allow) to fail-closed (unknown/unresolvable location → block), and explicitly rejects Cloudflare's anonymized codes (T1, XX). The getFreeModeCountryAccess helper replaces the old getCountryCode/isCountryAllowedForFreeMode functions and returns a structured result consumed by both the freebuff session handler and the chat-completions endpoint; the CLI renders a distinct message when the country resolves to UNKNOWN.

Confidence Score: 5/5

Safe to merge; the logic change is intentional and well-tested, and the two findings are minor simplification suggestions.

All remaining comments are P2 style/simplification suggestions with no correctness impact. The fail-closed behavior is correct and covered by focused unit tests across three test files.

No files require special attention.

Important Files Changed

Filename Overview
web/src/server/free-mode-country.ts Core change: replaces fail-open getCountryCode with fail-closed getFreeModeCountryAccess returning a rich result; logic is correct and well-structured, with minor simplification possible for the getFreeModeCountryCodeForClient helper.
web/src/app/api/v1/chat/completions/_post.ts Correctly switches to getFreeModeCountryAccess and blocks unknown locations; minor: extractClientIp is called twice (once inside the helper, once for logging).
web/src/app/api/v1/freebuff/session/_handlers.ts Early country gate now uses getFreeModeCountryAccess and correctly blocks unknown/anonymized locations before queue admission.
web/src/server/tests/free-mode-country.test.ts New unit tests cover all four cases: allowed country, disallowed country, Cloudflare anonymized code (T1), and missing IP — good coverage of the new logic.
web/src/app/api/v1/freebuff/session/tests/session.test.ts Test helper now defaults cf-ipcountry to US when not specified (preventing existing tests from failing due to the new fail-closed gate); new tests verify T1 and missing-country blocking.
web/src/app/api/v1/chat/completions/tests/completions.test.ts Adds allowedFreeModeHeaders helper (includes cf-ipcountry: US) and two new tests for unknown-location and anonymized-country blocking; existing free-mode tests updated to pass the new country check.
cli/src/components/waiting-room-screen.tsx Renders a distinct 'couldn't verify location' message when countryCode === 'UNKNOWN'; formatting-only changes elsewhere.
common/src/types/freebuff-session.ts Doc comment updated to note countryCode can now be UNKNOWN; no type changes.
cli/src/hooks/use-freebuff-session.ts Comment updated to remove the stale 'null detection → admitted' language; formatting-only changes to the response-parsing blocks.
cli/src/utils/error-handling.ts Comment updated to match the new fail-closed policy; no logic changes.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: web/src/app/api/v1/chat/completions/_post.ts
Line: 263-264

Comment:
**Double `extractClientIp` call**

`extractClientIp(req)` is called a second time here (line 264), but `getFreeModeCountryAccess` already called it internally. The only reason for the second call is to log `'[redacted]'` vs `undefined`, which means the header IP parsing runs twice per free-mode request. Adding a boolean field like `hasClientIp` to `FreeModeCountryAccess` would remove this redundancy and keep all IP-related logic inside `getFreeModeCountryAccess`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: web/src/server/free-mode-country.ts
Line: 106-110

Comment:
**`getFreeModeCountryCodeForClient` is a trivial one-liner**

The function body is just `countryAccess.countryCode ?? 'UNKNOWN'`. Naming it adds a layer of indirection without encapsulating any real logic. Inlining at both call sites (`_handlers.ts` and `_post.ts`) removes an unnecessary export and makes the intent immediately visible to readers without a detour to this file.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Block unverifiable free-mode countries" | Re-trigger Greptile

Comment on lines 263 to 264
const countryAccess = getFreeModeCountryAccess(req)
const clientIp = extractClientIp(req)
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.

P2 Double extractClientIp call

extractClientIp(req) is called a second time here (line 264), but getFreeModeCountryAccess already called it internally. The only reason for the second call is to log '[redacted]' vs undefined, which means the header IP parsing runs twice per free-mode request. Adding a boolean field like hasClientIp to FreeModeCountryAccess would remove this redundancy and keep all IP-related logic inside getFreeModeCountryAccess.

Prompt To Fix With AI
This is a comment left during a code review.
Path: web/src/app/api/v1/chat/completions/_post.ts
Line: 263-264

Comment:
**Double `extractClientIp` call**

`extractClientIp(req)` is called a second time here (line 264), but `getFreeModeCountryAccess` already called it internally. The only reason for the second call is to log `'[redacted]'` vs `undefined`, which means the header IP parsing runs twice per free-mode request. Adding a boolean field like `hasClientIp` to `FreeModeCountryAccess` would remove this redundancy and keep all IP-related logic inside `getFreeModeCountryAccess`.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread web/src/server/free-mode-country.ts Outdated
Comment on lines 106 to 110
export function getFreeModeCountryCodeForClient(
countryAccess: FreeModeCountryAccess,
): string {
return countryAccess.countryCode ?? 'UNKNOWN'
}
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.

P2 getFreeModeCountryCodeForClient is a trivial one-liner

The function body is just countryAccess.countryCode ?? 'UNKNOWN'. Naming it adds a layer of indirection without encapsulating any real logic. Inlining at both call sites (_handlers.ts and _post.ts) removes an unnecessary export and makes the intent immediately visible to readers without a detour to this file.

Prompt To Fix With AI
This is a comment left during a code review.
Path: web/src/server/free-mode-country.ts
Line: 106-110

Comment:
**`getFreeModeCountryCodeForClient` is a trivial one-liner**

The function body is just `countryAccess.countryCode ?? 'UNKNOWN'`. Naming it adds a layer of indirection without encapsulating any real logic. Inlining at both call sites (`_handlers.ts` and `_post.ts`) removes an unnecessary export and makes the intent immediately visible to readers without a detour to this file.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@jahooma jahooma merged commit 35819f6 into main Apr 26, 2026
34 checks passed
@jahooma jahooma deleted the jahooma/block-vpn-country branch April 26, 2026 01:58
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