Add CSP to local web UI forbidding JavaScript eval()

This commit is contained in:
Max Berger
2026-03-18 21:04:50 +01:00
parent 238e3f9529
commit 99e9b7ec56
5 changed files with 34 additions and 14 deletions

View File

@@ -198,7 +198,10 @@ def _serve_traversable(
return NOT_FOUND
content_type = MIMETYPES.get(
os.path.splitext(traversable.name)[1].lower(), FALLBACK_MIMETYPE)
headers = {"Content-Type": content_type}
headers = {
"Content-Type": content_type,
"Content-Security-Policy": "default-src 'self'; object-src 'none'"
}
if isinstance(traversable, pathlib.Path):
headers["Last-Modified"] = time.strftime(
"%a, %d %b %Y %H:%M:%S GMT",

View File

@@ -112,6 +112,7 @@ main {
#logoutview span {
width: calc(100% - 60px);
display: inline-block;
word-wrap: break-word;
}
#logoutview a {
@@ -191,6 +192,7 @@ main {
font-size: 1em;
max-height: 130px;
overflow: overlay;
word-wrap: break-word;
}
#collectionsscene article:hover ul {
@@ -518,3 +520,7 @@ button.inline {
margin: 0 2px;
width: 1.4em;
}
.hidden {
display: none !important;
}

View File

@@ -14,17 +14,12 @@
<title>Radicale Web Interface</title>
<link href="css/main.css" type="text/css" media="screen" rel="stylesheet">
<link href="css/icon.png" type="image/png" rel="icon">
<style>
.hidden {
display: none !important;
}
</style>
<script type="module" src="js/main.js"></script>
</head>
<body>
<nav id="logoutview" class="hidden">
<span data-name="user" style="word-wrap:break-word;"></span>
<span data-name="user"></span>
<a href="#" class="green" data-name="refresh" title="Refresh">Refresh</a>
<a href="#" class="red" data-name="logout" title="Logout">Logout</a>
</nav>
@@ -86,8 +81,8 @@
<span data-name="WEBCAL">Webcal</span>
</small>
<small data-name="contentcount"></small>
<input type="text" data-name="url" value="" readonly="" onfocus="this.setSelectionRange(0, 99999);">
<p data-name="description" style="word-wrap:break-word;">Description</p>
<input type="text" data-name="url" value="" readonly="" class="selectall">
<p data-name="description">Description</p>
<ul>
<li>
<a href="" title="Download" class="green" data-name="download">
@@ -162,7 +157,7 @@
</td>
<td><span class="pill" data-name="rw">rw</span><span class="pill" data-name="ro">ro</span></td>
<td><input type="text" data-name="pathortoken" value="" readonly=""
onfocus="this.setSelectionRange(0, 99999);" class="inline"></td>
class="inline selectall"></td>
<td>
<button type="button" class="red inline" data-name="delete"><img src="css/icons/delete.svg"
class="small_icon" alt="Delete"></button>
@@ -187,7 +182,7 @@
</td>
<td><span class="pill" data-name="rw">rw</span><span class="pill" data-name="ro">ro</span></td>
<td><input type="text" data-name="pathortoken" value="" readonly=""
onfocus="this.setSelectionRange(0, 99999);" class="inline"></td>
class="inline selectall"></td>
<td>
<button type="button" class="red inline" data-name="delete"><img src="css/icons/delete.svg"
class="small_icon" alt="Delete"></button>
@@ -225,7 +220,7 @@
<tbody data-name="incomingsharesbody">
<tr data-name="incomingsharerowtemplate" class="hidden">
<td><input type="text" data-name="pathortoken" value="" readonly=""
onfocus="this.setSelectionRange(0, 99999);" class="inline"></td>
class="inline selectall"></td>
<td data-name="owner"></td>
<td data-name="permissions"><span class="pill" data-name="rw">rw</span><span class="pill"
data-name="ro">ro</span></td>

View File

@@ -19,12 +19,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { LoginScene } from "./scenes/LoginScene.js";
import { LoadingScene } from "./scenes/LoadingScene.js";
import { LoginScene } from "./scenes/LoginScene.js";
import { push_scene } from "./scenes/scene_manager.js";
import { setupSelectAll } from "./utils/misc.js";
// Add selection handler for input fields with 'selectall' class.
setupSelectAll();
// Hide startup loading message
// This works because the LoadingScene is the one that is already active in index.html,
// and all other scenes are hidden.
new LoadingScene().hide();
push_scene(new LoginScene());
push_scene(new LoginScene());

View File

@@ -108,3 +108,14 @@ export function bytesToHumanReadable(bytes) {
i = Math.min(i, units.length - 1);
return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + ' ' + units[i];
}
/**
* Add selection handler for input fields with 'selectall' class.
*/
export function setupSelectAll() {
document.addEventListener("focusin", (event) => {
if (event.target instanceof HTMLInputElement && event.target.classList.contains("selectall")) {
event.target.setSelectionRange(0, 99999);
}
});
}