# 安全测试流水线技术价值与实战挑战

在现代软件开发中,安全测试流水线已不再是可选项,而是企业级应用的必备基础设施。通过将 ZAP(Zed Attack Proxy)与 CI/CD 系统深度集成,我们可以实现从 "安全事后检查" 到 "安全全程嵌入" 的转变。

# 核心技术价值

  1. 安全左移实践:将安全测试前移到开发阶段,降低修复成本
  2. 自动化安全回归:每次代码变更都触发安全检查,防止安全退化
  3. 量化安全指标:通过持续扫描建立安全基线,实现安全状况可度量
  4. 团队安全意识:通过 CI 失败反馈,提升开发团队安全敏感度

# 实战挑战与解决方案

  • 挑战 1:误报率控制 - 通过智能规则配置和上下文感知降低误报
  • 挑战 2:性能影响 - 通过增量扫描和并行执行优化构建时间
  • 挑战 3:维护成本 - 通过模板化配置和自动化更新降低维护负担

# Juice Shop 安全测试架构分析

# 应用安全特征

Juice Shop 作为一个故意设计的安全训练平台,包含了 OWASP Top 10 中的大部分漏洞类型,这使其成为安全测试流水线的理想目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Juice Shop 漏洞分布分析
const vulnerabilityProfile = {
injection: ['SQL注入', 'NoSQL注入', 'LDAP注入'],
xss: ['反射型XSS', '存储型XSS', 'DOM型XSS'],
auth: ['弱认证', '会话管理', '权限绕过'],
sensitive: ['敏感数据暴露', '配置错误'],
access: ['访问控制缺失', '权限提升'],
crypto: ['弱加密', '随机数问题'],
injection2: ['命令注入', '路径遍历'],
xxe: ['XXE外部实体注入'],
security: ['安全配置错误', '依赖漏洞'],
logging: ['日志不足', '监控缺失']
};

# 测试目标分层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 安全测试目标分层策略
* 根据业务影响和攻击概率划分优先级
*/
class SecurityTestTargets {
constructor() {
this.criticalPaths = [
'/rest/user/login', // 认证接口
'/rest/basket/order', // 订单接口
'/api/Products', // 产品接口
'/rest/file-upload' // 文件上传
];

this.highRiskPages = [
'/#/login', // 登录页面
'/#/register', // 注册页面
'/#/basket', // 购物车
'/#/profile' // 用户资料
];

this.mediumRiskPages = [
'/#/', // 首页
'/#/search', // 搜索页面
'/#/about' // 关于页面
];
}

/**
* 获取测试优先级配置
* @returns {Object} 优先级配置对象
*/
getTestPriority() {
return {
critical: {
paths: this.criticalPaths,
scanType: 'active',
threshold: 0, // 任何问题都失败
timeout: 300000 // 5分钟超时
},
high: {
paths: this.highRiskPages,
scanType: 'baseline',
threshold: 2, // 允许2个低风险问题
timeout: 180000 // 3分钟超时
},
medium: {
paths: this.mediumRiskPages,
scanType: 'passive',
threshold: 5, // 允许5个信息级问题
timeout: 120000 // 2分钟超时
}
};
}
}

# ZAP 安全测试框架深度实现

