Skip to content

fix(security): escape survey WebView payload to prevent </script> injection (ENG-1813) - #72

Merged
mattinannt merged 3 commits into
mainfrom
fix/ENG-1813-js-payload-backtick-fix
Jul 21, 2026
Merged

fix(security): escape survey WebView payload to prevent </script> injection (ENG-1813)#72
mattinannt merged 3 commits into
mainfrom
fix/ENG-1813-js-payload-backtick-fix

Conversation

@pandeymangg

@pandeymanggpandeymangg commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

What & why

ENG-1813 — script injection via the survey WebView payload.

renderHtml embeds the survey payload directly into an inline <script>:

constoptions=${JSON.stringify(options)};

JSON.stringify escapes " and \, so the classic string/backtick breakout is already prevented here — but it does not escape <. A survey field containing </script> (or <script, <!--) terminates the inline <script> at the HTML-parser level, regardless of JS string context, and injects arbitrary markup/JS into the WebView. The payload includes server/admin-authored survey content (props.survey, styling, …), so this is attacker-influenceable.

This is the React Native variant of the same ENG-1813 issue fixed on iOS (formbricks/ios#51) and Android (formbricks/android#55).

Fix

Escape << in the serialized payload:

constoptions=${JSON.stringify(options).replace(/</g,"\\u003c")};

< only ever appears inside JSON string values (never in structural positions), and the JS engine decodes < back to < when parsing the object literal — so the payload is preserved exactly while </script> / <script / <!-- can no longer form in the script's raw text.

Chose escaping over the iOS/Android base64 approach because React Native's JS runtime has no global Buffer/btoa (would need a polyfill); the escape is dependency-free, keeps options a plain object literal (no JSON.parse needed), and fully closes the </script> vector.

Verification

  • tsc --noEmit — clean.
  • vitest run — 155/155 pass.
  • Node check: a headline of `${alert(1)}` </script><img src=x onerror=alert(1)> produces output with no literal </script> (in fact no literal < at all) and round-trips to the exact original object.

Notes / scope

@coderabbitai

coderabbitaiBot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc5ef727-936b-4d34-98be-1aae524246d5

📥 Commits

Reviewing files that changed from the base of the PR and between 54b3f30 and bc4056e.

📒 Files selected for processing (1)
  • packages/react-native/src/components/survey-web-view.tsx

Walkthrough

renderHtml now serializes survey options into optionsJson, replacing < characters with escaped Unicode sequences before embedding the JSON in the inline script. The script initializes options from this preprocessed value instead of directly embedding JSON.stringify(options).

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main security fix: escaping the survey WebView payload to prevent script injection.
Description check✅ PassedThe description directly explains the vulnerability, the fix, and verification, all of which match the changeset.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

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.

@pandeymanggpandeymangg changed the title fix: fixes the js payload backtick bugfix(security): escape survey WebView payload to prevent </script> injection (ENG-1813)Jul 21, 2026
Move the "<" escaping into a `const optionsJson` before the template and
use `replaceAll` + `String.raw` instead of `replace(/</g, "<")`.
Clears three SonarQube smells introduced by the fix:
- S7781 (prefer replaceAll over replace with a global regex)
- S7780 (prefer String.raw over a string literal with an escaped backslash)
- the same S7780 on the outer template literal, whose raw text previously
contained "<" via the inline explanatory comment (now moved out).
Behaviour is unchanged: String.raw`<` yields the same 6-char escape,
so survey content still can't break out of the inline <script>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@mattinanntmattinannt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@pandeymangg thanks for the PR :-)

@mattinannt
mattinannt added this pull request to the merge queueJul 21, 2026
Merged via the queue into main with commit 779d353Jul 21, 2026
10 checks passed
@mattinannt
mattinannt deleted the fix/ENG-1813-js-payload-backtick-fix branch July 21, 2026 21:02
Sign up for freeto 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.

2 participants

@pandeymangg@mattinannt