Security

    Is it safe to use unsafe-inline in an AI app's WebView?

    A 2026 diagram of a mobile WebView with a Content Security Policy using unsafe-inline, showing an injected inline script running and calling a native bridge method to reach device capabilities

    A Content Security Policy with unsafe-inline looks harmless because the app still works, but it quietly removes the main thing CSP is there to do: stop injected scripts from running. In a mobile WebView that talks to native code through a bridge, that is not a web problem you can shrug off; an injected script can reach the device. AI builders reach for unsafe-inline because it is the path of least resistance. Here is why it is risky and what to use instead.

    Short answer

    No, unsafe-inline is not safe in a WebView, especially one with a native bridge. unsafe-inline lets inline scripts and event handlers run, which removes the protection a Content Security Policy gives you against cross-site scripting. In a WebView that exposes a native bridge, common in hybrid and AI-built apps, an injected script that runs because of unsafe-inline can call that bridge and reach native capabilities, turning a web flaw into device-level access. The fix is to drop unsafe-inline, use nonces or hashes for any required inline script, load only trusted content, and lock down the bridge.

    What you should know

    • unsafe-inline disables CSP's core defense: it allows inline scripts, so injected scripts run.
    • WebView bridges raise the stakes: an XSS can call native methods exposed to the WebView.
    • AI builders default to it: a permissive CSP is the easy path, so generated code often includes it.
    • Nonces and hashes replace it: they let your real inline scripts run without allowing injected ones.
    • Trusted content only: a bridged WebView should never load untrusted or remote content.

    What does unsafe-inline actually do?

    It tells the browser or WebView to trust any inline script, which is exactly what CSP is meant to prevent. A Content Security Policy without unsafe-inline blocks inline <script> blocks and inline event handlers, so even if an attacker injects markup, the script does not execute. Adding unsafe-inline to script-src removes that block, and the policy no longer distinguishes your scripts from injected ones. At that point the CSP still restricts where external scripts load from, but it has given up its strongest protection, which is stopping inline injection. For a page that reflects any user or remote content, that is the difference between a contained bug and a running exploit.

    Why is it worse in a WebView with a bridge?

    Because a WebView is often wired to native code, so an injected script does not stay in the web layer. Hybrid and AI-built apps expose a bridge so the web content can call native functions, on Android through an interface object and on iOS through WKWebView message handlers, and that bridge does not verify which frame or origin is calling it. So when unsafe-inline lets an injected script run, that script can call the same native methods your app uses, which OWASP describes as a path to data access and privilege escalation. The table shows the chain and where to break it.

    SettingRiskSafer pattern
    script-src with unsafe-inlineInjected inline scripts executeUse nonces or hashes and drop unsafe-inline
    WebView loads remote or untrusted contentAn XSS reaches the bridgeLoad only trusted, bundled content; allow-list origins
    Bridge exposed to all framesAny frame can call native methodsRestrict by origin and minimize the exposed surface
    postMessage target set to a wildcardMessages accepted from any originSpecify the exact expected origin, never a wildcard
    JavaScript enabled when it is not neededLarger attack surfaceDisable JavaScript in WebViews that do not need it

    How do you fix it?

    Remove unsafe-inline and replace it with a precise policy. For the inline scripts you genuinely need, add a nonce or a hash to the CSP and to the script tag, so your scripts run while injected ones do not. Load only content you control, ideally bundled with the app rather than fetched from a remote origin, and block file URLs and untrusted origins. On the bridge, expose the smallest possible set of native methods, check the origin of messages, and never set a postMessage target to a wildcard. If a WebView does not need JavaScript at all, disable it, which OWASP recommends as the simplest reduction of attack surface. A tightened CSP looks like this:

    Content-Security-Policy: script-src 'self' 'nonce-Rand0mPerRequest'; object-src 'none'; base-uri 'self'
    

    Why do AI-built apps default to unsafe-inline?

    Because it makes the code work on the first try. When a builder generates a WebView screen or a hybrid page, inline scripts and styles are the quickest way to get something rendering, and adding unsafe-inline silences the CSP error that would otherwise block them. The model is optimizing for a working result, not a hardened one, so the permissive policy ships unless you change it. The same is true when there is no CSP at all, which is effectively the same exposure. Treat a generated CSP as a starting point to tighten, not a setting to accept.

    What to watch out for

    The first trap is thinking a WebView that only loads your own content is safe with unsafe-inline; any reflection of a URL parameter, a deep link, or remote data is an injection path, so defense in depth still matters. The second is leaving the bridge broad while you focus on the CSP, since the bridge is what makes an XSS dangerous in the first place. A pre-submission scan such as PTKD.com (https://ptkd.com) reads the compiled APK, AAB, or IPA against OWASP MASVS and reports WebView and bridge configuration, including a permissive CSP and an exposed native interface, so you can find these before submitting. The limit is that a scan flags the configuration; you still write the tighter policy and lock the bridge.

    What to take away

    • unsafe-inline removes CSP's protection against injected scripts, so it is not safe in a WebView.
    • A native bridge turns a web XSS into device-level access, which is why unsafe-inline is worse in mobile WebViews than on a plain website.
    • Drop unsafe-inline, use nonces or hashes for real inline scripts, load only trusted content, and lock the bridge to a minimal, origin-checked surface.
    • Scan the build with a pre-submission scan such as PTKD.com to catch a permissive CSP or an exposed WebView bridge before you ship.
    • #content-security-policy
    • #unsafe-inline
    • #webview
    • #javascript-bridge
    • #xss
    • #ai-coded-apps
    • #owasp-masvs

    Frequently asked questions

    What does unsafe-inline do in a CSP?
    It tells the browser or WebView to trust any inline script, which is the opposite of what a Content Security Policy is meant to do. Without it, a CSP blocks inline script blocks and inline event handlers, so injected markup does not execute. Adding unsafe-inline to script-src removes that block, and the policy can no longer tell your scripts apart from an attacker's injected ones.
    Why is unsafe-inline dangerous in a WebView specifically?
    Because a WebView is usually wired to native code through a bridge. Hybrid and AI-built apps expose native methods to the web layer, and that bridge does not verify which frame is calling it. When unsafe-inline lets an injected script run, it can call those native methods, which OWASP describes as a route to data access and privilege escalation. A web XSS becomes device-level access, far worse than on a plain website.
    How do I keep an inline script without unsafe-inline?
    Use a nonce or a hash. Add a unique nonce value to the CSP and to the matching script tag, generated per request, so only your tagged scripts run and injected inline scripts are blocked. Alternatively, add the script's hash to the policy. Both let your real inline code execute while preserving the protection unsafe-inline throws away, which is why they are the recommended replacement.
    My WebView only loads my own content, so is unsafe-inline okay?
    It is still risky. Any place the page reflects a URL parameter, a deep link, or remote data is an injection path, so even mostly-trusted content can carry an XSS. Because a bridged WebView turns that into native access, defense in depth matters: keep unsafe-inline out, use nonces, and lock the bridge, rather than relying on the content being entirely under your control.
    How do I lock down the WebView bridge?
    Expose the smallest set of native methods the app actually needs, check the origin of incoming messages, and never set a postMessage target to a wildcard. Load only trusted, bundled content and block file URLs and untrusted origins. If a WebView does not need JavaScript, disable it. These steps cut the path an injected script would use to reach native code, even if something does get injected.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free