1 of 15

Trusted Types &

Dynamic Code Brand Checks

2025/02/27�@hiroppy

2 of 15

Trusted Types

3 of 15

Bad Examples

  • element.innerHTML = location.href;
  • element.innerHTML = "<img src=x onerror=alert(1)>";
  • new Function("alert(1)");
  • script.src = "https://example.com/attack.js";
  • script.textContent = "alert(1)";
  • 🙂‍↔️

4 of 15

Content-Security-Policy

Content-Security-Policy: trusted-types;

Content-Security-Policy: trusted-types 'none';

Content-Security-Policy: trusted-types <policyName>;

Content-Security-Policy: trusted-types <policyName> <policyName> 'allow-duplicates';

Content-Security-Policy: require-trusted-types-for 'script';

Refused to create a TrustedTypePolicy named 'myPolicy' because it violates the following Content Security Policy directive: "trusted-types ".�

Uncaught TypeError: Failed to execute 'createPolicy' on 'TrustedTypePolicyFactory': Policy "myPolicy" disallowed.

5 of 15

Injection Sinks

  • TrustedHTML (createHTML)
    • innerHTML
    • outerHTML
    • insertAdjacentHTML
    • iframe.srcdoc
    • document.write
    • document.writeln
    • DOMParser.parseFromString

  • TrustedScript (createScript)
    • script.textContent
    • eval
    • setTimeout
    • setInterval
    • new Function()
  • TrustedScriptURL (createScriptURL)
    • script.src
    • embed.src

6 of 15

e.g. HTMLをサニタイズし安全に作る

content-security-policy: trusted-types foo;

const escapeHTMLPolicy = trustedTypes.createPolicy("foo", {

createHTML: (string) =>

string

.replace(/&/g, "&amp;")

.replace(/</g, "&lt;")

.replace(/"/g, "&quot;")

.replace(/'/g, "&apos;"),

});

const escaped = escapeHTMLPolicy.createHTML("<img src=x onerror=alert(1)>");

el.innerHTML = escaped;

// bad

el.innerHTML = "<img src=x onerror=alert(1)>";

7 of 15

e.g. scriptの実行を阻止する

content-security-policy: require-trusted-types-for 'script'

eval("alert('Hello!');");

This document requires 'TrustedScript' assignment.

Uncaught EvalError: Refused to evaluate a string as JavaScript because this document requires 'Trusted Type' assignment.

content-security-policy: require-trusted-types-for 'script'; trusted-types foo;

const policy = trustedTypes.createPolicy("foo", { createScript: (string) => string });

const safeScript = policy.createScript("alert('Hello!');");

eval(safeScript);

8 of 15

V8 option via Node.js

$ cat script.js

$ eval("console.log('hi!')")

$ node script.js

$ hi!

$ node --disallow_code_generation_from_strings script.js # runtimeで実行中に落ちる

$ EvalError: Code generation from strings disallowed for this context

👍 (new Function("console.log('hi!')"))(); なども同様

9 of 15

Dynamic Code Brand Checks

�stage3

10 of 15

Motivation

> eval is Evil

> The eval function is the most misused feature of JavaScript. Avoid it.

> -- Douglas Crockford, "JavaScript: The Good Parts"

  • コードが肥大化していく中、特にnode_modules内にあるevalなどは実装者が把握しづらく、Trusted Typesや --disallow_code_generation_from_stringsはグローバルで無効化してしまうので、動かなくなってしまうケースがある
  • ホスト(実行環境)側にコンテキストを与え、より細かく信頼の決定を行えるようにすること => その決定がブランドチェックオブジェクトにより決定される可能性がある

11 of 15

Brand Check?

  • ブランドは偽装不可能な内部スロット( [Internal Slot]] )の存在を保証するためのマークであり、その有無の確認をブランドチェックという
  • この仕組みにより、正しいオブジェクトに対して正しいプロパティやメソッドの利用を保証する
  • ブランドが合わないと TypeError が発生する。

const map = new Map();

const obj = {};

obj.get = map.get;

try {

// [[MapData]] internal slot

console.log(obj.get('foo'));

} catch (error) {

// TypeError: Method Map.prototype.get called on incompatible receiver #<Object>

}

12 of 15

Spec Goal

HostEnsureCanCompileStrings( calleeRealm, parameterStrings, bodyString, direct )

👇

HostEnsureCanCompileStrings( calleeRealm, parameterStrings, bodyString, codeString, compilationType, parameterArgs, bodyArg )

仕様上の抽象操作の一つで、実行環境が文字列からECMAScriptコードに動的にコンパイルする際に呼び出されるフックであり、ブロックしたり呼び出していいかをチェックします

他にも言語仕様書で定義される抽象操作のHostGetCodeForEval、HostEnsureCanCompileStrings、PerformEval、CreateDynamicFunctionが追加・変更される

13 of 15

Goal

  • evalで文字列以外にもCodeLikeオブジェクトも受け入れれるようにする
    • TrustedScriptをevalで実行するため�
  • ホストへ移譲する際に型情報を渡す
    • 現状: calleeRealm, parameterStrings, bodyString, direct
    • HostEnsureCanCompileStringsへコンテキストを追加
      • codeString, compilationType, parameterArgs, bodyArg�
  • ホストへ移譲する際に実際に実行される最終的なコード文字列を渡す
    • 現状: パラメータ用のソースコードとボディ用のソースコードが分割された状態で渡る

14 of 15

Wrapping Up

  • Trusted Typesを利用することにより、DOMStringを許容するsinkからXSSを防ぎやすくなる
  • Node.js(V8)でも現状、evalやnew Functionを防ぐオプションはある

15 of 15

Stuff