Skip to content

fix(security): restrict password compliance CORS#1750

Open
riderx wants to merge 3 commits intomainfrom
riderx/fix-cors
Open

fix(security): restrict password compliance CORS#1750
riderx wants to merge 3 commits intomainfrom
riderx/fix-cors

Conversation

@riderx
Copy link
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
    • Added origin validation to the password compliance endpoint, enabling origin-based access control with a configurable allowlist and recognition for native/local origins.
  • Tests
    • Added tests covering Origin header handling to ensure disallowed origins are rejected and native/localhost origins are accepted.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 144cbe1f-e0b5-41a4-9ba0-8f6b15560f65

📥 Commits

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

📒 Files selected for processing (1)
  • tests/password-policy.test.ts

📝 Walkthrough

Walkthrough

Adds origin-validation middleware to the password compliance function: new helpers normalize and verify request Origins against WEBAPP_URL and a configurable allowlist, and the middleware is mounted globally to reject forbidden Origins before existing password validation logic runs.

Changes

Cohort / File(s) Summary
Origin Validation Middleware
supabase/functions/_backend/private/validate_password_compliance.ts
Added imports (Context, getEnv), new helpers (normalizeOrigin, getAllowedOrigins, isNativeOrLocalOrigin, validateOrigin), and applied app.use('*', validateOrigin) to enforce Origin-based access control (responds 403 for forbidden Origins). Existing password validation handler left unchanged.
Tests — Origin handling
tests/password-policy.test.ts
Added tests covering Origin header behavior for /private/validate_password_compliance: rejects disallowed Origin (expects 403 forbidden_origin) and allows capacitor/localhost origins (expects password policy error 400 as before).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Function as PasswordComplianceFunction
  participant Env as Config
  Client->>Function: POST /private/validate_password_compliance (includes Origin)
  Function->>Env: read WEBAPP_URL and allowlist via getEnv
  Function->>Function: normalizeOrigin -> validateOrigin against allowed list
  alt Origin allowed
    Function->>Function: proceed to password validation handler
    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 sniffed the headers, sly and spry,
Matched Origins under moonlit sky,
I hop the gates and guard the line,
Only rightful callers may pass inside 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(security): restrict password compliance CORS' clearly and accurately summarizes the main security-focused change: restricting CORS for the password compliance endpoint.
Description check ✅ Passed The description includes a summary, motivation, and test plan sections, covering the main changes and intent. However, the Test Plan section has unchecked items for manual testing and the Checklist lacks complete verification of documentation changes.
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 docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/fix-cors

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

Copy link

@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)

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
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
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

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 8, 2026

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