@stll/oxlint-plugin 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bun-test-hygiene.cjs +401 -0
- package/dist/bun-test-hygiene.cjs.map +1 -0
- package/dist/bun-test-hygiene.d.cts +4 -0
- package/dist/bun-test-hygiene.d.mts +5 -0
- package/dist/bun-test-hygiene.mjs +397 -0
- package/dist/bun-test-hygiene.mjs.map +1 -0
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -19
- package/dist/index.d.mts +27 -19
- package/dist/index.mjs +10 -1
- package/dist/index.mjs.map +1 -1
- package/dist/no-unsafe-inner-html.cjs +210 -44
- package/dist/no-unsafe-inner-html.cjs.map +1 -1
- package/dist/no-unsafe-inner-html.d.cts +20 -3
- package/dist/no-unsafe-inner-html.d.mts +20 -3
- package/dist/no-unsafe-inner-html.mjs +209 -46
- package/dist/no-unsafe-inner-html.mjs.map +1 -1
- package/dist/no-wall-clock-bound.cjs.map +1 -1
- package/dist/no-wall-clock-bound.mjs.map +1 -1
- package/dist/require-loader-prefetch.mjs +1 -1
- package/dist/utils.cjs +76 -3
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.mjs +71 -4
- package/dist/utils.mjs.map +1 -1
- package/package.json +13 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isAstNode, c as memberPropertyName, d as
|
|
1
|
+
import { a as isAstNode, c as memberPropertyName, d as staticStringValue, f as unwrapExpression, i as invokedCallee, o as isIdentifier, r as getPropertyName, s as isStringLiteral, u as resolveLiteralArraySpreads } from "./utils.mjs";
|
|
2
2
|
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
3
3
|
//#region src/no-unsafe-inner-html.ts
|
|
4
4
|
const DOM_ELEMENT_PRODUCERS = [
|
|
@@ -87,6 +87,11 @@ const DOM_ELEMENT_PRODUCERS = [
|
|
|
87
87
|
kind: "collection",
|
|
88
88
|
receiver: "either"
|
|
89
89
|
},
|
|
90
|
+
{
|
|
91
|
+
name: "children",
|
|
92
|
+
kind: "collection-property",
|
|
93
|
+
receiver: "element"
|
|
94
|
+
},
|
|
90
95
|
{
|
|
91
96
|
name: "parentElement",
|
|
92
97
|
kind: "property",
|
|
@@ -148,11 +153,35 @@ const HTML_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
148
153
|
]);
|
|
149
154
|
const HTML_FIRST_ARGUMENT_METHODS = /* @__PURE__ */ new Set(["createContextualFragment", "setHTMLUnsafe"]);
|
|
150
155
|
const DOCUMENT_WRITE_METHODS = /* @__PURE__ */ new Set(["write", "writeln"]);
|
|
156
|
+
const HTML_ATTRIBUTE_SINKS = [{
|
|
157
|
+
name: "setAttribute",
|
|
158
|
+
namespaceIndex: null,
|
|
159
|
+
nameIndex: 0,
|
|
160
|
+
valueIndex: 1
|
|
161
|
+
}, {
|
|
162
|
+
name: "setAttributeNS",
|
|
163
|
+
namespaceIndex: 0,
|
|
164
|
+
nameIndex: 1,
|
|
165
|
+
valueIndex: 2
|
|
166
|
+
}];
|
|
151
167
|
const DOCUMENT_MEMBERS = /* @__PURE__ */ new Set([
|
|
152
168
|
"contentDocument",
|
|
153
169
|
"document",
|
|
154
170
|
"ownerDocument"
|
|
155
171
|
]);
|
|
172
|
+
const FILE_SYSTEM_MODULES = /* @__PURE__ */ new Set([
|
|
173
|
+
"fs",
|
|
174
|
+
"fs/promises",
|
|
175
|
+
"node:fs",
|
|
176
|
+
"node:fs/promises"
|
|
177
|
+
]);
|
|
178
|
+
const CHILD_PROCESS_MODULES = /* @__PURE__ */ new Set(["child_process", "node:child_process"]);
|
|
179
|
+
const CHILD_PROCESS_FACTORIES = /* @__PURE__ */ new Set([
|
|
180
|
+
"exec",
|
|
181
|
+
"execFile",
|
|
182
|
+
"fork",
|
|
183
|
+
"spawn"
|
|
184
|
+
]);
|
|
156
185
|
const HTML_SANITIZER_LEAF_RE = /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;
|
|
157
186
|
const SANITIZER_RESULT_MEMBERS = /* @__PURE__ */ new Set([
|
|
158
187
|
"value",
|
|
@@ -219,7 +248,15 @@ const isOpaqueProperty = (property) => property.type === "SpreadElement" || prop
|
|
|
219
248
|
const objectProperties = (node) => {
|
|
220
249
|
const expression = unwrapExpression(node);
|
|
221
250
|
if (expression?.type !== "ObjectExpression" || !Array.isArray(expression.properties)) return [];
|
|
222
|
-
|
|
251
|
+
const properties = [];
|
|
252
|
+
for (const property of expression.properties.filter(isAstNode)) {
|
|
253
|
+
if (property.type === "SpreadElement" && unwrapExpression(property.argument)?.type === "ObjectExpression") {
|
|
254
|
+
properties.push(...objectProperties(property.argument));
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
properties.push(property);
|
|
258
|
+
}
|
|
259
|
+
return properties;
|
|
223
260
|
};
|
|
224
261
|
const appliedArgumentsFor = (args) => {
|
|
225
262
|
const applied = unwrapExpression(args.at(1));
|
|
@@ -260,13 +297,6 @@ const invokedArguments = (call) => {
|
|
|
260
297
|
}
|
|
261
298
|
return appliedArgumentsFor(resolved.args);
|
|
262
299
|
};
|
|
263
|
-
const isDocumentReceiver = (node) => {
|
|
264
|
-
const receiver = unwrapExpression(node);
|
|
265
|
-
if (isIdentifier(receiver, "document")) return true;
|
|
266
|
-
if (receiver?.type !== "MemberExpression") return false;
|
|
267
|
-
const name = memberPropertyName(receiver);
|
|
268
|
-
return name !== null && DOCUMENT_MEMBERS.has(name);
|
|
269
|
-
};
|
|
270
300
|
var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
271
301
|
meta: { name: "no-unsafe-inner-html" },
|
|
272
302
|
rules: { "no-unsafe-inner-html": {
|
|
@@ -282,35 +312,155 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
282
312
|
}],
|
|
283
313
|
messages: {
|
|
284
314
|
unsafeInnerHtml: "Dynamic HTML injected into the DOM must be static, proven by the configured sanitizer policy, or have an adjacent `// safe-html: <provenance>` comment naming the trusted source. Function names outside the allowlist are not proof because a local identity function can shadow them.",
|
|
285
|
-
unsafeInnerHtmlSpread: "Do not spread an object
|
|
315
|
+
unsafeInnerHtmlSpread: "Do not spread an object in dangerouslySetInnerHTML. Keep `__html` inline so this rule can prove the HTML value is sanitized or escaped.",
|
|
316
|
+
unsafeInnerHtmlComputedKey: "Do not use an unresolved computed key in dangerouslySetInnerHTML. Write an explicit `__html` property so this rule can prove the HTML value is sanitized or escaped."
|
|
286
317
|
}
|
|
287
318
|
},
|
|
288
319
|
createOnce(context) {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
320
|
+
const resolveStableBinding = ({ node, scope, seen }) => {
|
|
321
|
+
let target = unwrapExpression(node);
|
|
322
|
+
while (isIdentifier(target)) {
|
|
292
323
|
let current = scope;
|
|
324
|
+
let variable;
|
|
293
325
|
while (current !== null) {
|
|
294
|
-
|
|
295
|
-
if (variable
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
326
|
+
variable = current.set.get(target.name);
|
|
327
|
+
if (variable !== void 0) break;
|
|
328
|
+
current = current.upper;
|
|
329
|
+
}
|
|
330
|
+
if (variable === void 0 || variable.defs.length === 0) return {
|
|
331
|
+
kind: "expression",
|
|
332
|
+
node: target,
|
|
333
|
+
scope
|
|
334
|
+
};
|
|
335
|
+
if (seen.has(variable)) return null;
|
|
336
|
+
seen.add(variable);
|
|
337
|
+
const definition = variable.defs.at(0);
|
|
338
|
+
if (variable.defs.length === 1 && (definition?.type === "ClassName" || definition?.type === "FunctionName") && (definition.node.type === "ClassDeclaration" || definition.node.type === "FunctionDeclaration") && !variable.references.some((reference) => reference.isWrite())) return {
|
|
339
|
+
kind: "expression",
|
|
340
|
+
node: unwrapExpression(definition.node),
|
|
341
|
+
scope
|
|
342
|
+
};
|
|
343
|
+
if (variable.defs.length !== 1 || definition?.type !== "Variable" || definition.node.type !== "VariableDeclarator" || definition.node.id.type !== "Identifier" || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
|
|
344
|
+
const declarator = definition.node;
|
|
345
|
+
if (declarator.init !== null) {
|
|
346
|
+
target = unwrapExpression(declarator.init);
|
|
347
|
+
scope = context.sourceCode.getScope(declarator);
|
|
348
|
+
continue;
|
|
310
349
|
}
|
|
350
|
+
const declaration = declarator.parent;
|
|
351
|
+
if (declaration.type !== "VariableDeclaration") return null;
|
|
352
|
+
const loop = declaration.parent;
|
|
353
|
+
if (loop.type !== "ForOfStatement" || loop.left !== declaration) return null;
|
|
354
|
+
return {
|
|
355
|
+
kind: "iteration",
|
|
356
|
+
node: unwrapExpression(loop.right),
|
|
357
|
+
scope: context.sourceCode.getScope(loop.right)
|
|
358
|
+
};
|
|
311
359
|
}
|
|
360
|
+
return {
|
|
361
|
+
kind: "expression",
|
|
362
|
+
node: target,
|
|
363
|
+
scope
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
const isKnownNonEmptyNamespace = (namespace, scope) => {
|
|
367
|
+
const source = resolveStableBinding({
|
|
368
|
+
node: namespace,
|
|
369
|
+
scope,
|
|
370
|
+
seen: /* @__PURE__ */ new Set()
|
|
371
|
+
});
|
|
372
|
+
if (source?.kind !== "expression") return false;
|
|
373
|
+
const text = staticStringValue(source.node);
|
|
374
|
+
if (text !== null) return text !== "";
|
|
375
|
+
if (source.node?.type !== "Literal") return false;
|
|
376
|
+
const { value } = source.node;
|
|
377
|
+
return typeof value === "number" || typeof value === "boolean" || typeof value === "bigint";
|
|
378
|
+
};
|
|
379
|
+
const variableFor = (identifier, initialScope) => {
|
|
380
|
+
let current = initialScope;
|
|
381
|
+
while (current !== null) {
|
|
382
|
+
const variable = current.set.get(identifier.name);
|
|
383
|
+
if (variable !== void 0) return variable;
|
|
384
|
+
current = current.upper;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
const isKnownNonDocumentReceiver = (node, scope) => {
|
|
388
|
+
const isUnshadowedGlobal = (identifier, name) => isIdentifier(identifier, name) && variableFor(identifier, scope) === void 0;
|
|
389
|
+
const isFileSystemImport = (identifier) => {
|
|
390
|
+
if (!isIdentifier(identifier)) return false;
|
|
391
|
+
const variable = variableFor(identifier, scope);
|
|
392
|
+
if (variable?.defs.length !== 1) return false;
|
|
393
|
+
const definition = variable.defs.at(0);
|
|
394
|
+
return definition?.type === "ImportBinding" && definition.parent?.type === "ImportDeclaration" && FILE_SYSTEM_MODULES.has(definition.parent.source.value);
|
|
395
|
+
};
|
|
396
|
+
const isChildProcess = (identifier) => {
|
|
397
|
+
if (!isIdentifier(identifier)) return false;
|
|
398
|
+
const variable = variableFor(identifier, scope);
|
|
399
|
+
const definition = variable?.defs.at(0);
|
|
400
|
+
if (variable?.defs.length !== 1 || definition?.type !== "Variable" || definition.node.type !== "VariableDeclarator" || definition.node.id.type !== "Identifier" || definition.node.parent.type !== "VariableDeclaration" || definition.node.parent.kind !== "const") return false;
|
|
401
|
+
const init = unwrapExpression(definition.node.init);
|
|
402
|
+
if (init?.type !== "CallExpression") return false;
|
|
403
|
+
const callee = unwrapExpression(init.callee);
|
|
404
|
+
if (callee?.type === "MemberExpression") return memberPropertyName(callee) === "spawn" && isUnshadowedGlobal(unwrapExpression(callee.object), "Bun");
|
|
405
|
+
if (!isIdentifier(callee)) return false;
|
|
406
|
+
const factory = variableFor(callee, scope);
|
|
407
|
+
const binding = factory?.defs.at(0);
|
|
408
|
+
return factory?.defs.length === 1 && binding?.type === "ImportBinding" && binding.node.type === "ImportSpecifier" && binding.node.imported.type === "Identifier" && CHILD_PROCESS_FACTORIES.has(binding.node.imported.name) && binding.parent?.type === "ImportDeclaration" && CHILD_PROCESS_MODULES.has(binding.parent.source.value);
|
|
409
|
+
};
|
|
410
|
+
const root = unwrapExpression(node);
|
|
411
|
+
if (isUnshadowedGlobal(root, "Bun") || isFileSystemImport(root)) return true;
|
|
412
|
+
if (root?.type !== "MemberExpression") return false;
|
|
413
|
+
const stream = memberPropertyName(root);
|
|
414
|
+
const owner = unwrapExpression(root.object);
|
|
415
|
+
return (stream === "stdout" || stream === "stderr") && isUnshadowedGlobal(owner, "process") || stream === "stdin" && isChildProcess(owner);
|
|
416
|
+
};
|
|
417
|
+
const isDocumentReceiver = (node, scope) => {
|
|
418
|
+
if (isKnownNonDocumentReceiver(node, scope)) return false;
|
|
419
|
+
const source = resolveStableBinding({
|
|
420
|
+
node,
|
|
421
|
+
scope,
|
|
422
|
+
seen: /* @__PURE__ */ new Set()
|
|
423
|
+
});
|
|
424
|
+
if (source === null || source.kind !== "expression") return true;
|
|
425
|
+
const receiver = source.node;
|
|
426
|
+
if (isIdentifier(receiver, "document")) return true;
|
|
427
|
+
if (receiver?.type !== "MemberExpression") return false;
|
|
428
|
+
const name = memberPropertyName(receiver);
|
|
429
|
+
return name !== null && DOCUMENT_MEMBERS.has(name);
|
|
430
|
+
};
|
|
431
|
+
const isDocumentSinkReceiver = (node, scope) => {
|
|
432
|
+
if (isKnownNonDocumentReceiver(node, scope)) return false;
|
|
433
|
+
const receiver = unwrapExpression(node);
|
|
434
|
+
if (!isIdentifier(receiver)) return true;
|
|
435
|
+
let current = scope;
|
|
436
|
+
let variable;
|
|
437
|
+
while (current !== null) {
|
|
438
|
+
variable = current.set.get(receiver.name);
|
|
439
|
+
if (variable !== void 0) break;
|
|
440
|
+
current = current.upper;
|
|
441
|
+
}
|
|
442
|
+
if (variable === void 0 || variable.defs.length !== 1) return true;
|
|
443
|
+
const definition = variable.defs.at(0);
|
|
444
|
+
if (definition?.type !== "Variable" || definition.node.type !== "VariableDeclarator" || definition.node.id.type !== "Identifier" || definition.node.init === null || unwrapExpression(definition.node.init)?.type !== "ObjectExpression" || definition.node.parent.type !== "VariableDeclaration" || definition.node.parent.kind !== "const" || variable.scope.variableScope !== scope.variableScope || variable.references.some((reference) => reference.isWrite() && !reference.init)) return true;
|
|
445
|
+
return false;
|
|
446
|
+
};
|
|
447
|
+
const isKnownDomTarget = ({ node, scope, seen = /* @__PURE__ */ new Set(), kind = "element" }) => {
|
|
448
|
+
const source = resolveStableBinding({
|
|
449
|
+
node,
|
|
450
|
+
scope,
|
|
451
|
+
seen
|
|
452
|
+
});
|
|
453
|
+
if (source === null) return true;
|
|
454
|
+
if (source.kind === "iteration") return kind === "element" && isKnownDomTarget({
|
|
455
|
+
node: source.node,
|
|
456
|
+
scope: source.scope,
|
|
457
|
+
seen,
|
|
458
|
+
kind: "collection"
|
|
459
|
+
});
|
|
460
|
+
const target = source.node;
|
|
461
|
+
scope = source.scope;
|
|
312
462
|
const hasProducerReceiver = (receiver, producer) => {
|
|
313
|
-
if (producer.receiver !== "element" && isDocumentReceiver(receiver)) return true;
|
|
463
|
+
if (producer.receiver !== "element" && isDocumentReceiver(receiver, scope)) return true;
|
|
314
464
|
return producer.receiver !== "document" && isKnownDomTarget({
|
|
315
465
|
node: receiver,
|
|
316
466
|
scope,
|
|
@@ -323,12 +473,12 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
323
473
|
seen,
|
|
324
474
|
kind: "collection"
|
|
325
475
|
});
|
|
326
|
-
if (
|
|
476
|
+
if (target?.type === "MemberExpression") {
|
|
327
477
|
const property = memberPropertyName(target);
|
|
328
|
-
const producer = DOM_ELEMENT_PRODUCERS.find((entry) => entry.kind === "property" && entry.name === property);
|
|
478
|
+
const producer = DOM_ELEMENT_PRODUCERS.find((entry) => entry.kind === (kind === "collection" ? "collection-property" : "property") && entry.name === property);
|
|
329
479
|
if (producer !== void 0) return hasProducerReceiver(target.object, producer);
|
|
330
480
|
const index = unwrapExpression(target.property);
|
|
331
|
-
if (target.computed === true && index?.type === "Literal" && typeof index.value === "number" && Number.isInteger(index.value) && index.value >= 0) return isElementCollection(target.object);
|
|
481
|
+
if (kind === "element" && target.computed === true && index?.type === "Literal" && typeof index.value === "number" && Number.isInteger(index.value) && index.value >= 0) return isElementCollection(target.object);
|
|
332
482
|
}
|
|
333
483
|
if (target?.type === "CallExpression") {
|
|
334
484
|
const callee = invokedCallee(target);
|
|
@@ -338,7 +488,7 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
338
488
|
const producer = DOM_ELEMENT_PRODUCERS.find((entry) => entry.kind === (kind === "collection" ? "collection" : "method") && entry.name === method);
|
|
339
489
|
return producer !== void 0 && hasProducerReceiver(callee.object, producer);
|
|
340
490
|
}
|
|
341
|
-
return kind === "element" && isDocumentReceiver(target);
|
|
491
|
+
return kind === "element" && isDocumentReceiver(target, scope);
|
|
342
492
|
};
|
|
343
493
|
let sanitizerPolicy = SANITIZER_POLICY.allowlist;
|
|
344
494
|
const waiverLines = /* @__PURE__ */ new Set();
|
|
@@ -349,6 +499,11 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
349
499
|
kind
|
|
350
500
|
});
|
|
351
501
|
};
|
|
502
|
+
const messageIdFor = (kind) => {
|
|
503
|
+
if (kind === "opaque-payload-spread") return "unsafeInnerHtmlSpread";
|
|
504
|
+
if (kind === "opaque-payload-computed-key") return "unsafeInnerHtmlComputedKey";
|
|
505
|
+
return "unsafeInnerHtml";
|
|
506
|
+
};
|
|
352
507
|
const consumeWaiver = (node) => {
|
|
353
508
|
const line = startLine(node) - 1;
|
|
354
509
|
if (!waiverLines.has(line)) return false;
|
|
@@ -366,7 +521,7 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
366
521
|
}
|
|
367
522
|
for (const property of objectProperties(expression)) {
|
|
368
523
|
if (isOpaqueProperty(property)) {
|
|
369
|
-
observeSink(property, "opaque-payload");
|
|
524
|
+
observeSink(property, property.type === "SpreadElement" ? "opaque-payload-spread" : "opaque-payload-computed-key");
|
|
370
525
|
continue;
|
|
371
526
|
}
|
|
372
527
|
if (property.type === "Property" && objectPropertyKey(property) === htmlKey) reportIfUnsafe(property.value);
|
|
@@ -383,6 +538,14 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
383
538
|
default: throw new Error(`Unknown React HTML prop: ${String(prop)}`);
|
|
384
539
|
}
|
|
385
540
|
};
|
|
541
|
+
const checkReactProps = (value) => {
|
|
542
|
+
for (const property of objectProperties(value)) {
|
|
543
|
+
if (property.type !== "Property") continue;
|
|
544
|
+
const key = objectPropertyKey(property);
|
|
545
|
+
const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);
|
|
546
|
+
if (prop?.kind === "value") checkReactHtmlProp(prop, property.value);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
386
549
|
const checkHtmlPropertySources = (sources, domTarget) => {
|
|
387
550
|
for (const source of sources) {
|
|
388
551
|
if (unwrapExpression(source)?.type !== "ObjectExpression") {
|
|
@@ -412,7 +575,7 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
412
575
|
if (kind === "safe" || kind === "unsafe" && waived) continue;
|
|
413
576
|
context.report({
|
|
414
577
|
node,
|
|
415
|
-
messageId: kind
|
|
578
|
+
messageId: messageIdFor(kind)
|
|
416
579
|
});
|
|
417
580
|
}
|
|
418
581
|
sinks.length = 0;
|
|
@@ -428,6 +591,9 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
428
591
|
const value = unwrapExpression(node.value);
|
|
429
592
|
checkReactHtmlProp(prop, value?.type === "JSXExpressionContainer" ? value.expression : value);
|
|
430
593
|
},
|
|
594
|
+
JSXSpreadAttribute(node) {
|
|
595
|
+
checkReactProps(node.argument);
|
|
596
|
+
},
|
|
431
597
|
Property(node) {
|
|
432
598
|
const key = objectPropertyKey(node);
|
|
433
599
|
const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);
|
|
@@ -447,12 +613,7 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
447
613
|
const invoked = invokedArguments(node);
|
|
448
614
|
const factory = staticCalleeName(callee);
|
|
449
615
|
if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {
|
|
450
|
-
|
|
451
|
-
if (property.type !== "Property") continue;
|
|
452
|
-
const key = objectPropertyKey(property);
|
|
453
|
-
const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);
|
|
454
|
-
if (prop?.kind === "value") checkReactHtmlProp(prop, property.value);
|
|
455
|
-
}
|
|
616
|
+
checkReactProps(invoked.args.at(1));
|
|
456
617
|
return;
|
|
457
618
|
}
|
|
458
619
|
if (callee?.type !== "MemberExpression") return;
|
|
@@ -474,15 +635,17 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
474
635
|
reportIfUnsafe(invoked.args.at(1));
|
|
475
636
|
return;
|
|
476
637
|
}
|
|
477
|
-
|
|
638
|
+
const attributeSink = HTML_ATTRIBUTE_SINKS.find((entry) => entry.name === method);
|
|
639
|
+
if (attributeSink !== void 0) {
|
|
640
|
+
if (attributeSink.namespaceIndex !== null && isKnownNonEmptyNamespace(invoked.args.at(attributeSink.namespaceIndex), context.sourceCode.getScope(node))) return;
|
|
478
641
|
if (invoked.status === "opaque") {
|
|
479
642
|
reportIfUnsafe(node);
|
|
480
643
|
return;
|
|
481
644
|
}
|
|
482
|
-
if (staticStringValue(invoked.args.at(
|
|
645
|
+
if (staticStringValue(invoked.args.at(attributeSink.nameIndex))?.toLowerCase() === "srcdoc") reportIfUnsafe(invoked.args.at(attributeSink.valueIndex));
|
|
483
646
|
return;
|
|
484
647
|
}
|
|
485
|
-
if (DOCUMENT_WRITE_METHODS.has(method) &&
|
|
648
|
+
if (DOCUMENT_WRITE_METHODS.has(method) && isDocumentSinkReceiver(callee.object, context.sourceCode.getScope(node))) {
|
|
486
649
|
if (invoked.status === "opaque") {
|
|
487
650
|
reportIfUnsafe(node);
|
|
488
651
|
return;
|
|
@@ -504,6 +667,6 @@ var no_unsafe_inner_html_default = eslintCompatPlugin({
|
|
|
504
667
|
} }
|
|
505
668
|
});
|
|
506
669
|
//#endregion
|
|
507
|
-
export { DOM_ELEMENT_PRODUCERS, REACT_HTML_PROPS, no_unsafe_inner_html_default as default };
|
|
670
|
+
export { DOCUMENT_WRITE_METHODS, DOM_ELEMENT_PRODUCERS, HTML_ATTRIBUTE_SINKS, REACT_ELEMENT_FACTORIES, REACT_HTML_PROPS, no_unsafe_inner_html_default as default };
|
|
508
671
|
|
|
509
672
|
//# sourceMappingURL=no-unsafe-inner-html.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-unsafe-inner-html.mjs","names":["exhaustive"],"sources":["../src/no-unsafe-inner-html.ts"],"sourcesContent":["// Forbid injecting un-proven HTML into the DOM.\n//\n// Raw HTML may only reach the DOM from a value that is provably sanitized or\n// escaped. This rule covers common DOM and JSX sinks while allowing explicit\n// sanitizer calls and narrowly scoped provenance comments.\n//\n// Sinks, all sharing one allowlist:\n// • `__html` of a `dangerouslySetInnerHTML` object, whether the object is a\n// JSX attribute value or a property of any object literal (props passed\n// to `createElement` / `jsx()`, or spread into JSX).\n// • assignment to `innerHTML`, `outerHTML` or `srcdoc`, dot or static\n// bracket notation, and the same keys in an `Object.assign` source.\n// • the JSX `srcDoc` attribute.\n// • `insertAdjacentHTML`, `setHTMLUnsafe`, `createContextualFragment`,\n// `setAttribute(\"srcdoc\", …)`, and `write` / `writeln` on a document.\n//\n// In the default `allowlist` policy, a value is allowed when it is:\n// • a static string Literal or TemplateLiteral whose interpolations are all\n// independently static, OR\n// • passed through the shared HTML sanitizer allowlist (`escapeHtml`,\n// `escapeXml`, `sanitizeHtml`, `sanitizeDom`, `DOMPurify.sanitize`, and\n// related established names), including `.value` / `.data` / `.html`\n// result accessors; OR\n// • annotated by a `// safe-html: <provenance>` comment with non-empty text\n// on the line directly above the sink. One comment covers one sink: a\n// second sink starting on the same line needs its own annotation.\n// The `provenance` policy omits the sanitizer-name allowlist and requires the\n// comment for dynamic sanitizer results as well.\n//\n// The sanitizer allowlist preserves the shared plugin contract. For dynamic\n// values outside that list, function names are not proof: a local identity\n// function can be named `sanitizeHtml`. Add explicit provenance at the sink\n// until the codebase has a branded SafeHtml boundary.\n\nimport type { Scope, Variable } from \"@oxlint/plugins\";\nimport { eslintCompatPlugin } from \"@oxlint/plugins\";\n\nimport type { AstNode, LiteralArgumentResolution } from \"./utils.ts\";\nimport {\n getPropertyName,\n invokedCallee,\n isAstNode,\n isIdentifier,\n isStringLiteral,\n memberPropertyName,\n resolveLiteralArraySpreads,\n staticStringValue,\n unwrapExpression,\n} from \"./utils.ts\";\n\ntype DomElementProducer = {\n name: string;\n kind: \"property\" | \"method\" | \"collection\";\n receiver: \"document\" | \"element\" | \"either\";\n};\n\n// DOM element provenance and the generated equivalence corpus share this table.\nexport const DOM_ELEMENT_PRODUCERS = [\n { name: \"body\", kind: \"property\", receiver: \"document\" },\n { name: \"head\", kind: \"property\", receiver: \"document\" },\n { name: \"documentElement\", kind: \"property\", receiver: \"document\" },\n { name: \"activeElement\", kind: \"property\", receiver: \"document\" },\n { name: \"scrollingElement\", kind: \"property\", receiver: \"document\" },\n { name: \"fullscreenElement\", kind: \"property\", receiver: \"document\" },\n { name: \"createElement\", kind: \"method\", receiver: \"document\" },\n { name: \"createElementNS\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementById\", kind: \"method\", receiver: \"document\" },\n { name: \"querySelector\", kind: \"method\", receiver: \"either\" },\n { name: \"closest\", kind: \"method\", receiver: \"element\" },\n { name: \"elementFromPoint\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementsByClassName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagNameNS\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByName\", kind: \"collection\", receiver: \"document\" },\n { name: \"querySelectorAll\", kind: \"collection\", receiver: \"either\" },\n { name: \"parentElement\", kind: \"property\", receiver: \"element\" },\n { name: \"firstElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"lastElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"nextElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"previousElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"offsetParent\", kind: \"property\", receiver: \"element\" },\n] as const satisfies readonly DomElementProducer[];\n\n// The provenance text after the marker must be non-empty.\nconst WAIVER_RE = /^\\s*safe-html:\\s*\\S/u;\n\nexport const REACT_HTML_PROPS = [\n { name: \"dangerouslySetInnerHTML\", kind: \"payload\", htmlKey: \"__html\" },\n { name: \"srcDoc\", kind: \"value\" },\n { name: \"srcdoc\", kind: \"value\" },\n] as const;\ntype ReactHtmlProp = (typeof REACT_HTML_PROPS)[number];\nconst REACT_ELEMENT_FACTORIES: ReadonlySet<string> = new Set([\n \"createElement\",\n \"React.createElement\",\n \"jsx\",\n \"jsxs\",\n \"jsxDEV\",\n]);\nconst HTML_PROPERTIES: ReadonlySet<string> = new Set([\n \"innerHTML\",\n \"outerHTML\",\n \"srcdoc\",\n]);\n// Methods whose first argument is parsed as HTML.\nconst HTML_FIRST_ARGUMENT_METHODS: ReadonlySet<string> = new Set([\n \"createContextualFragment\",\n \"setHTMLUnsafe\",\n]);\nconst DOCUMENT_WRITE_METHODS: ReadonlySet<string> = new Set([\n \"write\",\n \"writeln\",\n]);\nconst DOCUMENT_MEMBERS: ReadonlySet<string> = new Set([\n \"contentDocument\",\n \"document\",\n \"ownerDocument\",\n]);\n\nconst HTML_SANITIZER_LEAF_RE =\n /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;\nconst SANITIZER_RESULT_MEMBERS = new Set([\"value\", \"data\", \"html\"]);\nconst SANITIZER_POLICY = {\n allowlist: \"allowlist\",\n provenance: \"provenance\",\n} as const;\nconst SANITIZER_POLICY_VALUES = Object.values(SANITIZER_POLICY);\ntype SanitizerPolicy = (typeof SANITIZER_POLICY)[keyof typeof SANITIZER_POLICY];\n\nconst sanitizerPolicyFrom = (option: unknown): SanitizerPolicy => {\n if (option === undefined) {\n return SANITIZER_POLICY.allowlist;\n }\n if (typeof option !== \"object\" || option === null || Array.isArray(option)) {\n throw new Error(\"no-unsafe-inner-html options must be an object\");\n }\n const policy = Reflect.get(option, \"sanitizerPolicy\");\n switch (policy) {\n case undefined:\n case SANITIZER_POLICY.allowlist:\n return SANITIZER_POLICY.allowlist;\n case SANITIZER_POLICY.provenance:\n return SANITIZER_POLICY.provenance;\n default:\n throw new Error(`unknown sanitizer policy: ${String(policy)}`);\n }\n};\n\n// Use the same static member-name resolution for sinks and safe-value proofs.\nconst staticCalleeName = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (isIdentifier(expression)) return expression.name;\n if (expression?.type !== \"MemberExpression\") return null;\n const receiver = staticCalleeName(expression.object);\n const property = memberPropertyName(expression);\n return receiver === null || property === null\n ? null\n : `${receiver}.${property}`;\n};\n\nconst isSanitizerCall = (node: unknown): boolean => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return false;\n }\n const dotted = staticCalleeName(node.callee);\n if (dotted === null) {\n return false;\n }\n const parts = dotted.split(\".\");\n const leaf = parts.at(-1);\n if (leaf === undefined) {\n return false;\n }\n if (HTML_SANITIZER_LEAF_RE.test(leaf)) {\n return true;\n }\n return leaf === \"sanitize\" && /^dompurify$/iu.test(parts.at(-2) ?? \"\");\n};\n\nconst isSanitizerResult = (node: unknown): boolean => {\n const expression = unwrapExpression(node);\n if (expression?.type !== \"MemberExpression\") {\n return false;\n }\n const property = memberPropertyName(expression);\n return (\n property !== null &&\n SANITIZER_RESULT_MEMBERS.has(property) &&\n isSanitizerCall(expression.object)\n );\n};\n\ntype Located = { loc: { start: { line: number } } };\n\nconst hasLocation = (node: unknown): node is Located =>\n typeof node === \"object\" &&\n node !== null &&\n \"loc\" in node &&\n typeof node.loc === \"object\" &&\n node.loc !== null &&\n \"start\" in node.loc &&\n typeof node.loc.start === \"object\" &&\n node.loc.start !== null &&\n \"line\" in node.loc.start &&\n typeof node.loc.start.line === \"number\";\n\n// A node without location data never matches a waiver line.\nconst startLine = (node: unknown): number =>\n hasLocation(node) ? node.loc.start.line : Number.NaN;\n\ntype WaiverComment = {\n value: string;\n loc: { end: { line: number } };\n};\n\nconst isWaiverComment = (value: unknown): value is WaiverComment =>\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n typeof value.value === \"string\" &&\n WAIVER_RE.test(value.value) &&\n \"loc\" in value &&\n typeof value.loc === \"object\" &&\n value.loc !== null;\n\nconst jsxAttributeName = (node: unknown): string | null =>\n isAstNode(node) && node.type === \"JSXIdentifier\" && typeof node.name === \"string\"\n ? node.name\n : null;\n\n// A value is proven safe by its own static shape, independent of comments.\nconst isProvenSafeValue = (\n node: unknown,\n sanitizerPolicy: SanitizerPolicy,\n): boolean => {\n const expression = unwrapExpression(node);\n if (expression === null) {\n return false;\n }\n if (isStringLiteral(expression)) {\n return true;\n }\n if (\n sanitizerPolicy === SANITIZER_POLICY.allowlist &&\n (isSanitizerCall(expression) || isSanitizerResult(expression))\n ) {\n return true;\n }\n return (\n expression.type === \"TemplateLiteral\" &&\n Array.isArray(expression.expressions) &&\n expression.expressions.every((entry) =>\n isProvenSafeValue(entry, sanitizerPolicy),\n )\n );\n};\n\n// The static key of an object-literal Property: `a`, `\"a\"`, `[\"a\"]`.\nconst objectPropertyKey = (property: unknown): string | null => {\n if (!isAstNode(property)) {\n return null;\n }\n if (property.computed === true) {\n return staticStringValue(property.key);\n }\n return getPropertyName(property.key);\n};\n\nconst isOpaqueProperty = (property: AstNode): boolean =>\n property.type === \"SpreadElement\" ||\n (property.type === \"Property\" &&\n property.computed === true &&\n objectPropertyKey(property) === null);\n\nconst objectProperties = (node: unknown): AstNode[] => {\n const expression = unwrapExpression(node);\n if (\n expression?.type !== \"ObjectExpression\" ||\n !Array.isArray(expression.properties)\n ) {\n return [];\n }\n return expression.properties.filter(isAstNode);\n};\n\ntype AppliedArguments = LiteralArgumentResolution;\n\nconst appliedArgumentsFor = (args: unknown[]): AppliedArguments => {\n const applied = unwrapExpression(args.at(1));\n if (\n applied === null ||\n (applied.type === \"Literal\" && applied.value === null) ||\n isIdentifier(applied, \"undefined\")\n ) {\n return { status: \"known\", args: [] };\n }\n if (applied.type !== \"ArrayExpression\" || !Array.isArray(applied.elements)) {\n return { status: \"opaque\", args: [], spread: args.at(1), tail: [] };\n }\n return resolveLiteralArraySpreads(applied.elements);\n};\n\n// The arguments the invoked function receives: `f.call(thisArg, ...args)`\n// shifts them by one; `f.apply(thisArg, [...])` forwards a literal array.\nconst invokedArguments = (call: unknown): AppliedArguments => {\n if (!isAstNode(call)) {\n return { status: \"known\", args: [] };\n }\n const rawArguments = Array.isArray(call.arguments) ? call.arguments : [];\n const resolved = resolveLiteralArraySpreads(rawArguments);\n const callee = unwrapExpression(call.callee);\n if (callee?.type !== \"MemberExpression\") return resolved;\n const method = memberPropertyName(callee);\n if (method === \"call\") {\n return { ...resolved, args: resolved.args.slice(1) };\n }\n if (method !== \"apply\") return resolved;\n if (resolved.status === \"opaque\") {\n if (resolved.args.length >= 2) return appliedArgumentsFor(resolved.args);\n return { ...resolved, args: [], tail: [] };\n }\n return appliedArgumentsFor(resolved.args);\n};\n\n// `document.write`, `window.document.write`, `frame.contentDocument.write`,\n// `node.ownerDocument.write`.\nconst isDocumentReceiver = (node: unknown): boolean => {\n const receiver = unwrapExpression(node);\n if (isIdentifier(receiver, \"document\")) {\n return true;\n }\n if (receiver?.type !== \"MemberExpression\") {\n return false;\n }\n const name = memberPropertyName(receiver);\n return name !== null && DOCUMENT_MEMBERS.has(name);\n};\n\nexport default eslintCompatPlugin({\n meta: { name: \"no-unsafe-inner-html\" },\n rules: {\n \"no-unsafe-inner-html\": {\n meta: {\n type: \"problem\",\n schema: [\n {\n type: \"object\",\n properties: {\n sanitizerPolicy: {\n type: \"string\",\n enum: SANITIZER_POLICY_VALUES,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n unsafeInnerHtml:\n \"Dynamic HTML injected into the DOM must be static, proven by \" +\n \"the configured sanitizer policy, or have an adjacent \" +\n \"`// safe-html: <provenance>` comment naming the trusted source. \" +\n \"Function names outside the allowlist are not proof because a \" +\n \"local identity function can shadow them.\",\n unsafeInnerHtmlSpread:\n \"Do not spread an object into dangerouslySetInnerHTML. Keep \" +\n \"`__html` inline so this rule can prove the HTML value is \" +\n \"sanitized or escaped.\",\n },\n },\n createOnce(context) {\n type DomTargetOptions = {\n node: unknown;\n scope: Scope;\n seen?: Set<Variable>;\n kind?: \"element\" | \"collection\";\n };\n const isKnownDomTarget = ({\n node,\n scope,\n seen = new Set<Variable>(),\n kind = \"element\",\n }: DomTargetOptions): boolean => {\n const target = unwrapExpression(node);\n if (isIdentifier(target)) {\n let current: Scope | null = scope;\n while (current !== null) {\n const variable = current.set.get(target.name);\n if (variable === undefined) {\n current = current.upper;\n continue;\n }\n if (variable.defs.length === 0) {\n return kind === \"element\" && isDocumentReceiver(target);\n }\n if (seen.has(variable)) return false;\n seen.add(variable);\n const definition = variable.defs.at(0);\n if (\n variable.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.init === null ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return false;\n }\n return isKnownDomTarget({\n node: definition.node.init,\n scope: context.sourceCode.getScope(definition.node),\n seen,\n kind,\n });\n }\n }\n const hasProducerReceiver = (\n receiver: unknown,\n producer: DomElementProducer,\n ): boolean => {\n if (\n producer.receiver !== \"element\" &&\n isDocumentReceiver(receiver)\n ) {\n return true;\n }\n return (\n producer.receiver !== \"document\" &&\n isKnownDomTarget({ node: receiver, scope, seen })\n );\n };\n const isElementCollection = (collection: unknown): boolean =>\n isKnownDomTarget({ node: collection, scope, seen, kind: \"collection\" });\n if (kind === \"element\" && target?.type === \"MemberExpression\") {\n const property = memberPropertyName(target);\n const producer = DOM_ELEMENT_PRODUCERS.find(\n (entry) => entry.kind === \"property\" && entry.name === property,\n );\n if (producer !== undefined) {\n return hasProducerReceiver(target.object, producer);\n }\n const index = unwrapExpression(target.property);\n if (\n target.computed === true &&\n index?.type === \"Literal\" &&\n typeof index.value === \"number\" &&\n Number.isInteger(index.value) &&\n index.value >= 0\n ) {\n return isElementCollection(target.object);\n }\n }\n if (target?.type === \"CallExpression\") {\n const callee = invokedCallee(target);\n if (callee?.type !== \"MemberExpression\") return false;\n const method = memberPropertyName(callee);\n if (\n kind === \"element\" &&\n method === \"item\" &&\n isElementCollection(callee.object)\n ) {\n return true;\n }\n const producer = DOM_ELEMENT_PRODUCERS.find(\n (entry) =>\n entry.kind === (kind === \"collection\" ? \"collection\" : \"method\") &&\n entry.name === method,\n );\n return (\n producer !== undefined &&\n hasProducerReceiver(callee.object, producer)\n );\n }\n return kind === \"element\" && isDocumentReceiver(target);\n };\n\n let sanitizerPolicy: SanitizerPolicy = SANITIZER_POLICY.allowlist;\n // Associate waivers after visiting the file, so source position decides\n // the first covered sink even when AST visitors inspect values out of order.\n const waiverLines = new Set<number>();\n type SinkObservation = {\n node: AstNode;\n kind: \"safe\" | \"unsafe\" | \"opaque-payload\";\n };\n const sinks: SinkObservation[] = [];\n const observeSink = (\n node: unknown,\n kind: SinkObservation[\"kind\"],\n ): void => {\n if (isAstNode(node)) sinks.push({ node, kind });\n };\n\n const consumeWaiver = (node: unknown): boolean => {\n const line = startLine(node) - 1;\n if (!waiverLines.has(line)) {\n return false;\n }\n waiverLines.delete(line);\n return true;\n };\n\n const reportIfUnsafe = (node: unknown): void => {\n observeSink(\n node,\n isProvenSafeValue(node, sanitizerPolicy) ? \"safe\" : \"unsafe\",\n );\n };\n\n // The value of a `dangerouslySetInnerHTML` prop: an inline object has\n // its `__html` checked and its spreads rejected; anything else is a\n // hoisted payload whose HTML this rule cannot see.\n const checkDangerousPayload = (value: unknown, htmlKey: string): void => {\n const expression = unwrapExpression(value);\n if (expression?.type !== \"ObjectExpression\") {\n reportIfUnsafe(value);\n return;\n }\n for (const property of objectProperties(expression)) {\n if (isOpaqueProperty(property)) {\n observeSink(property, \"opaque-payload\");\n continue;\n }\n if (\n property.type === \"Property\" &&\n objectPropertyKey(property) === htmlKey\n ) {\n reportIfUnsafe(property.value);\n }\n }\n };\n\n const checkReactHtmlProp = (prop: ReactHtmlProp, value: unknown): void => {\n switch (prop.kind) {\n case \"payload\":\n checkDangerousPayload(value, prop.htmlKey);\n return;\n case \"value\":\n reportIfUnsafe(value);\n return;\n default: {\n const exhaustive: never = prop;\n throw new Error(`Unknown React HTML prop: ${String(exhaustive)}`);\n }\n }\n };\n\n const checkHtmlPropertySources = (\n sources: unknown[],\n domTarget: boolean,\n ): void => {\n for (const source of sources) {\n const expression = unwrapExpression(source);\n if (expression?.type !== \"ObjectExpression\") {\n if (domTarget) {\n reportIfUnsafe(source);\n }\n continue;\n }\n for (const property of objectProperties(source)) {\n if (isOpaqueProperty(property)) {\n if (domTarget) {\n reportIfUnsafe(property);\n }\n continue;\n }\n const key =\n property.type === \"Property\"\n ? objectPropertyKey(property)\n : null;\n if (key !== null && HTML_PROPERTIES.has(key)) {\n reportIfUnsafe(property.value);\n }\n }\n }\n };\n\n return {\n before() {\n sanitizerPolicy = sanitizerPolicyFrom(context.options.at(0));\n waiverLines.clear();\n sinks.length = 0;\n },\n \"Program:exit\"() {\n sinks.sort((left, right) => left.node.range[0] - right.node.range[0]);\n for (const { node, kind } of sinks) {\n const waived = consumeWaiver(node);\n if (kind === \"safe\" || (kind === \"unsafe\" && waived)) continue;\n context.report({\n node,\n messageId:\n kind === \"opaque-payload\"\n ? \"unsafeInnerHtmlSpread\"\n : \"unsafeInnerHtml\",\n });\n }\n sinks.length = 0;\n },\n Program(node) {\n const comments: unknown[] = Array.isArray(node.comments)\n ? node.comments\n : [];\n for (const comment of comments) {\n if (isWaiverComment(comment)) {\n waiverLines.add(comment.loc.end.line);\n }\n }\n },\n\n JSXAttribute(node) {\n const name = jsxAttributeName(node.name);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === name);\n if (prop === undefined) return;\n const value = unwrapExpression(node.value);\n checkReactHtmlProp(\n prop,\n value?.type === \"JSXExpressionContainer\" ? value.expression : value,\n );\n },\n\n // Payload objects remain checked when hoisted or spread into JSX.\n // Direct value props require a recognized factory-call context below.\n Property(node) {\n const key = objectPropertyKey(node);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n if (prop?.kind !== \"payload\") return;\n const container = node.parent;\n if (isAstNode(container) && container.type === \"ObjectExpression\") {\n checkReactHtmlProp(prop, node.value);\n }\n },\n\n AssignmentExpression(node) {\n const target = unwrapExpression(node.left);\n if (target?.type !== \"MemberExpression\") {\n return;\n }\n const name = memberPropertyName(target);\n if (name !== null && HTML_PROPERTIES.has(name)) {\n reportIfUnsafe(node.right);\n }\n },\n\n CallExpression(node) {\n const call: unknown = node;\n const callee = isAstNode(call) ? invokedCallee(call) : null;\n const invoked = invokedArguments(node);\n const factory = staticCalleeName(callee);\n if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {\n for (const property of objectProperties(invoked.args.at(1))) {\n if (property.type !== \"Property\") continue;\n const key = objectPropertyKey(property);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n // Payload props are already checked by the Property visitor.\n if (prop?.kind === \"value\") {\n checkReactHtmlProp(prop, property.value);\n }\n }\n return;\n }\n if (callee?.type !== \"MemberExpression\") {\n return;\n }\n const method = memberPropertyName(callee);\n if (method === null) {\n return;\n }\n if (HTML_FIRST_ARGUMENT_METHODS.has(method)) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(0));\n return;\n }\n if (method === \"insertAdjacentHTML\") {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(1));\n return;\n }\n if (method === \"setAttribute\") {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n const attribute = staticStringValue(invoked.args.at(0));\n if (attribute?.toLowerCase() === \"srcdoc\") {\n reportIfUnsafe(invoked.args.at(1));\n }\n return;\n }\n if (\n DOCUMENT_WRITE_METHODS.has(method) &&\n isDocumentReceiver(callee.object)\n ) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n observeSink(\n node,\n invoked.args.every((argument) =>\n isProvenSafeValue(argument, sanitizerPolicy),\n )\n ? \"safe\"\n : \"unsafe\",\n );\n return;\n }\n if (isIdentifier(callee.object, \"Object\") && method === \"assign\") {\n const domTarget = isKnownDomTarget({\n node: invoked.args.at(0),\n scope: context.sourceCode.getScope(node),\n });\n checkHtmlPropertySources(\n [\n ...invoked.args.slice(1),\n ...(invoked.status === \"opaque\" ? invoked.tail : []),\n ],\n domTarget,\n );\n if (invoked.status === \"opaque\" && domTarget) {\n reportIfUnsafe(invoked.spread);\n }\n }\n },\n };\n },\n },\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"no-unsafe-inner-html.mjs","names":["exhaustive"],"sources":["../src/no-unsafe-inner-html.ts"],"sourcesContent":["// Forbid injecting un-proven HTML into the DOM.\n//\n// Raw HTML may only reach the DOM from a value that is provably sanitized or\n// escaped. This rule covers common DOM and JSX sinks while allowing explicit\n// sanitizer calls and narrowly scoped provenance comments.\n//\n// Sinks, all sharing one allowlist:\n// • `__html` of a `dangerouslySetInnerHTML` object, whether the object is a\n// JSX attribute value or a property of any object literal (props passed\n// to `createElement` / `jsx()`, or spread into JSX).\n// • assignment to `innerHTML`, `outerHTML` or `srcdoc`, dot or static\n// bracket notation, and the same keys in an `Object.assign` source.\n// • the JSX `srcDoc` attribute.\n// • `insertAdjacentHTML`, `setHTMLUnsafe`, `createContextualFragment`,\n// `setAttribute(\"srcdoc\", …)` / `setAttributeNS(…, \"srcdoc\", …)`,\n// and `write` / `writeln` on a document.\n//\n// In the default `allowlist` policy, a value is allowed when it is:\n// • a static string Literal or TemplateLiteral whose interpolations are all\n// independently static, OR\n// • passed through the shared HTML sanitizer allowlist (`escapeHtml`,\n// `escapeXml`, `sanitizeHtml`, `sanitizeDom`, `DOMPurify.sanitize`, and\n// related established names), including `.value` / `.data` / `.html`\n// result accessors; OR\n// • annotated by a `// safe-html: <provenance>` comment with non-empty text\n// on the line directly above the sink. One comment covers one sink: a\n// second sink starting on the same line needs its own annotation.\n// The `provenance` policy omits the sanitizer-name allowlist and requires the\n// comment for dynamic sanitizer results as well.\n//\n// The sanitizer allowlist preserves the shared plugin contract. For dynamic\n// values outside that list, function names are not proof: a local identity\n// function can be named `sanitizeHtml`. Add explicit provenance at the sink\n// until the codebase has a branded SafeHtml boundary.\n\nimport type { Scope, Variable } from \"@oxlint/plugins\";\nimport { eslintCompatPlugin } from \"@oxlint/plugins\";\n\nimport type { AstNode, LiteralArgumentResolution } from \"./utils.ts\";\nimport {\n getPropertyName,\n invokedCallee,\n isAstNode,\n isIdentifier,\n isStringLiteral,\n memberPropertyName,\n resolveLiteralArraySpreads,\n staticStringValue,\n unwrapExpression,\n} from \"./utils.ts\";\n\ntype DomElementProducer = {\n name: string;\n kind: \"property\" | \"method\" | \"collection\" | \"collection-property\";\n receiver: \"document\" | \"element\" | \"either\";\n};\n\n// DOM element provenance and the generated equivalence corpus share this table.\nexport const DOM_ELEMENT_PRODUCERS = [\n { name: \"body\", kind: \"property\", receiver: \"document\" },\n { name: \"head\", kind: \"property\", receiver: \"document\" },\n { name: \"documentElement\", kind: \"property\", receiver: \"document\" },\n { name: \"activeElement\", kind: \"property\", receiver: \"document\" },\n { name: \"scrollingElement\", kind: \"property\", receiver: \"document\" },\n { name: \"fullscreenElement\", kind: \"property\", receiver: \"document\" },\n { name: \"createElement\", kind: \"method\", receiver: \"document\" },\n { name: \"createElementNS\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementById\", kind: \"method\", receiver: \"document\" },\n { name: \"querySelector\", kind: \"method\", receiver: \"either\" },\n { name: \"closest\", kind: \"method\", receiver: \"element\" },\n { name: \"elementFromPoint\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementsByClassName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagNameNS\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByName\", kind: \"collection\", receiver: \"document\" },\n { name: \"querySelectorAll\", kind: \"collection\", receiver: \"either\" },\n { name: \"children\", kind: \"collection-property\", receiver: \"element\" },\n { name: \"parentElement\", kind: \"property\", receiver: \"element\" },\n { name: \"firstElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"lastElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"nextElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"previousElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"offsetParent\", kind: \"property\", receiver: \"element\" },\n] as const satisfies readonly DomElementProducer[];\n\n// The provenance text after the marker must be non-empty.\nconst WAIVER_RE = /^\\s*safe-html:\\s*\\S/u;\n\nexport const REACT_HTML_PROPS = [\n { name: \"dangerouslySetInnerHTML\", kind: \"payload\", htmlKey: \"__html\" },\n { name: \"srcDoc\", kind: \"value\" },\n { name: \"srcdoc\", kind: \"value\" },\n] as const;\ntype ReactHtmlProp = (typeof REACT_HTML_PROPS)[number];\nexport const REACT_ELEMENT_FACTORIES: ReadonlySet<string> = new Set([\n \"createElement\",\n \"React.createElement\",\n \"jsx\",\n \"jsxs\",\n \"jsxDEV\",\n]);\nconst HTML_PROPERTIES: ReadonlySet<string> = new Set([\n \"innerHTML\",\n \"outerHTML\",\n \"srcdoc\",\n]);\n// Methods whose first argument is parsed as HTML.\nconst HTML_FIRST_ARGUMENT_METHODS: ReadonlySet<string> = new Set([\n \"createContextualFragment\",\n \"setHTMLUnsafe\",\n]);\nexport const DOCUMENT_WRITE_METHODS: ReadonlySet<string> = new Set([\n \"write\",\n \"writeln\",\n]);\nexport const HTML_ATTRIBUTE_SINKS = [\n { name: \"setAttribute\", namespaceIndex: null, nameIndex: 0, valueIndex: 1 },\n { name: \"setAttributeNS\", namespaceIndex: 0, nameIndex: 1, valueIndex: 2 },\n] as const;\nconst DOCUMENT_MEMBERS: ReadonlySet<string> = new Set([\n \"contentDocument\",\n \"document\",\n \"ownerDocument\",\n]);\nconst FILE_SYSTEM_MODULES: ReadonlySet<string> = new Set([\n \"fs\",\n \"fs/promises\",\n \"node:fs\",\n \"node:fs/promises\",\n]);\nconst CHILD_PROCESS_MODULES: ReadonlySet<string> = new Set([\n \"child_process\",\n \"node:child_process\",\n]);\n// Calls that return a ChildProcess, whose stdin is a writable stream.\nconst CHILD_PROCESS_FACTORIES: ReadonlySet<string> = new Set([\n \"exec\",\n \"execFile\",\n \"fork\",\n \"spawn\",\n]);\nconst HTML_SANITIZER_LEAF_RE =\n /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;\nconst SANITIZER_RESULT_MEMBERS = new Set([\"value\", \"data\", \"html\"]);\nconst SANITIZER_POLICY = {\n allowlist: \"allowlist\",\n provenance: \"provenance\",\n} as const;\nconst SANITIZER_POLICY_VALUES = Object.values(SANITIZER_POLICY);\ntype SanitizerPolicy = (typeof SANITIZER_POLICY)[keyof typeof SANITIZER_POLICY];\n\nconst sanitizerPolicyFrom = (option: unknown): SanitizerPolicy => {\n if (option === undefined) {\n return SANITIZER_POLICY.allowlist;\n }\n if (typeof option !== \"object\" || option === null || Array.isArray(option)) {\n throw new Error(\"no-unsafe-inner-html options must be an object\");\n }\n const policy = Reflect.get(option, \"sanitizerPolicy\");\n switch (policy) {\n case undefined:\n case SANITIZER_POLICY.allowlist:\n return SANITIZER_POLICY.allowlist;\n case SANITIZER_POLICY.provenance:\n return SANITIZER_POLICY.provenance;\n default:\n throw new Error(`unknown sanitizer policy: ${String(policy)}`);\n }\n};\n\n// Use the same static member-name resolution for sinks and safe-value proofs.\nconst staticCalleeName = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (isIdentifier(expression)) return expression.name;\n if (expression?.type !== \"MemberExpression\") return null;\n const receiver = staticCalleeName(expression.object);\n const property = memberPropertyName(expression);\n return receiver === null || property === null\n ? null\n : `${receiver}.${property}`;\n};\n\nconst isSanitizerCall = (node: unknown): boolean => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return false;\n }\n const dotted = staticCalleeName(node.callee);\n if (dotted === null) {\n return false;\n }\n const parts = dotted.split(\".\");\n const leaf = parts.at(-1);\n if (leaf === undefined) {\n return false;\n }\n if (HTML_SANITIZER_LEAF_RE.test(leaf)) {\n return true;\n }\n return leaf === \"sanitize\" && /^dompurify$/iu.test(parts.at(-2) ?? \"\");\n};\n\nconst isSanitizerResult = (node: unknown): boolean => {\n const expression = unwrapExpression(node);\n if (expression?.type !== \"MemberExpression\") {\n return false;\n }\n const property = memberPropertyName(expression);\n return (\n property !== null &&\n SANITIZER_RESULT_MEMBERS.has(property) &&\n isSanitizerCall(expression.object)\n );\n};\n\ntype Located = { loc: { start: { line: number } } };\n\nconst hasLocation = (node: unknown): node is Located =>\n typeof node === \"object\" &&\n node !== null &&\n \"loc\" in node &&\n typeof node.loc === \"object\" &&\n node.loc !== null &&\n \"start\" in node.loc &&\n typeof node.loc.start === \"object\" &&\n node.loc.start !== null &&\n \"line\" in node.loc.start &&\n typeof node.loc.start.line === \"number\";\n\n// A node without location data never matches a waiver line.\nconst startLine = (node: unknown): number =>\n hasLocation(node) ? node.loc.start.line : Number.NaN;\n\ntype WaiverComment = {\n value: string;\n loc: { end: { line: number } };\n};\n\nconst isWaiverComment = (value: unknown): value is WaiverComment =>\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n typeof value.value === \"string\" &&\n WAIVER_RE.test(value.value) &&\n \"loc\" in value &&\n typeof value.loc === \"object\" &&\n value.loc !== null;\n\nconst jsxAttributeName = (node: unknown): string | null =>\n isAstNode(node) &&\n node.type === \"JSXIdentifier\" &&\n typeof node.name === \"string\"\n ? node.name\n : null;\n\n// A value is proven safe by its own static shape, independent of comments.\nconst isProvenSafeValue = (\n node: unknown,\n sanitizerPolicy: SanitizerPolicy,\n): boolean => {\n const expression = unwrapExpression(node);\n if (expression === null) {\n return false;\n }\n if (isStringLiteral(expression)) {\n return true;\n }\n if (\n sanitizerPolicy === SANITIZER_POLICY.allowlist &&\n (isSanitizerCall(expression) || isSanitizerResult(expression))\n ) {\n return true;\n }\n return (\n expression.type === \"TemplateLiteral\" &&\n Array.isArray(expression.expressions) &&\n expression.expressions.every((entry) =>\n isProvenSafeValue(entry, sanitizerPolicy),\n )\n );\n};\n\n// The static key of an object-literal Property: `a`, `\"a\"`, `[\"a\"]`.\nconst objectPropertyKey = (property: unknown): string | null => {\n if (!isAstNode(property)) {\n return null;\n }\n if (property.computed === true) {\n return staticStringValue(property.key);\n }\n return getPropertyName(property.key);\n};\n\nconst isOpaqueProperty = (property: AstNode): boolean =>\n property.type === \"SpreadElement\" ||\n (property.type === \"Property\" &&\n property.computed === true &&\n objectPropertyKey(property) === null);\n\nconst objectProperties = (node: unknown): AstNode[] => {\n const expression = unwrapExpression(node);\n if (\n expression?.type !== \"ObjectExpression\" ||\n !Array.isArray(expression.properties)\n ) {\n return [];\n }\n const properties: AstNode[] = [];\n for (const property of expression.properties.filter(isAstNode)) {\n if (\n property.type === \"SpreadElement\" &&\n unwrapExpression(property.argument)?.type === \"ObjectExpression\"\n ) {\n properties.push(...objectProperties(property.argument));\n continue;\n }\n properties.push(property);\n }\n return properties;\n};\n\ntype AppliedArguments = LiteralArgumentResolution;\n\nconst appliedArgumentsFor = (args: unknown[]): AppliedArguments => {\n const applied = unwrapExpression(args.at(1));\n if (\n applied === null ||\n (applied.type === \"Literal\" && applied.value === null) ||\n isIdentifier(applied, \"undefined\")\n ) {\n return { status: \"known\", args: [] };\n }\n if (applied.type !== \"ArrayExpression\" || !Array.isArray(applied.elements)) {\n return { status: \"opaque\", args: [], spread: args.at(1), tail: [] };\n }\n return resolveLiteralArraySpreads(applied.elements);\n};\n\n// The arguments the invoked function receives: `f.call(thisArg, ...args)`\n// shifts them by one; `f.apply(thisArg, [...])` forwards a literal array.\nconst invokedArguments = (call: unknown): AppliedArguments => {\n if (!isAstNode(call)) {\n return { status: \"known\", args: [] };\n }\n const rawArguments = Array.isArray(call.arguments) ? call.arguments : [];\n const resolved = resolveLiteralArraySpreads(rawArguments);\n const callee = unwrapExpression(call.callee);\n if (callee?.type !== \"MemberExpression\") return resolved;\n const method = memberPropertyName(callee);\n if (method === \"call\") {\n return { ...resolved, args: resolved.args.slice(1) };\n }\n if (method !== \"apply\") return resolved;\n if (resolved.status === \"opaque\") {\n if (resolved.args.length >= 2) return appliedArgumentsFor(resolved.args);\n return { ...resolved, args: [], tail: [] };\n }\n return appliedArgumentsFor(resolved.args);\n};\n\nexport default eslintCompatPlugin({\n meta: { name: \"no-unsafe-inner-html\" },\n rules: {\n \"no-unsafe-inner-html\": {\n meta: {\n type: \"problem\",\n schema: [\n {\n type: \"object\",\n properties: {\n sanitizerPolicy: {\n type: \"string\",\n enum: SANITIZER_POLICY_VALUES,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n unsafeInnerHtml:\n \"Dynamic HTML injected into the DOM must be static, proven by \" +\n \"the configured sanitizer policy, or have an adjacent \" +\n \"`// safe-html: <provenance>` comment naming the trusted source. \" +\n \"Function names outside the allowlist are not proof because a \" +\n \"local identity function can shadow them.\",\n unsafeInnerHtmlSpread:\n \"Do not spread an object in dangerouslySetInnerHTML. Keep \" +\n \"`__html` inline so this rule can prove the HTML value is \" +\n \"sanitized or escaped.\",\n unsafeInnerHtmlComputedKey:\n \"Do not use an unresolved computed key in \" +\n \"dangerouslySetInnerHTML. Write an explicit `__html` property \" +\n \"so this rule can \" +\n \"prove the HTML value is sanitized or escaped.\",\n },\n },\n createOnce(context) {\n type BindingOptions = {\n node: unknown;\n scope: Scope;\n seen: Set<Variable>;\n };\n type BindingSource = {\n kind: \"expression\" | \"iteration\";\n node: AstNode | null;\n scope: Scope;\n };\n // All document, element and collection checks share one stable-binding path.\n const resolveStableBinding = ({\n node,\n scope,\n seen,\n }: BindingOptions): BindingSource | null => {\n let target = unwrapExpression(node);\n while (isIdentifier(target)) {\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(target.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length === 0) {\n return { kind: \"expression\", node: target, scope };\n }\n if (seen.has(variable)) return null;\n seen.add(variable);\n const definition = variable.defs.at(0);\n // A never-reassigned class or function declaration holds a\n // function, which is neither an element nor a document.\n if (\n variable.defs.length === 1 &&\n (definition?.type === \"ClassName\" ||\n definition?.type === \"FunctionName\") &&\n (definition.node.type === \"ClassDeclaration\" ||\n definition.node.type === \"FunctionDeclaration\") &&\n !variable.references.some((reference) => reference.isWrite())\n ) {\n return {\n kind: \"expression\",\n node: unwrapExpression(definition.node),\n scope,\n };\n }\n if (\n variable.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return null;\n }\n const declarator = definition.node;\n if (declarator.init !== null) {\n target = unwrapExpression(declarator.init);\n scope = context.sourceCode.getScope(declarator);\n continue;\n }\n const declaration = declarator.parent;\n if (declaration.type !== \"VariableDeclaration\") return null;\n const loop = declaration.parent;\n if (loop.type !== \"ForOfStatement\" || loop.left !== declaration) {\n return null;\n }\n return {\n kind: \"iteration\",\n node: unwrapExpression(loop.right),\n scope: context.sourceCode.getScope(loop.right),\n };\n }\n return { kind: \"expression\", node: target, scope };\n };\n const isKnownNonEmptyNamespace = (\n namespace: unknown,\n scope: Scope,\n ): boolean => {\n const source = resolveStableBinding({\n node: namespace,\n scope,\n seen: new Set<Variable>(),\n });\n if (source?.kind !== \"expression\") return false;\n const text = staticStringValue(source.node);\n // DOM normalizes the empty namespace to null. Unknowns stay checked.\n if (text !== null) return text !== \"\";\n if (source.node?.type !== \"Literal\") return false;\n const { value } = source.node;\n return (\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n );\n };\n\n const variableFor = (\n identifier: { name: string },\n initialScope: Scope,\n ): Variable | undefined => {\n let current: Scope | null = initialScope;\n while (current !== null) {\n const variable = current.set.get(identifier.name);\n if (variable !== undefined) return variable;\n current = current.upper;\n }\n return undefined;\n };\n const isKnownNonDocumentReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n const isUnshadowedGlobal = (\n identifier: unknown,\n name: string,\n ): boolean =>\n isIdentifier(identifier, name) &&\n variableFor(identifier, scope) === undefined;\n const isFileSystemImport = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n if (variable?.defs.length !== 1) return false;\n const definition = variable.defs.at(0);\n return (\n definition?.type === \"ImportBinding\" &&\n definition.parent?.type === \"ImportDeclaration\" &&\n FILE_SYSTEM_MODULES.has(definition.parent.source.value)\n );\n };\n // A const bound once to a child process factory call: a node\n // child_process import or the unshadowed Bun.spawn.\n const isChildProcess = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n const definition = variable?.defs.at(0);\n if (\n variable?.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\"\n )\n return false;\n const init = unwrapExpression(definition.node.init);\n if (init?.type !== \"CallExpression\") return false;\n const callee = unwrapExpression(init.callee);\n if (callee?.type === \"MemberExpression\")\n return (\n memberPropertyName(callee) === \"spawn\" &&\n isUnshadowedGlobal(unwrapExpression(callee.object), \"Bun\")\n );\n if (!isIdentifier(callee)) return false;\n const factory = variableFor(callee, scope);\n const binding = factory?.defs.at(0);\n return (\n factory?.defs.length === 1 &&\n binding?.type === \"ImportBinding\" &&\n binding.node.type === \"ImportSpecifier\" &&\n binding.node.imported.type === \"Identifier\" &&\n CHILD_PROCESS_FACTORIES.has(binding.node.imported.name) &&\n binding.parent?.type === \"ImportDeclaration\" &&\n CHILD_PROCESS_MODULES.has(binding.parent.source.value)\n );\n };\n const root = unwrapExpression(node);\n if (isUnshadowedGlobal(root, \"Bun\") || isFileSystemImport(root)) {\n return true;\n }\n if (root?.type !== \"MemberExpression\") return false;\n const stream = memberPropertyName(root);\n const owner = unwrapExpression(root.object);\n return (\n ((stream === \"stdout\" || stream === \"stderr\") &&\n isUnshadowedGlobal(owner, \"process\")) ||\n (stream === \"stdin\" && isChildProcess(owner))\n );\n };\n const isDocumentReceiver = (node: unknown, scope: Scope): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const source = resolveStableBinding({\n node,\n scope,\n seen: new Set<Variable>(),\n });\n if (source === null || source.kind !== \"expression\") return true;\n const receiver = source.node;\n if (isIdentifier(receiver, \"document\")) return true;\n if (receiver?.type !== \"MemberExpression\") return false;\n const name = memberPropertyName(receiver);\n return name !== null && DOCUMENT_MEMBERS.has(name);\n };\n\n const isDocumentSinkReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const receiver = unwrapExpression(node);\n if (!isIdentifier(receiver)) return true;\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(receiver.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length !== 1) return true;\n const definition = variable.defs.at(0);\n if (\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.init === null ||\n unwrapExpression(definition.node.init)?.type !==\n \"ObjectExpression\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\" ||\n variable.scope.variableScope !== scope.variableScope ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return true;\n }\n return false;\n };\n type DomTargetOptions = {\n node: unknown;\n scope: Scope;\n seen?: Set<Variable>;\n kind?: \"element\" | \"collection\";\n };\n const isKnownDomTarget = ({\n node,\n scope,\n seen = new Set<Variable>(),\n kind = \"element\",\n }: DomTargetOptions): boolean => {\n const source = resolveStableBinding({ node, scope, seen });\n // An unstable binding may have been reassigned to a DOM target. Fail\n // closed unless the resolver can prove a stable non-DOM value.\n if (source === null) return true;\n if (source.kind === \"iteration\") {\n return (\n kind === \"element\" &&\n isKnownDomTarget({\n node: source.node,\n scope: source.scope,\n seen,\n kind: \"collection\",\n })\n );\n }\n const target = source.node;\n scope = source.scope;\n const hasProducerReceiver = (\n receiver: unknown,\n producer: DomElementProducer,\n ): boolean => {\n if (\n producer.receiver !== \"element\" &&\n isDocumentReceiver(receiver, scope)\n ) {\n return true;\n }\n return (\n producer.receiver !== \"document\" &&\n isKnownDomTarget({ node: receiver, scope, seen })\n );\n };\n const isElementCollection = (collection: unknown): boolean =>\n isKnownDomTarget({\n node: collection,\n scope,\n seen,\n kind: \"collection\",\n });\n if (target?.type === \"MemberExpression\") {\n const property = memberPropertyName(target);\n const producer = DOM_ELEMENT_PRODUCERS.find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\"\n ? \"collection-property\"\n : \"property\") && entry.name === property,\n );\n if (producer !== undefined) {\n return hasProducerReceiver(target.object, producer);\n }\n const index = unwrapExpression(target.property);\n if (\n kind === \"element\" &&\n target.computed === true &&\n index?.type === \"Literal\" &&\n typeof index.value === \"number\" &&\n Number.isInteger(index.value) &&\n index.value >= 0\n ) {\n return isElementCollection(target.object);\n }\n }\n if (target?.type === \"CallExpression\") {\n const callee = invokedCallee(target);\n if (callee?.type !== \"MemberExpression\") return false;\n const method = memberPropertyName(callee);\n if (\n kind === \"element\" &&\n method === \"item\" &&\n isElementCollection(callee.object)\n ) {\n return true;\n }\n const producer = DOM_ELEMENT_PRODUCERS.find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\" ? \"collection\" : \"method\") &&\n entry.name === method,\n );\n return (\n producer !== undefined &&\n hasProducerReceiver(callee.object, producer)\n );\n }\n return kind === \"element\" && isDocumentReceiver(target, scope);\n };\n\n let sanitizerPolicy: SanitizerPolicy = SANITIZER_POLICY.allowlist;\n // Associate waivers after visiting the file, so source position decides\n // the first covered sink even when AST visitors inspect values out of order.\n const waiverLines = new Set<number>();\n type SinkObservation = {\n node: AstNode;\n kind:\n | \"safe\"\n | \"unsafe\"\n | \"opaque-payload-spread\"\n | \"opaque-payload-computed-key\";\n };\n const sinks: SinkObservation[] = [];\n const observeSink = (\n node: unknown,\n kind: SinkObservation[\"kind\"],\n ): void => {\n if (isAstNode(node)) sinks.push({ node, kind });\n };\n const messageIdFor = (kind: SinkObservation[\"kind\"]) => {\n if (kind === \"opaque-payload-spread\") {\n return \"unsafeInnerHtmlSpread\";\n }\n if (kind === \"opaque-payload-computed-key\") {\n return \"unsafeInnerHtmlComputedKey\";\n }\n return \"unsafeInnerHtml\";\n };\n\n const consumeWaiver = (node: unknown): boolean => {\n const line = startLine(node) - 1;\n if (!waiverLines.has(line)) {\n return false;\n }\n waiverLines.delete(line);\n return true;\n };\n\n const reportIfUnsafe = (node: unknown): void => {\n observeSink(\n node,\n isProvenSafeValue(node, sanitizerPolicy) ? \"safe\" : \"unsafe\",\n );\n };\n\n // The value of a `dangerouslySetInnerHTML` prop: an inline object has\n // its `__html` checked and its spreads rejected; anything else is a\n // hoisted payload whose HTML this rule cannot see.\n const checkDangerousPayload = (\n value: unknown,\n htmlKey: string,\n ): void => {\n const expression = unwrapExpression(value);\n if (expression?.type !== \"ObjectExpression\") {\n reportIfUnsafe(value);\n return;\n }\n for (const property of objectProperties(expression)) {\n if (isOpaqueProperty(property)) {\n observeSink(\n property,\n property.type === \"SpreadElement\"\n ? \"opaque-payload-spread\"\n : \"opaque-payload-computed-key\",\n );\n continue;\n }\n if (\n property.type === \"Property\" &&\n objectPropertyKey(property) === htmlKey\n ) {\n reportIfUnsafe(property.value);\n }\n }\n };\n\n const checkReactHtmlProp = (\n prop: ReactHtmlProp,\n value: unknown,\n ): void => {\n switch (prop.kind) {\n case \"payload\":\n checkDangerousPayload(value, prop.htmlKey);\n return;\n case \"value\":\n reportIfUnsafe(value);\n return;\n default: {\n const exhaustive: never = prop;\n throw new Error(`Unknown React HTML prop: ${String(exhaustive)}`);\n }\n }\n };\n\n const checkReactProps = (value: unknown): void => {\n for (const property of objectProperties(value)) {\n if (property.type !== \"Property\") continue;\n const key = objectPropertyKey(property);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n // Payload properties are checked once by the Property visitor.\n if (prop?.kind === \"value\")\n checkReactHtmlProp(prop, property.value);\n }\n };\n\n const checkHtmlPropertySources = (\n sources: unknown[],\n domTarget: boolean,\n ): void => {\n for (const source of sources) {\n const expression = unwrapExpression(source);\n if (expression?.type !== \"ObjectExpression\") {\n if (domTarget) {\n reportIfUnsafe(source);\n }\n continue;\n }\n for (const property of objectProperties(source)) {\n if (isOpaqueProperty(property)) {\n if (domTarget) {\n reportIfUnsafe(property);\n }\n continue;\n }\n const key =\n property.type === \"Property\"\n ? objectPropertyKey(property)\n : null;\n if (key !== null && HTML_PROPERTIES.has(key)) {\n reportIfUnsafe(property.value);\n }\n }\n }\n };\n\n return {\n before() {\n sanitizerPolicy = sanitizerPolicyFrom(context.options.at(0));\n waiverLines.clear();\n sinks.length = 0;\n },\n \"Program:exit\"() {\n sinks.sort(\n (left, right) => left.node.range[0] - right.node.range[0],\n );\n for (const { node, kind } of sinks) {\n const waived = consumeWaiver(node);\n if (kind === \"safe\" || (kind === \"unsafe\" && waived)) continue;\n context.report({\n node,\n messageId: messageIdFor(kind),\n });\n }\n sinks.length = 0;\n },\n Program(node) {\n const comments: unknown[] = Array.isArray(node.comments)\n ? node.comments\n : [];\n for (const comment of comments) {\n if (isWaiverComment(comment)) {\n waiverLines.add(comment.loc.end.line);\n }\n }\n },\n\n JSXAttribute(node) {\n const name = jsxAttributeName(node.name);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === name);\n if (prop === undefined) return;\n const value = unwrapExpression(node.value);\n checkReactHtmlProp(\n prop,\n value?.type === \"JSXExpressionContainer\"\n ? value.expression\n : value,\n );\n },\n\n JSXSpreadAttribute(node) {\n checkReactProps(node.argument);\n },\n\n // Payload objects remain checked when hoisted or spread into JSX.\n // Direct value props use the JSX and factory props contexts.\n Property(node) {\n const key = objectPropertyKey(node);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n if (prop?.kind !== \"payload\") return;\n const container = node.parent;\n if (isAstNode(container) && container.type === \"ObjectExpression\") {\n checkReactHtmlProp(prop, node.value);\n }\n },\n\n AssignmentExpression(node) {\n const target = unwrapExpression(node.left);\n if (target?.type !== \"MemberExpression\") {\n return;\n }\n const name = memberPropertyName(target);\n if (name !== null && HTML_PROPERTIES.has(name)) {\n reportIfUnsafe(node.right);\n }\n },\n\n CallExpression(node) {\n const call: unknown = node;\n const callee = isAstNode(call) ? invokedCallee(call) : null;\n const invoked = invokedArguments(node);\n const factory = staticCalleeName(callee);\n if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {\n checkReactProps(invoked.args.at(1));\n return;\n }\n if (callee?.type !== \"MemberExpression\") {\n return;\n }\n const method = memberPropertyName(callee);\n if (method === null) {\n return;\n }\n if (HTML_FIRST_ARGUMENT_METHODS.has(method)) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(0));\n return;\n }\n if (method === \"insertAdjacentHTML\") {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(1));\n return;\n }\n const attributeSink = HTML_ATTRIBUTE_SINKS.find(\n (entry) => entry.name === method,\n );\n if (attributeSink !== undefined) {\n if (\n attributeSink.namespaceIndex !== null &&\n isKnownNonEmptyNamespace(\n invoked.args.at(attributeSink.namespaceIndex),\n context.sourceCode.getScope(node),\n )\n ) {\n return;\n }\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n const attribute = staticStringValue(\n invoked.args.at(attributeSink.nameIndex),\n );\n if (attribute?.toLowerCase() === \"srcdoc\") {\n reportIfUnsafe(invoked.args.at(attributeSink.valueIndex));\n }\n return;\n }\n if (\n DOCUMENT_WRITE_METHODS.has(method) &&\n isDocumentSinkReceiver(\n callee.object,\n context.sourceCode.getScope(node),\n )\n ) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n observeSink(\n node,\n invoked.args.every((argument) =>\n isProvenSafeValue(argument, sanitizerPolicy),\n )\n ? \"safe\"\n : \"unsafe\",\n );\n return;\n }\n if (isIdentifier(callee.object, \"Object\") && method === \"assign\") {\n const domTarget = isKnownDomTarget({\n node: invoked.args.at(0),\n scope: context.sourceCode.getScope(node),\n });\n checkHtmlPropertySources(\n [\n ...invoked.args.slice(1),\n ...(invoked.status === \"opaque\" ? invoked.tail : []),\n ],\n domTarget,\n );\n if (invoked.status === \"opaque\" && domTarget) {\n reportIfUnsafe(invoked.spread);\n }\n }\n },\n };\n },\n },\n },\n});\n"],"mappings":"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"}
|