Skip to content

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

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

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 protocol_version paywalls; mis-timed CSP injection or overly strict connect-src could break self-contained bundles that rely on same-origin fetches.

Overview
Paywalls V2 web_view components that declare protocol_version are now treated as v1 isolated bundles: the SDK enforces a fixed Content-Security-Policy instead of trusting the hosted page alone.

protocol_version flows from the paywall schema into WebViewComponentStyle via StyleFactory. Legacy configs without it keep protocolVersion == null and no CSP (unchanged behavior).

The renderer turns on isolation when protocolVersion != null: geolocation is disabled on the WebView, and onPageStarted injects an idempotent script that inserts a <meta http-equiv="Content-Security-Policy"> at the front of <head> before page scripts run. Policy string lives in WebViewContentSecurityPolicy (same-origin default-src, scripts/styles with inline allowances for self-contained bundles, img-src/font-src with data:, connect-src 'self' only).

Unit tests cover policy shape, meta injection/escaping, and protocolVersion mapping in StyleFactoryTests.

Reviewed by Cursor Bugbot for commit ef64abf. 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.48%. Comparing base (4fb9844) to head (ef64abf).

Additional details and impacted files
@@                          Coverage Diff                           @@
##           alexrepty/paywalls-web-view-rendering    #3652   +/-   ##
======================================================================
  Coverage                                  80.48%   80.48%           
======================================================================
  Files                                        404      404           
  Lines                                      16728    16728           
  Branches                                    2389     2389           
======================================================================
  Hits                                       13464    13464           
  Misses                                      2322     2322           
  Partials                                     942      942           

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

@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

3 modified, 590 unchanged

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

🛸 Powered by Emerge Tools

@AlvaroBrey AlvaroBrey force-pushed the alexrepty/paywalls-web-view-csp branch from 9349754 to 428d1c2 Compare July 6, 2026 08:12
@AlvaroBrey AlvaroBrey force-pushed the alexrepty/paywalls-web-view-rendering branch from bc0f4e0 to 2634d3b Compare July 6, 2026 08:12
@AlvaroBrey AlvaroBrey self-assigned this Jul 6, 2026
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
@AlvaroBrey AlvaroBrey force-pushed the alexrepty/paywalls-web-view-csp branch from 428d1c2 to ef64abf Compare July 6, 2026 13:57
@AlvaroBrey AlvaroBrey force-pushed the alexrepty/paywalls-web-view-rendering branch from 2634d3b to 4fb9844 Compare July 6, 2026 13:57
@AlvaroBrey AlvaroBrey marked this pull request as draft July 6, 2026 14:42
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.

2 participants