fix: CSP 策略移除 unsafe-inline,改用 nonce 机制

- SecurityHeaders 中间件生成随机 nonce,注入 context
- BuildPageData/BuildAdminPageData 将 CSPNonce 传递给模板
- 所有 19 个模板文件 <script> 标签添加 nonce 属性
- 移除所有内联事件处理(onclick/onerror),改用事件监听
- 移除所有 javascript:void(0) 链接,改用 # 号 + preventDefault
- utils.js 添加全局 avatar 图片加载失败回退处理
- common.js 登出按钮添加 preventDefault
- audit.js 关闭弹窗按钮改用事件监听
This commit is contained in:
2026-06-02 16:10:23 +08:00
parent 9477155bcf
commit 53f18ca6ee
25 changed files with 122 additions and 55 deletions

View File

@ -338,18 +338,18 @@
{{if eq .ActiveTab "collections"}}
<!-- 左侧收藏夹列表 -->
<aside class="space-sidebar collection-sidebar">
<h2 class="space-sidebar-title collapsible" onclick="toggleFolderSection(this)">
<h2 class="space-sidebar-title collapsible" id="folderSectionToggle">
我创建的收藏夹
<svg class="collapse-arrow" viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
</h2>
<nav class="space-sidebar-nav" id="folder-nav">
<a href="javascript:void(0)" class="space-sidebar-link new-folder-btn" onclick="showNewFolderInput()">
<a href="#" class="space-sidebar-link new-folder-btn" id="showNewFolderBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
新建收藏夹
</a>
<div id="new-folder-input" style="display:none;">
<input type="text" id="new-folder-name" placeholder="收藏夹名称" maxlength="50" class="new-folder-input">
<button onclick="createFolder()" class="new-folder-submit">确定</button>
<button id="createFolderBtn" class="new-folder-submit">确定</button>
</div>
{{if .FoldersResult}}
{{range $f := .FoldersResult.Folders}}
@ -576,11 +576,11 @@
{{template "layout/footer.html" .}}
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}"></script>
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}" nonce="{{.CSPNonce}}"></script>
<!-- 访客空间:关注按钮交互 -->
{{if and (not .IsOwnSpace) (or (eq .ActiveTab "following") (eq .ActiveTab "followers"))}}
<script>
<script nonce="{{.CSPNonce}}">
(function() {
var cards = document.querySelectorAll('.space-user-card');
if (!cards.length) return;
@ -678,7 +678,7 @@
<!-- 收藏夹交互脚本 -->
{{if eq .ActiveTab "collections"}}
<script>
<script nonce="{{.CSPNonce}}">
function toggleFolderSection(el) {
el.parentElement.querySelector('#folder-nav').classList.toggle('collapsed');
el.classList.toggle('collapsed');
@ -701,6 +701,18 @@ function createFolder() {
});
}
// 事件绑定(替代内联 onclick满足 CSP 无 unsafe-inline
document.addEventListener('DOMContentLoaded', function() {
var toggle = document.getElementById('folderSectionToggle');
if (toggle) toggle.addEventListener('click', function() { toggleFolderSection(this); });
var showBtn = document.getElementById('showNewFolderBtn');
if (showBtn) showBtn.addEventListener('click', function(e) { e.preventDefault(); showNewFolderInput(); });
var createBtn = document.getElementById('createFolderBtn');
if (createBtn) createBtn.addEventListener('click', function() { createFolder(); });
});
// 隐私开关变更保存
document.querySelectorAll('.privacy-settings-grid .toggle-switch input').forEach(function(cb) {
cb.addEventListener('change', function() {
@ -712,7 +724,7 @@ document.querySelectorAll('.privacy-settings-grid .toggle-switch input').forEach
<!-- 设置页交互脚本 -->
{{if eq .ActiveTab "settings"}}
<script>
<script nonce="{{.CSPNonce}}">
// 隐私开关变更时自动保存到后端
document.querySelectorAll('#privacy-follow-list, #privacy-follower-list').forEach(function(cb) {
cb.addEventListener('change', function() {