Skip to content

feat(paywalls): isolate web_view content with a fixed CSP (PWENG-98)#3652

Open
alexrepty wants to merge 1 commit into
alexrepty/paywalls-web-view-renderingfrom
alexrepty/paywalls-web-view-csp
Open

feat(paywalls): isolate web_view content with a fixed CSP (PWENG-98)#3652
alexrepty wants to merge 1 commit into
alexrepty/paywalls-web-view-renderingfrom
alexrepty/paywalls-web-view-csp

Conversation

@alexrepty

@alexrepty alexrepty commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-ios and hybrids

Motivation

A hosted web bundle rendered inside the paywall must be isolated from external sources. For v1 the bundle is required to be fully self-contained, so the SDK enforces this rather than relying on the bundle to behave.

Part of PWENG-98.

Description

  • When a web_view declares a protocol_version, injects a fixed Content-Security-Policy <meta> at document start: same-origin scripts/images/fonts only, no data: for images/fonts, and no XHR/fetch/WebSocket (connect-src 'none'). Geolocation is disabled.
  • WebViewContentSecurityPolicy is internal to :ui:revenuecatui (its only consumer is the renderer).
  • Tested by WebViewContentSecurityPolicyTest and StyleFactoryTests.

iOS parity: mirrors purchases-ios (web-view-bridge-wiring).
Stack (merge bottom-up): schema → rendering → CSP → value types → message model → variables provider → JS bridge → wiring.


Note

Medium Risk
Security-sensitive WebView behavior changes for v1 web bundles; CSP effectiveness depends on injection timing relative to page scripts, and legacy web_views without protocol_version remain unenforced.

Overview
Paywalls V2 web_view components that declare protocol_version are now treated as v1 self-contained bundles: the SDK enforces network isolation instead of trusting the hosted page.

protocol_version is threaded from the paywall schema through StyleFactory into WebViewComponentStyle. When it is non-null, WebViewComponentView turns on CSP enforcement; legacy configs with null keep today’s behavior (no injected policy).

On load, WebViewClient.onPageStarted runs contentSecurityPolicyMetaScript() via evaluateJavascript so a fixed Content-Security-Policy meta is inserted at the top of <head> before page scripts run. The new WebViewContentSecurityPolicy module defines that policy (same-origin default-src, connect-src 'self', img-src/font-src with data:, inline script allowances for the trusted bundle) plus escaping and idempotent install. WebView setup also disables geolocation.

Coverage: WebViewContentSecurityPolicyTest and StyleFactoryTests for protocolVersion passthrough.

Reviewed by Cursor Bugbot for commit 9349754. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.32%. Comparing base (bc0f4e0) to head (9349754).

Additional details and impacted files
@@                          Coverage Diff                           @@
##           alexrepty/paywalls-web-view-rendering    #3652   +/-   ##
======================================================================
  Coverage                                  80.32%   80.32%           
======================================================================
  Files                                        385      385           
  Lines                                      15845    15845           
  Branches                                    2225     2225           
======================================================================
  Hits                                       12727    12727           
  Misses                                      2229     2229           
  Partials                                     889      889           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-rendering branch from 38f5302 to 6c73e7c Compare June 26, 2026 14:20
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-csp branch from a3e9e1a to 0dffacc Compare June 26, 2026 14:20
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-rendering branch from 6c73e7c to 71e76e1 Compare June 26, 2026 14:26
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-csp branch from 0dffacc to 353f584 Compare June 26, 2026 14:26
@alexrepty alexrepty changed the title feat(paywalls): isolate web_view content with a fixed CSP feat(paywalls): isolate web_view content with a fixed CSP (PWENG-98) Jun 29, 2026
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-csp branch from 353f584 to df0ae01 Compare June 29, 2026 13:58
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-rendering branch 2 times, most recently from 88cb259 to fd0371b Compare June 29, 2026 14:14
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-csp branch from df0ae01 to 22be911 Compare June 29, 2026 14:14
@alexrepty alexrepty marked this pull request as ready for review June 29, 2026 14:42
@alexrepty alexrepty requested a review from a team as a code owner June 29, 2026 14:42
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-csp branch from 22be911 to 9cff885 Compare June 30, 2026 07:44
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-rendering branch from fd0371b to bc0f4e0 Compare June 30, 2026 07:44

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9cff885. Configure here.

meta.setAttribute('http-equiv', 'Content-Security-Policy');
meta.setAttribute('content', "${policy.escapeForMetaContent()}");
var head = document.head || document.getElementsByTagName('head')[0] || document.documentElement;
if (head.firstChild) { head.insertBefore(meta, head.firstChild); } else { head.appendChild(meta); }

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.

CSP meta placed outside head

High Severity

When document.head is not yet available, the injection script falls back to document.documentElement and inserts the CSP meta as a direct child of html. CSP meta tags are only applied when they live inside head, so this placement is ignored and isolation does not take effect. Setting window.__revenueCatCspInstalled on that failed attempt can also block later injections on the same navigation.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9cff885. Configure here.

// scripts run.
if (enforceContentSecurityPolicy) {
view.evaluateJavascript(contentSecurityPolicyMetaScript(), null)
}

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.

Async CSP injection races page load

Medium Severity

CSP is installed via evaluateJavascript from onPageStarted, which runs asynchronously after navigation begins. Page HTML can parse and run scripts or start cross-origin subresource requests before the injected meta exists, so external network access may occur despite protocol_version isolation.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9cff885. Configure here.

When a `web_view` declares a `protocol_version`, the renderer injects a fixed Content-Security-Policy meta tag at document start to isolate the content from external sources: only same-origin images/scripts/fonts are allowed, `data:` references for images/fonts are blocked, and XHR/fetch/WebSocket are disallowed (`connect-src 'none'`). Geolocation is disabled. The bundle must be fully self-contained.

Recommended labels: pr:other, pr:RevenueCatUI, feat:Paywalls_V2
@alexrepty alexrepty force-pushed the alexrepty/paywalls-web-view-csp branch from 9cff885 to 9349754 Compare June 30, 2026 08:43
@emerge-tools

emerge-tools Bot commented Jun 30, 2026

Copy link
Copy Markdown

📸 Snapshot Test

1 modified, 592 unchanged

Name Added Removed Modified Renamed Unchanged Errored Approval
TestPurchasesUIAndroidCompatibility
com.revenuecat.testpurchasesuiandroidcompatibility
0 0 1 0 334 0 ⏳ Needs approval
TestPurchasesUIAndroidCompatibility Paparazzi
com.revenuecat.testpurchasesuiandroidcompatibility.paparazzi
0 0 0 0 258 0 N/A

🛸 Powered by Emerge Tools

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant