Skip to content

Conversation

@UnbornAztecKing
Copy link
Contributor

@UnbornAztecKing UnbornAztecKing commented Oct 16, 2025

Summary

Connections from restricted origins are now immediately rejected with a **403

GeoBlocked** response and no compliance state changes.

Connections from non-restricted origins normalize any prior blocked or first-strike states to COMPLIANT, simplifying the state model.

Closed addresses are not upgraded on connection from unrestricted origins.

Changes

Restricted Origin Handling

  • Added early 403 rejection in both /geoblock and /geoblock-keplr routes:
    • No DB writes, no compliance_status_changed metrics emitted.
    • Applies even to previously whitelisted addresses.
  • Simplifies compliance behavior: restricted traffic is fully short-circuited.

Non-Restricted Origin Normalization

  • For non-restricted requests, the following compliance statuses are upgraded to COMPLIANT:
    • FIRST_STRIKECOMPLIANT
    • FIRST_STRIKE_CLOSE_ONLYCOMPLIANT
  • New addresses from non-restricted origins now upsert directly as COMPLIANT.
  • CLOSE_ONLY and BLOCKED status remains unchanged on connection from unrestricted origin.

Survey Behavior

  • VALID_SURVEY and non-restricted origin now transitions to COMPLIANT.
  • INVALID_SURVEY or restricted origin transitions to CLOSE_ONLY.

Tests

Updated restricted-origin test cases to:

  • Expect 403 with INDEXER_GEOBLOCKED_PAYLOAD

  • Verify no ComplianceStatusTable mutations

  • Confirm no metric increments for compliance_status_changed

Added coverage for non-restricted upgrades:

Verified BLOCKED, FIRST_STRIKE, and FIRST_STRIKE_CLOSE_ONLY upgrade to COMPLIANT.

Adjusted whitelisted address tests to expect 403 under restricted origins.

Deployed to internal network and testnet.

https://linear.app/dydx/issue/ENG-1111/raise-403-on-in-scope-restricted-connections-and-unban-ok-connections

Summary by CodeRabbit

  • New Features

    • Geoblocking enforcement on compliance endpoints: requests from restricted regions now receive 403 responses with a standardized geoblock error payload.
  • Bug Fixes

    • Compliance status no longer mutates for geoblocked requests; restricted flows now follow expected "close-only"/blocked behavior while non-restricted flows remain compliant.
  • Tests

    • Expanded tests to validate 403 geoblock responses, standardized error payloads, and unchanged state for geoblocked scenarios.

@UnbornAztecKing UnbornAztecKing requested a review from a team as a code owner October 16, 2025 01:30
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 16, 2025

Walkthrough

Adds geoblocking to Compliance V2: restricted-country requests now short-circuit with 403 and an INDEXER_GEOBLOCKED_PAYLOAD (BlockedCode.GEOBLOCKED). Controller upsert logic is made geo-aware (COMPLIANT / CLOSE_ONLY / BLOCKED) and tests updated to assert 403 responses and no DB state mutations for geoblocked flows.

Changes

Cohort / File(s) Summary
Controller logic (geo-aware)
indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts
Adds restricted-country checks at POST endpoints to return 403 with INDEXER_GEOBLOCKED_PAYLOAD and BlockedCode.GEOBLOCKED. Replaces prior progression mapping with geo-aware status transitions (COMPLIANT / CLOSE_ONLY / BLOCKED) depending on prior status, region, and wallet presence. Updates imports.
Tests (geoblock expectations)
indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts
Updates imports to include INDEXER_GEOBLOCKED_PAYLOAD, isRestrictedCountryHeaders, isWhitelistedAddress, and BlockedCode. Adjusts assertions to expect 403 GEOBLOCKED responses for restricted-country scenarios and verifies no compliance_status table mutations for geoblocked flows.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Controller
  participant GeoUtils
  participant DB

  Note over Controller: Geo-aware compliance flow
  Client->>Controller: POST /api/v4/compliance/... (headers, body)
  Controller->>GeoUtils: isRestrictedCountryHeaders(headers)
  alt Restricted country
    Controller-->>Client: 403 { INDEXER_GEOBLOCKED_PAYLOAD, code: BlockedCode.GEOBLOCKED }
    Note right of Controller #aab4ff: No DB mutation or status transition
  else Not restricted
    Controller->>DB: read existing compliance_status
    alt No existing status
      Controller->>DB: upsert COMPLIANT (or BLOCKED if wallet absent rules)
    else Existing status
      Controller->>DB: transition per geo-aware rules (FIRST_STRIKE / FIRST_STRIKE_CLOSE_ONLY -> CLOSE_ONLY, else -> COMPLIANT)
    end
    Controller-->>Client: 200 ComplianceV2Response
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • tqin7
  • jaredvu
  • shrenujb
  • tyleroooo

