# `is-unsafe`
> Zero-dependency, DOM-free, tree-shakeable pure predicate for detecting unsafe strings across HTML, XML, SVG, SQL, SQL-STRICT, SHELL, REDOS, NOSQL, and LOG contexts.
[](https://www.npmjs.com/package/is-unsafe)
[](LICENSE)
---
## Why `is-unsafe`?
Sanitizer libraries like [DOMPurify](https://github.com/cure53/DOMPurify) require a DOM. They cannot run inside XML parsers, template engines, or server-side pipelines that process strings before they ever reach a browser.
`is-unsafe` fills that gap. It is a **pure predicate** — it answers one question:
> *Is this string value unsafe in a given context?*
It never mutates strings. It never touches the DOM. It has zero runtime dependencies.
### Motivating use case: `@nodable/entities` / `fast-xml-parser`
DOCTYPE blocks can define custom entities with arbitrary values:
```xml
alert(1)', HTML);
// {
// context: 'HTML',
// id: 'html-script-open',
// description: '', HTML);
// [
// { context: 'HTML', id: 'html-script-open', ... },
// { context: 'HTML', id: 'html-script-close', ... },
// { context: 'HTML', id: 'html-inline-event-handler', ... }
// ]
```
---
### Named context exports
Each context is a named export. Import only what your code uses — unused contexts are dropped by your bundler.
```js
import { HTML, XML, SVG, SQL, SQL_STRICT, SHELL, REDOS, NOSQL, LOG } from 'is-unsafe';
```
Note: `SQL-STRICT` is exported as `SQL_STRICT` (hyphens are not valid in JS identifiers).
---
### Custom `PatternList`
You can supply your own pattern list alongside or instead of the built-in contexts:
```js
import { isUnsafe, whyUnsafe, HTML } from 'is-unsafe';
const INTERNAL_RULES = [
{ id: 'no-internal-ref', description: 'Blocks references to internal hostnames', pattern: /\.internal\b/i },
{ id: 'no-admin-path', description: 'Blocks paths starting with /admin', pattern: /\/admin\b/i },
];
// Optional — sets the context label in MatchResult. Defaults to 'CUSTOM'.
INTERNAL_RULES.label = 'INTERNAL';
isUnsafe('https://api.internal/data', INTERNAL_RULES) // true
isUnsafe('https://example.com/page', INTERNAL_RULES) // false
// Mix with built-in contexts
isUnsafe(value, [HTML, INTERNAL_RULES]);
const result = whyUnsafe('https://api.internal/admin', INTERNAL_RULES);
// { context: 'INTERNAL', id: 'no-internal-ref', description: '...', pattern: /.../ }
```
Without setting `.label`, `MatchResult.context` will be `'CUSTOM'`.
---
### `VALID_CONTEXTS`
A convenience object that re-exports all contexts under their canonical names. Useful for tooling or exhaustive checks across all contexts.
> **Warning:** importing `VALID_CONTEXTS` pulls in all 9 context modules. If your bundle size matters and you only need a few contexts, import them individually instead.
```js
import { VALID_CONTEXTS } from 'is-unsafe';
// { HTML: [...], XML: [...], SVG: [...], SQL: [...], 'SQL-STRICT': [...],
// SHELL: [...], REDOS: [...], NOSQL: [...], LOG: [...] }
// Iterate all contexts:
for (const [name, ctx] of Object.entries(VALID_CONTEXTS)) {
if (isUnsafe(value, ctx)) console.log(`Unsafe in ${name}`);
}
```
---
## Contexts
### `HTML`
XSS vectors when a string is rendered as HTML:
| Rule ID | What it catches |
|---------|----------------|
| `html-script-open` | `