fix(sentry): 修复 CSP 内联脚本问题并增强敏感数据脱敏

- 新增 sentry-config.js 外链脚本替代 HTML 内联配置
- 修改 HTML 文件引用外链脚本以符合 CSP 策略
- 重写 inject-sentry-config.js 仅处理配置文件
- 添加 sentry-sdk-loaded 事件触发,修复 SDK 超时覆盖问题
- 增强 beforeSend 脱敏逻辑,覆盖 query_string 和更多敏感参数
This commit is contained in:
Abner
2025-12-13 23:36:44 +08:00
parent a82603acdf
commit 65068df5dd
5 changed files with 76 additions and 48 deletions

View File

@@ -2,7 +2,7 @@
/**
* Sentry 配置注入脚本
*
* 在构建时将 Sentry 环境变量注入到 HTML 文件中
* 在构建时将 Sentry 环境变量注入到 sentry-config.js 文件中
* 用法:在 npm run build 中调用此脚本
*/
@@ -14,48 +14,39 @@ const SENTRY_ENVIRONMENT = process.env.SENTRY_ENVIRONMENT || 'production';
const SENTRY_RELEASE = process.env.SENTRY_RELEASE ||
(process.env.COMMIT_REF ? `esim-tools@${process.env.COMMIT_REF.slice(0, 7)}` : 'esim-tools@unknown');
// 要处理的 HTML 文件列表
const htmlFiles = [
'dist/index.html',
'dist/src/giffgaff/giffgaff_modular.html',
'dist/src/giffgaff/giffgaff_complete_esim.html',
'dist/src/simyo/simyo_modular.html',
];
// 要处理的配置文件(改为处理独立的 JS 配置文件,而非 HTML 内联脚本)
const configFile = 'dist/src/js/sentry-config.js';
console.log('[Sentry Config] 开始注入配置...');
console.log(` DSN: ${SENTRY_DSN ? '已配置' : '未配置'}`);
console.log(` Environment: ${SENTRY_ENVIRONMENT}`);
console.log(` Release: ${SENTRY_RELEASE}`);
let processedCount = 0;
const fullPath = path.join(process.cwd(), configFile);
htmlFiles.forEach(filePath => {
const fullPath = path.join(process.cwd(), filePath);
if (!fs.existsSync(fullPath)) {
console.error(` ✗ 错误: ${configFile} 不存在`);
console.log('[Sentry Config] 失败,请确保构建流程正确复制了 sentry-config.js');
process.exit(1);
}
if (!fs.existsSync(fullPath)) {
console.log(` 跳过: ${filePath} (文件不存在)`);
return;
}
let content = fs.readFileSync(fullPath, 'utf8');
let content = fs.readFileSync(fullPath, 'utf8');
// 替换配置占位符
content = content.replace(
/window\.SENTRY_DSN\s*=\s*['"][^'"]*['"]/,
`window.SENTRY_DSN = '${SENTRY_DSN}'`
);
content = content.replace(
/window\.SENTRY_ENVIRONMENT\s*=\s*['"][^'"]*['"]/,
`window.SENTRY_ENVIRONMENT = '${SENTRY_ENVIRONMENT}'`
);
content = content.replace(
/window\.SENTRY_RELEASE\s*=\s*['"][^'"]*['"]/,
`window.SENTRY_RELEASE = '${SENTRY_RELEASE}'`
);
// 替换配置占位符
content = content.replace(
/window\.SENTRY_DSN\s*=\s*['"][^'"]*['"]/,
`window.SENTRY_DSN = '${SENTRY_DSN}'`
);
content = content.replace(
/window\.SENTRY_ENVIRONMENT\s*=\s*['"][^'"]*['"]/,
`window.SENTRY_ENVIRONMENT = '${SENTRY_ENVIRONMENT}'`
);
content = content.replace(
/window\.SENTRY_RELEASE\s*=\s*['"][^'"]*['"]/,
`window.SENTRY_RELEASE = '${SENTRY_RELEASE}'`
);
fs.writeFileSync(fullPath, content, 'utf8');
console.log(` ✓ 已处理: ${configFile}`);
fs.writeFileSync(fullPath, content, 'utf8');
console.log(` ✓ 已处理: ${filePath}`);
processedCount++;
});
console.log(`[Sentry Config] 完成,共处理 ${processedCount} 个文件`);
console.log('[Sentry Config] 完成');

View File