# 基础扫描引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/**
* ZAP 扫描引擎核心实现
* 提供统一的扫描接口和配置管理
*/
class ZAPScanner {
constructor(config = {}) {
this.config = {
zapHost: config.zapHost || 'localhost',
zapPort: config.zapPort || 8080,
apiKey: config.apiKey || '',
targetUrl: config.targetUrl || 'http://localhost:3000',
...config
};

this.scanResults = {
baseline: null,
active: null,
alerts: [],
statistics: {}
};
}

/**
* 初始化 ZAP 连接
* @returns {Promise<boolean>} 连接是否成功
*/
async initialize() {
try {
const response = await this.makeRequest('GET', '/JSON/core/view/version/');
console.log('ZAP版本:', response.version);
return true;
} catch (error) {
console.error('ZAP连接失败:', error.message);
return false;
}
}

/**
* 执行基线扫描
* @param {Object} options 扫描选项
* @returns {Promise<Object>} 扫描结果
*/
async runBaselineScan(options = {}) {
const scanConfig = {
target: this.config.targetUrl,
maxDepth: options.maxDepth || 5,
maxChildren: options.maxChildren || 10,
contextName: options.contextName || 'default',
...options
};

try {
console.log(`开始基线扫描: ${scanConfig.target}`);

// 1. 启动 Spider
const spiderId = await this.startSpider(scanConfig);
await this.waitForSpider(spiderId);

// 2. 执行被动扫描
await this.waitForPassiveScan();

// 3. 生成报告
const results = await this.generateReport('baseline');

this.scanResults.baseline = results;
return results;

} catch (error) {
console.error('基线扫描失败:', error);
throw error;
}
}

/**
* 执行主动扫描
* @param {Object} options 扫描选项
* @returns {Promise<Object>} 扫描结果
*/
async runActiveScan(options = {}) {
const scanConfig = {
target: this.config.targetUrl,
policy: options.policy || 'Default Policy',
maxScanDuration: options.maxScanDuration || 3600000, // 1小时
...options
};

try {
console.log(`开始主动扫描: ${scanConfig.target}`);

// 1. 创建扫描上下文
const contextId = await this.createContext(scanConfig);

// 2. 添加认证信息(如果需要)
if (scanConfig.auth) {
await this.setAuthentication(contextId, scanConfig.auth);
}

// 3. 启动主动扫描
const scanId = await this.startActiveScan(contextId, scanConfig);
await this.waitForActiveScan(scanId);

// 4. 生成报告
const results = await this.generateReport('active');

this.scanResults.active = results;
return results;

} catch (error) {
console.error('主动扫描失败:', error);
throw error;
}
}

/**
* 启动 Spider 爬虫
* @param {Object} config 扫描配置
* @returns {Promise<string>} Spider ID
*/
async startSpider(config) {
const params = new URLSearchParams({
url: config.target,
maxDepth: config.maxDepth,
maxChildren: config.maxChildren,
contextName: config.contextName
});

const response = await this.makeRequest('GET', `/JSON/spider/action/scan/?${params}`);
return response.scan;
}

/**
* 等待 Spider 完成
* @param {string} spiderId Spider ID
* @returns {Promise<void>}
*/
async waitForSpider(spiderId) {
return new Promise((resolve, reject) => {
const checkStatus = async () => {
try {
const status = await this.makeRequest('GET', `/JSON/spider/view/status/?scanId=${spiderId}`);
const progress = parseInt(status.status);

console.log(`Spider进度: ${progress}%`);

if (progress >= 100) {
resolve();
} else {
setTimeout(checkStatus, 5000); // 5秒后再次检查
}
} catch (error) {
reject(error);
}
};

checkStatus();
});
}

/**
* 等待被动扫描完成
* @returns {Promise<void>}
*/
async waitForPassiveScan() {
return new Promise((resolve, reject) => {
const checkStatus = async () => {
try {
const recordsToScan = await this.makeRequest('GET', '/JSON/pscan/view/recordsToScan/');
const count = parseInt(recordsToScan.recordsToScan);

console.log(`被动扫描剩余: ${count} 条记录`);

if (count === 0) {
resolve();
} else {
setTimeout(checkStatus, 3000); // 3秒后再次检查
}
} catch (error) {
reject(error);
}
};

checkStatus();
});
}

/**
* 启动主动扫描
* @param {string} contextId 上下文ID
* @param {Object} config 扫描配置
* @returns {Promise<string>} 扫描ID
*/
async startActiveScan(contextId, config) {
const params = new URLSearchParams({
url: config.target,
contextId: contextId,
policyId: config.policy,
maxScanDuration: config.maxScanDuration
});

const response = await this.makeRequest('GET', `/JSON/ascan/action/scan/?${params}`);
return response.scan;
}

/**
* 等待主动扫描完成
* @param {string} scanId 扫描ID
* @returns {Promise<void>}
*/
async waitForActiveScan(scanId) {
return new Promise((resolve, reject) => {
const checkStatus = async () => {
try {
const status = await this.makeRequest('GET', `/JSON/ascan/view/status/?scanId=${scanId}`);
const progress = parseInt(status.status);

console.log(`主动扫描进度: ${progress}%`);

if (progress >= 100) {
resolve();
} else {
setTimeout(checkStatus, 10000); // 10秒后再次检查
}
} catch (error) {
reject(error);
}
};

checkStatus();
});
}

/**
* 生成扫描报告
* @param {string} scanType 扫描类型
* @returns {Promise<Object>} 报告数据
*/
async generateReport(scanType) {
try {
// 获取告警信息
const alerts = await this.makeRequest('GET', '/JSON/core/view/alerts/');

// 获取统计信息
const stats = await this.makeRequest('GET', '/JSON/stats/view/sites/');

const report = {
scanType: scanType,
timestamp: new Date().toISOString(),
target: this.config.targetUrl,
alerts: alerts.alerts || [],
statistics: stats,
summary: this.generateSummary(alerts.alerts || [])
};

return report;

} catch (error) {
console.error('生成报告失败:', error);
throw error;
}
}

/**
* 生成扫描摘要
* @param {Array} alerts 告警列表
* @returns {Object} 摘要信息
*/
generateSummary(alerts) {
const summary = {
total: alerts.length,
high: 0,
medium: 0,
low: 0,
informational: 0
};

alerts.forEach(alert => {
const risk = alert.risk.toLowerCase();
if (risk.includes('high')) summary.high++;
else if (risk.includes('medium')) summary.medium++;
else if (risk.includes('low')) summary.low++;
else summary.informational++;
});

return summary;
}

/**
* 发送 HTTP 请求到 ZAP API
* @param {string} method HTTP 方法
* @param {string} endpoint API 端点
* @param {Object} data 请求数据
* @returns {Promise<Object>} 响应数据
*/
async makeRequest(method, endpoint, data = null) {
const url = `http://${this.config.zapHost}:${this.config.zapPort}${endpoint}`;

const options = {
method: method,
headers: {
'Content-Type': 'application/json'
}
};

if (this.config.apiKey) {
url += `${endpoint.includes('?') ? '&' : '?'}apikey=${this.config.apiKey}`;
}

try {
const response = await fetch(url, options);

if (!response.ok) {
throw new Error(`ZAP API请求失败: ${response.status} ${response.statusText}`);
}

return await response.json();
} catch (error) {
throw new Error(`ZAP API通信错误: ${error.message}`);
}
}
}