Poem

I hop through headers, gentle and bright,
Closed doors for places that hide from the light.
A 403 thump, no database stir,
Carrots kept safe, the logs purr.
🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ⚠️ Warning The PR description provides a well-structured and detailed explanation of the changes and test plan. It includes a clear "Summary" section describing the restricted origin handling and non-restricted origin normalization, followed by organized subsections under "Changes" that cover restricted origins, non-restricted normalization, and survey behavior. The "Tests" section describes updates to test cases, added coverage, and deployment verification. However, the description is missing the entire "Author/Reviewer Checklist" section specified in the template, which requires the author to confirm applicable labels (state-breaking, indexer-postgres-breaking, proposal-breaking, feature labels, backport labels, and category labels like refactor, chore, or bug). This is a required procedural section that should be included in the PR description. Add the "Author/Reviewer Checklist" section from the template to the PR description. Ensure the author confirms or documents which of the following apply: state-breaking changes, indexer-postgres-breaking changes, proposal-breaking changes, feature labels, backport requirements, and appropriate category labels (refactor, chore, or bug). This procedural section is essential for proper PR triage and labeling within the repository's workflow.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat(api): don't strike users from /geoblock*, raise 403" clearly and specifically describes the main change in the pull request. It captures the core behavior shift: users from restricted origins connecting to the geoblock endpoints no longer receive compliance strikes and instead receive a 403 response. The asterisk notation appropriately covers both affected endpoints (/geoblock and /geoblock-keplr). The title is concise, free of noise, and immediately conveys what was changed to someone reviewing the commit history.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rm-geo-origin-strike

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts (1)

439-461: CRITICAL: INVALID_SURVEY from non-restricted origin incorrectly transitions to COMPLIANT.

The action parameter is declared but never used in the non-restricted code path (lines 452-458). This causes INVALID_SURVEY from non-restricted origins to incorrectly transition to COMPLIANT instead of CLOSE_ONLY, violating the documented behavior in the PR objectives: "INVALID_SURVEY or a restricted origin transitions to CLOSE_ONLY".

Apply this diff to restore action-aware transitions:

   if (
     complianceStatus[0].status === ComplianceStatus.FIRST_STRIKE ||
     complianceStatus[0].status === ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY ||
     complianceStatus[0].status === ComplianceStatus.BLOCKED ||
     complianceStatus[0].status === ComplianceStatus.COMPLIANT
   ) {
     if (restricted) {
       return ComplianceStatusTable.update({
         address,
         status: ComplianceStatus.CLOSE_ONLY,
         reason: getGeoComplianceReason(req.headers as GeoOriginHeaders)!,
         updatedAt,
       });
-    } else {
+    }
+
+    if (action === ComplianceAction.INVALID_SURVEY) {
+      return ComplianceStatusTable.update({
+        address,
+        status: ComplianceStatus.CLOSE_ONLY,
+        updatedAt,
+      });
+    }
+
-      return ComplianceStatusTable.update({
-        address,
-        status: ComplianceStatus.COMPLIANT,
-        updatedAt,
-      });
-    }
+    return ComplianceStatusTable.update({
+      address,
+      status: ComplianceStatus.COMPLIANT,
+      updatedAt,
+    });
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acc045c and a312a10.

📒 Files selected for processing (2)
  • indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts (9 hunks)
  • indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts (5)
indexer/packages/compliance/src/geoblocking/restrict-countries.ts (1)
  • isRestrictedCountryHeaders (6-31)
indexer/packages/compliance/src/types.ts (1)
  • GeoOriginHeaders (8-12)
indexer/services/comlink/src/lib/helpers.ts (1)
  • create4xxResponse (159-171)
indexer/packages/compliance/src/constants.ts (1)
  • INDEXER_GEOBLOCKED_PAYLOAD (1-1)
indexer/services/comlink/src/helpers/compliance/compliance-utils.ts (1)
  • getGeoComplianceReason (30-46)
indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts (3)
indexer/packages/postgres/src/types/db-model-types.ts (1)
  • ComplianceStatusFromDatabase (248-254)
indexer/packages/compliance/src/constants.ts (1)
  • INDEXER_GEOBLOCKED_PAYLOAD (1-1)
indexer/services/comlink/__tests__/helpers/helpers.ts (1)
  • sendRequest (82-106)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-ecs-service-ender / (ender) Build and Push
  • GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-ecs-service-socks / (socks) Build and Push
  • GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-ecs-service-roundtable / (roundtable) Build and Push
  • GitHub Check: test / run_command
  • GitHub Check: check-build-bazooka
  • GitHub Check: run_command
  • GitHub Check: build-and-push-mainnet
  • GitHub Check: lint
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Summary
🔇 Additional comments (2)
indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts (1)

