In a browser, injected script can steal a session. In a Cordova 129 app it can also call every installed plugin. The defenses are layered: a CSP without 'unsafe-inline' against injected script (CSP and Viewport Tags), <allow-navigation> against navigation to a hostile page, the bridge secret against pages that try to call plugins directly (exec() and Callbacks), and the InAppBrowser for any web content you do not control (Dialogs and InAppBrowser).
BookNest builds its list with innerHTML from books.json, so a title containing markup would be injected. The classic payload shows the CSP stopping it:
const blocked = [];
document.addEventListener('securitypolicyviolation',
(e) => blocked.push(`${e.violatedDirective} blocked ${e.blockedURI}`));
document.body.insertAdjacentHTML('beforeend', '<img src="x.png" onerror="window.owned = 1">');
await new Promise((resolve) => setTimeout(resolve, 500));
({ owned: window.owned ?? 'no', blocked });{ owned: 'no', blocked: [ 'script-src-attr blocked inline' ] }The image failed to load, but its onerror handler never ran, and the WebView reported the blocked directive. <allow-navigation> is the second wall: with no entries, the WebView refuses to leave the app's own origin (allow-intent), so even a successful injection cannot load a remote page that inherits the bridge. Prefer textContent over innerHTML for data you do not control.