From ui5-modernization
Fixes Content Security Policy (CSP) compliance issues by moving inline scripts to external files, as reported by UI5 linter.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ui5-modernization:fix-csp-complianceThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. **NEVER delete inline script content.** Always extract it to an external `.js` file and replace the inline `<script>...</script>` with `<script src="filename.js"></script>`. Even trivial config objects, debug flags, or seemingly unused code must be externalized — removal is a functional regression.
.js file and replace the inline <script>...</script> with <script src="filename.js"></script>. Even trivial config objects, debug flags, or seemingly unused code must be externalized — removal is a functional regression.appConfig.js for configuration, init.js for initialization).This skill fixes Content Security Policy (CSP) compliance issues that the UI5 linter detects but cannot auto-fix because they require restructuring code into external files.
| Rule ID | Message Pattern | Severity | This Skill's Action |
|---|---|---|---|
csp-unsafe-inline-script | Use of unsafe inline script | Warning | Move to external JS file |
Apply this skill when you see linter output like:
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
Content Security Policy (CSP) is a security feature that helps prevent:
Inline scripts are considered unsafe because an attacker who manages to inject HTML can also inject malicious JavaScript. CSP-compliant apps use script-src 'self' which blocks inline scripts.
Documentation: Content Security Policy
The linter flags <script> tags that:
src attribute ANDFlagged:
<script>
console.log("Inline code"); <!-- Flagged -->
</script>
<script type="text/javascript">
doSomething(); <!-- Flagged -->
</script>
<script type="module">
import { foo } from './foo.js'; <!-- Flagged -->
</script>
Not flagged:
<script src="app.js"></script> <!-- External = OK -->
<script src="app.js">
// This content is ignored by browser anyway
</script>
<script type="text/xmldata">
<!-- Non-JS MIME type = OK -->
<data>...</data>
</script>
The fix for every inline script is the same: move it to an external file and add a <script src="..."> tag in its place. The content of the inline script is preserved — just in a separate .js file instead of inside the HTML. Never delete inline script content; always externalize it.
Problem: Inline JavaScript in HTML.
<!-- Before - index.html -->
<!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>
Fix Strategy: Move inline scripts to external files.
<!-- After - index.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
}
};
});
Problem: Inline script after UI5 bootstrap.
<!-- Before -->
<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>
Fix Strategy: Use data-sap-ui-on-init attribute.
<!-- After - index.html -->
<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");
});
Problem: Inline configuration object.
<!-- Before -->
<script>
window.APP_CONFIG = {
apiEndpoint: "https://api.example.com",
features: {
darkMode: true,
analytics: false
}
};
</script>
Fix Strategy A: External JSON file loaded at runtime.
<!-- After - Option A: JSON file -->
<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
}
}
Fix Strategy B: UI5 module with configuration.
// 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);
});
Problem: Inline event handlers in HTML attributes.
<!-- Before -->
<button onclick="handleClick()">Click me</button>
<img src="logo.png" onerror="handleError(this)">
<body onload="init()">
Fix Strategy: Use external script with event listeners.
<!-- After -->
<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
}
Problem: QUnit test files with inline scripts.
<!-- Before - myTest.qunit.html -->
<!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>
Fix Strategy: Use Test Starter (also fixes prefer-test-starter).
<!-- After - myTest.qunit.html -->
<!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>
Problem: Script content generated dynamically.
<!-- Before -->
<script>
var userId = "<%= user.id %>"; // Server-side template
var token = "<?php echo $token; ?>";
</script>
Fix Strategy: Use data attributes or meta tags.
<!-- After -->
<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;
Identify all inline scripts from linter output
Categorize each script:
data-sap-ui-on-initaddEventListenerCreate external files for the script content
Update HTML to reference external files
Test the application to ensure functionality is preserved
| Inline Pattern | CSP-Compliant Solution |
|---|---|
<script>code</script> | <script src="file.js"> |
data-sap-ui-on-init with inline | data-sap-ui-on-init="module:path/to/init" |
onclick="fn()" | element.addEventListener("click", fn) |
onerror="fn()" | element.addEventListener("error", fn) |
onload="fn()" | DOMContentLoaded event listener |
| Server-rendered config | <meta> tags + JS reader |
| QUnit inline bootstrap | Test Starter runTest.js |
data-sap-ui-on-init attribute accepts module:path/to/module format for AMD modules<meta> tags or data-* attributesclaude plugin install ui5-modernization@claude-plugins-officialRestricts what resources the browser may load or execute in a web application, reducing XSS risk. Useful for apps with user-generated content, third-party scripts, or inline JavaScript.
Enforces SAP UI5 coding standards including async loading, ComponentSupport, CSP compliance, OData binding, i18n, TypeScript events, CAP integration, and form layouts (Form with ColumnLayout).
Design and deploy Content-Security-Policy (CSP) to prevent XSS attacks and unauthorized resource loading.