mcp-wordpress 2.11.13 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -29
- package/dist/cache/CacheInvalidation.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +7 -0
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +21 -7
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/HttpCacheWrapper.js.map +1 -1
- package/dist/cache/SEOCacheManager.d.ts.map +1 -1
- package/dist/cache/SEOCacheManager.js +6 -1
- package/dist/cache/SEOCacheManager.js.map +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/client/CachedWordPressClient.d.ts.map +1 -1
- package/dist/client/CachedWordPressClient.js.map +1 -1
- package/dist/client/MockWordPressClient.d.ts.map +1 -1
- package/dist/client/MockWordPressClient.js.map +1 -1
- package/dist/client/SEOWordPressClient.d.ts.map +1 -1
- package/dist/client/SEOWordPressClient.js.map +1 -1
- package/dist/client/api.d.ts +11 -26
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +111 -203
- package/dist/client/api.js.map +1 -1
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/auth.js.map +1 -1
- package/dist/client/managers/AuthManager.d.ts.map +1 -1
- package/dist/client/managers/RequestManager.d.ts.map +1 -1
- package/dist/client/managers/RequestManager.js +6 -5
- package/dist/client/managers/RequestManager.js.map +1 -1
- package/dist/client/managers/composed/MigrationAdapter.d.ts +3 -3
- package/dist/client/managers/composed/MigrationAdapter.d.ts.map +1 -1
- package/dist/client/managers/composed/MigrationAdapter.js +2 -2
- package/dist/client/managers/composed/MigrationAdapter.js.map +1 -1
- package/dist/client/managers/composed/index.d.ts +7 -7
- package/dist/client/managers/composed/index.d.ts.map +1 -1
- package/dist/client/managers/composed/index.js +6 -6
- package/dist/client/managers/composed/index.js.map +1 -1
- package/dist/client/managers/implementations/ConfigurationProviderImpl.d.ts +1 -1
- package/dist/client/managers/implementations/ConfigurationProviderImpl.d.ts.map +1 -1
- package/dist/client/managers/implementations/ErrorHandlerImpl.d.ts +1 -1
- package/dist/client/managers/implementations/ErrorHandlerImpl.d.ts.map +1 -1
- package/dist/client/managers/implementations/ParameterValidatorImpl.d.ts +1 -1
- package/dist/client/managers/implementations/ParameterValidatorImpl.d.ts.map +1 -1
- package/dist/client/operations/comments.d.ts +58 -0
- package/dist/client/operations/comments.d.ts.map +1 -0
- package/dist/client/operations/comments.js +74 -0
- package/dist/client/operations/comments.js.map +1 -0
- package/dist/client/operations/index.d.ts +12 -0
- package/dist/client/operations/index.d.ts.map +1 -0
- package/dist/client/operations/index.js +12 -0
- package/dist/client/operations/index.js.map +1 -0
- package/dist/client/operations/media.d.ts +55 -0
- package/dist/client/operations/media.d.ts.map +1 -0
- package/dist/client/operations/media.js +132 -0
- package/dist/client/operations/media.js.map +1 -0
- package/dist/client/operations/pages.d.ts +50 -0
- package/dist/client/operations/pages.d.ts.map +1 -0
- package/dist/client/operations/pages.js +56 -0
- package/dist/client/operations/pages.js.map +1 -0
- package/dist/client/operations/posts.d.ts +50 -0
- package/dist/client/operations/posts.d.ts.map +1 -0
- package/dist/client/operations/posts.js +53 -0
- package/dist/client/operations/posts.js.map +1 -0
- package/dist/client/operations/site.d.ts +60 -0
- package/dist/client/operations/site.d.ts.map +1 -0
- package/dist/client/operations/site.js +83 -0
- package/dist/client/operations/site.js.map +1 -0
- package/dist/client/operations/taxonomies.d.ts +69 -0
- package/dist/client/operations/taxonomies.d.ts.map +1 -0
- package/dist/client/operations/taxonomies.js +87 -0
- package/dist/client/operations/taxonomies.js.map +1 -0
- package/dist/client/operations/users.d.ts +50 -0
- package/dist/client/operations/users.d.ts.map +1 -0
- package/dist/client/operations/users.js +57 -0
- package/dist/client/operations/users.js.map +1 -0
- package/dist/config/ServerConfiguration.d.ts.map +1 -1
- package/dist/config/ServerConfiguration.js.map +1 -1
- package/dist/docs/DocumentationGenerator.js.map +1 -1
- package/dist/performance/MetricsCollector.d.ts.map +1 -1
- package/dist/performance/MetricsCollector.js.map +1 -1
- package/dist/performance/PerformanceMonitor.js.map +1 -1
- package/dist/security/AISecurityScanner.d.ts.map +1 -1
- package/dist/security/AISecurityScanner.js +3 -2
- package/dist/security/AISecurityScanner.js.map +1 -1
- package/dist/security/AutomatedRemediation.js.map +1 -1
- package/dist/security/InputValidator.d.ts.map +1 -1
- package/dist/security/InputValidator.js +30 -18
- package/dist/security/InputValidator.js.map +1 -1
- package/dist/security/SecurityCIPipeline.d.ts +19 -196
- package/dist/security/SecurityCIPipeline.d.ts.map +1 -1
- package/dist/security/SecurityCIPipeline.js +95 -639
- package/dist/security/SecurityCIPipeline.js.map +1 -1
- package/dist/security/SecurityConfig.js.map +1 -1
- package/dist/security/SecurityConfigManager.js.map +1 -1
- package/dist/security/SecurityGateExecutor.d.ts +67 -0
- package/dist/security/SecurityGateExecutor.d.ts.map +1 -0
- package/dist/security/SecurityGateExecutor.js +363 -0
- package/dist/security/SecurityGateExecutor.js.map +1 -0
- package/dist/security/SecurityMonitoring.js.map +1 -1
- package/dist/security/SecurityReportGenerator.d.ts +65 -0
- package/dist/security/SecurityReportGenerator.d.ts.map +1 -0
- package/dist/security/SecurityReportGenerator.js +210 -0
- package/dist/security/SecurityReportGenerator.js.map +1 -0
- package/dist/security/SecurityReviewer.js.map +1 -1
- package/dist/security/SecurityTypes.d.ts +188 -0
- package/dist/security/SecurityTypes.d.ts.map +1 -0
- package/dist/security/SecurityTypes.js +6 -0
- package/dist/security/SecurityTypes.js.map +1 -0
- package/dist/security/index.d.ts +5 -28
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +4 -0
- package/dist/security/index.js.map +1 -1
- package/dist/server/ConnectionTester.d.ts.map +1 -1
- package/dist/server/ConnectionTester.js.map +1 -1
- package/dist/server/ToolRegistry.d.ts.map +1 -1
- package/dist/server/ToolRegistry.js.map +1 -1
- package/dist/tools/BaseToolManager.d.ts.map +1 -1
- package/dist/tools/BaseToolManager.js.map +1 -1
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/cache.d.ts.map +1 -1
- package/dist/tools/cache.js.map +1 -1
- package/dist/tools/comments.d.ts.map +1 -1
- package/dist/tools/comments.js.map +1 -1
- package/dist/tools/media.d.ts.map +1 -1
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/pages.d.ts.map +1 -1
- package/dist/tools/pages.js.map +1 -1
- package/dist/tools/performance/PerformanceHelpers.d.ts +116 -0
- package/dist/tools/performance/PerformanceHelpers.d.ts.map +1 -0
- package/dist/tools/performance/PerformanceHelpers.js +298 -0
- package/dist/tools/performance/PerformanceHelpers.js.map +1 -0
- package/dist/tools/performance/PerformanceTools.d.ts +54 -0
- package/dist/tools/performance/PerformanceTools.d.ts.map +1 -0
- package/dist/tools/performance/PerformanceTools.js +687 -0
- package/dist/tools/performance/PerformanceTools.js.map +1 -0
- package/dist/tools/performance/index.d.ts +8 -0
- package/dist/tools/performance/index.d.ts.map +1 -0
- package/dist/tools/performance/index.js +8 -0
- package/dist/tools/performance/index.js.map +1 -0
- package/dist/tools/performance.d.ts +12 -69
- package/dist/tools/performance.d.ts.map +1 -1
- package/dist/tools/performance.js +12 -920
- package/dist/tools/performance.js.map +1 -1
- package/dist/tools/posts.d.ts.map +1 -1
- package/dist/tools/seo/analyzers/ContentAnalyzer.d.ts.map +1 -1
- package/dist/tools/seo/analyzers/ContentAnalyzer.js +14 -3
- package/dist/tools/seo/analyzers/ContentAnalyzer.js.map +1 -1
- package/dist/tools/seo/auditors/SiteAuditor.d.ts.map +1 -1
- package/dist/tools/seo/auditors/SiteAuditor.js +12 -3
- package/dist/tools/seo/auditors/SiteAuditor.js.map +1 -1
- package/dist/tools/seo/generators/MetaGenerator.d.ts.map +1 -1
- package/dist/tools/seo/generators/MetaGenerator.js +25 -8
- package/dist/tools/seo/generators/MetaGenerator.js.map +1 -1
- package/dist/tools/seo/generators/SchemaGenerator.d.ts.map +1 -1
- package/dist/tools/seo/generators/SchemaGenerator.js.map +1 -1
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.d.ts.map +1 -1
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.js.map +1 -1
- package/dist/tools/site.d.ts.map +1 -1
- package/dist/tools/site.js.map +1 -1
- package/dist/tools/taxonomies.d.ts.map +1 -1
- package/dist/tools/taxonomies.js.map +1 -1
- package/dist/tools/users.d.ts.map +1 -1
- package/dist/tools/users.js.map +1 -1
- package/dist/utils/CircuitBreaker.d.ts +243 -0
- package/dist/utils/CircuitBreaker.d.ts.map +1 -0
- package/dist/utils/CircuitBreaker.js +456 -0
- package/dist/utils/CircuitBreaker.js.map +1 -0
- package/dist/utils/debug.d.ts.map +1 -1
- package/dist/utils/debug.js.map +1 -1
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/toolWrapper.d.ts.map +1 -1
- package/docs/DEPRECATIONS.md +157 -0
- package/package.json +2 -3
- package/src/cache/CacheInvalidation.ts +1 -1
- package/src/cache/CacheManager.ts +25 -8
- package/src/cache/HttpCacheWrapper.ts +1 -1
- package/src/cache/SEOCacheManager.ts +9 -3
- package/src/cache/index.ts +1 -1
- package/src/client/CachedWordPressClient.ts +6 -6
- package/src/client/MockWordPressClient.ts +3 -3
- package/src/client/SEOWordPressClient.ts +6 -6
- package/src/client/api.ts +129 -215
- package/src/client/auth.ts +3 -3
- package/src/client/managers/AuthManager.ts +1 -1
- package/src/client/managers/RequestManager.ts +6 -7
- package/src/client/managers/composed/MigrationAdapter.ts +4 -4
- package/src/client/managers/composed/index.ts +7 -7
- package/src/client/managers/implementations/ConfigurationProviderImpl.ts +1 -1
- package/src/client/managers/implementations/ErrorHandlerImpl.ts +1 -1
- package/src/client/managers/implementations/ParameterValidatorImpl.ts +1 -1
- package/src/client/operations/comments.ts +96 -0
- package/src/client/operations/index.ts +12 -0
- package/src/client/operations/media.ts +162 -0
- package/src/client/operations/pages.ts +71 -0
- package/src/client/operations/posts.ts +68 -0
- package/src/client/operations/site.ts +106 -0
- package/src/client/operations/taxonomies.ts +115 -0
- package/src/client/operations/users.ts +72 -0
- package/src/config/ServerConfiguration.ts +6 -6
- package/src/docs/DocumentationGenerator.ts +3 -3
- package/src/performance/MetricsCollector.ts +4 -4
- package/src/performance/PerformanceMonitor.ts +1 -1
- package/src/security/AISecurityScanner.ts +4 -3
- package/src/security/AutomatedRemediation.ts +1 -1
- package/src/security/InputValidator.ts +36 -19
- package/src/security/SecurityCIPipeline.ts +130 -953
- package/src/security/SecurityConfig.ts +1 -1
- package/src/security/SecurityConfigManager.ts +1 -1
- package/src/security/SecurityGateExecutor.ts +485 -0
- package/src/security/SecurityMonitoring.ts +1 -1
- package/src/security/SecurityReportGenerator.ts +272 -0
- package/src/security/SecurityReviewer.ts +1 -1
- package/src/security/SecurityTypes.ts +199 -0
- package/src/security/index.ts +6 -1
- package/src/server/ConnectionTester.ts +4 -4
- package/src/server/ToolRegistry.ts +6 -6
- package/src/tools/BaseToolManager.ts +2 -2
- package/src/tools/auth.ts +3 -3
- package/src/tools/cache.ts +3 -3
- package/src/tools/comments.ts +3 -3
- package/src/tools/media.ts +3 -3
- package/src/tools/pages.ts +3 -3
- package/src/tools/performance/PerformanceHelpers.ts +330 -0
- package/src/tools/performance/PerformanceTools.ts +854 -0
- package/src/tools/performance/index.ts +8 -0
- package/src/tools/performance.ts +12 -1073
- package/src/tools/posts.ts +1 -1
- package/src/tools/seo/analyzers/ContentAnalyzer.ts +21 -7
- package/src/tools/seo/auditors/SiteAuditor.ts +18 -7
- package/src/tools/seo/generators/MetaGenerator.ts +33 -12
- package/src/tools/seo/generators/SchemaGenerator.ts +3 -3
- package/src/tools/seo/optimizers/InternalLinkingSuggester.ts +4 -4
- package/src/tools/site.ts +3 -3
- package/src/tools/taxonomies.ts +3 -3
- package/src/tools/users.ts +4 -4
- package/src/utils/CircuitBreaker.ts +572 -0
- package/src/utils/debug.ts +3 -3
- package/src/utils/error.ts +1 -1
- package/src/utils/index.ts +3 -0
- package/src/utils/logger.ts +1 -1
- package/src/utils/toolWrapper.ts +2 -2
- package/docs/BRANCH_PROTECTION.md +0 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Security CI/CD Pipeline Integration
|
|
3
3
|
* Provides security checks and gates for continuous integration and deployment
|
|
4
|
+
*
|
|
5
|
+
* This module orchestrates security gates using:
|
|
6
|
+
* - SecurityGateExecutor: Handles gate and check execution
|
|
7
|
+
* - SecurityReportGenerator: Handles report generation and statistics
|
|
8
|
+
* - SecurityTypes: Shared type definitions
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
import { AISecurityScanner } from "./AISecurityScanner.js";
|
|
@@ -8,102 +13,24 @@ import { AutomatedRemediation } from "./AutomatedRemediation.js";
|
|
|
8
13
|
import { SecurityReviewer } from "./SecurityReviewer.js";
|
|
9
14
|
import { SecurityConfigManager } from "./SecurityConfigManager.js";
|
|
10
15
|
import { SecurityUtils } from "./SecurityConfig.js";
|
|
11
|
-
import { LoggerFactory } from "
|
|
16
|
+
import { LoggerFactory } from "@/utils/logger.js";
|
|
17
|
+
import { SecurityGateExecutor } from "./SecurityGateExecutor.js";
|
|
18
|
+
import { SecurityReportGenerator } from "./SecurityReportGenerator.js";
|
|
19
|
+
import type {
|
|
20
|
+
SecurityGate,
|
|
21
|
+
SecurityCheck,
|
|
22
|
+
PipelineSecurityReport,
|
|
23
|
+
PipelineContext,
|
|
24
|
+
GateExecutionOptions,
|
|
25
|
+
PipelineStatistics,
|
|
26
|
+
ReportFilterOptions,
|
|
27
|
+
} from "./SecurityTypes.js";
|
|
28
|
+
|
|
29
|
+
// Re-export types for backward compatibility
|
|
30
|
+
export type { PipelineSecurityReport } from "./SecurityTypes.js";
|
|
12
31
|
|
|
13
32
|
const logger = LoggerFactory.security();
|
|
14
33
|
|
|
15
|
-
interface SecurityGate {
|
|
16
|
-
id: string;
|
|
17
|
-
name: string;
|
|
18
|
-
stage: "pre-commit" | "pre-build" | "pre-deploy" | "post-deploy";
|
|
19
|
-
enabled: boolean;
|
|
20
|
-
blocking: boolean;
|
|
21
|
-
checks: SecurityCheck[];
|
|
22
|
-
thresholds: {
|
|
23
|
-
maxCritical: number;
|
|
24
|
-
maxHigh: number;
|
|
25
|
-
maxMedium: number;
|
|
26
|
-
minSecurityScore: number;
|
|
27
|
-
};
|
|
28
|
-
exceptions: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface SecurityCheck {
|
|
32
|
-
id: string;
|
|
33
|
-
name: string;
|
|
34
|
-
type: "scan" | "review" | "dependency" | "configuration" | "secrets" | "compliance";
|
|
35
|
-
enabled: boolean;
|
|
36
|
-
timeout: number;
|
|
37
|
-
retries: number;
|
|
38
|
-
parameters: Record<string, unknown>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface PipelineSecurityReport {
|
|
42
|
-
reportId: string;
|
|
43
|
-
timestamp: Date;
|
|
44
|
-
stage: string;
|
|
45
|
-
status: "passed" | "failed" | "warning";
|
|
46
|
-
duration: number;
|
|
47
|
-
gates: GateResult[];
|
|
48
|
-
summary: {
|
|
49
|
-
totalIssues: number;
|
|
50
|
-
criticalIssues: number;
|
|
51
|
-
highIssues: number;
|
|
52
|
-
mediumIssues: number;
|
|
53
|
-
lowIssues: number;
|
|
54
|
-
securityScore: number;
|
|
55
|
-
compliance: boolean;
|
|
56
|
-
};
|
|
57
|
-
recommendations: string[];
|
|
58
|
-
artifacts: string[];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
interface GateResult {
|
|
62
|
-
gateId: string;
|
|
63
|
-
gateName: string;
|
|
64
|
-
status: "passed" | "failed" | "warning" | "skipped";
|
|
65
|
-
duration: number;
|
|
66
|
-
checks: CheckResult[];
|
|
67
|
-
blocking: boolean;
|
|
68
|
-
message: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface CheckResult {
|
|
72
|
-
checkId: string;
|
|
73
|
-
checkName: string;
|
|
74
|
-
status: "passed" | "failed" | "warning" | "error";
|
|
75
|
-
duration: number;
|
|
76
|
-
findings: SecurityFinding[];
|
|
77
|
-
details: string;
|
|
78
|
-
score: number;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
interface SecurityFinding {
|
|
82
|
-
id: string;
|
|
83
|
-
severity: "critical" | "high" | "medium" | "low" | "info";
|
|
84
|
-
type: string;
|
|
85
|
-
description: string;
|
|
86
|
-
file?: string | undefined;
|
|
87
|
-
line?: number | undefined;
|
|
88
|
-
remediation?: string | undefined;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
interface PipelineContext {
|
|
92
|
-
repositoryUrl: string;
|
|
93
|
-
branch: string;
|
|
94
|
-
commit: string;
|
|
95
|
-
author: string;
|
|
96
|
-
pullRequest?: {
|
|
97
|
-
id: string;
|
|
98
|
-
title: string;
|
|
99
|
-
source: string;
|
|
100
|
-
target: string;
|
|
101
|
-
};
|
|
102
|
-
environment: string;
|
|
103
|
-
buildNumber: string;
|
|
104
|
-
artifacts: string[];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
34
|
/**
|
|
108
35
|
* Security CI/CD Pipeline Manager
|
|
109
36
|
*/
|
|
@@ -114,8 +41,10 @@ export class SecurityCIPipeline {
|
|
|
114
41
|
public reviewer: SecurityReviewer;
|
|
115
42
|
public configManager: SecurityConfigManager;
|
|
116
43
|
public config: Record<string, unknown>;
|
|
44
|
+
|
|
117
45
|
private gates: Map<string, SecurityGate> = new Map();
|
|
118
|
-
private
|
|
46
|
+
private gateExecutor: SecurityGateExecutor;
|
|
47
|
+
private reportGenerator: SecurityReportGenerator;
|
|
119
48
|
|
|
120
49
|
constructor(
|
|
121
50
|
config: Record<string, unknown> = { projectPath: "/" },
|
|
@@ -140,77 +69,72 @@ export class SecurityCIPipeline {
|
|
|
140
69
|
|
|
141
70
|
this.config = config;
|
|
142
71
|
|
|
143
|
-
// Instantiate components (tests provide vi.mocks which should replace constructors in the dist/ runtime)
|
|
144
72
|
// Allow dependency injection for tests to provide mocked implementations
|
|
145
73
|
this.scanner = (deps && deps.scanner) || new AISecurityScanner();
|
|
146
74
|
this.remediation = (deps && deps.remediation) || new AutomatedRemediation();
|
|
147
75
|
this.reviewer = (deps && deps.reviewer) || new SecurityReviewer();
|
|
148
76
|
this.configManager = (deps && deps.configManager) || new SecurityConfigManager();
|
|
149
77
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
78
|
+
// Ensure methods exist and are spyable under vitest
|
|
79
|
+
this.makeMockable(this.scanner, ["scanCodeForVulnerabilities", "performScan", "scanDependencies", "scanSecrets"]);
|
|
80
|
+
this.makeMockable(this.reviewer, ["reviewCode", "reviewDirectory", "reviewConfiguration"]);
|
|
81
|
+
this.makeMockable(this.remediation, ["autoFix", "generateRecommendations"]);
|
|
82
|
+
this.makeMockable(this.configManager, ["getSecurityConfig", "validateConfiguration", "initialize"]);
|
|
83
|
+
|
|
84
|
+
// Initialize executor and report generator
|
|
85
|
+
this.gateExecutor = new SecurityGateExecutor({
|
|
86
|
+
scanner: this.scanner,
|
|
87
|
+
reviewer: this.reviewer,
|
|
88
|
+
configManager: this.configManager,
|
|
89
|
+
});
|
|
90
|
+
this.reportGenerator = new SecurityReportGenerator();
|
|
157
91
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
_objRecord[m] = () => Promise.resolve(null);
|
|
169
|
-
}
|
|
170
|
-
} catch (_e) {
|
|
171
|
-
// ignore and continue
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
};
|
|
92
|
+
this.initializeDefaultGates();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Helper to make methods mockable in tests
|
|
97
|
+
*/
|
|
98
|
+
private makeMockable(obj: unknown, methods: string[]): void {
|
|
99
|
+
const _objRecord = obj as Record<string, unknown>;
|
|
100
|
+
const viRef = (globalThis as Record<string, unknown> & { vi?: Record<string, unknown> }).vi;
|
|
101
|
+
if (!obj) return;
|
|
175
102
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
103
|
+
for (const m of methods) {
|
|
104
|
+
try {
|
|
105
|
+
const current = _objRecord[m];
|
|
106
|
+
if (typeof current !== "undefined") continue;
|
|
180
107
|
|
|
181
|
-
|
|
108
|
+
if (viRef && typeof viRef.fn === "function") {
|
|
109
|
+
_objRecord[m] = (viRef.fn as () => unknown)();
|
|
110
|
+
} else {
|
|
111
|
+
_objRecord[m] = () => Promise.resolve(null);
|
|
112
|
+
}
|
|
113
|
+
} catch (_e) {
|
|
114
|
+
// ignore and continue
|
|
115
|
+
}
|
|
116
|
+
}
|
|
182
117
|
}
|
|
183
118
|
|
|
184
|
-
// --- Public
|
|
119
|
+
// --- Public Gate Execution API ---
|
|
185
120
|
|
|
186
121
|
async executePreCommitGate(options: Record<string, unknown> = {}): Promise<PipelineSecurityReport> {
|
|
187
122
|
const context = this.buildDefaultContext();
|
|
188
|
-
return this.executeSecurityGates(
|
|
189
|
-
"pre-commit",
|
|
190
|
-
context,
|
|
191
|
-
options as { skipNonBlocking?: boolean; continueOnFailure?: boolean; dryRun?: boolean },
|
|
192
|
-
);
|
|
123
|
+
return this.executeSecurityGates("pre-commit", context, options as GateExecutionOptions);
|
|
193
124
|
}
|
|
194
125
|
|
|
195
126
|
async executePreBuildGate(options: Record<string, unknown> = {}): Promise<PipelineSecurityReport> {
|
|
196
127
|
const context = this.buildDefaultContext();
|
|
197
|
-
return this.executeSecurityGates(
|
|
198
|
-
"pre-build",
|
|
199
|
-
context,
|
|
200
|
-
options as { skipNonBlocking?: boolean; continueOnFailure?: boolean; dryRun?: boolean },
|
|
201
|
-
);
|
|
128
|
+
return this.executeSecurityGates("pre-build", context, options as GateExecutionOptions);
|
|
202
129
|
}
|
|
203
130
|
|
|
204
131
|
async executePreDeployGate(options: Record<string, unknown> = {}): Promise<PipelineSecurityReport> {
|
|
205
132
|
const context = this.buildDefaultContext();
|
|
206
|
-
return this.executeSecurityGates(
|
|
207
|
-
"pre-deploy",
|
|
208
|
-
context,
|
|
209
|
-
options as { skipNonBlocking?: boolean; continueOnFailure?: boolean; dryRun?: boolean },
|
|
210
|
-
);
|
|
133
|
+
return this.executeSecurityGates("pre-deploy", context, options as GateExecutionOptions);
|
|
211
134
|
}
|
|
212
135
|
|
|
213
|
-
//
|
|
136
|
+
// --- Individual Check Runners ---
|
|
137
|
+
|
|
214
138
|
async runVulnerabilityCheck(opts: { timeout?: number; retries?: number } = {}): Promise<Record<string, unknown>> {
|
|
215
139
|
const check: SecurityCheck = {
|
|
216
140
|
id: "vulnerability-scan",
|
|
@@ -237,7 +161,7 @@ export class SecurityCIPipeline {
|
|
|
237
161
|
try {
|
|
238
162
|
const scanPromise = scanner.scanCodeForVulnerabilities
|
|
239
163
|
? scanner.scanCodeForVulnerabilities()
|
|
240
|
-
: scanner.performScan?.() ?? Promise.resolve({ vulnerabilities: [], riskScore: 0 });
|
|
164
|
+
: (scanner.performScan?.() ?? Promise.resolve({ vulnerabilities: [], riskScore: 0 }));
|
|
241
165
|
|
|
242
166
|
if (opts.timeout && opts.timeout > 0) {
|
|
243
167
|
const res = await Promise.race([
|
|
@@ -249,19 +173,14 @@ export class SecurityCIPipeline {
|
|
|
249
173
|
return { checkId: check.id, status: "timeout" };
|
|
250
174
|
}
|
|
251
175
|
|
|
252
|
-
// treat the scan result like a successful check
|
|
253
176
|
return { checkId: check.id, status: "passed", result: res } as Record<string, unknown>;
|
|
254
177
|
}
|
|
255
178
|
|
|
256
179
|
const res = await scanPromise;
|
|
257
|
-
// Validate response: treat null/undefined as invalid, but accept objects that may not include
|
|
258
|
-
// a vulnerabilities array (normalize to empty array). This keeps tests' mocked scanner
|
|
259
|
-
// results compatible while still flagging truly malformed responses.
|
|
260
180
|
if (res === null || typeof res === "undefined") {
|
|
261
181
|
return { checkId: check.id, status: "failed", error: "Invalid response" } as Record<string, unknown>;
|
|
262
182
|
}
|
|
263
183
|
|
|
264
|
-
// Normalize vulnerabilities field
|
|
265
184
|
const resWithVulns = res as Record<string, unknown> & { vulnerabilities?: unknown[] };
|
|
266
185
|
if (!Array.isArray(resWithVulns.vulnerabilities)) {
|
|
267
186
|
resWithVulns.vulnerabilities = [];
|
|
@@ -272,7 +191,6 @@ export class SecurityCIPipeline {
|
|
|
272
191
|
if (attempts >= maxAttempts) {
|
|
273
192
|
return { checkId: check.id, status: "failed", error: String(err) } as Record<string, unknown>;
|
|
274
193
|
}
|
|
275
|
-
// otherwise retry
|
|
276
194
|
}
|
|
277
195
|
}
|
|
278
196
|
return { checkId: check.id, status: "failed", error: "Retries exhausted" } as Record<string, unknown>;
|
|
@@ -291,7 +209,6 @@ export class SecurityCIPipeline {
|
|
|
291
209
|
}
|
|
292
210
|
|
|
293
211
|
async runCodeReviewCheck(): Promise<Record<string, unknown>> {
|
|
294
|
-
// Prefer reviewCode if present in mocked reviewer, else fallback
|
|
295
212
|
const reviewer = this.reviewer as unknown as {
|
|
296
213
|
reviewCode?: (path: string) => Promise<{ issues: unknown[] }>;
|
|
297
214
|
reviewDirectory?: (path: string) => Promise<{ issues: unknown[] }>;
|
|
@@ -301,7 +218,8 @@ export class SecurityCIPipeline {
|
|
|
301
218
|
return { checkId: "code-review", status: "passed", result: res };
|
|
302
219
|
}
|
|
303
220
|
|
|
304
|
-
// Gate
|
|
221
|
+
// --- Gate Management ---
|
|
222
|
+
|
|
305
223
|
configureGate(gate: Partial<SecurityGate>): void {
|
|
306
224
|
if (!gate || !gate.id || !gate.stage) {
|
|
307
225
|
throw new Error("Invalid gate configuration");
|
|
@@ -337,27 +255,51 @@ export class SecurityCIPipeline {
|
|
|
337
255
|
return !!g && g.enabled;
|
|
338
256
|
}
|
|
339
257
|
|
|
340
|
-
|
|
258
|
+
getSecurityGate(gateId: string): SecurityGate | null {
|
|
259
|
+
return this.gates.get(gateId) || null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
updateSecurityGate(gateId: string, updates: Partial<SecurityGate>): boolean {
|
|
263
|
+
const gate = this.gates.get(gateId);
|
|
264
|
+
if (!gate) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const updatedGate = { ...gate, ...updates, id: gateId };
|
|
269
|
+
this.gates.set(gateId, updatedGate);
|
|
270
|
+
|
|
271
|
+
logger.info(`Updated security gate: ${updatedGate.name}`, { gateName: updatedGate.name });
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// --- Reporting ---
|
|
276
|
+
|
|
341
277
|
async generateReport(): Promise<PipelineSecurityReport> {
|
|
342
|
-
|
|
343
|
-
|
|
278
|
+
const latest = this.reportGenerator.getLatestReport();
|
|
279
|
+
if (latest) return latest;
|
|
280
|
+
return this.reportGenerator.createEmptyReport(SecurityUtils.generateSecureToken(8), "summary", Date.now());
|
|
344
281
|
}
|
|
345
282
|
|
|
346
283
|
async exportReport(format: string): Promise<string> {
|
|
347
284
|
const report = await this.generateReport();
|
|
348
|
-
|
|
349
|
-
if (format === "xml") return `<report>${JSON.stringify(report)}</report>`;
|
|
350
|
-
return JSON.stringify(report);
|
|
285
|
+
return this.reportGenerator.exportReport(report, format);
|
|
351
286
|
}
|
|
352
287
|
|
|
353
288
|
async calculateSecurityMetrics(): Promise<{ overallScore: number; riskLevel: string; complianceStatus: boolean }> {
|
|
354
289
|
const report = await this.generateReport();
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
290
|
+
return this.reportGenerator.calculateSecurityMetrics(report);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
getReports(options: ReportFilterOptions = {}): PipelineSecurityReport[] {
|
|
294
|
+
return this.reportGenerator.getReports(options);
|
|
358
295
|
}
|
|
359
296
|
|
|
360
|
-
|
|
297
|
+
getStatistics(): PipelineStatistics {
|
|
298
|
+
return this.reportGenerator.getStatistics();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// --- Remediation ---
|
|
302
|
+
|
|
361
303
|
async executeAutoRemediation(): Promise<Record<string, unknown>> {
|
|
362
304
|
try {
|
|
363
305
|
const remediation = this.remediation as unknown as Record<string, unknown> & { autoFix?: () => Promise<unknown> };
|
|
@@ -375,7 +317,8 @@ export class SecurityCIPipeline {
|
|
|
375
317
|
return remediation.generateRecommendations?.() ?? [];
|
|
376
318
|
}
|
|
377
319
|
|
|
378
|
-
// Full
|
|
320
|
+
// --- Full Pipeline Orchestration ---
|
|
321
|
+
|
|
379
322
|
async executeFullPipeline(): Promise<Record<string, unknown>> {
|
|
380
323
|
const start = Date.now();
|
|
381
324
|
const stages: PipelineSecurityReport[] = [];
|
|
@@ -411,7 +354,8 @@ export class SecurityCIPipeline {
|
|
|
411
354
|
return { stages, overallStatus, duration: Math.max(1, Date.now() - start) };
|
|
412
355
|
}
|
|
413
356
|
|
|
414
|
-
// Notifications
|
|
357
|
+
// --- Notifications ---
|
|
358
|
+
|
|
415
359
|
sendNotification(payload: Record<string, unknown>): void {
|
|
416
360
|
logger.info("Sending notification", { payload });
|
|
417
361
|
}
|
|
@@ -422,55 +366,33 @@ export class SecurityCIPipeline {
|
|
|
422
366
|
return { subject, body };
|
|
423
367
|
}
|
|
424
368
|
|
|
425
|
-
// Configuration
|
|
369
|
+
// --- Configuration ---
|
|
370
|
+
|
|
426
371
|
reloadConfiguration(newConfig: Record<string, unknown>): void {
|
|
427
372
|
if (!newConfig || newConfig.projectPath == null) throw new Error("Invalid configuration");
|
|
428
|
-
// preserve gate enabled/disabled states
|
|
429
373
|
const state: Record<string, boolean> = {};
|
|
430
374
|
for (const [id, g] of this.gates.entries()) state[id] = g.enabled;
|
|
431
375
|
|
|
432
376
|
this.config = newConfig;
|
|
433
377
|
|
|
434
|
-
// reapply states
|
|
435
378
|
for (const [id, enabled] of Object.entries(state)) {
|
|
436
379
|
const g = this.gates.get(id);
|
|
437
380
|
if (g) g.enabled = enabled;
|
|
438
381
|
}
|
|
439
382
|
}
|
|
440
383
|
|
|
441
|
-
// Helper to build a default pipeline context
|
|
442
|
-
private buildDefaultContext(): PipelineContext {
|
|
443
|
-
return {
|
|
444
|
-
repositoryUrl: this.config.repositoryUrl ?? "",
|
|
445
|
-
branch: this.config.branch ?? "main",
|
|
446
|
-
commit: this.config.commitHash ?? "",
|
|
447
|
-
author: this.config.author ?? "",
|
|
448
|
-
environment: this.config.environment ?? "test",
|
|
449
|
-
buildNumber: this.config.buildNumber ?? "0",
|
|
450
|
-
artifacts: this.config.artifacts ?? [],
|
|
451
|
-
} as PipelineContext;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Initialize the security pipeline
|
|
456
|
-
*/
|
|
457
384
|
async initialize(): Promise<void> {
|
|
458
385
|
logger.info("Initializing security CI/CD pipeline");
|
|
459
386
|
await this.configManager.initialize();
|
|
460
387
|
logger.info("Security pipeline ready");
|
|
461
388
|
}
|
|
462
389
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
*/
|
|
390
|
+
// --- Core Gate Execution ---
|
|
391
|
+
|
|
466
392
|
async executeSecurityGates(
|
|
467
393
|
stage: SecurityGate["stage"],
|
|
468
394
|
context: PipelineContext,
|
|
469
|
-
options: {
|
|
470
|
-
skipNonBlocking?: boolean;
|
|
471
|
-
continueOnFailure?: boolean;
|
|
472
|
-
dryRun?: boolean;
|
|
473
|
-
} = {},
|
|
395
|
+
options: GateExecutionOptions = {},
|
|
474
396
|
): Promise<PipelineSecurityReport> {
|
|
475
397
|
const reportId = SecurityUtils.generateSecureToken(16);
|
|
476
398
|
const startTime = Date.now();
|
|
@@ -485,27 +407,26 @@ export class SecurityCIPipeline {
|
|
|
485
407
|
|
|
486
408
|
if (applicableGates.length === 0) {
|
|
487
409
|
logger.warn(`No security gates configured for stage: ${stage}`, { stage });
|
|
488
|
-
return this.createEmptyReport(reportId, stage, startTime);
|
|
410
|
+
return this.reportGenerator.createEmptyReport(reportId, stage, startTime);
|
|
489
411
|
}
|
|
490
412
|
|
|
491
|
-
const gateResults: GateResult[] = [];
|
|
413
|
+
const gateResults: import("./SecurityTypes.js").GateResult[] = [];
|
|
492
414
|
let overallStatus: "passed" | "failed" | "warning" = "passed";
|
|
493
415
|
|
|
494
416
|
for (const gate of applicableGates) {
|
|
495
417
|
logger.info(`Executing gate: ${gate.name}`, { gateName: gate.name });
|
|
496
418
|
|
|
497
419
|
try {
|
|
498
|
-
const gateResult = await this.
|
|
420
|
+
const gateResult = await this.gateExecutor.executeGate(gate, context, options);
|
|
499
421
|
gateResults.push(gateResult);
|
|
500
422
|
|
|
501
|
-
// Update overall status
|
|
502
423
|
if (gateResult.status === "failed" && gate.blocking) {
|
|
503
424
|
overallStatus = "failed";
|
|
504
425
|
} else if (gateResult.status === "warning" && overallStatus === "passed") {
|
|
505
426
|
overallStatus = "warning";
|
|
506
427
|
}
|
|
507
428
|
|
|
508
|
-
// Send notification for failed gates
|
|
429
|
+
// Send notification for failed gates
|
|
509
430
|
try {
|
|
510
431
|
if (gateResult.status === "failed") {
|
|
511
432
|
const criticalCount = gateResult.checks
|
|
@@ -516,18 +437,17 @@ export class SecurityCIPipeline {
|
|
|
516
437
|
);
|
|
517
438
|
}
|
|
518
439
|
} catch (_e) {
|
|
519
|
-
// ignore notification errors
|
|
440
|
+
// ignore notification errors
|
|
520
441
|
}
|
|
521
442
|
|
|
522
|
-
// Stop on blocking failure unless continuing on failure
|
|
523
443
|
if (gateResult.status === "failed" && gate.blocking && !options.continueOnFailure) {
|
|
524
444
|
logger.error(`Stopping pipeline due to blocking gate failure: ${gate.name}`, { gateName: gate.name });
|
|
525
445
|
break;
|
|
526
446
|
}
|
|
527
447
|
} catch (_error) {
|
|
528
|
-
logger.error(`Gate execution
|
|
448
|
+
logger.error(`Gate execution error: ${gate.name}`, { gateName: gate.name, _error });
|
|
529
449
|
|
|
530
|
-
const errorResult: GateResult = {
|
|
450
|
+
const errorResult: import("./SecurityTypes.js").GateResult = {
|
|
531
451
|
gateId: gate.id,
|
|
532
452
|
gateName: gate.name,
|
|
533
453
|
status: "failed",
|
|
@@ -546,647 +466,29 @@ export class SecurityCIPipeline {
|
|
|
546
466
|
}
|
|
547
467
|
}
|
|
548
468
|
|
|
549
|
-
const report = this.
|
|
469
|
+
const report = this.reportGenerator.generateReport(reportId, stage, startTime, overallStatus, gateResults, context);
|
|
550
470
|
|
|
551
|
-
this.
|
|
471
|
+
this.reportGenerator.storeReport(report);
|
|
552
472
|
|
|
553
473
|
logger.info(`${stage} gates completed`, { stage, status: overallStatus });
|
|
554
474
|
|
|
555
475
|
return report;
|
|
556
476
|
}
|
|
557
477
|
|
|
558
|
-
|
|
559
|
-
* Execute a single security gate
|
|
560
|
-
*/
|
|
561
|
-
private async executeSecurityGate(
|
|
562
|
-
gate: SecurityGate,
|
|
563
|
-
context: PipelineContext,
|
|
564
|
-
options: { dryRun?: boolean } = {},
|
|
565
|
-
): Promise<GateResult> {
|
|
566
|
-
const startTime = Date.now();
|
|
567
|
-
const checkResults: CheckResult[] = [];
|
|
568
|
-
|
|
569
|
-
for (const check of gate.checks) {
|
|
570
|
-
if (!check.enabled) {
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
logger.info(`Running check: ${check.name}`, { checkName: check.name });
|
|
575
|
-
|
|
576
|
-
try {
|
|
577
|
-
const checkResult = await this.executeSecurityCheck(check, context, options);
|
|
578
|
-
checkResults.push(checkResult);
|
|
579
|
-
} catch (_error) {
|
|
580
|
-
logger.error(`Check execution _error: ${check.name}`, { checkName: check.name, _error });
|
|
581
|
-
|
|
582
|
-
checkResults.push({
|
|
583
|
-
checkId: check.id,
|
|
584
|
-
checkName: check.name,
|
|
585
|
-
status: "error",
|
|
586
|
-
duration: Date.now() - startTime,
|
|
587
|
-
findings: [],
|
|
588
|
-
details: `Check execution failed: ${_error instanceof Error ? _error.message : String(_error)}`,
|
|
589
|
-
score: 0,
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Evaluate gate status based on check results and thresholds
|
|
595
|
-
const gateStatus = this.evaluateGateStatus(gate, checkResults);
|
|
596
|
-
|
|
597
|
-
return {
|
|
598
|
-
gateId: gate.id,
|
|
599
|
-
gateName: gate.id,
|
|
600
|
-
status: gateStatus.status,
|
|
601
|
-
duration: Date.now() - startTime,
|
|
602
|
-
checks: checkResults,
|
|
603
|
-
blocking: gate.blocking,
|
|
604
|
-
message: gateStatus.message,
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* Execute a single security check
|
|
610
|
-
*/
|
|
611
|
-
private async executeSecurityCheck(
|
|
612
|
-
check: SecurityCheck,
|
|
613
|
-
context: PipelineContext,
|
|
614
|
-
options: { dryRun?: boolean } = {},
|
|
615
|
-
): Promise<CheckResult> {
|
|
616
|
-
const startTime = Date.now();
|
|
617
|
-
const findings: SecurityFinding[] = [];
|
|
618
|
-
let score: number = 100; // Initialize with safe default
|
|
619
|
-
let details = "";
|
|
620
|
-
|
|
621
|
-
if (options.dryRun) {
|
|
622
|
-
return {
|
|
623
|
-
checkId: check.id,
|
|
624
|
-
checkName: check.name,
|
|
625
|
-
status: "passed",
|
|
626
|
-
duration: Date.now() - startTime,
|
|
627
|
-
findings: [],
|
|
628
|
-
details: "Dry run - no actual checks performed",
|
|
629
|
-
score: 100,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
try {
|
|
634
|
-
switch (check.type) {
|
|
635
|
-
case "scan":
|
|
636
|
-
const scanResults = await this.executeScanCheck(check, context);
|
|
637
|
-
findings.push(...scanResults.findings);
|
|
638
|
-
score = scanResults.score;
|
|
639
|
-
details = scanResults.details;
|
|
640
|
-
break;
|
|
641
|
-
|
|
642
|
-
case "review":
|
|
643
|
-
const reviewResults = await this.executeReviewCheck(check, context);
|
|
644
|
-
findings.push(...reviewResults.findings);
|
|
645
|
-
score = reviewResults.score;
|
|
646
|
-
details = reviewResults.details;
|
|
647
|
-
break;
|
|
648
|
-
|
|
649
|
-
case "dependency":
|
|
650
|
-
const depResults = await this.executeDependencyCheck(check, context);
|
|
651
|
-
findings.push(...depResults.findings);
|
|
652
|
-
score = depResults.score;
|
|
653
|
-
details = depResults.details;
|
|
654
|
-
break;
|
|
655
|
-
|
|
656
|
-
case "configuration":
|
|
657
|
-
const configResults = await this.executeConfigurationCheck(check, context);
|
|
658
|
-
findings.push(...configResults.findings);
|
|
659
|
-
score = configResults.score;
|
|
660
|
-
details = configResults.details;
|
|
661
|
-
break;
|
|
662
|
-
|
|
663
|
-
case "secrets":
|
|
664
|
-
const secretResults = await this.executeSecretsCheck(check, context);
|
|
665
|
-
findings.push(...secretResults.findings);
|
|
666
|
-
score = secretResults.score;
|
|
667
|
-
details = secretResults.details;
|
|
668
|
-
break;
|
|
669
|
-
|
|
670
|
-
case "compliance":
|
|
671
|
-
const complianceResults = await this.executeComplianceCheck(check, context);
|
|
672
|
-
findings.push(...complianceResults.findings);
|
|
673
|
-
score = complianceResults.score;
|
|
674
|
-
details = complianceResults.details;
|
|
675
|
-
break;
|
|
676
|
-
|
|
677
|
-
default:
|
|
678
|
-
throw new Error(`Unknown check type: ${check.type}`);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// Determine check status based on findings
|
|
682
|
-
const criticalCount = findings.filter((f) => f.severity === "critical").length;
|
|
683
|
-
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
684
|
-
|
|
685
|
-
let status: CheckResult["status"];
|
|
686
|
-
if (criticalCount > 0) {
|
|
687
|
-
status = "failed";
|
|
688
|
-
} else if (highCount > 0) {
|
|
689
|
-
status = "warning";
|
|
690
|
-
} else {
|
|
691
|
-
status = "passed";
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
return {
|
|
695
|
-
checkId: check.id,
|
|
696
|
-
checkName: check.name,
|
|
697
|
-
status,
|
|
698
|
-
duration: Date.now() - startTime,
|
|
699
|
-
findings,
|
|
700
|
-
details,
|
|
701
|
-
score,
|
|
702
|
-
};
|
|
703
|
-
} catch (_error) {
|
|
704
|
-
// Log the original error for observability while converting to warning
|
|
705
|
-
logger.warn(`Security check ${check.name} encountered error (converting to warning)`, {
|
|
706
|
-
checkId: check.id,
|
|
707
|
-
checkName: check.name,
|
|
708
|
-
error: String(_error),
|
|
709
|
-
errorStack: _error instanceof Error ? _error.stack : undefined,
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
// Instead of throwing (which produced 'error' status externally and failed gates),
|
|
713
|
-
// convert this into a non-blocking warning result so default gates pass unless
|
|
714
|
-
// tests intentionally inject critical/high findings.
|
|
715
|
-
return {
|
|
716
|
-
checkId: check.id,
|
|
717
|
-
checkName: check.name,
|
|
718
|
-
status: "warning",
|
|
719
|
-
duration: Date.now() - startTime,
|
|
720
|
-
findings: [],
|
|
721
|
-
details: `Check execution issue treated as warning: ${String(_error)}`,
|
|
722
|
-
// Use 90 as neutral score to indicate uncertainty (lower than perfect 100 but still passing)
|
|
723
|
-
score: Math.min(score ?? 100, 90),
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* Execute security scan check
|
|
730
|
-
*/
|
|
731
|
-
private async executeScanCheck(
|
|
732
|
-
check: SecurityCheck,
|
|
733
|
-
context: PipelineContext,
|
|
734
|
-
): Promise<{
|
|
735
|
-
findings: SecurityFinding[];
|
|
736
|
-
score: number;
|
|
737
|
-
details: string;
|
|
738
|
-
}> {
|
|
739
|
-
const scanParams = check.parameters as {
|
|
740
|
-
targets?: string[];
|
|
741
|
-
depth?: "shallow" | "deep" | "comprehensive";
|
|
742
|
-
includeRuntime?: boolean;
|
|
743
|
-
includeFileSystem?: boolean;
|
|
744
|
-
};
|
|
745
|
-
// Prefer explicit scanner APIs when present (tests mock these). Fall back to performScan when needed.
|
|
746
|
-
const scannerAny = this.scanner as unknown as {
|
|
747
|
-
scanCodeForVulnerabilities?: () => Promise<unknown>;
|
|
748
|
-
performScan?: (opts?: unknown) => Promise<unknown>;
|
|
749
|
-
};
|
|
750
|
-
|
|
751
|
-
let scanResult: unknown;
|
|
752
|
-
if (typeof scannerAny.scanCodeForVulnerabilities === "function") {
|
|
753
|
-
scanResult = await scannerAny.scanCodeForVulnerabilities();
|
|
754
|
-
} else if (typeof scannerAny.performScan === "function") {
|
|
755
|
-
scanResult = await scannerAny.performScan({
|
|
756
|
-
targets: scanParams.targets ?? ["src/"],
|
|
757
|
-
depth: scanParams.depth ?? "deep",
|
|
758
|
-
includeRuntime: scanParams.includeRuntime ?? false,
|
|
759
|
-
includeFileSystem: scanParams.includeFileSystem ?? true,
|
|
760
|
-
});
|
|
761
|
-
} else {
|
|
762
|
-
scanResult = { vulnerabilities: [], summary: { total: 0, critical: 0, high: 0, medium: 0 } };
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// Normalize scanResult shape if mocks provide only vulnerabilities without summary
|
|
766
|
-
const scanResultTyped = scanResult as
|
|
767
|
-
| { vulnerabilities?: unknown[]; summary?: Record<string, unknown> }
|
|
768
|
-
| null
|
|
769
|
-
| undefined;
|
|
770
|
-
const vulns = Array.isArray(scanResultTyped?.vulnerabilities) ? scanResultTyped.vulnerabilities : [];
|
|
771
|
-
const summary = scanResultTyped?.summary
|
|
772
|
-
? scanResultTyped.summary
|
|
773
|
-
: {
|
|
774
|
-
total: vulns.length,
|
|
775
|
-
critical: vulns.filter((v: unknown) => (v as { severity?: string })?.severity === "critical").length,
|
|
776
|
-
high: vulns.filter((v: unknown) => (v as { severity?: string })?.severity === "high").length,
|
|
777
|
-
medium: vulns.filter((v: unknown) => (v as { severity?: string })?.severity === "medium").length,
|
|
778
|
-
};
|
|
779
|
-
|
|
780
|
-
const findings: SecurityFinding[] = (vulns || []).map((vuln: unknown) => {
|
|
781
|
-
const v = vuln as {
|
|
782
|
-
id?: string;
|
|
783
|
-
severity?: string;
|
|
784
|
-
type?: string;
|
|
785
|
-
description?: string;
|
|
786
|
-
location?: { file?: string; line?: number };
|
|
787
|
-
remediation?: { suggested?: string };
|
|
788
|
-
};
|
|
789
|
-
return {
|
|
790
|
-
id: v.id || "unknown",
|
|
791
|
-
severity: (v.severity as SecurityFinding["severity"]) || "medium",
|
|
792
|
-
type: v.type || "vulnerability",
|
|
793
|
-
description: v.description || "No description",
|
|
794
|
-
file: v.location?.file,
|
|
795
|
-
line: v.location?.line,
|
|
796
|
-
remediation: v.remediation?.suggested,
|
|
797
|
-
};
|
|
798
|
-
});
|
|
799
|
-
const summaryTyped = summary as { critical?: number; high?: number; medium?: number };
|
|
800
|
-
const score = Math.max(
|
|
801
|
-
0,
|
|
802
|
-
100 - ((summaryTyped.critical || 0) * 10 + (summaryTyped.high || 0) * 5 + (summaryTyped.medium || 0) * 2),
|
|
803
|
-
);
|
|
804
|
-
|
|
805
|
-
return {
|
|
806
|
-
findings,
|
|
807
|
-
score,
|
|
808
|
-
details: `Scanned codebase: ${summary.total} vulnerabilities found`,
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
/**
|
|
813
|
-
* Execute code review check
|
|
814
|
-
*/
|
|
815
|
-
private async executeReviewCheck(
|
|
816
|
-
check: SecurityCheck,
|
|
817
|
-
context: PipelineContext,
|
|
818
|
-
): Promise<{
|
|
819
|
-
findings: SecurityFinding[];
|
|
820
|
-
score: number;
|
|
821
|
-
details: string;
|
|
822
|
-
}> {
|
|
823
|
-
const reviewParams = check.parameters as { rules?: string[]; excludeRules?: string[]; aiAnalysis?: boolean };
|
|
824
|
-
|
|
825
|
-
// Support either reviewer.reviewDirectory (returns array) or reviewer.reviewCode (returns single summary)
|
|
826
|
-
const reviewerAny = this.reviewer as unknown as {
|
|
827
|
-
reviewDirectory?: (path: string, opts?: unknown) => Promise<unknown>;
|
|
828
|
-
reviewCode?: (path: string, opts?: unknown) => Promise<unknown>;
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
const raw =
|
|
832
|
-
typeof reviewerAny.reviewDirectory === "function"
|
|
833
|
-
? await reviewerAny.reviewDirectory("src/", {
|
|
834
|
-
recursive: true,
|
|
835
|
-
rules: reviewParams.rules ?? [],
|
|
836
|
-
excludeRules: reviewParams.excludeRules ?? [],
|
|
837
|
-
aiAnalysis: reviewParams.aiAnalysis ?? false,
|
|
838
|
-
})
|
|
839
|
-
: typeof reviewerAny.reviewCode === "function"
|
|
840
|
-
? await reviewerAny.reviewCode("src/", {
|
|
841
|
-
rules: reviewParams.rules ?? [],
|
|
842
|
-
aiAnalysis: reviewParams.aiAnalysis ?? false,
|
|
843
|
-
})
|
|
844
|
-
: [];
|
|
845
|
-
|
|
846
|
-
const reviewResults = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
847
|
-
|
|
848
|
-
const allFindings: SecurityFinding[] = [];
|
|
849
|
-
let totalScore = 0;
|
|
850
|
-
|
|
851
|
-
for (const result of reviewResults) {
|
|
852
|
-
const resultTyped = result as { findings?: unknown[]; file?: string };
|
|
853
|
-
const resultFindings = (resultTyped.findings || []).map((finding: unknown) => {
|
|
854
|
-
const f = finding as {
|
|
855
|
-
id?: string;
|
|
856
|
-
severity?: string;
|
|
857
|
-
category?: string;
|
|
858
|
-
type?: string;
|
|
859
|
-
message?: string;
|
|
860
|
-
description?: string;
|
|
861
|
-
line?: number;
|
|
862
|
-
recommendation?: string;
|
|
863
|
-
remediation?: string;
|
|
864
|
-
};
|
|
865
|
-
return {
|
|
866
|
-
id: f.id || "unknown",
|
|
867
|
-
severity: (f.severity as SecurityFinding["severity"]) || "medium",
|
|
868
|
-
type: f.category || f.type || "review",
|
|
869
|
-
description: f.message || f.description || "No description",
|
|
870
|
-
file: resultTyped.file,
|
|
871
|
-
line: f.line,
|
|
872
|
-
remediation: f.recommendation || f.remediation,
|
|
873
|
-
};
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
allFindings.push(...resultFindings);
|
|
877
|
-
totalScore += this.calculateFileScore(result.findings || []);
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
const averageScore = reviewResults.length > 0 ? totalScore / reviewResults.length : 100;
|
|
881
|
-
|
|
882
|
-
return {
|
|
883
|
-
findings: allFindings,
|
|
884
|
-
score: averageScore,
|
|
885
|
-
details: `Reviewed ${reviewResults.length} files: ${allFindings.length} security issues found`,
|
|
886
|
-
};
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* Execute dependency check
|
|
891
|
-
*/
|
|
892
|
-
private async executeDependencyCheck(
|
|
893
|
-
check: SecurityCheck,
|
|
894
|
-
context: PipelineContext,
|
|
895
|
-
): Promise<{
|
|
896
|
-
findings: SecurityFinding[];
|
|
897
|
-
score: number;
|
|
898
|
-
details: string;
|
|
899
|
-
}> {
|
|
900
|
-
// This would integrate with npm audit, Snyk, or similar tools
|
|
901
|
-
logger.info("Dependency check - integration with external tools required");
|
|
902
|
-
|
|
903
|
-
return {
|
|
904
|
-
findings: [],
|
|
905
|
-
score: 100,
|
|
906
|
-
details: "Dependency check completed - no vulnerabilities found",
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Execute configuration check
|
|
912
|
-
*/
|
|
913
|
-
private async executeConfigurationCheck(
|
|
914
|
-
check: SecurityCheck,
|
|
915
|
-
context: PipelineContext,
|
|
916
|
-
): Promise<{
|
|
917
|
-
findings: SecurityFinding[];
|
|
918
|
-
score: number;
|
|
919
|
-
details: string;
|
|
920
|
-
}> {
|
|
921
|
-
// Some test mocks provide validateCompliance, others may not. Fall back to compliant=true when unavailable.
|
|
922
|
-
let compliance: { compliant: boolean; violations: string[] } = { compliant: true, violations: [] };
|
|
923
|
-
try {
|
|
924
|
-
const configManager = this.configManager as unknown as Record<string, unknown> & {
|
|
925
|
-
validateCompliance?: (env: string) => Promise<{ compliant: boolean; violations: string[] }>;
|
|
926
|
-
getSecurityConfig?: () => Record<string, unknown> & { compliant?: boolean; violations?: string[] };
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
if (typeof configManager.validateCompliance === "function") {
|
|
930
|
-
compliance = await configManager.validateCompliance(context.environment);
|
|
931
|
-
} else if (typeof configManager.getSecurityConfig === "function") {
|
|
932
|
-
// derive basic compliance from config when validateCompliance is not available
|
|
933
|
-
const cfg = configManager.getSecurityConfig() || {};
|
|
934
|
-
compliance = { compliant: !!cfg.compliant, violations: cfg.violations ?? [] };
|
|
935
|
-
}
|
|
936
|
-
} catch (_e) {
|
|
937
|
-
compliance = { compliant: true, violations: [] };
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
const findings: SecurityFinding[] = compliance.violations.map((violation, index) => ({
|
|
941
|
-
id: `config-${index}`,
|
|
942
|
-
severity: "medium" as const,
|
|
943
|
-
type: "Configuration",
|
|
944
|
-
description: violation,
|
|
945
|
-
remediation: "Review security configuration",
|
|
946
|
-
}));
|
|
947
|
-
|
|
948
|
-
const score = compliance.compliant ? 100 : Math.max(0, 100 - compliance.violations.length * 10);
|
|
949
|
-
|
|
950
|
-
return {
|
|
951
|
-
findings,
|
|
952
|
-
score,
|
|
953
|
-
details: `Configuration compliance: ${compliance.compliant ? "compliant" : "non-compliant"}`,
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
/**
|
|
958
|
-
* Execute secrets check
|
|
959
|
-
*/
|
|
960
|
-
private async executeSecretsCheck(
|
|
961
|
-
check: SecurityCheck,
|
|
962
|
-
context: PipelineContext,
|
|
963
|
-
): Promise<{
|
|
964
|
-
findings: SecurityFinding[];
|
|
965
|
-
score: number;
|
|
966
|
-
details: string;
|
|
967
|
-
}> {
|
|
968
|
-
// This would integrate with tools like TruffleHog, GitLeaks, etc.
|
|
969
|
-
logger.info("Secrets check - integration with secret scanning tools required");
|
|
970
|
-
|
|
971
|
-
return {
|
|
972
|
-
findings: [],
|
|
973
|
-
score: 100,
|
|
974
|
-
details: "Secrets scan completed - no exposed secrets found",
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
/**
|
|
979
|
-
* Execute compliance check
|
|
980
|
-
*/
|
|
981
|
-
private async executeComplianceCheck(
|
|
982
|
-
check: SecurityCheck,
|
|
983
|
-
context: PipelineContext,
|
|
984
|
-
): Promise<{
|
|
985
|
-
findings: SecurityFinding[];
|
|
986
|
-
score: number;
|
|
987
|
-
details: string;
|
|
988
|
-
}> {
|
|
989
|
-
const complianceParams = check.parameters as { frameworks?: string[] };
|
|
990
|
-
const frameworks: string[] = complianceParams.frameworks ?? ["OWASP", "CWE"];
|
|
991
|
-
const findings: SecurityFinding[] = [];
|
|
992
|
-
|
|
993
|
-
// Check for compliance with security frameworks
|
|
994
|
-
for (const framework of frameworks) {
|
|
995
|
-
// This would integrate with compliance checking tools
|
|
996
|
-
logger.info(`Checking ${framework} compliance`, { framework });
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
return {
|
|
1000
|
-
findings,
|
|
1001
|
-
score: 100,
|
|
1002
|
-
details: `Compliance check completed for frameworks: ${frameworks.join(", ")}`,
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Calculate security score for file findings
|
|
1008
|
-
*/
|
|
1009
|
-
private calculateFileScore(findings: Array<{ severity: string }>): number {
|
|
1010
|
-
const severityWeights: Record<string, number> = { critical: 20, high: 10, medium: 5, low: 2, info: 1 };
|
|
1011
|
-
const penalty = findings.reduce((sum: number, finding) => {
|
|
1012
|
-
return sum + (severityWeights[finding.severity] || 0);
|
|
1013
|
-
}, 0);
|
|
1014
|
-
return Math.max(0, 100 - penalty);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
* Evaluate gate status based on check results and thresholds
|
|
1019
|
-
*/
|
|
1020
|
-
private evaluateGateStatus(
|
|
1021
|
-
gate: SecurityGate,
|
|
1022
|
-
checkResults: CheckResult[],
|
|
1023
|
-
): {
|
|
1024
|
-
status: "passed" | "failed" | "warning";
|
|
1025
|
-
message: string;
|
|
1026
|
-
} {
|
|
1027
|
-
const allFindings = checkResults.flatMap((result) => result.findings);
|
|
1028
|
-
|
|
1029
|
-
const criticalCount = allFindings.filter((f) => f.severity === "critical").length;
|
|
1030
|
-
const highCount = allFindings.filter((f) => f.severity === "high").length;
|
|
1031
|
-
const mediumCount = allFindings.filter((f) => f.severity === "medium").length;
|
|
1032
|
-
|
|
1033
|
-
// Exclude error checks from average score calculation
|
|
1034
|
-
const validChecks = checkResults.filter((result) => result.status !== "error");
|
|
1035
|
-
const averageScore =
|
|
1036
|
-
validChecks.length > 0 ? validChecks.reduce((sum, result) => sum + result.score, 0) / validChecks.length : 100;
|
|
1037
|
-
|
|
1038
|
-
// Check thresholds
|
|
1039
|
-
if (criticalCount > gate.thresholds.maxCritical) {
|
|
1040
|
-
return {
|
|
1041
|
-
status: "failed",
|
|
1042
|
-
message: `Critical vulnerabilities (${criticalCount}) exceed threshold (${gate.thresholds.maxCritical})`,
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
if (highCount > gate.thresholds.maxHigh) {
|
|
1047
|
-
return {
|
|
1048
|
-
status: "failed",
|
|
1049
|
-
message: `High-severity vulnerabilities (${highCount}) exceed threshold (${gate.thresholds.maxHigh})`,
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
if (averageScore < gate.thresholds.minSecurityScore) {
|
|
1054
|
-
return {
|
|
1055
|
-
status: "failed",
|
|
1056
|
-
message: `Security score (${averageScore.toFixed(1)}) below threshold (${gate.thresholds.minSecurityScore})`,
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
if (mediumCount > gate.thresholds.maxMedium) {
|
|
1061
|
-
return {
|
|
1062
|
-
status: "warning",
|
|
1063
|
-
message: `Medium-severity vulnerabilities (${mediumCount}) exceed threshold (${gate.thresholds.maxMedium})`,
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
return {
|
|
1068
|
-
status: "passed",
|
|
1069
|
-
message: "All security checks passed",
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
/**
|
|
1074
|
-
* Generate pipeline security report
|
|
1075
|
-
*/
|
|
1076
|
-
private generatePipelineReport(
|
|
1077
|
-
reportId: string,
|
|
1078
|
-
stage: string,
|
|
1079
|
-
startTime: number,
|
|
1080
|
-
status: "passed" | "failed" | "warning",
|
|
1081
|
-
gateResults: GateResult[],
|
|
1082
|
-
context: PipelineContext,
|
|
1083
|
-
): PipelineSecurityReport {
|
|
1084
|
-
const allFindings = gateResults.flatMap((gate) => gate.checks.flatMap((check) => check.findings));
|
|
1085
|
-
|
|
1086
|
-
const summary = {
|
|
1087
|
-
totalIssues: allFindings.length,
|
|
1088
|
-
criticalIssues: allFindings.filter((f) => f.severity === "critical").length,
|
|
1089
|
-
highIssues: allFindings.filter((f) => f.severity === "high").length,
|
|
1090
|
-
mediumIssues: allFindings.filter((f) => f.severity === "medium").length,
|
|
1091
|
-
lowIssues: allFindings.filter((f) => f.severity === "low").length,
|
|
1092
|
-
securityScore: this.calculateOverallSecurityScore(gateResults),
|
|
1093
|
-
compliance: status === "passed",
|
|
1094
|
-
};
|
|
1095
|
-
|
|
1096
|
-
const recommendations = this.generateRecommendations(gateResults, summary);
|
|
1097
|
-
|
|
1098
|
-
return {
|
|
1099
|
-
reportId,
|
|
1100
|
-
timestamp: new Date(),
|
|
1101
|
-
stage,
|
|
1102
|
-
status,
|
|
1103
|
-
duration: Date.now() - startTime,
|
|
1104
|
-
gates: gateResults,
|
|
1105
|
-
summary,
|
|
1106
|
-
recommendations,
|
|
1107
|
-
artifacts: this.generateArtifacts(reportId, gateResults),
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
/**
|
|
1112
|
-
* Calculate overall security score
|
|
1113
|
-
*/
|
|
1114
|
-
private calculateOverallSecurityScore(gateResults: GateResult[]): number {
|
|
1115
|
-
const allChecks = gateResults.flatMap((gate) => gate.checks);
|
|
1116
|
-
|
|
1117
|
-
if (allChecks.length === 0) {
|
|
1118
|
-
return 100;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
const totalScore = allChecks.reduce((sum, check) => sum + check.score, 0);
|
|
1122
|
-
return totalScore / allChecks.length;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
/**
|
|
1126
|
-
* Generate recommendations based on results
|
|
1127
|
-
*/
|
|
1128
|
-
private generateRecommendations(
|
|
1129
|
-
gateResults: GateResult[],
|
|
1130
|
-
summary: { criticalIssues: number; highIssues: number; securityScore: number; [key: string]: unknown },
|
|
1131
|
-
): string[] {
|
|
1132
|
-
const recommendations: string[] = [];
|
|
1133
|
-
|
|
1134
|
-
if (summary.criticalIssues > 0) {
|
|
1135
|
-
recommendations.push("Address critical security vulnerabilities immediately before deployment");
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
if (summary.highIssues > 5) {
|
|
1139
|
-
recommendations.push("Review and remediate high-severity security issues");
|
|
1140
|
-
}
|
|
478
|
+
// --- Private Helpers ---
|
|
1141
479
|
|
|
1142
|
-
|
|
1143
|
-
recommendations.push("Improve overall security posture through code review and security training");
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
const failedGates = gateResults.filter((gate) => gate.status === "failed");
|
|
1147
|
-
if (failedGates.length > 0) {
|
|
1148
|
-
recommendations.push(`Review failed security gates: ${failedGates.map((g) => g.gateName).join(", ")}`);
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
return recommendations;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/**
|
|
1155
|
-
* Generate artifacts for the security report
|
|
1156
|
-
*/
|
|
1157
|
-
private generateArtifacts(reportId: string, gateResults: GateResult[]): string[] {
|
|
1158
|
-
// In a real implementation, this would generate SARIF files, security reports, etc.
|
|
1159
|
-
return [`security-report-${reportId}.json`, `security-findings-${reportId}.sarif`];
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
/**
|
|
1163
|
-
* Create empty report for stages with no gates
|
|
1164
|
-
*/
|
|
1165
|
-
private createEmptyReport(reportId: string, stage: string, startTime: number): PipelineSecurityReport {
|
|
480
|
+
private buildDefaultContext(): PipelineContext {
|
|
1166
481
|
return {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
criticalIssues: 0,
|
|
1176
|
-
highIssues: 0,
|
|
1177
|
-
mediumIssues: 0,
|
|
1178
|
-
lowIssues: 0,
|
|
1179
|
-
securityScore: 100,
|
|
1180
|
-
compliance: true,
|
|
1181
|
-
},
|
|
1182
|
-
recommendations: [],
|
|
1183
|
-
artifacts: [],
|
|
1184
|
-
};
|
|
482
|
+
repositoryUrl: this.config.repositoryUrl ?? "",
|
|
483
|
+
branch: this.config.branch ?? "main",
|
|
484
|
+
commit: this.config.commitHash ?? "",
|
|
485
|
+
author: this.config.author ?? "",
|
|
486
|
+
environment: this.config.environment ?? "test",
|
|
487
|
+
buildNumber: this.config.buildNumber ?? "0",
|
|
488
|
+
artifacts: this.config.artifacts ?? [],
|
|
489
|
+
} as PipelineContext;
|
|
1185
490
|
}
|
|
1186
491
|
|
|
1187
|
-
/**
|
|
1188
|
-
* Initialize default security gates
|
|
1189
|
-
*/
|
|
1190
492
|
private initializeDefaultGates(): void {
|
|
1191
493
|
// Pre-commit gate
|
|
1192
494
|
this.gates.set("pre-commit", {
|
|
@@ -1314,129 +616,4 @@ export class SecurityCIPipeline {
|
|
|
1314
616
|
exceptions: [],
|
|
1315
617
|
});
|
|
1316
618
|
}
|
|
1317
|
-
|
|
1318
|
-
/**
|
|
1319
|
-
* Get security gate configuration
|
|
1320
|
-
*/
|
|
1321
|
-
getSecurityGate(gateId: string): SecurityGate | null {
|
|
1322
|
-
return this.gates.get(gateId) || null;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
/**
|
|
1326
|
-
* Update security gate configuration
|
|
1327
|
-
*/
|
|
1328
|
-
updateSecurityGate(gateId: string, updates: Partial<SecurityGate>): boolean {
|
|
1329
|
-
const gate = this.gates.get(gateId);
|
|
1330
|
-
if (!gate) {
|
|
1331
|
-
return false;
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
const updatedGate = { ...gate, ...updates, id: gateId };
|
|
1335
|
-
this.gates.set(gateId, updatedGate);
|
|
1336
|
-
|
|
1337
|
-
logger.info(`Updated security gate: ${updatedGate.name}`, { gateName: updatedGate.name });
|
|
1338
|
-
return true;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
/**
|
|
1342
|
-
* Get pipeline reports
|
|
1343
|
-
*/
|
|
1344
|
-
getReports(
|
|
1345
|
-
options: {
|
|
1346
|
-
stage?: string;
|
|
1347
|
-
status?: string;
|
|
1348
|
-
since?: Date;
|
|
1349
|
-
limit?: number;
|
|
1350
|
-
} = {},
|
|
1351
|
-
): PipelineSecurityReport[] {
|
|
1352
|
-
let reports = [...this.reports];
|
|
1353
|
-
|
|
1354
|
-
if (options.stage) {
|
|
1355
|
-
reports = reports.filter((r) => r.stage === options.stage);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
if (options.status) {
|
|
1359
|
-
reports = reports.filter((r) => r.status === options.status);
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
if (options.since) {
|
|
1363
|
-
reports = reports.filter((r) => r.timestamp >= options.since!);
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
// Sort by timestamp (newest first)
|
|
1367
|
-
reports.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
1368
|
-
|
|
1369
|
-
if (options.limit) {
|
|
1370
|
-
reports = reports.slice(0, options.limit);
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
return reports;
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
/**
|
|
1377
|
-
* Get pipeline statistics
|
|
1378
|
-
*/
|
|
1379
|
-
getStatistics(): {
|
|
1380
|
-
totalReports: number;
|
|
1381
|
-
passRate: number;
|
|
1382
|
-
averageSecurityScore: number;
|
|
1383
|
-
mostCommonIssues: { type: string; count: number }[];
|
|
1384
|
-
gatePerformance: { gateId: string; successRate: number; averageDuration: number }[];
|
|
1385
|
-
} {
|
|
1386
|
-
const totalReports = this.reports.length;
|
|
1387
|
-
const passedReports = this.reports.filter((r) => r.status === "passed").length;
|
|
1388
|
-
const passRate = totalReports > 0 ? passedReports / totalReports : 1;
|
|
1389
|
-
|
|
1390
|
-
const averageSecurityScore =
|
|
1391
|
-
totalReports > 0 ? this.reports.reduce((sum, r) => sum + r.summary.securityScore, 0) / totalReports : 100;
|
|
1392
|
-
|
|
1393
|
-
// Count issue types
|
|
1394
|
-
const issueTypes: Record<string, number> = {};
|
|
1395
|
-
this.reports.forEach((report) => {
|
|
1396
|
-
report.gates.forEach((gate) => {
|
|
1397
|
-
gate.checks.forEach((check) => {
|
|
1398
|
-
check.findings.forEach((finding) => {
|
|
1399
|
-
issueTypes[finding.type] = (issueTypes[finding.type] || 0) + 1;
|
|
1400
|
-
});
|
|
1401
|
-
});
|
|
1402
|
-
});
|
|
1403
|
-
});
|
|
1404
|
-
|
|
1405
|
-
const mostCommonIssues = Object.entries(issueTypes)
|
|
1406
|
-
.map(([type, count]) => ({ type, count }))
|
|
1407
|
-
.sort((a, b) => b.count - a.count)
|
|
1408
|
-
.slice(0, 5);
|
|
1409
|
-
|
|
1410
|
-
// Calculate gate performance
|
|
1411
|
-
const gateStats: Record<string, { total: number; passed: number; totalDuration: number }> = {};
|
|
1412
|
-
|
|
1413
|
-
this.reports.forEach((report) => {
|
|
1414
|
-
report.gates.forEach((gate) => {
|
|
1415
|
-
if (!gateStats[gate.gateId]) {
|
|
1416
|
-
gateStats[gate.gateId] = { total: 0, passed: 0, totalDuration: 0 };
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
gateStats[gate.gateId].total++;
|
|
1420
|
-
gateStats[gate.gateId].totalDuration += gate.duration;
|
|
1421
|
-
|
|
1422
|
-
if (gate.status === "passed") {
|
|
1423
|
-
gateStats[gate.gateId].passed++;
|
|
1424
|
-
}
|
|
1425
|
-
});
|
|
1426
|
-
});
|
|
1427
|
-
|
|
1428
|
-
const gatePerformance = Object.entries(gateStats).map(([gateId, stats]) => ({
|
|
1429
|
-
gateId,
|
|
1430
|
-
successRate: stats.total > 0 ? stats.passed / stats.total : 0,
|
|
1431
|
-
averageDuration: stats.total > 0 ? stats.totalDuration / stats.total : 0,
|
|
1432
|
-
}));
|
|
1433
|
-
|
|
1434
|
-
return {
|
|
1435
|
-
totalReports,
|
|
1436
|
-
passRate,
|
|
1437
|
-
averageSecurityScore,
|
|
1438
|
-
mostCommonIssues,
|
|
1439
|
-
gatePerformance,
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
619
|
}
|