---
title: Unsafe HTML assignment
description: >-
  Detects `innerHTML`, `outerHTML`, and `insertAdjacentHTML` receiving
  non-literal values.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/security/app-security-checks/unsafe-innerhtml
  md: >-
    https://shopify.dev/docs/apps/build/security/app-security-checks/unsafe-innerhtml.md
---

# Unsafe HTML assignment

Detects `innerHTML`, `outerHTML`, and `insertAdjacentHTML` receiving non-literal values.

| Detail | Value |
| - | - |
| Check ID | `UNSAFE_INNERHTML` |
| Severity | High |
| How it runs | Deterministic scan and agent review |
| Applies to | Apps with a theme app extension |

***

## Vulnerable example

## extensions/product-badge/assets/badge.js

```js
const params = new URLSearchParams(window.location.search);


// Any non-literal value assigned to innerHTML, outerHTML, or
// insertAdjacentHTML is a script-execution sink. eval() and new
// Function() are flagged the same way.
document.querySelector(".badge").innerHTML = params.get("label");
```

***

## How to fix

Use `textContent` where possible or sanitize with DOMPurify before rendering.

For more detail, see [Cross-site scripting](https://shopify.dev/docs/apps/build/security/following-security-best-practices#cross-site-scripting).

## extensions/product-badge/assets/badge.js

```js
const params = new URLSearchParams(window.location.search);


// textContent renders the value as text, so markup in it stays
// inert. When you genuinely need HTML, sanitize with DOMPurify
// first.
document.querySelector(".badge").textContent = params.get("label");
```

***

## Related

* [`shopify app security check`](https://shopify.dev/docs/api/shopify-cli/app/app-security-check), the command that runs this check
* [All app security checks](https://shopify.dev/docs/apps/build/security/app-security-checks)

***
