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

@pandeymangg pandeymangg 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>:

const options = ${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:

const options = ${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

coderabbitai Bot 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 name Status Explanation
Title check ✅ Passed The title clearly summarizes the main security fix: escaping the survey WebView payload to prevent script injection.
Description check ✅ Passed The description directly explains the vulnerability, the fix, and verification, all of which match the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check 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.

@pandeymangg pandeymangg changed the title fix: fixes the js payload backtick bug fix(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>
@pandeymangg
pandeymangg requested a review from mattinannt July 21, 2026 07:37
@sonarqubecloud

Copy link
Copy Markdown

@mattinannt mattinannt 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 queue Jul 21, 2026
Merged via the queue into main with commit 779d353 Jul 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 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.

2 participants