mcpField
F
fix-csp-compliance

fix-csp-compliance

github.com/ui5安全44143 次安装1 周前更新
代码仓库

关于 fix-csp-compliance

面向 UI5 开发者的技能,通过将内联 JavaScript 移至外部文件来处理 `csp-unsafe-inline-script` 警告,同时保留原有代码行为。

主要功能

  1. 01将内联 JavaScript 外置
  2. 02保留脚本内容
  3. 03处理 UI5 初始化
  4. 04替换内联事件处理器
  5. 05涵盖测试与动态脚本

适用场景

  1. 01处理 CSP 检查警告
  2. 02将 UI5 启动代码移出 HTML
  3. 03替换 HTML 事件属性
  4. 04重构内联测试引导代码

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ui5/plugins-coding-agents --skill fix-csp-compliance

安装后在 Claude Code 中输入/fix-csp-compliance确认已加载。

详细介绍

修复 CSP 合规性问题 - 不安全的内联脚本

关键规则

  1. 绝不删除内联脚本内容。 始终将其提取到外部 .js 文件中,并将内联 <script>...</script> 替换为 <script src="filename.js"></script>。即使是简单的配置对象、调试标志或看似未使用的代码,也必须外置——删除会导致功能回退。
  2. 文件命名:使用能体现内容用途的描述性名称(例如,配置使用 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;

实施步骤

  1. 从 linter 输出中识别所有内联脚本

  2. 对每个脚本进行分类:

    • UI5 初始化 → 使用 data-sap-ui-on-init
    • 配置 → 外部 JS 文件
    • 事件处理程序 → 使用 addEventListener 的外部脚本
    • 测试样板代码 → 使用 Test Starter
  3. 为脚本内容创建外部文件

  4. 更新 HTML,使其引用外部文件

  5. 测试应用,确保功能得到保留

常见模式

内联模式符合 CSP 的解决方案
<script>code</script><script src="file.js">
带内联代码的 data-sap-ui-on-initdata-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 回调可能需要特殊处理

评论

支持 Markdown