From 7101ef733364c6889ccc83ee331ba4662806b575 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:31:56 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]=20Fi?= =?UTF-8?q?x=20XSS=20vulnerability=20in=20WebView=20injection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: TargetMisser <52361977+TargetMisser@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ package-lock.json | 17 +---------------- src/screens/HomeScreen.tsx | 25 ++++++++++++++++--------- src/screens/ShiftScreen.tsx | 27 ++++++++++++++++----------- 4 files changed, 37 insertions(+), 36 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..8ac65fe --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-04-29 - Cross-Site Scripting (XSS) risk via injectJavaScript +**Vulnerability:** React Native WebViews used `injectJavaScript` with dynamically stringified data (e.g. `JSON.stringify(base64List)`) that could be intercepted or manipulated, leading to potential Cross-Site Scripting (XSS) in the WebView context. +**Learning:** Even when the data being injected is ostensibly secure (like base64 image strings), directly injecting it into a JavaScript string for evaluation within `injectJavaScript` carries XSS risks. +**Prevention:** Use `webViewRef.current.postMessage` to pass data safely across the React Native and WebView boundary. Implement `message` event listeners in the WebView to handle the incoming data instead of dynamically constructing script strings. diff --git a/package-lock.json b/package-lock.json index 854f98b..bcf5b1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -86,7 +86,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1498,7 +1497,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -1776,7 +1774,6 @@ "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-6.1.2.tgz", "integrity": "sha512-nvM+Qv45QH7pmYvP8JB1G8JpScrWND3KrMA6ZKe62cwwNiX/BjHU28Ear0v/4bQWXlOY0mv6B8CDIm8JxXde9g==", "license": "MIT", - "peer": true, "dependencies": { "anser": "^1.4.9", "pretty-format": "^29.7.0", @@ -2461,7 +2458,6 @@ "integrity": "sha512-sLo8cu9JyFNfuuF1C+8NJ4DHE/PEFaXGd4enkcxi/OJjGG8+sOQrdjNQ4i+cVh/2c+ah1mEMwsYjc3z0+/MqSg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-clean": "20.1.3", "@react-native-community/cli-config": "20.1.3", @@ -3429,7 +3425,6 @@ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4139,7 +4134,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5062,7 +5056,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz", "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.23", @@ -5178,7 +5171,6 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -8547,7 +8539,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8833,7 +8824,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8853,7 +8843,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -8872,7 +8861,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -9015,7 +9003,6 @@ "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.1.tgz", "integrity": "sha512-If0eHhoEdOYDcHsX+xBFwHMbWBGK1BvGDQDQdVkwtSIXiq1uiqjkpWVP2uQ1as94J0CzvFE9PUNDuhiX0Z6ubw==", "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" @@ -9114,7 +9101,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10385,9 +10371,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 7779ce3..0945c00 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -48,6 +48,20 @@ window.runTesseract = async function(base64JsonStr) { window.ReactNativeWebView.postMessage(JSON.stringify({ success: false, error: e.message || e.toString() })); } }; +document.addEventListener("message", function(event) { + if (window.runTesseract) { + window.runTesseract(event.data); + } else { + window.ReactNativeWebView.postMessage(JSON.stringify({success:false,error:'OCR non pronto'})); + } +}); +window.addEventListener("message", function(event) { + if (window.runTesseract) { + window.runTesseract(event.data); + } else { + window.ReactNativeWebView.postMessage(JSON.stringify({success:false,error:'OCR non pronto'})); + } +}); `; function PinnedFlightCardComponent({ item, colors }: { item: any; colors: any }) { @@ -283,15 +297,8 @@ export default function HomeScreen({ isFocused }: { isFocused?: boolean }) { setProcessing(true); setOcrText(''); const base64List = result.assets.map(a => `data:image/jpeg;base64,${a.base64}`); const base64Json = JSON.stringify(base64List); - // Use postMessage pattern to avoid script-injection risks with injectJavaScript - webViewRef.current?.injectJavaScript(` - if(window.runTesseract){ - window.runTesseract(${JSON.stringify(base64Json)}); - } else { - window.ReactNativeWebView.postMessage(JSON.stringify({success:false,error:'OCR non pronto'})); - } - true; - `); + // Use postMessage to securely send data to WebView + webViewRef.current?.postMessage(base64Json); } } catch (e) { if (__DEV__) console.error('[imagePicker]', e); setProcessing(false); } }; diff --git a/src/screens/ShiftScreen.tsx b/src/screens/ShiftScreen.tsx index 128e708..ef936a4 100644 --- a/src/screens/ShiftScreen.tsx +++ b/src/screens/ShiftScreen.tsx @@ -32,17 +32,8 @@ export default function ShiftScreen() { setOcrText(''); const base64List = result.assets.map(a => `data:image/jpeg;base64,${a.base64}`); - const base64Json = JSON.stringify(base64List).replace(/'/g, "\\'"); - - const jsCode = ` - if (window.runTesseract) { - window.runTesseract('${base64Json}'); - } else { - window.ReactNativeWebView.postMessage(JSON.stringify({ success: false, error: "Motore OCR non pronto." })); - } - true; - `; - webViewRef.current?.injectJavaScript(jsCode); + const base64Json = JSON.stringify(base64List); + webViewRef.current?.postMessage(base64Json); } } catch (e) { Alert.alert("Errore OCR", "Impossibile elaborare l'immagine."); @@ -230,6 +221,20 @@ export default function ShiftScreen() { window.ReactNativeWebView.postMessage(JSON.stringify({ success: false, error: e.message || e.toString() })); } }; + document.addEventListener("message", function(event) { + if (window.runTesseract) { + window.runTesseract(event.data); + } else { + window.ReactNativeWebView.postMessage(JSON.stringify({ success: false, error: "Motore OCR non pronto." })); + } + }); + window.addEventListener("message", function(event) { + if (window.runTesseract) { + window.runTesseract(event.data); + } else { + window.ReactNativeWebView.postMessage(JSON.stringify({ success: false, error: "Motore OCR non pronto." })); + } + });