Skip to content

fix(security): restrict password compliance CORS#1750

Merged
riderx merged 6 commits into
mainfrom
riderx/fix-cors
Mar 13, 2026
Merged

fix(security): restrict password compliance CORS#1750
riderx merged 6 commits into
mainfrom
riderx/fix-cors

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 8, 2026

Summary (AI generated)

  • Hardened /private/validate_password_compliance origin validation so browser-origin requests are restricted to configured allowed origins instead of accepting all origins.
  • Added support for native-first app contexts by allowing capacitor://localhost and loopback origins (http://localhost, https://localhost, http://127.0.0.1, https://127.0.0.1) when validating the Origin header.
  • Added PASSWORD_COMPLIANCE_ALLOWED_ORIGINS support for operationally configurable allowlisting.

Motivation (AI generated)

  • The initial restriction to WEBAPP_URL alone blocked valid mobile flows where password checks originate from a Capacitor webview.
  • Mobile password-compliance requests need first-party access without widening CORS policy beyond what is necessary.

Business Impact (AI generated)

  • Improves reliability for app account-password workflows across web and native clients.
  • Reduces risk of blocking legitimate first-party auth flows while keeping cross-origin exposure limited.

Screenshots (AI generated)

  • Not applicable (backend-only change).

Test Plan (AI generated)

  • bun lint:backend
  • Manual test: request with Origin: https://<WEBAPP_URL> returns success from a valid account-password payload (not run in this environment)
  • Manual test: request with Origin: capacitor://localhost returns success from a valid account-password payload (not run in this environment)
  • Manual test: request with Origin: http://malicious.example returns forbidden_origin (not run in this environment)

Checklist (AI generated)

  • Lint check passes for changed backend file.
  • Sensitive endpoint behavior remains guarded with existing rate limiting and auth flow checks.
  • Existing PR branch remains up to date with origin/main.
  • Optional: run dedicated manual device validation for iOS/Android clients.

Summary by CodeRabbit

  • New Features
    • Password compliance endpoint now enforces an origin allowlist and explicitly permits native/local app origins and localhost, rejecting disallowed origins.
  • Tests
    • Added tests to verify Origin header handling: disallowed origins are rejected and native/localhost origins are accepted (while password policy errors still surface).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds origin-validation middleware to the password-compliance function: helpers normalize and verify Origin headers against WEBAPP_URL plus configured allowlist, detect native/local origins, and short-circuit forbidden Origins with 403. Middleware is mounted globally; checkOrgReadAccess now accepts a requestId for correlated logging.

Changes

Cohort / File(s) Summary
Origin validation + wiring
supabase/functions/_backend/private/validate_password_compliance.ts
Introduced Context import and getEnv use; added normalizeOrigin, getAllowedOrigins, isNativeOrLocalOrigin, validateOrigin; mounted app.use('*', validateOrigin); updated checkOrgReadAccess signature call to pass requestId.
Tests — Origin handling
tests/password-policy.test.ts
Added tests asserting Origin handling: rejects disallowed Origin (403 forbidden_origin) and allows native/ionic/capacitor/localhost Origins (exercise existing password-policy responses).
Manifest / metadata
package.json
Minor test-related manifest updates referenced by tests (metadata lines updated).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Function as PasswordComplianceFunction
  participant Env as ConfigService
  participant Auth as OrgReadAccess

  Client->>Function: POST /private/validate_password_compliance (Origin header)
  Function->>Env: getEnv(WEBAPP_URL, allowed_origins)
  Function->>Function: normalizeOrigin -> getAllowedOrigins -> isNativeOrLocalOrigin
  alt Origin allowed
    Function->>Auth: checkOrgReadAccess(supabase, orgId, requestId)
    Auth-->>Function: OrgReadAccessResult
    Function-->>Client: 200/400 (password validation response)
  else Origin forbidden
    Function-->>Client: 403 (forbidden_origin)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I sniff the headers, soft and bright,