# 高级扫描策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/**
* 高级安全扫描策略实现
* 支持多阶段扫描和智能决策
*/
class AdvancedScanStrategy {
constructor() {
this.strategies = new Map();
this.scanHistory = [];
this.riskThresholds = {
critical: 0,
high: 1,
medium: 3,
low: 5
};
}

/**
* 注册扫描策略
* @param {string} name 策略名称
* @param {Object} strategy 策略配置
*/
registerStrategy(name, strategy) {
this.strategies.set(name, {
...strategy,
name: name,
registeredAt: new Date()
});
}

/**
* 执行智能扫描决策
* @param {Object} context 扫描上下文
* @returns {Promise<Object>} 扫描结果
*/
async executeIntelligentScan(context) {
// 1. 分析变更影响
const changeImpact = await this.analyzeChangeImpact(context);

// 2. 选择扫描策略
const strategy = this.selectScanStrategy(changeImpact);

// 3. 执行扫描
const results = await this.executeScan(strategy, context);

// 4. 分析结果
const analysis = this.analyzeResults(results, strategy);

// 5. 记录历史
this.recordScanHistory({
context: context,
strategy: strategy,
results: results,
analysis: analysis,
timestamp: new Date()
});

return {
strategy: strategy.name,
results: results,
analysis: analysis,
recommendation: this.generateRecommendation(analysis)
};
}

/**
* 分析代码变更影响
* @param {Object} context 上下文信息
* @returns {Promise<Object>} 影响分析结果
*/
async analyzeChangeImpact(context) {
const impact = {
files: context.changedFiles || [],
components: new Set(),
riskLevel: 'low',
scanScope: 'partial'
};

// 分析变更文件类型
context.changedFiles.forEach(file => {
if (file.includes('auth') || file.includes('login')) {
impact.components.add('authentication');
impact.riskLevel = 'high';
} else if (file.includes('api') || file.includes('controller')) {
impact.components.add('api');
impact.riskLevel = impact.riskLevel === 'high' ? 'high' : 'medium';
} else if (file.includes('model') || file.includes('database')) {
impact.components.add('database');
impact.riskLevel = impact.riskLevel === 'high' ? 'high' : 'medium';
} else if (file.includes('view') || file.includes('template')) {
impact.components.add('frontend');
impact.riskLevel = impact.riskLevel === 'low' ? 'low' : 'medium';
}
});

// 确定扫描范围
if (impact.riskLevel === 'high' || impact.components.has('authentication')) {
impact.scanScope = 'full';
} else if (impact.components.size > 1) {
impact.scanScope = 'comprehensive';
}

return impact;
}

/**
* 选择扫描策略
* @param {Object} impact 影响分析
* @returns {Object} 扫描策略
*/
selectScanStrategy(impact) {
if (impact.scanScope === 'full') {
return this.strategies.get('comprehensive');
} else if (impact.scanScope === 'comprehensive') {
return this.strategies.get('targeted');
} else {
return this.strategies.get('baseline');
}
}

/**
* 执行扫描
* @param {Object} strategy 扫描策略
* @param {Object} context 执行上下文
* @returns {Promise<Object>} 扫描结果
*/
async executeScan(strategy, context) {
const scanner = new ZAPScanner(strategy.zapConfig);

await scanner.initialize();

const results = {};

// 执行策略中定义的扫描阶段
for (const phase of strategy.phases) {
console.log(`执行扫描阶段: ${phase.name}`);

switch (phase.type) {
case 'baseline':
results.baseline = await scanner.runBaselineScan(phase.config);
break;
case 'active':
results.active = await scanner.runActiveScan(phase.config);
break;
case 'custom':
results.custom = await this.executeCustomScan(phase, context);
break;
}

// 检查是否需要提前终止
if (this.shouldStopEarly(results, strategy)) {
console.log('达到停止条件,提前终止扫描');
break;
}
}

return results;
}

/**
* 分析扫描结果
* @param {Object} results 扫描结果
* @param {Object} strategy 扫描策略
* @returns {Object} 分析结果
*/
analyzeResults(results, strategy) {
const analysis = {
totalAlerts: 0,
riskDistribution: { high: 0, medium: 0, low: 0, informational: 0 },
criticalIssues: [],
recommendations: [],
passThreshold: true
};

// 聚合所有扫描阶段的告警
Object.values(results).forEach(result => {
if (result && result.alerts) {
result.alerts.forEach(alert => {
analysis.totalAlerts++;
const risk = alert.risk.toLowerCase();

if (risk.includes('high')) {
analysis.riskDistribution.high++;
if (alert.alert.includes('Critical') || alert.alert.includes('SQL Injection')) {
analysis.criticalIssues.push(alert);
}
} else if (risk.includes('medium')) {
analysis.riskDistribution.medium++;
} else if (risk.includes('low')) {
analysis.riskDistribution.low++;
} else {
analysis.riskDistribution.informational++;
}
});
}
});

// 检查是否通过阈值
const thresholds = strategy.thresholds || this.riskThresholds;
if (analysis.riskDistribution.high > thresholds.high ||
analysis.riskDistribution.medium > thresholds.medium ||
analysis.riskDistribution.low > thresholds.low) {
analysis.passThreshold = false;
}

// 生成建议
analysis.recommendations = this.generateRecommendations(analysis);

return analysis;
}

/**
* 生成修复建议
* @param {Object} analysis 分析结果
* @returns {Array} 建议列表
*/
generateRecommendations(analysis) {
const recommendations = [];

if (analysis.criticalIssues.length > 0) {
recommendations.push({
priority: 'critical',
title: '立即修复关键安全漏洞',
description: `发现 ${analysis.criticalIssues.length} 个关键安全问题,需要立即修复`,
actions: analysis.criticalIssues.map(issue => ({
issue: issue.alert,
url: issue.url,
recommendation: this.getFixRecommendation(issue.alert)
}))
});
}

if (analysis.riskDistribution.high > 0) {
recommendations.push({
priority: 'high',
title: '修复高风险安全问题',
description: `发现 ${analysis.riskDistribution.high} 个高风险安全问题`,
actions: ['进行代码安全审查', '实施输入验证', '加强访问控制']
});
}

if (analysis.totalAlerts > 10) {
recommendations.push({
priority: 'medium',
title: '优化安全配置',
description: '安全问题数量较多,建议优化整体安全配置',
actions: ['更新安全策略', '加强安全培训', '定期安全扫描']
});
}

return recommendations;
}

/**
* 获取特定问题的修复建议
* @param {string} issue 问题类型
* @returns {string} 修复建议
*/
getFixRecommendation(issue) {
const recommendations = {
'SQL Injection': '使用参数化查询或ORM,避免字符串拼接SQL',
'Cross Site Scripting': '实施输出编码和CSP策略',
'Broken Authentication': '加强密码策略和会话管理',
'Sensitive Data Exposure': '加密敏感数据,使用HTTPS',
'Security Misconfiguration': '遵循安全配置最佳实践',
'default': '参考OWASP安全指南进行修复'
};

return recommendations[issue] || recommendations['default'];
}

/**
* 记录扫描历史
* @param {Object} record 扫描记录
*/
recordScanHistory(record) {
this.scanHistory.push(record);

// 保持历史记录在合理范围内
if (this.scanHistory.length > 100) {
this.scanHistory = this.scanHistory.slice(-50);
}
}

/**
* 生成综合建议
* @param {Object} analysis 分析结果
* @returns {Object} 综合建议
*/
generateRecommendation(analysis) {
return {
shouldDeploy: analysis.passThreshold,
priority: analysis.criticalIssues.length > 0 ? 'critical' :
analysis.riskDistribution.high > 0 ? 'high' : 'medium',
nextSteps: analysis.recommendations,
estimatedFixTime: this.estimateFixTime(analysis),
securityScore: this.calculateSecurityScore(analysis)
};
}

/**
* 估算修复时间
* @param {Object} analysis 分析结果
* @returns {string} 时间估算
*/
estimateFixTime(analysis) {
let hours = 0;

hours += analysis.criticalIssues.length * 4; // 每个关键问题4小时
hours += analysis.riskDistribution.high * 2; // 每个高风险问题2小时
hours += analysis.riskDistribution.medium * 1; // 每个中风险问题1小时
hours += analysis.riskDistribution.low * 0.5; // 每个低风险问题0.5小时

return `${Math.ceil(hours)} 小时`;
}

/**
* 计算安全评分
* @param {Object} analysis 分析结果
* @returns {number} 安全评分 (0-100)
*/
calculateSecurityScore(analysis) {
let score = 100;

score -= analysis.criticalIssues.length * 25;
score -= analysis.riskDistribution.high * 10;
score -= analysis.riskDistribution.medium * 5;
score -= analysis.riskDistribution.low * 2;
score -= analysis.riskDistribution.informational * 0.5;

return Math.max(0, Math.round(score));
}
}

