diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..d9e31de --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,7 @@ +## 2026-04-27 - XSS in React Native WebView + +**Vulnerability:** React Native WebViews `injectJavaScript` was using unsafe string interpolation with base64 images that could lead to XSS attacks or syntax errors if the string contained single or double quotes unescaped correctly. + +**Learning:** Passing user-supplied strings directly into `injectJavaScript` using template interpolation exposes a risk for Script Injection/XSS in WebViews. + +**Prevention:** To avoid this, prefer establishing a two-way communication channel using `postMessage` (from WebView to React Native) and `webViewRef.current?.postMessage` (from React Native to WebView). Ensure the HTML snippet adds `message` listeners for both `window` and `document`, and uses a `READY` message handshake to confirm the engine is ready. diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 0f8e1d5..7054278 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -42,19 +42,41 @@ const weatherMap: Record = { const engineHtml = ` `; function PinnedFlightCardComponent({ item, colors }: { item: any; colors: any }) { @@ -289,16 +311,8 @@ export default function HomeScreen({ isFocused }: { isFocused?: boolean }) { setImageList(result.assets.map(a => a.uri)); 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; - `); + webViewRef.current?.postMessage(JSON.stringify({ type: 'RUN_OCR', images: base64List })); } } catch (e) { if (__DEV__) console.error('[imagePicker]', e); setProcessing(false); } }; @@ -306,6 +320,10 @@ export default function HomeScreen({ isFocused }: { isFocused?: boolean }) { const handleWebViewMessage = (event: any) => { try { const r = JSON.parse(event.nativeEvent.data); + if (r.type === 'READY') { + // Engine is ready, can proceed with messages if needed + return; + } if (r.success) setOcrText(r.text); else Alert.alert('Errore riconoscimento testo', r.error || 'Prova con un\'immagine più nitida o meglio illuminata.'); } catch (e) { if (__DEV__) console.error('[ocrMessage]', e); } finally { setProcessing(false); } diff --git a/src/screens/ShiftScreen.tsx b/src/screens/ShiftScreen.tsx index e04f2fb..8c5ea70 100644 --- a/src/screens/ShiftScreen.tsx +++ b/src/screens/ShiftScreen.tsx @@ -31,17 +31,7 @@ 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); + webViewRef.current?.postMessage(JSON.stringify({ type: 'RUN_OCR', images: base64List })); } } catch (e) { Alert.alert("Errore OCR", "Impossibile elaborare l'immagine."); @@ -53,6 +43,10 @@ export default function ShiftScreen() { const rawData = event.nativeEvent.data; try { const result = JSON.parse(rawData); + if (result.type === 'READY') { + // Engine is ready + return; + } if (result.success) { setOcrText(result.text); } else { @@ -216,19 +210,41 @@ export default function ShiftScreen() {