关于 fix-csp-compliance
面向 UI5 开发者的技能,通过将内联 JavaScript 移至外部文件来处理 `csp-unsafe-inline-script` 警告,同时保留原有代码行为。
主要功能
- 01将内联 JavaScript 外置
- 02保留脚本内容
- 03处理 UI5 初始化
- 04替换内联事件处理器
- 05涵盖测试与动态脚本
适用场景
- 01处理 CSP 检查警告
- 02将 UI5 启动代码移出 HTML
- 03替换 HTML 事件属性
- 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 linter 检测到、但无法自动修复的内容安全策略(CSP)合规性问题,因为这些问题需要将代码重构到外部文件中。
处理的 Linter 规则
| 规则 ID | 消息模式 | 严重性 | 此技能的操作 |
|---|---|---|---|
csp-unsafe-inline-script | 使用不安全的内联脚本 | 警告 | 移至外部 JS 文件 |
使用时机
当你看到类似以下内容的 linter 输出时,应用此技能:
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',从而阻止内联脚本。
文档:内容安全策略
检测
Linter 会标记符合以下条件的 <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="..."> 标签。 内联脚本的内容会被保留,只是放在单独的 .js 文件中,而不是写在 HTML 里。绝不删除内联脚本内容;始终将其外置。
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;
实施步骤
-
从 linter 输出中识别所有内联脚本
-
对每个脚本进行分类:
- 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-*属性 - 测试文件应使用 Test Starter,以符合 CSP 并遵循最佳实践
- 某些第三方库可能需要调整 CSP 设置,请查阅其文档
- 在 CSP 配置中,JSONP 回调可能需要特殊处理