# 企业级 CI/CD 安全集成架构

# 多层安全流水线设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# 企业级安全流水线配置
name: enterprise-security-pipeline

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
# 阶段1: 代码质量检查
code-quality:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run linting
run: npm run lint

- name: Run unit tests
run: npm run test:unit

- name: Code coverage
run: npm run coverage

- name: Upload coverage
uses: codecov/codecov-action@v3

# 阶段2: 静态安全分析
static-security:
runs-on: ubuntu-latest
needs: code-quality
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high

- name: Run npm audit
run: npm audit --audit-level=high

- name: Semgrep analysis
uses: returntocorp/semgrep-action@v1
with:
config: auto

# 阶段3: 构建和部署测试环境
build-and-deploy:
runs-on: ubuntu-latest
needs: static-security
outputs:
app-url: ${{ steps.deploy.outputs.app-url }}
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Build Docker image
run: |
docker build -t ${{ env.IMAGE_NAME }}:${{ github.sha }} .
docker tag ${{ env.IMAGE_NAME }}:${{ github.sha }} ${{ env.IMAGE_NAME }}:latest

- name: Deploy to staging
id: deploy
run: |
# 部署到临时环境
docker run -d --name juice-shop-${{ github.run_number }} \
-p 3000:3000 \
${{ env.IMAGE_NAME }}:${{ github.sha }}
echo "app-url=http://localhost:3000" >> $GITHUB_OUTPUT

# 阶段4: 动态安全测试
dynamic-security:
runs-on: ubuntu-latest
needs: build-and-deploy
strategy:
matrix:
scan-type: [baseline, active]
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup ZAP
run: |
docker pull owasp/zap2docker-stable

- name: Run ZAP ${{ matrix.scan-type }} scan
run: |
if [ "${{ matrix.scan-type }}" = "baseline" ]; then
docker run --network host -t owasp/zap2docker-stable \
zap-baseline.py \
-t ${{ needs.build-and-deploy.outputs.app-url }} \
-r zap-${{ matrix.scan-type }}-report.html \
-m 3 \
-J zap-${{ matrix.scan-type }}-report.json || true
else
docker run --network host -t owasp/zap2docker-stable \
zap-full-scan.py \
-t ${{ needs.build-and-deploy.outputs.app-url }} \
-r zap-${{ matrix.scan-type }}-report.html \
-m 5 \
-J zap-${{ matrix.scan-type }}-report.json || true
fi

- name: Parse ZAP results
id: zap-results
run: |
node .github/scripts/parse-zap-results.js \
--report zap-${{ matrix.scan-type }}-report.json \
--output zap-${{ matrix.scan-type }}-summary.json

- name: Evaluate security gate
run: |
node .github/scripts/security-gate.js \
--summary zap-${{ matrix.scan-type }}-summary.json \
--threshold-high 2 \
--threshold-medium 5

- name: Upload ZAP report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-${{ matrix.scan-type }}-report
path: zap-${{ matrix.scan-type }}-report.html

# 阶段5: 性能和安全集成测试
integration-tests:
runs-on: ubuntu-latest
needs: [build-and-deploy, dynamic-security]
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run security integration tests
run: |
npm run test:security

- name: Run performance tests
run: |
npm run test:performance

- name: Generate security report
run: |
node .github/scripts/generate-security-report.js

# 阶段6: 部署决策
deploy-decision:
runs-on: ubuntu-latest
needs: [code-quality, static-security, dynamic-security, integration-tests]
if: github.ref == 'refs/heads/main'
steps:
- name: Evaluate deployment readiness
run: |
echo "所有安全检查通过,准备部署到生产环境"