196-203: LGTM: Early 403 responses prevent DB writes for restricted origins.

The implementation correctly returns 403 with INDEXER_GEOBLOCKED_PAYLOAD before any validation or database operations occur. This applies to both /geoblock and /geoblock-keplr endpoints and aligns with the PR objective to prevent DB writes and metric emissions for restricted countries.

Also applies to: 249-256

indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts (1)

471-719: LGTM: Comprehensive test coverage for restricted-country scenarios.

The tests thoroughly verify that restricted-country requests receive 403 responses with the geoblocked payload, with no database writes or status changes occurring. Coverage includes whitelisted addresses, various existing compliance statuses, and all action types (CONNECT, INVALID_SURVEY, VALID_SURVEY).

Comment on lines 721 to 770
it.each([
ComplianceStatus.BLOCKED,
ComplianceStatus.FIRST_STRIKE,
ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY,
])('should set status to COMPLIANT for any action from a non-restricted country with existing compliance status not CLOSE_ONLY', async (status: ComplianceStatus) => {
isRestrictedCountryHeadersSpy.mockReturnValue(false);

await ComplianceStatusTable.create({
address: testConstants.defaultAddress,
status,
});

const response: any = await sendRequest({
type: RequestMethod.POST,
path: endpoint,
body,
expectedStatus: 200,
});
expect(stats.increment).toHaveBeenCalledWith(
`${config.SERVICE_NAME}.compliance-v2-controller.${endpoint === geoblockEndpoint ? 'geo_block' : 'geo_block_keplr'}.compliance_status_changed.count`,
{
newStatus: ComplianceStatus.FIRST_STRIKE,
});

expect(response.body.updatedAt).toBeDefined();
expect(response.body.status).toEqual(ComplianceStatus.COMPLIANT);

const data: ComplianceStatusFromDatabase[] = await ComplianceStatusTable.findAll({}, [], {});

expect(data).toHaveLength(1);
expect(data[0]).toEqual(expect.objectContaining({
address: testConstants.defaultAddress,
status: ComplianceStatus.FIRST_STRIKE,
reason: ComplianceReason.US_GEO,
status: ComplianceStatus.COMPLIANT,
}));
});

expect(response.body.status).toEqual(ComplianceStatus.FIRST_STRIKE);
expect(response.body.reason).toEqual(ComplianceReason.US_GEO);
expect(response.body.updatedAt).toBeDefined();
it('should set status to COMPLIANT for any action from a non-restricted country with no existing compliance status', async () => {
isRestrictedCountryHeadersSpy.mockReturnValue(false);

const response: any = await sendRequest({
type: RequestMethod.POST,
path: endpoint,
body,
expectedStatus: 200,
});

const data: ComplianceStatusFromDatabase[] = await ComplianceStatusTable.findAll({}, [], {});
expect(data).toHaveLength(1);
expect(data[0]).toEqual(expect.objectContaining({
address: testConstants.defaultAddress,
status: ComplianceStatus.COMPLIANT,
}));

expect(response.body.status).toEqual(ComplianceStatus.COMPLIANT);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing test: INVALID_SURVEY from non-restricted origin should transition to CLOSE_ONLY.

The test suite lacks coverage for INVALID_SURVEY action from a non-restricted country. Per the PR objectives, this should transition to CLOSE_ONLY, but the current controller implementation (due to the unused action parameter bug) would incorrectly set it to COMPLIANT. Adding this test would have caught the critical issue.

Do you want me to generate a test case that verifies INVALID_SURVEY from non-restricted origins correctly transitions to CLOSE_ONLY?

🤖 Prompt for AI Agents
In
indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts
around lines 721 to 770, add a test that posts an INVALID_SURVEY action from a
non-restricted country and asserts the controller persists status CLOSE_ONLY
(both in response.body.status and in the ComplianceStatusTable row), to cover
the missing case where a non-restricted INVALID_SURVEY should transition to
CLOSE_ONLY rather than COMPLIANT; set
isRestrictedCountryHeadersSpy.mockReturnValue(false), send the POST with
body.action = 'INVALID_SURVEY', expect HTTP 200, expect response.body.status ===
ComplianceStatus.CLOSE_ONLY and that ComplianceStatusTable.findAll returns one
entry with address testConstants.defaultAddress and status
ComplianceStatus.CLOSE_ONLY.

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

Labels

Development

Successfully merging this pull request may close these issues.

2 participants