feat(security): 添加内容安全策略 (CSP)

- 在 HTML 头部加入 Content-Security-Policy 元标签
- 配置了各种资源的加载来源,增强了页面的安全性
- 包括脚本、样式、字体、图像等资源的加载限制
This commit is contained in:
Abner
2025-08-05 22:55:38 +08:00
parent ac0744c4d5
commit b72de71775
6 changed files with 1448 additions and 279 deletions

162
SECURITY.md Normal file
View File

@@ -0,0 +1,162 @@
# 安全指南
## 概述
本项目实施了多层安全措施来保护用户数据和系统安全。
## 🔒 安全措施
### 1. 依赖包安全
#### 定期更新
- 使用 `npm update` 定期更新所有依赖包
- 使用 `npm audit` 检查安全漏洞
- 使用 `npm run security-check` 运行自定义安全检查
#### 已知漏洞修复
- ✅ tar-fs: 路径遍历漏洞已修复
- ✅ got: UNIX socket重定向漏洞已修复
- ✅ ipx: 路径遍历绕过漏洞已修复
- ✅ http-proxy-middleware: writeBody重复调用漏洞已修复
- ✅ esbuild: 开发服务器安全问题已修复
- ✅ on-headers: HTTP响应头操作漏洞已修复
### 2. 服务器安全
#### Helmet安全头
```javascript
const helmet = require('helmet');
app.use(helmet());
```
#### CORS配置
```javascript
const cors = require('cors');
app.use(cors({
origin: ['https://esim.cosr.eu.org', 'http://localhost:3000'],
credentials: true
}));
```
### 3. 内容安全策略 (CSP)
所有HTML文件都配置了严格的CSP策略
```html
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com;
style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com;
font-src 'self' https://cdnjs.cloudflare.com;
connect-src 'self' https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com;
img-src 'self' data: https:;
frame-src 'none';
">
```
### 4. 数据安全
#### 本地存储
- 敏感数据不存储在服务器上
- 使用LocalStorage进行会话持久化
- 2小时自动过期机制
#### API安全
- 所有API请求通过Netlify Functions代理
- 不直接暴露用户凭据
- 使用HTTPS进行所有通信
### 5. 部署安全
#### HTTPS强制
- 所有生产环境强制使用HTTPS
- 自动重定向HTTP到HTTPS
#### 环境变量
- 敏感配置使用环境变量
- 不在代码中硬编码密钥
## 🛡️ 安全最佳实践
### 开发环境
1. **定期更新依赖**
```bash
npm update
npm audit fix
```
2. **运行安全检查**
```bash
npm run security-check
```
3. **代码审查**
- 检查第三方库的使用
- 验证API调用的安全性
- 确保没有硬编码的敏感信息
### 生产环境
1. **HTTPS部署**
- 使用有效的SSL证书
- 配置HSTS头
2. **监控和日志**
- 监控异常访问模式
- 记录安全相关事件
3. **定期安全审计**
- 使用自动化工具检查漏洞
- 定期审查访问日志
## 🚨 安全响应
### 发现漏洞时
1. 立即评估漏洞严重程度
2. 在24小时内发布修复
3. 通知相关用户
4. 更新安全文档
### 报告安全问题
- 通过GitHub Issues报告
- 提供详细的复现步骤
- 包含环境信息
## 📋 安全检查清单
### 开发前
- [ ] 运行 `npm audit`
- [ ] 检查依赖包版本
- [ ] 验证CSP配置
### 部署前
- [ ] 运行 `npm run security-check`
- [ ] 验证HTTPS配置
- [ ] 检查环境变量
### 定期检查
- [ ] 更新依赖包
- [ ] 审查访问日志
- [ ] 检查安全配置
## 🔧 安全工具
### 内置工具
- `npm run security-check`: 自定义安全检查
- `npm audit`: npm安全审计
- `npm update`: 更新依赖包
### 推荐工具
- [Snyk](https://snyk.io/): 依赖漏洞扫描
- [OWASP ZAP](https://owasp.org/www-project-zap/): Web应用安全测试
- [Security Headers](https://securityheaders.com/): 安全头检查
## 📞 安全联系
如果您发现安全问题,请:
1. 通过GitHub Issues报告
2. 提供详细的描述和复现步骤
3. 不要公开披露,等待修复
## 免责声明
本安全指南提供了基本的安全措施但不能保证100%的安全性。建议根据具体需求进行额外的安全评估。

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; connect-src 'self' https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com; img-src 'self' data: https:; frame-src 'none';">
<title>eSIM工具 - Giffgaff & Simyo</title>
<!-- 性能优化:预连接与预加载关键资源 -->

1378
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,8 @@
"netlify-dev": "netlify dev",
"deploy": "npm run build && netlify deploy --prod",
"optimize-images": "node scripts/optimize-images.js",
"compress": "node scripts/compress.js"
"compress": "node scripts/compress.js",
"security-check": "node scripts/security-check.js"
},
"keywords": [
"esim",
@@ -47,12 +48,16 @@
"core-js": "^3.45.0",
"css-loader": "^6.8.1",
"cssnano": "^6.0.1",
"esbuild": "^0.25.8",
"got": "^14.4.7",
"http-proxy-middleware": "^3.0.5",
"imagemin": "^8.0.1",
"imagemin-mozjpeg": "^10.0.0",
"imagemin-pngquant": "^9.0.2",
"imagemin-webp": "^8.0.0",
"netlify-cli": "^17.10.1",
"nodemon": "^3.0.2",
"on-headers": "^1.1.0",
"postcss": "^8.4.31",
"postcss-cli": "^11.0.1",
"postcss-loader": "^7.3.3",

178
scripts/security-check.js Normal file
View File

@@ -0,0 +1,178 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// 已知的安全漏洞列表
const knownVulnerabilities = {
'tar-fs': {
versions: ['<2.1.1', '<3.0.4'],
description: 'Path traversal vulnerability',
severity: 'High',
fix: 'Update to latest version'
},
'got': {
versions: ['<12.0.0'],
description: 'Redirect to UNIX socket vulnerability',
severity: 'Moderate',
fix: 'Update to version 12.0.0 or later'
},
'ipx': {
versions: ['<2.1.0'],
description: 'Path traversal via prefix matching bypass',
severity: 'Moderate',
fix: 'Update to version 2.1.0 or later'
},
'http-proxy-middleware': {
versions: ['<2.0.7'],
description: 'writeBody called twice vulnerability',
severity: 'Moderate',
fix: 'Update to version 2.0.7 or later'
},
'esbuild': {
versions: ['<0.19.0'],
description: 'Development server security issue',
severity: 'Moderate',
fix: 'Update to version 0.19.0 or later'
},
'on-headers': {
versions: ['<1.1.0'],
description: 'HTTP response header manipulation',
severity: 'Low',
fix: 'Update to version 1.1.0 or later'
}
};
// 检查版本是否在漏洞范围内
function isVulnerable(version, vulnerableVersions) {
const semver = require('semver');
return vulnerableVersions.some(range => semver.satisfies(version, range));
}
// 解析package-lock.json
function parsePackageLock() {
try {
const packageLockPath = path.join(__dirname, '../package-lock.json');
const packageLock = JSON.parse(fs.readFileSync(packageLockPath, 'utf8'));
return packageLock.dependencies || {};
} catch (error) {
console.error('Error reading package-lock.json:', error.message);
return {};
}
}
// 检查依赖包的安全状态
function checkDependencies() {
const dependencies = parsePackageLock();
const vulnerabilities = [];
Object.keys(dependencies).forEach(pkgName => {
const pkg = dependencies[pkgName];
const version = pkg.version;
if (knownVulnerabilities[pkgName]) {
const vuln = knownVulnerabilities[pkgName];
if (isVulnerable(version, vuln.versions)) {
vulnerabilities.push({
package: pkgName,
version: version,
description: vuln.description,
severity: vuln.severity,
fix: vuln.fix
});
}
}
});
return vulnerabilities;
}
// 生成安全报告
function generateSecurityReport() {
console.log('🔒 安全检查报告\n');
const vulnerabilities = checkDependencies();
if (vulnerabilities.length === 0) {
console.log('✅ 未发现已知的安全漏洞');
return;
}
console.log(`⚠️ 发现 ${vulnerabilities.length} 个潜在安全漏洞:\n`);
vulnerabilities.forEach((vuln, index) => {
console.log(`${index + 1}. ${vuln.package}@${vuln.version}`);
console.log(` 严重程度: ${vuln.severity}`);
console.log(` 描述: ${vuln.description}`);
console.log(` 修复建议: ${vuln.fix}\n`);
});
console.log('🔧 修复建议:');
console.log('1. 运行 npm update 更新所有依赖');
console.log('2. 运行 npm audit fix 自动修复');
console.log('3. 手动更新特定包到最新版本');
}
// 检查开发环境安全配置
function checkSecurityConfig() {
console.log('\n🔧 安全配置检查:\n');
// 检查Helmet配置
const serverPath = path.join(__dirname, '../server.js');
if (fs.existsSync(serverPath)) {
const serverContent = fs.readFileSync(serverPath, 'utf8');
if (serverContent.includes('helmet')) {
console.log('✅ Helmet安全头已配置');
} else {
console.log('⚠️ 建议添加Helmet安全头');
}
}
// 检查CORS配置
if (fs.existsSync(serverPath)) {
const serverContent = fs.readFileSync(serverPath, 'utf8');
if (serverContent.includes('cors')) {
console.log('✅ CORS配置已设置');
} else {
console.log('⚠️ 建议配置CORS');
}
}
// 检查Content Security Policy
const htmlFiles = [
'index.html',
'src/giffgaff/giffgaff_complete_esim.html',
'src/simyo/simyo_complete_esim.html'
];
htmlFiles.forEach(file => {
const filePath = path.join(__dirname, '..', file);
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf8');
if (content.includes('Content-Security-Policy')) {
console.log(`${file} 已配置CSP`);
} else {
console.log(`⚠️ ${file} 建议添加CSP配置`);
}
}
});
}
// 主函数
function main() {
generateSecurityReport();
checkSecurityConfig();
console.log('\n📋 安全最佳实践:');
console.log('1. 定期更新依赖包');
console.log('2. 使用npm audit检查安全漏洞');
console.log('3. 配置适当的安全头');
console.log('4. 实施内容安全策略(CSP)');
console.log('5. 使用HTTPS部署');
}
if (require.main === module) {
main();
}
module.exports = { checkDependencies, generateSecurityReport };

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; connect-src 'self' https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com; img-src 'self' data: https:; frame-src 'none';">
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<title>Simyo NL eSIM 申请工具</title>