fix(security): escape survey WebView payload to prevent </script> injection (ENG-1813)#72
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
Walkthrough
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
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>
|
mattinannt
left a comment
There was a problem hiding this comment.
@pandeymangg thanks for the PR :-)



What & why
ENG-1813 — script injection via the survey WebView payload.
renderHtmlembeds the survey payload directly into an inline<script>:JSON.stringifyescapes"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:<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, keepsoptionsa plain object literal (noJSON.parseneeded), and fully closes the</script>vector.Verification
tsc --noEmit— clean.vitest run— 155/155 pass.`${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
react-native-webviewdefaults —onReceivedSslError→cancel()), WebView debugging (webviewDebuggingEnablednever set; library gates it behindDEBUG), and external URL scheme (openExternalUrlalready allowlistshttp/https).