- name: Deploy to production
if: success()
run: |
echo "部署到生产环境"
# 实际部署逻辑

- name: Notify security team
if: failure()
run: |
echo "通知安全团队:安全检查未通过,部署被阻止"

# 安全门控系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/**
* 安全门控系统实现
* 根据扫描结果决定是否允许部署
*/
class SecurityGateSystem {
constructor(config = {}) {
this.config = {
thresholds: {
critical: 0,
high: config.highThreshold || 2,
medium: config.mediumThreshold || 5,
low: config.lowThreshold || 10
},
policies: {
requireApproval: config.requireApproval || false,
blockOnCritical: config.blockOnCritical !== false,
allowOverride: config.allowOverride || false
},
...config
};

this.approvers = new Set(config.approvers || []);
this.overrides = new Map();
}

/**
* 评估安全门控
* @param {Object} scanResults 扫描结果
* @param {Object} context 上下文信息
* @returns {Promise<Object>} 评估结果
*/
async evaluateGate(scanResults, context = {}) {
const evaluation = {
passed: false,
blocked: false,
requiresApproval: false,
reasons: [],
recommendations: [],
riskScore: 0,
canOverride: false
};

// 1. 计算风险评分
evaluation.riskScore = this.calculateRiskScore(scanResults);

// 2. 检查关键问题
const criticalIssues = this.findCriticalIssues(scanResults);
if (criticalIssues.length > 0) {
evaluation.blocked = this.config.policies.blockOnCritical;
evaluation.reasons.push(`发现 ${criticalIssues.length} 个关键安全问题`);
evaluation.recommendations.push(...this.getCriticalRecommendations(criticalIssues));
}

// 3. 检查阈值违规
const thresholdViolations = this.checkThresholds(scanResults);
if (thresholdViolations.length > 0) {
evaluation.passed = false;
evaluation.reasons.push(...thresholdViolations.map(v => v.message));
evaluation.recommendations.push(...thresholdViolations.map(v => v.recommendation));
}

// 4. 确定是否需要审批
if (evaluation.riskScore > 70 || !evaluation.passed) {
evaluation.requiresApproval = this.config.policies.requireApproval;
}

// 5. 检查是否可以覆盖
evaluation.canOverride = this.config.policies.allowOverride &&
!evaluation.blocked &&
context.hasPermission;

// 6. 最终决策
if (!evaluation.blocked && thresholdViolations.length === 0) {
evaluation.passed = true;
}

// 7. 记录评估历史
this.recordEvaluation({
timestamp: new Date(),
scanResults: scanResults,
context: context,
evaluation: evaluation
});

return evaluation;
}

/**
* 计算风险评分
* @param {Object} scanResults 扫描结果
* @returns {number} 风险评分 (0-100)
*/
calculateRiskScore(scanResults) {
let score = 0;

if (!scanResults || !scanResults.alerts) {
return score;
}

scanResults.alerts.forEach(alert => {
const risk = alert.risk.toLowerCase();
const confidence = alert.confidence.toLowerCase();

let riskWeight = 0;
let confidenceMultiplier = 1;

// 风险权重
if (risk.includes('critical')) riskWeight = 50;
else if (risk.includes('high')) riskWeight = 25;
else if (risk.includes('medium')) riskWeight = 10;
else if (risk.includes('low')) riskWeight = 5;
else riskWeight = 1;

// 置信度乘数
if (confidence.includes('high')) confidenceMultiplier = 1.0;
else if (confidence.includes('medium')) confidenceMultiplier = 0.7;
else if (confidence.includes('low')) confidenceMultiplier = 0.4;

score += riskWeight * confidenceMultiplier;
});

return Math.min(100, Math.round(score));
}

/**
* 查找关键安全问题
* @param {Object} scanResults 扫描结果
* @returns {Array} 关键问题列表
*/
findCriticalIssues(scanResults) {
const criticalIssues = [];
const criticalPatterns = [
'SQL Injection',
'Remote Code Execution',
'XXE',
'Deserialization',
'Insecure Direct Object Reference',
'Authentication Bypass'
];

if (!scanResults || !scanResults.alerts) {
return criticalIssues;
}

scanResults.alerts.forEach(alert => {
if (criticalPatterns.some(pattern => alert.alert.includes(pattern))) {
criticalIssues.push({
type: alert.alert,
url: alert.url,
risk: alert.risk,
description: alert.desc,
solution: alert.solution
});
}
});

return criticalIssues;
}

/**
* 检查阈值违规
* @param {Object} scanResults 扫描结果
* @returns {Array} 违规列表
*/
checkThresholds(scanResults) {
const violations = [];

if (!scanResults || !scanResults.alerts) {
return violations;
}

const distribution = this.getRiskDistribution(scanResults.alerts);
const thresholds = this.config.thresholds;

Object.entries(thresholds).forEach(([risk, threshold]) => {
const count = distribution[risk] || 0;
if (count > threshold) {
violations.push({
risk: risk,
count: count,
threshold: threshold,
message: `${risk} 风险问题数量 (${count}) 超过阈值 (${threshold})`,
recommendation: `需要修复 ${count - threshold}${risk} 风险问题`
});
}
});

return violations;
}

/**
* 获取风险分布
* @param {Array} alerts 告警列表
* @returns {Object} 风险分布
*/
getRiskDistribution(alerts) {
const distribution = {
critical: 0,
high: 0,
medium: 0,
low: 0,
informational: 0
};

alerts.forEach(alert => {
const risk = alert.risk.toLowerCase();
if (risk.includes('critical')) distribution.critical++;
else if (risk.includes('high')) distribution.high++;
else if (risk.includes('medium')) distribution.medium++;
else if (risk.includes('low')) distribution.low++;
else distribution.informational++;
});

return distribution;
}

/**
* 获取关键问题修复建议
* @param {Array} criticalIssues 关键问题列表
* @returns {Array} 修复建议
*/
getCriticalRecommendations(criticalIssues) {
const recommendations = [];

criticalIssues.forEach(issue => {
recommendations.push({
priority: 'critical',
issue: issue.type,
url: issue.url,
action: '立即修复',
estimate: '2-4小时',
resources: this.getFixResources(issue.type)
});
});

return recommendations;
}

/**
* 获取修复资源
* @param {string} issueType 问题类型
* @returns {Array} 资源链接
*/
getFixResources(issueType) {
const resources = {
'SQL Injection': [
'https://owasp.org/www-community/attacks/SQL_Injection',
'https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html'
],
'Remote Code Execution': [
'https://owasp.org/www-project-top-ten/2017/A1_2017-Injection',
'https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html'
],
'default': [
'https://owasp.org/www-project-top-ten/',
'https://cheatsheetseries.owasp.org/'
]
};

return resources[issueType] || resources['default'];
}

/**
* 记录评估历史
* @param {Object} record 评估记录
*/
recordEvaluation(record) {
// 这里可以集成数据库或日志系统
console.log('安全门控评估记录:', {
timestamp: record.timestamp,
passed: record.evaluation.passed,
blocked: record.evaluation.blocked,
riskScore: record.evaluation.riskScore
});
}

/**
* 请求安全审批
* @param {Object} evaluation 评估结果
* @param {string} requester 请求者
* @returns {Promise<string>} 审批ID
*/
async requestApproval(evaluation, requester) {
const approvalId = `approval-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

const approvalRequest = {
id: approvalId,
timestamp: new Date(),
requester: requester,
evaluation: evaluation,
status: 'pending',
approvers: Array.from(this.approvers),
comments: []
};

// 这里可以集成审批系统(如Jira、ServiceNow等)
console.log('创建安全审批请求:', approvalRequest);

return approvalId;
}

/**
* 处理审批响应
* @param {string} approvalId 审批ID
* @param {string} approver 审批者
* @param {boolean} approved 是否批准
* @param {string} comments 评论
* @returns {Promise<Object>} 处理结果
*/
async processApproval(approvalId, approver, approved, comments = '') {
if (!this.approvers.has(approver)) {
throw new Error(`用户 ${approver} 没有审批权限`);
}

const approval = {
id: approvalId,
approver: approver,
approved: approved,
comments: comments,
timestamp: new Date()
};

this.overrides.set(approvalId, approval);

console.log('安全审批处理:', approval);

return approval;
}
}

# 安全测试自动化框架

# 完整的测试套件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
/**
* 安全测试自动化框架
* 提供完整的测试执行和报告生成功能
*/
class SecurityTestFramework {
constructor(config = {}) {
this.config = {
testTimeout: config.testTimeout || 600000, // 10分钟
parallelTests: config.parallelTests || 3,
reportFormat: config.reportFormat || ['html', 'json', 'junit'],
...config
};

this.testSuites = new Map();
this.testResults = new Map();
this.reportGenerators = new Map();
}

/**
* 注册测试套件
* @param {string} name 套件名称
* @param {Object} testSuite 测试套件配置
*/
registerTestSuite(name, testSuite) {
this.testSuites.set(name, {
...testSuite,
name: name,
registeredAt: new Date()
});
}

/**
* 执行所有测试套件
* @param {Object} context 执行上下文
* @returns {Promise<Object>} 执行结果
*/
async runAllTests(context = {}) {
const startTime = Date.now();
const results = {
summary: {
totalSuites: this.testSuites.size,
passedSuites: 0,
failedSuites: 0,
skippedSuites: 0,
totalTests: 0,
totalDuration: 0
},
suites: {},
overall: {
passed: false,
score: 0,
recommendations: []
}
};

console.log(`开始执行 ${this.testSuites.size} 个测试套件`);

// 并行执行测试套件
const suitePromises = Array.from(this.testSuites.entries()).map(
async ([name, suite]) => {
try {
const suiteResult = await this.runTestSuite(name, suite, context);
results.suites[name] = suiteResult;

if (suiteResult.passed) {
results.summary.passedSuites++;
} else {
results.summary.failedSuites++;
}

results.summary.totalTests += suiteResult.totalTests;

} catch (error) {
console.error(`测试套件 ${name} 执行失败:`, error);
results.suites[name] = {
name: name,
passed: false,
error: error.message,
totalTests: 0,
duration: 0
};
results.summary.failedSuites++;
}
}
);

await Promise.all(suitePromises);

// 计算总体结果
results.summary.totalDuration = Date.now() - startTime;
results.overall.passed = results.summary.failedSuites === 0;
results.overall.score = this.calculateOverallScore(results);
results.overall.recommendations = this.generateOverallRecommendations(results);

return results;
}

/**
* 执行单个测试套件
* @param {string} name 套件名称
* @param {Object} suite 测试套件配置
* @param {Object} context 执行上下文
* @returns {Promise<Object>} 套件执行结果
*/
async runTestSuite(name, suite, context) {
console.log(`执行测试套件: ${name}`);
const startTime = Date.now();

const result = {
name: name,
passed: true,
totalTests: 0,
passedTests: 0,
failedTests: 0,
skippedTests: 0,
duration: 0,
tests: {},
issues: []
};

try {
// 执行前置条件
if (suite.beforeAll) {
await suite.beforeAll(context);
}

// 执行测试用例
for (const [testName, testConfig] of Object.entries(suite.tests || {})) {
const testResult = await this.runSingleTest(testName, testConfig, context);
result.tests[testName] = testResult;
result.totalTests++;

if (testResult.passed) {
result.passedTests++;
} else if (testResult.skipped) {
result.skippedTests++;
} else {
result.failedTests++;
result.passed = false;

if (testResult.issue) {
result.issues.push(testResult.issue);
}
}
}

// 执行后置条件
if (suite.afterAll) {
await suite.afterAll(context);
}

} catch (error) {
console.error(`测试套件 ${name} 执行异常:`, error);
result.passed = false;
result.error = error.message;
}

result.duration = Date.now() - startTime;

console.log(`测试套件 ${name} 完成: ${result.passed ? '通过' : '失败'} (${result.duration}ms)`);

return result;
}

/**
* 执行单个测试用例
* @param {string} name 测试名称
* @param {Object} testConfig 测试配置
* @param {Object} context 执行上下文
* @returns {Promise<Object>} 测试结果
*/
async runSingleTest(name, testConfig, context) {
console.log(` 执行测试: ${name}`);
const startTime = Date.now();

const result = {
name: name,
passed: false,
skipped: false,
duration: 0,
message: '',
details: {},
issue: null
};

try {
// 检查测试条件
if (testConfig.when && !testConfig.when(context)) {
result.skipped = true;
result.passed = true; // 跳过的测试不算失败
result.message = '测试条件不满足,跳过执行';
return result;
}

// 执行测试逻辑
const testResult = await testConfig.test(context);

result.passed = testResult.passed;
result.message = testResult.message || '';
result.details = testResult.details || {};

if (!result.passed && testResult.issue) {
result.issue = {
severity: testResult.issue.severity || 'medium',
type: testResult.issue.type || 'test_failure',
description: testResult.issue.description || result.message,
recommendation: testResult.issue.recommendation || '请检查测试配置'
};
}

} catch (error) {
console.error(`测试 ${name} 执行异常:`, error);
result.passed = false;
result.message = `测试执行异常: ${error.message}`;
result.issue = {
severity: 'high',
type: 'test_error',
description: error.message,
recommendation: '检查测试环境和配置'
};
}

result.duration = Date.now() - startTime;

console.log(` 测试 ${name}: ${result.passed ? '通过' : '失败'} (${result.duration}ms)`);

return result;
}

/**
* 计算总体评分
* @param {Object} results 测试结果
* @returns {number} 总体评分 (0-100)
*/
calculateOverallScore(results) {
if (results.summary.totalTests === 0) {
return 0;
}

const passRate = (results.summary.passedTests / results.summary.totalTests) * 100;
const suitePassRate = (results.summary.passedSuites / results.summary.totalSuites) * 100;

// 综合评分:测试通过率权重70%,套件通过率权重30%
const score = Math.round(passRate * 0.7 + suitePassRate * 0.3);

return Math.min(100, Math.max(0, score));
}

/**
* 生成总体建议
* @param {Object} results 测试结果
* @returns {Array} 建议列表
*/
generateOverallRecommendations(results) {
const recommendations = [];

if (results.summary.failedSuites > 0) {
recommendations.push({
priority: 'high',
title: '修复失败的测试套件',
description: `有 ${results.summary.failedSuites} 个测试套件失败,需要优先修复`,
actions: ['检查失败原因', '更新测试配置', '修复相关安全问题']
});
}

if (results.overall.score < 80) {
recommendations.push({
priority: 'medium',
title: '提升安全测试覆盖率',
description: `当前安全评分 ${results.overall.score},建议提升到80分以上`,
actions: ['增加测试用例', '完善测试场景', '加强边界测试']
});
}

// 收集所有问题并分类
const allIssues = [];
Object.values(results.suites).forEach(suite => {
if (suite.issues) {
allIssues.push(...suite.issues);
}
});

const criticalIssues = allIssues.filter(issue => issue.severity === 'critical');
if (criticalIssues.length > 0) {
recommendations.push({
priority: 'critical',
title: '立即处理关键安全问题',
description: `发现 ${criticalIssues.length} 个关键安全问题`,
actions: criticalIssues.map(issue => issue.recommendation)
});
}

return recommendations;
}

/**
* 生成测试报告
* @param {Object} results 测试结果
* @param {string} outputPath 输出路径
* @returns {Promise<Array>} 生成的报告文件列表
*/
async generateReports(results, outputPath = './reports') {
const generatedReports = [];

for (const format of this.config.reportFormat) {
try {
const reportPath = await this.generateReport(results, format, outputPath);
generatedReports.push(reportPath);
console.log(`生成 ${format} 格式报告: ${reportPath}`);
} catch (error) {
console.error(`生成 ${format} 格式报告失败:`, error);
}
}

return generatedReports;
}

/**
* 生成指定格式的报告
* @param {Object} results 测试结果
* @param {string} format 报告格式
* @param {string} outputPath 输出路径
* @returns {Promise<string>} 报告文件路径
*/
async generateReport(results, format, outputPath) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const fileName = `security-test-report-${timestamp}.${format}`;
const filePath = `${outputPath}/${fileName}`;

// 确保输出目录存在
await fs.ensureDir(outputPath);

switch (format) {
case 'html':
await this.generateHTMLReport(results, filePath);
break;
case 'json':
await this.generateJSONReport(results, filePath);
break;
case 'junit':
await this.generateJUnitReport(results, filePath);
break;
default:
throw new Error(`不支持的报告格式: ${format}`);
}

return filePath;
}

/**
* 生成HTML报告
* @param {Object} results 测试结果
* @param {string} filePath 文件路径
*/
async generateHTMLReport(results, filePath) {
const html = `
<!DOCTYPE html>
<html>
<head>
<title>安全测试报告</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; }
.summary { display: flex; gap: 20px; margin: 20px 0; }
.metric { background: white; border: 1px solid #ddd; padding: 15px; border-radius: 5px; text-align: center; }
.metric-value { font-size: 2em; font-weight: bold; }
.metric-label { color: #666; }
.passed { color: #28a745; }
.failed { color: #dc3545; }
.skipped { color: #ffc107; }
.suite { margin: 20px 0; border: 1px solid #ddd; border-radius: 5px; }
.suite-header { background: #f8f9fa; padding: 15px; font-weight: bold; }
.suite-content { padding: 15px; }
.test { margin: 10px 0; padding: 10px; border-left: 3px solid #ddd; }
.test.passed { border-left-color: #28a745; }
.test.failed { border-left-color: #dc3545; }
.test.skipped { border-left-color: #ffc107; }
.recommendations { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; }
</style>
</head>
<body>
<div class="header">
<h1>安全测试报告</h1>
<p>生成时间: ${new Date().toLocaleString()}</p>
<p>总体评分: <span class="${results.overall.score >= 80 ? 'passed' : 'failed'}">${results.overall.score}/100</span></p>
</div>

<div class="summary">
<div class="metric">
<div class="metric-value passed">${results.summary.passedSuites}</div>
<div class="metric-label">通过套件</div>
</div>
<div class="metric">
<div class="metric-value failed">${results.summary.failedSuites}</div>
<div class="metric-label">失败套件</div>
</div>
<div class="metric">
<div class="metric-value">${results.summary.totalTests}</div>
<div class="metric-label">总测试数</div>
</div>
<div class="metric">
<div class="metric-value">${Math.round(results.summary.totalDuration / 1000)}s</div>
<div class="metric-label">执行时间</div>
</div>
</div>

${Object.values(results.suites).map(suite => `
<div class="suite">
<div class="suite-header">
${suite.name} - <span class="${suite.passed ? 'passed' : 'failed'}">${suite.passed ? '通过' : '失败'}</span>
</div>
<div class="suite-content">
<p>测试数: ${suite.totalTests}, 通过: ${suite.passedTests}, 失败: ${suite.failedTests}, 跳过: ${suite.skippedTests}</p>
${Object.values(suite.tests || {}).map(test => `
<div class="test ${test.passed ? 'passed' : test.skipped ? 'skipped' : 'failed'}">
<strong>${test.name}</strong> - ${test.passed ? '通过' : test.skipped ? '跳过' : '失败'}
${test.message ? `<br><em>${test.message}</em>` : ''}
${test.duration ? `<br><small>耗时: ${test.duration}ms</small>` : ''}
</div>
`).join('')}
</div>
</div>
`).join('')}

${results.overall.recommendations.length > 0 ? `
<div class="recommendations">
<h3>改进建议</h3>
${results.overall.recommendations.map(rec => `
<div style="margin: 10px 0;">
<strong>${rec.title}</strong> (${rec.priority})
<p>${rec.description}</p>
<ul>
${rec.actions.map(action => `<li>${action}</li>`).join('')}
</ul>
</div>
`).join('')}
</div>
` : ''}
</body>
</html>`;

await fs.writeFile(filePath, html);
}

/**
* 生成JSON报告
* @param {Object} results 测试结果
* @param {string} filePath 文件路径
*/
async generateJSONReport(results, filePath) {
const jsonReport = {
metadata: {
generatedAt: new Date().toISOString(),
framework: 'SecurityTestFramework',
version: '1.0.0'
},
results: results
};

await fs.writeFile(filePath, JSON.stringify(jsonReport, null, 2));
}

/**
* 生成JUnit报告
* @param {Object} results 测试结果
* @param {string} filePath 文件路径
*/
async generateJUnitReport(results, filePath) {
let junitXml = '<?xml version="1.0" encoding="UTF-8"?>\n<testsuites>\n';

Object.values(results.suites).forEach(suite => {
junitXml += ` <testsuite name="${suite.name}" tests="${suite.totalTests}" failures="${suite.failedTests}" skipped="${suite.skippedTests}" time="${suite.duration / 1000}">\n`;

Object.values(suite.tests || {}).forEach(test => {
const status = test.passed ? '' : test.skipped ? 'skipped="1"' : 'failure="1"';
junitXml += ` <testcase name="${test.name}" classname="${suite.name}" time="${test.duration / 1000}" ${status}>\n`;

if (!test.passed && !test.skipped) {
junitXml += ` <failure message="${test.message}">\n`;
junitXml += ` <![CDATA[${test.message}]]>\n`;
junitXml += ` </failure>\n`;
} else if (test.skipped) {
junitXml += ` <skipped message="${test.message}"/>\n`;
}

junitXml += ` </testcase>\n`;
});

junitXml += ` </testsuite>\n`;
});

junitXml += '</testsuites>';

await fs.writeFile(filePath, junitXml);
}
}

# 总结与技术延伸

# 核心价值实现

通过构建完整的安全测试流水线,我们实现了:

  1. 安全左移:将安全测试前移到开发阶段,降低修复成本
  2. 自动化回归:每次构建都进行安全检查,防止安全退化
  3. 量化管理:通过评分和阈值实现安全状况可度量
  4. 团队协作:通过报告和门控促进开发与安全团队协作

# 技术演进方向

  1. AI 辅助安全测试:利用机器学习优化扫描策略和误报识别
  2. 容器化安全:集成镜像扫描和运行时安全监控
  3. 微服务安全:适配分布式架构的安全测试需求
  4. 零信任架构:在 CI/CD 中实现零信任安全验证

# 最佳实践建议

  1. 渐进式实施:从基线扫描开始,逐步增加复杂度
  2. 阈值优化:根据团队情况调整安全门控阈值
  3. 持续改进:定期回顾和优化安全测试策略
  4. 团队培训:提升开发团队安全意识和技能

这套安全测试流水线不仅适用于 Juice Shop 项目,更是企业级应用安全防护的标准化解决方案,为构建安全可靠的软件系统提供了坚实的技术基础。