@@ -7,12 +7,8 @@
<meta name="description" content="Giffgaff eSIM 工具,支持 OAuth 登录、MFA 验证、二维码生成与设备更换。">
<!-- Sentry 错误监控配置(必须在其他脚本之前) -->
<script>
// Sentry 配置 - 在 Netlify 部署时会通过环境变量注入
window.SENTRY_DSN = ''; // 在 Netlify 环境变量中配置
window.SENTRY_ENVIRONMENT = 'production';
window.SENTRY_RELEASE = 'esim-tools@unknown';
</script>
<!-- 使用外链脚本以符合 CSP 策略 -->
<script src="/src/js/sentry-config.js"></script>
<script src="/src/js/sentry-loader.js"></script>
<!-- 预连接优化 -->

11
src/js/sentry-config.js Normal file
View File

@@ -0,0 +1,11 @@
/**
* Sentry 配置文件
*
* 此文件用于设置 Sentry 配置变量,在构建时由 inject-sentry-config.js 注入实际值
* 使用外链脚本而非内联脚本,以符合 CSP 策略
*/
// Sentry 配置 - 构建时会被注入实际值
window.SENTRY_DSN = '';
window.SENTRY_ENVIRONMENT = 'production';
window.SENTRY_RELEASE = 'esim-tools@unknown';

View File

@@ -113,7 +113,35 @@
// 敏感数据过滤
beforeSend: function(event) {
// 脱敏 URL 中的敏感查询参数
function sanitizeQueryString(url) {
if (!url) return url;
var sensitiveParams = ['token', 'key', 'password', 'code', 'state', 'access_token', 'refresh_token', 'auth', 'secret'];
try {
var urlObj = new URL(url, window.location.origin);
sensitiveParams.forEach(function(param) {
if (urlObj.searchParams.has(param)) {
urlObj.searchParams.set(param, '***');
}
});
return urlObj.toString();
} catch (e) {
// URL 解析失败,使用正则替换
return url.replace(/([?&])(token|key|password|code|state|access_token|refresh_token|auth|secret)=[^&]*/gi, '$1$2=***');
}
}
if (event.request) {
// 脱敏 query_string
if (event.request.query_string) {
event.request.query_string = event.request.query_string
.replace(/(token|key|password|code|state|access_token|refresh_token|auth|secret)=[^&]*/gi, '$1=***');
}
// 脱敏 URL
if (event.request.url) {
event.request.url = sanitizeQueryString(event.request.url);
}
// 删除敏感 cookies 和 headers
delete event.request.cookies;
if (event.request.headers) {
delete event.request.headers['authorization'];
@@ -129,7 +157,11 @@
.replace(/token[=:]\s*[^\s,]+/gi, 'token=***')
.replace(/key[=:]\s*[^\s,]+/gi, 'key=***')
.replace(/password[=:]\s*[^\s,]+/gi, 'password=***')
.replace(/cookie[=:]\s*[^\s,]+/gi, 'cookie=***');
.replace(/cookie[=:]\s*[^\s,]+/gi, 'cookie=***')
.replace(/code[=:]\s*[^\s,]+/gi, 'code=***')
.replace(/state[=:]\s*[^\s,]+/gi, 'state=***')
.replace(/access_token[=:]\s*[^\s,]+/gi, 'access_token=***')
.replace(/refresh_token[=:]\s*[^\s,]+/gi, 'refresh_token=***');
}
});
}
@@ -200,6 +232,8 @@
console.log('[Sentry Loader] SDK 加载成功');
// 加载成功后立即初始化
initSentry();
// 触发自定义事件,通知其他模块 SDK 已加载
window.dispatchEvent(new Event('sentry-sdk-loaded'));
};
script.onerror = function() {

View File

@@ -7,12 +7,8 @@
<meta name="description" content="Simyo eSIM 工具,支持设备更换、短信验证码、二维码生成与安装确认,帮助您快速管理荷兰 Simyo eSIM。">
<!-- Sentry 错误监控配置(必须在其他脚本之前) -->
<script>
// Sentry 配置 - 在 Netlify 部署时会通过环境变量注入
window.SENTRY_DSN = ''; // 在 Netlify 环境变量中配置
window.SENTRY_ENVIRONMENT = 'production';
window.SENTRY_RELEASE = 'esim-tools@unknown';
</script>
<!-- 使用外链脚本以符合 CSP 策略 -->
<script src="/src/js/sentry-config.js"></script>
<script src="/src/js/sentry-loader.js"></script>
<!-- 预连接优化 -->