Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2025-02-17 - React Native WebView XSS via injectJavaScript
**Vulnerability:** Constructing stringified JavaScript payloads and inserting them directly via `WebView.injectJavaScript` inside `HomeScreen.tsx` and `ShiftScreen.tsx` (e.g. `window.runTesseract(${JSON.stringify(base64Json)});`) is susceptible to Cross-Site Scripting (XSS).
**Learning:** `injectJavaScript` behaves like `eval()` in a web context. If the data being injected isn't strictly controlled or escaped properly, a malicious payload inside an image base64 sequence or filename could theoretically escape the JSON string context and execute arbitrary JavaScript.
**Prevention:** Always use the postMessage API bridge for communicating between the React Native layer and the WebView. Establish event listeners (`window.addEventListener('message', ...)`) in the WebView's HTML template and invoke `webViewRef.current.postMessage(data)` from React Native.
16 changes: 7 additions & 9 deletions src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ const weatherMap: Record<number, { text: string; icon: string }> = {
const engineHtml = `<!DOCTYPE html><html lang="it"><head>
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js"></script></head>
<body style="background-color:transparent;"><script>
const messageHandler = function(event) {
if (window.runTesseract) window.runTesseract(event.data);
};
window.addEventListener('message', messageHandler);
document.addEventListener('message', messageHandler);
window.runTesseract = async function(base64JsonStr) {
try {
const images = JSON.parse(base64JsonStr);
Expand Down Expand Up @@ -287,15 +292,8 @@ export default function HomeScreen() {
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 pattern to avoid script-injection risks
webViewRef.current?.postMessage(base64Json);
}
} catch (e) { if (__DEV__) console.error('[imagePicker]', e); setProcessing(false); }
};
Expand Down
17 changes: 7 additions & 10 deletions src/screens/ShiftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,9 @@ 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(base64List));
}
} catch (e) {
Alert.alert("Errore OCR", "Impossibile elaborare l'immagine.");
Expand Down Expand Up @@ -216,6 +208,11 @@ export default function ShiftScreen() {
</head>
<body style="background-color: transparent;">
<script>
const messageHandler = function(event) {
if (window.runTesseract) window.runTesseract(event.data);
};
window.addEventListener('message', messageHandler);
document.addEventListener('message', messageHandler);
window.runTesseract = async function(base64JsonStr) {
try {
const images = JSON.parse(base64JsonStr);
Expand Down
Loading