fix-csp-compliance について
UI5 開発者向けのスキルです。インライン JavaScript を外部ファイルへ移し、動作を保ったまま `csp-unsafe-inline-script` 警告に対処します。
主な機能
- 01インライン JavaScript の外部化
- 02スクリプト内容の保持
- 03UI5 初期化への対応
- 04インラインイベント処理の置換
- 05テスト・動的スクリプトへの対応
活用シーン
- 01CSP リンター警告への対処
- 02UI5 起動コードの HTML 外部化
- 03HTML イベント属性の置換
- 04インラインテスト起動コードの再構成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ui5/ plugins-coding-agents --skill fix-csp-complianceインストール後、Claude Code で/fix-csp-complianceを入力して読み込まれたか確認します。
詳細
CSP 準拠の修正 - 安全でないインラインスクリプト
重要なルール
- インラインスクリプトの内容は絶対に削除しないでください。 必ず内容を外部の
.jsファイルに移し、インラインの<script>...</script>を<script src="filename.js"></script>に置き換えてください。簡単な設定オブジェクト、デバッグフラグ、使われていないように見えるコードも、すべて外部ファイルに移す必要があります。削除すると機能の退行につながります。 - ファイル名には、内容の目的に合った分かりやすい名前を使用してください(例:設定には
appConfig.js、初期化にはinit.js)。
このスキルは、UI5 リンターが検出するものの、コードを外部ファイルに再構成する必要があるため自動修正できない、コンテンツセキュリティポリシー(CSP)準拠の問題を修正します。
対応するリンタールール
| ルール ID | メッセージパターン | 重大度 | このスキルの対応 |
|---|---|---|---|
csp-unsafe-inline-script | 安全でないインラインスクリプトの使用 | 警告 | 外部 JS ファイルに移動 |
使用する場面
次のようなリンター出力が表示された場合に、このスキルを適用してください:
index.html:15:5 warning Use of unsafe inline script csp-unsafe-inline-script
test.html:20:5 warning Use of unsafe inline script csp-unsafe-inline-script
背景:CSP が重要な理由
コンテンツセキュリティポリシー(CSP)は、次の攻撃の防止に役立つセキュリティ機能です:
- クロスサイトスクリプティング(XSS)攻撃
- データインジェクション攻撃
- 不正なスクリプト実行
攻撃者が HTML の挿入に成功すると、悪意のある JavaScript も挿入できるため、インラインスクリプトは安全でないと見なされます。CSP に準拠したアプリでは script-src 'self' を使用し、インラインスクリプトをブロックします。
ドキュメント:コンテンツセキュリティポリシー
検出
リンターは、次の条件を満たす <script> タグにフラグを付けます:
src属性がない- インラインの JavaScript コンテンツがある
フラグが付く例:
<script>
console.log("Inline code");
</script>
<script type="text/javascript">
doSomething();
</script>
<script type="module">
import { foo } from './foo.js';
</script>
フラグが付かない例:
<script src="app.js"></script>
<script src="app.js">
// This content is ignored by browser anyway
</script>
<script type="text/xmldata">
<data>...</data>
</script>
修正方針
すべてのインラインスクリプトに対する修正方法は同じです。外部ファイルに移し、元の場所に <script src="..."> タグを追加します。 インラインスクリプトの内容は保持され、HTML 内ではなく別の .js ファイルに配置されます。インラインスクリプトの内容は絶対に削除せず、必ず外部ファイルに移してください。
1. 基本的なインラインスクリプト → 外部ファイル
問題:HTML 内のインライン JavaScript です。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My App</title>
<script>
window.myConfig = {
apiUrl: "/api/v1",
debug: true
};
</script>
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-async="true">
</script>
<script>
sap.ui.require(["my/app/init"], function(init) {
init.start();
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
修正方針:インラインスクリプトを外部ファイルに移します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My App</title>
<script src="config.js"></script>
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-async="true"
data-sap-ui-on-init="module:my/app/init">
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
// config.js
window.myConfig = {
apiUrl: "/api/v1",
debug: true
};
// my/app/init.js
sap.ui.define([], function() {
"use strict";
return {
start: function() {
// Initialization code
}
};
});
2. インライン初期化を含む UI5 ブートストラップ → data-sap-ui-on-init
問題:UI5 ブートストラップ後のインラインスクリプトです。
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-async="true"
data-sap-ui-resource-roots='{"my.app": "./"}'
data-sap-ui-compat-version="edge">
</script>
<script>
sap.ui.getCore().attachInit(function() {
sap.ui.require([
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
], function(Shell, ComponentContainer) {
new Shell({
app: new ComponentContainer({
name: "my.app",
async: true
})
}).placeAt("content");
});
});
</script>
修正方針:data-sap-ui-on-init 属性を使用します。
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-async="true"
data-sap-ui-resource-roots='{"my.app": "./"}'
data-sap-ui-compat-version="edge"
data-sap-ui-on-init="module:my/app/init">
</script>
// webapp/init.js
sap.ui.define([
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
], function(Shell, ComponentContainer) {
"use strict";
new Shell({
app: new ComponentContainer({
name: "my.app",
async: true
})
}).placeAt("content");
});
3. 設定データ → JSON またはモジュール
問題:インラインの設定オブジェクトです。
<script>
window.APP_CONFIG = {
apiEndpoint: "https://api.example.com",
features: {
darkMode: true,
analytics: false
}
};
</script>
修正方針 A:実行時に読み込む外部 JSON ファイルを使用します。
<script src="config.js"></script>
// config.js - loads JSON
(function() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "config.json", false); // Sync for config
xhr.send();
window.APP_CONFIG = JSON.parse(xhr.responseText);
})();
// config.json
{
"apiEndpoint": "https://api.example.com",
"features": {
"darkMode": true,
"analytics": false
}
}
修正方針 B:設定を含む UI5 モジュールを使用します。
// my/app/config.js
sap.ui.define([], function() {
"use strict";
return {
apiEndpoint: "https://api.example.com",
features: {
darkMode: true,
analytics: false
}
};
});
// Usage in other modules
sap.ui.define(["my/app/config"], function(config) {
console.log(config.apiEndpoint);
});
4. インラインイベントハンドラー → 外部スクリプト
問題:HTML 属性内のインラインイベントハンドラーです。
<button onclick="handleClick()">Click me</button>
<img src="logo.png" onerror="handleError(this)">
<body onload="init()">
修正方針:イベントリスナーを含む外部スクリプトを使用します。
<button id="myButton">Click me</button>
<img id="logo" src="logo.png">
<body>
// app.js
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("myButton").addEventListener("click", handleClick);
document.getElementById("logo").addEventListener("error", function() {
handleError(this);
});
init();
});
function handleClick() {
// Click handler
}
function handleError(element) {
// Error handler
}
function init() {
// Initialization
}
5. テスト用 HTML ファイル
問題:インラインスクリプトを含む QUnit テストファイルです。
<!DOCTYPE html>
<html>
<head>
<script src="resources/sap-ui-core.js"
data-sap-ui-async="true">
</script>
<script>
sap.ui.getCore().attachInit(function() {
sap.ui.require(["my/app/test/myTest"]);
});
</script>
</head>
<body>
<div id="qunit"></div>
</body>
</html>
修正方針:Test Starter を使用します(prefer-test-starter も修正されます)。
<!DOCTYPE html>
<html>
<head>
<script
src="resources/sap/ui/test/starter/runTest.js"
data-sap-ui-testsuite="test-resources/my/app/test/testsuite.qunit">
</script>
</head>
<body>
<div id="qunit"></div>
</body>
</html>
6. 動的なスクリプトコンテンツ
問題:スクリプトコンテンツが動的に生成されています。
<script>
var userId = "<%= user.id %>"; // Server-side template
var token = "<?php echo $token; ?>";
</script>
修正方針:データ属性または meta タグを使用します。
<meta name="user-id" content="<%= user.id %>">
<meta name="csrf-token" content="<?php echo $token; ?>">
<script src="app.js"></script>
// app.js
var userId = document.querySelector('meta[name="user-id"]').content;
var token = document.querySelector('meta[name="csrf-token"]').content;
実装手順
-
リンターの出力からすべてのインラインスクリプトを特定します
-
各スクリプトを分類します:
- UI5 の初期化 →
data-sap-ui-on-initを使用 - 設定 → 外部 JS ファイル
- イベントハンドラー →
addEventListenerを使用する外部スクリプト - テストのボイラープレート → Test Starter を使用
- UI5 の初期化 →
-
スクリプトの内容を保存する外部ファイルを作成します
-
外部ファイルを参照するようにHTML を更新します
-
機能が維持されていることを確認するため、アプリケーションをテストします
よくあるパターン
| インラインパターン | CSP 準拠の解決策 |
|---|---|
<script>code</script> | <script src="file.js"> |
インラインの data-sap-ui-on-init | data-sap-ui-on-init="module:path/to/init" |
onclick="fn()" | element.addEventListener("click", fn) |
onerror="fn()" | element.addEventListener("error", fn) |
onload="fn()" | DOMContentLoaded イベントリスナー |
| サーバーでレンダリングされた設定 | <meta> タグ + JS リーダー |
| QUnit のインラインブートストラップ | Test Starter の runTest.js |
注意事項
- CSP 準拠はエラーではなく警告です。一部の環境では必要ない場合があるためです
data-sap-ui-on-init属性は、AMD モジュール向けにmodule:path/to/module形式を受け付けます- サーバーでレンダリングされる動的な値には、
<meta>タグまたはdata-*属性を使用してください - テストファイルでは、CSP 準拠とベストプラクティスの両方を満たすために Test Starter を使用してください
- サードパーティライブラリによっては CSP の調整が必要な場合があります。ドキュメントを確認してください
- CSP の設定によっては、JSONP コールバックに特別な処理が必要となる場合があります