I hop through origins by moonlight,
I mark the allowed, block the rest,
I guard the gate for password tests,
A little hop, a careful light. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and concisely describes the main security change—restricting CORS for the password compliance endpoint—which aligns with the primary objective of hardening origin validation.
Description check ✅ Passed The PR description covers summary, motivation, and test plan comprehensively. However, it marks manual tests as not executed and the checklist completion is incomplete despite claiming backend lint passed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/fix-cors
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 760b259012

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +38 to +41
const webappUrl = normalizeOrigin(getEnv(c, 'WEBAPP_URL'))
const allowed = new Set<string>()
if (webappUrl)
allowed.add(webappUrl)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow native app origins for compliance endpoint

This origin allowlist only admits WEBAPP_URL, so any browser request from the Capacitor app context (which uses a non-web origin such as capacitor://localhost/http://localhost) will be rejected with forbidden_origin; that breaks legitimate password-compliance checks from the shared account settings flow (src/pages/settings/account/ChangePassword.vue) for mobile users even though the request is first-party. Please include the native app origins (or a configurable allowlist) so this security hardening does not lock out mobile clients.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
supabase/functions/_backend/private/validate_password_compliance.ts (1)

45-58: Add regression coverage for the new origin gate.

This middleware is now the security boundary for the endpoint, but the PR only lists manual checks. Please add tests for at least: allowed Origin, disallowed Origin, and requests with no Origin header, with WEBAPP_URL explicitly mocked in the test setup.

Also applies to: 119-120

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/_backend/private/validate_password_compliance.ts` around
lines 45 - 58, Add regression tests covering the new origin gate in
validateOrigin: write unit/integration tests that exercise validateOrigin (or
the middleware chain invoking validateOrigin) for three scenarios—an allowed
Origin header, a disallowed Origin header, and a request with no Origin
header—while explicitly mocking WEBAPP_URL in the test setup so
getAllowedOrigins returns predictable values; assert that allowed Origin calls
next(), disallowed Origin returns the quickError 403 path (and optionally that
cloudlog is called with the expected context), and no Origin header also calls
next(). Use the existing helpers normalizeOrigin, getAllowedOrigins, quickError,
and cloudlog symbols to locate the middleware and hook the assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@supabase/functions/_backend/private/validate_password_compliance.ts`:
- Around line 45-58: Add regression tests covering the new origin gate in
validateOrigin: write unit/integration tests that exercise validateOrigin (or
the middleware chain invoking validateOrigin) for three scenarios—an allowed
Origin header, a disallowed Origin header, and a request with no Origin
header—while explicitly mocking WEBAPP_URL in the test setup so
getAllowedOrigins returns predictable values; assert that allowed Origin calls
next(), disallowed Origin returns the quickError 403 path (and optionally that
cloudlog is called with the expected context), and no Origin header also calls
next(). Use the existing helpers normalizeOrigin, getAllowedOrigins, quickError,
and cloudlog symbols to locate the middleware and hook the assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24bf33d6-fa75-435c-911a-dc13668f0627

📥 Commits

Reviewing files that changed from the base of the PR and between 2af3a08 and 760b259.

📒 Files selected for processing (1)
  • supabase/functions/_backend/private/validate_password_compliance.ts

@riderx riderx force-pushed the riderx/fix-cors branch from 760b259 to 0924d6c Compare March 8, 2026 20:34
Copy link
Copy Markdown
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.

🧹 Nitpick comments (2)
supabase/functions/_backend/private/validate_password_compliance.ts (2)

82-95: Type annotation could be more specific for Context.

The middleware uses c.get('requestId') which expects MiddlewareKeyVariables. While this works at runtime, using Context<MiddlewareKeyVariables> would provide better type safety.

♻️ Optional: Use typed Context
-async function validateOrigin(c: Context, next: () => Promise<void>) {
+async function validateOrigin(c: Context<MiddlewareKeyVariables>, next: () => Promise<void>) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/_backend/private/validate_password_compliance.ts` around
lines 82 - 95, The Context parameter in validateOrigin is currently untyped and
should be made specific: change the function signature to use
Context<MiddlewareKeyVariables> so calls like c.get('requestId') are type-safe;
update the import or type reference for MiddlewareKeyVariables if needed and
ensure validateOrigin(c: Context<MiddlewareKeyVariables>, next: () =>
Promise<void>) compiles with existing usage of c.get('requestId') and other
middleware helpers.

62-77: Consider adding IPv6 loopback support.

The function correctly allows localhost and 127.0.0.1 for development/native scenarios, but omits the IPv6 loopback address ::1. If IPv6 local development is expected, this could cause unexpected rejections.

♻️ Optional: Add IPv6 loopback support
 function isNativeOrLocalOrigin(origin: string): boolean {
   try {
     const parsed = new URL(origin)
     if (parsed.protocol === 'capacitor:')
       return parsed.hostname === 'localhost'
     if (!(['http:', 'https:'].includes(parsed.protocol)))
       return false
-    return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1'
+    return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '[::1]'
   }
   catch {
     return false
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/_backend/private/validate_password_compliance.ts` around
lines 62 - 77, The isNativeOrLocalOrigin function currently allows 'localhost'
and '127.0.0.1' but omits IPv6 loopback; update the hostname checks in
isNativeOrLocalOrigin to also accept the IPv6 loopback ('::1') (note that
URL.hostname yields '::1' for bracketed IPv6 literals), and optionally include
the full expanded IPv6 loopback if you want broader coverage; make the change in
the parsed.hostname comparisons inside isNativeOrLocalOrigin.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@supabase/functions/_backend/private/validate_password_compliance.ts`:
- Around line 82-95: The Context parameter in validateOrigin is currently
untyped and should be made specific: change the function signature to use
Context<MiddlewareKeyVariables> so calls like c.get('requestId') are type-safe;
update the import or type reference for MiddlewareKeyVariables if needed and
ensure validateOrigin(c: Context<MiddlewareKeyVariables>, next: () =>
Promise<void>) compiles with existing usage of c.get('requestId') and other
middleware helpers.
- Around line 62-77: The isNativeOrLocalOrigin function currently allows
'localhost' and '127.0.0.1' but omits IPv6 loopback; update the hostname checks
in isNativeOrLocalOrigin to also accept the IPv6 loopback ('::1') (note that
URL.hostname yields '::1' for bracketed IPv6 literals), and optionally include
the full expanded IPv6 loopback if you want broader coverage; make the change in
the parsed.hostname comparisons inside isNativeOrLocalOrigin.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90fe79f5-1cd6-47a0-a5f1-48955fee4235

📥 Commits

Reviewing files that changed from the base of the PR and between 760b259 and c95c030.

📒 Files selected for processing (1)
  • supabase/functions/_backend/private/validate_password_compliance.ts

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/functions/_backend/private/validate_password_compliance.ts`:
- Around line 65-75: The origin normalization causes custom schemes to become
the string "null" and break native app validation; update validateOrigin to call
isNativeOrLocalOrigin(originalOrigin) using the raw header value before
performing new URL(origin).origin normalization so that isNativeOrLocalOrigin
(which checks for 'capacitor:' and 'ionic:' protocols) sees the original scheme
and returns true for valid native/webview clients—ensure you pass the unmodified
origin string into isNativeOrLocalOrigin and only use the normalized origin for
subsequent http/https comparisons.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eac23ee5-cfd7-44dc-bb1b-5fc02fa807d0

📥 Commits

Reviewing files that changed from the base of the PR and between 9171c64 and b75e974.

📒 Files selected for processing (1)
  • supabase/functions/_backend/private/validate_password_compliance.ts

Comment thread supabase/functions/_backend/private/validate_password_compliance.ts
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/functions/_backend/private/validate_password_compliance.ts`:
- Line 2: Update the helper signatures to use the middleware-typed context:
change both getAllowedOrigins(context) and validateOrigin(c) to accept
Context<MiddlewareKeyVariables> instead of raw Context, and import (or
reference) MiddlewareKeyVariables from the hono typings so calls like
c.get('requestId') compile; adjust any local references to the context parameter
name if needed to match the new typed signature.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d25cbe94-0cbe-4585-9825-05db6ef1c0f6

📥 Commits

Reviewing files that changed from the base of the PR and between b75e974 and a4e338e.

📒 Files selected for processing (2)
  • supabase/functions/_backend/private/validate_password_compliance.ts
  • tests/password-policy.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/password-policy.test.ts

Comment thread supabase/functions/_backend/private/validate_password_compliance.ts
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit e981593 into main Mar 13, 2026
15 checks passed
@riderx riderx deleted the riderx/fix-cors branch March 13, 2026 12:55
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