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
|
import { AISecurityScanner } from "./AISecurityScanner.js";
|
|
6
11
|
import { AutomatedRemediation } from "./AutomatedRemediation.js";
|
|
@@ -8,6 +13,8 @@ import { SecurityReviewer } from "./SecurityReviewer.js";
|
|
|
8
13
|
import { SecurityConfigManager } from "./SecurityConfigManager.js";
|
|
9
14
|
import { SecurityUtils } from "./SecurityConfig.js";
|
|
10
15
|
import { LoggerFactory } from "../utils/logger.js";
|
|
16
|
+
import { SecurityGateExecutor } from "./SecurityGateExecutor.js";
|
|
17
|
+
import { SecurityReportGenerator } from "./SecurityReportGenerator.js";
|
|
11
18
|
const logger = LoggerFactory.security();
|
|
12
19
|
/**
|
|
13
20
|
* Security CI/CD Pipeline Manager
|
|
@@ -20,7 +27,8 @@ export class SecurityCIPipeline {
|
|
|
20
27
|
configManager;
|
|
21
28
|
config;
|
|
22
29
|
gates = new Map();
|
|
23
|
-
|
|
30
|
+
gateExecutor;
|
|
31
|
+
reportGenerator;
|
|
24
32
|
constructor(config = { projectPath: "/" }, deps) {
|
|
25
33
|
// Basic validation expected by tests
|
|
26
34
|
const configWithPath = config;
|
|
@@ -31,46 +39,51 @@ export class SecurityCIPipeline {
|
|
|
31
39
|
throw new Error("Invalid scanner configuration");
|
|
32
40
|
}
|
|
33
41
|
this.config = config;
|
|
34
|
-
// Instantiate components (tests provide vi.mocks which should replace constructors in the dist/ runtime)
|
|
35
42
|
// Allow dependency injection for tests to provide mocked implementations
|
|
36
43
|
this.scanner = (deps && deps.scanner) || new AISecurityScanner();
|
|
37
44
|
this.remediation = (deps && deps.remediation) || new AutomatedRemediation();
|
|
38
45
|
this.reviewer = (deps && deps.reviewer) || new SecurityReviewer();
|
|
39
46
|
this.configManager = (deps && deps.configManager) || new SecurityConfigManager();
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
// Ensure methods exist and are spyable under vitest
|
|
48
|
+
this.makeMockable(this.scanner, ["scanCodeForVulnerabilities", "performScan", "scanDependencies", "scanSecrets"]);
|
|
49
|
+
this.makeMockable(this.reviewer, ["reviewCode", "reviewDirectory", "reviewConfiguration"]);
|
|
50
|
+
this.makeMockable(this.remediation, ["autoFix", "generateRecommendations"]);
|
|
51
|
+
this.makeMockable(this.configManager, ["getSecurityConfig", "validateConfiguration", "initialize"]);
|
|
52
|
+
// Initialize executor and report generator
|
|
53
|
+
this.gateExecutor = new SecurityGateExecutor({
|
|
54
|
+
scanner: this.scanner,
|
|
55
|
+
reviewer: this.reviewer,
|
|
56
|
+
configManager: this.configManager,
|
|
57
|
+
});
|
|
58
|
+
this.reportGenerator = new SecurityReportGenerator();
|
|
59
|
+
this.initializeDefaultGates();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Helper to make methods mockable in tests
|
|
63
|
+
*/
|
|
64
|
+
makeMockable(obj, methods) {
|
|
65
|
+
const _objRecord = obj;
|
|
66
|
+
const viRef = globalThis.vi;
|
|
67
|
+
if (!obj)
|
|
68
|
+
return;
|
|
69
|
+
for (const m of methods) {
|
|
70
|
+
try {
|
|
71
|
+
const current = _objRecord[m];
|
|
72
|
+
if (typeof current !== "undefined")
|
|
73
|
+
continue;
|
|
74
|
+
if (viRef && typeof viRef.fn === "function") {
|
|
75
|
+
_objRecord[m] = viRef.fn();
|
|
61
76
|
}
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
else {
|
|
78
|
+
_objRecord[m] = () => Promise.resolve(null);
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
makeMockable(this.configManager, ["getSecurityConfig", "validateConfiguration", "initialize"]);
|
|
71
|
-
this.initializeDefaultGates();
|
|
81
|
+
catch (_e) {
|
|
82
|
+
// ignore and continue
|
|
83
|
+
}
|
|
84
|
+
}
|
|
72
85
|
}
|
|
73
|
-
// --- Public
|
|
86
|
+
// --- Public Gate Execution API ---
|
|
74
87
|
async executePreCommitGate(options = {}) {
|
|
75
88
|
const context = this.buildDefaultContext();
|
|
76
89
|
return this.executeSecurityGates("pre-commit", context, options);
|
|
@@ -83,7 +96,7 @@ export class SecurityCIPipeline {
|
|
|
83
96
|
const context = this.buildDefaultContext();
|
|
84
97
|
return this.executeSecurityGates("pre-deploy", context, options);
|
|
85
98
|
}
|
|
86
|
-
//
|
|
99
|
+
// --- Individual Check Runners ---
|
|
87
100
|
async runVulnerabilityCheck(opts = {}) {
|
|
88
101
|
const check = {
|
|
89
102
|
id: "vulnerability-scan",
|
|
@@ -102,7 +115,7 @@ export class SecurityCIPipeline {
|
|
|
102
115
|
try {
|
|
103
116
|
const scanPromise = scanner.scanCodeForVulnerabilities
|
|
104
117
|
? scanner.scanCodeForVulnerabilities()
|
|
105
|
-
: scanner.performScan?.() ?? Promise.resolve({ vulnerabilities: [], riskScore: 0 });
|
|
118
|
+
: (scanner.performScan?.() ?? Promise.resolve({ vulnerabilities: [], riskScore: 0 }));
|
|
106
119
|
if (opts.timeout && opts.timeout > 0) {
|
|
107
120
|
const res = await Promise.race([
|
|
108
121
|
scanPromise,
|
|
@@ -111,17 +124,12 @@ export class SecurityCIPipeline {
|
|
|
111
124
|
if (res && res.__timeout) {
|
|
112
125
|
return { checkId: check.id, status: "timeout" };
|
|
113
126
|
}
|
|
114
|
-
// treat the scan result like a successful check
|
|
115
127
|
return { checkId: check.id, status: "passed", result: res };
|
|
116
128
|
}
|
|
117
129
|
const res = await scanPromise;
|
|
118
|
-
// Validate response: treat null/undefined as invalid, but accept objects that may not include
|
|
119
|
-
// a vulnerabilities array (normalize to empty array). This keeps tests' mocked scanner
|
|
120
|
-
// results compatible while still flagging truly malformed responses.
|
|
121
130
|
if (res === null || typeof res === "undefined") {
|
|
122
131
|
return { checkId: check.id, status: "failed", error: "Invalid response" };
|
|
123
132
|
}
|
|
124
|
-
// Normalize vulnerabilities field
|
|
125
133
|
const resWithVulns = res;
|
|
126
134
|
if (!Array.isArray(resWithVulns.vulnerabilities)) {
|
|
127
135
|
resWithVulns.vulnerabilities = [];
|
|
@@ -132,7 +140,6 @@ export class SecurityCIPipeline {
|
|
|
132
140
|
if (attempts >= maxAttempts) {
|
|
133
141
|
return { checkId: check.id, status: "failed", error: String(err) };
|
|
134
142
|
}
|
|
135
|
-
// otherwise retry
|
|
136
143
|
}
|
|
137
144
|
}
|
|
138
145
|
return { checkId: check.id, status: "failed", error: "Retries exhausted" };
|
|
@@ -148,13 +155,12 @@ export class SecurityCIPipeline {
|
|
|
148
155
|
return { checkId: "secrets-scan", status: "passed", result: res };
|
|
149
156
|
}
|
|
150
157
|
async runCodeReviewCheck() {
|
|
151
|
-
// Prefer reviewCode if present in mocked reviewer, else fallback
|
|
152
158
|
const reviewer = this.reviewer;
|
|
153
159
|
const reviewFn = reviewer.reviewCode ?? reviewer.reviewDirectory;
|
|
154
160
|
const res = (await reviewFn?.("src/")) ?? { issues: [] };
|
|
155
161
|
return { checkId: "code-review", status: "passed", result: res };
|
|
156
162
|
}
|
|
157
|
-
// Gate
|
|
163
|
+
// --- Gate Management ---
|
|
158
164
|
configureGate(gate) {
|
|
159
165
|
if (!gate || !gate.id || !gate.stage) {
|
|
160
166
|
throw new Error("Invalid gate configuration");
|
|
@@ -187,27 +193,41 @@ export class SecurityCIPipeline {
|
|
|
187
193
|
const g = this.gates.get(gateId);
|
|
188
194
|
return !!g && g.enabled;
|
|
189
195
|
}
|
|
190
|
-
|
|
196
|
+
getSecurityGate(gateId) {
|
|
197
|
+
return this.gates.get(gateId) || null;
|
|
198
|
+
}
|
|
199
|
+
updateSecurityGate(gateId, updates) {
|
|
200
|
+
const gate = this.gates.get(gateId);
|
|
201
|
+
if (!gate) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
const updatedGate = { ...gate, ...updates, id: gateId };
|
|
205
|
+
this.gates.set(gateId, updatedGate);
|
|
206
|
+
logger.info(`Updated security gate: ${updatedGate.name}`, { gateName: updatedGate.name });
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
// --- Reporting ---
|
|
191
210
|
async generateReport() {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
211
|
+
const latest = this.reportGenerator.getLatestReport();
|
|
212
|
+
if (latest)
|
|
213
|
+
return latest;
|
|
214
|
+
return this.reportGenerator.createEmptyReport(SecurityUtils.generateSecureToken(8), "summary", Date.now());
|
|
195
215
|
}
|
|
196
216
|
async exportReport(format) {
|
|
197
217
|
const report = await this.generateReport();
|
|
198
|
-
|
|
199
|
-
return `<html><body>${JSON.stringify(report)}</body></html>`;
|
|
200
|
-
if (format === "xml")
|
|
201
|
-
return `<report>${JSON.stringify(report)}</report>`;
|
|
202
|
-
return JSON.stringify(report);
|
|
218
|
+
return this.reportGenerator.exportReport(report, format);
|
|
203
219
|
}
|
|
204
220
|
async calculateSecurityMetrics() {
|
|
205
221
|
const report = await this.generateReport();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
return this.reportGenerator.calculateSecurityMetrics(report);
|
|
223
|
+
}
|
|
224
|
+
getReports(options = {}) {
|
|
225
|
+
return this.reportGenerator.getReports(options);
|
|
209
226
|
}
|
|
210
|
-
|
|
227
|
+
getStatistics() {
|
|
228
|
+
return this.reportGenerator.getStatistics();
|
|
229
|
+
}
|
|
230
|
+
// --- Remediation ---
|
|
211
231
|
async executeAutoRemediation() {
|
|
212
232
|
try {
|
|
213
233
|
const remediation = this.remediation;
|
|
@@ -222,7 +242,7 @@ export class SecurityCIPipeline {
|
|
|
222
242
|
const remediation = this.remediation;
|
|
223
243
|
return remediation.generateRecommendations?.() ?? [];
|
|
224
244
|
}
|
|
225
|
-
// Full
|
|
245
|
+
// --- Full Pipeline Orchestration ---
|
|
226
246
|
async executeFullPipeline() {
|
|
227
247
|
const start = Date.now();
|
|
228
248
|
const stages = [];
|
|
@@ -252,7 +272,7 @@ export class SecurityCIPipeline {
|
|
|
252
272
|
}
|
|
253
273
|
return { stages, overallStatus, duration: Math.max(1, Date.now() - start) };
|
|
254
274
|
}
|
|
255
|
-
// Notifications
|
|
275
|
+
// --- Notifications ---
|
|
256
276
|
sendNotification(payload) {
|
|
257
277
|
logger.info("Sending notification", { payload });
|
|
258
278
|
}
|
|
@@ -261,45 +281,26 @@ export class SecurityCIPipeline {
|
|
|
261
281
|
const body = `Stage: ${data.stage}\nStatus: ${data.status}\ncritical: ${data.criticalIssues ?? 0}`;
|
|
262
282
|
return { subject, body };
|
|
263
283
|
}
|
|
264
|
-
// Configuration
|
|
284
|
+
// --- Configuration ---
|
|
265
285
|
reloadConfiguration(newConfig) {
|
|
266
286
|
if (!newConfig || newConfig.projectPath == null)
|
|
267
287
|
throw new Error("Invalid configuration");
|
|
268
|
-
// preserve gate enabled/disabled states
|
|
269
288
|
const state = {};
|
|
270
289
|
for (const [id, g] of this.gates.entries())
|
|
271
290
|
state[id] = g.enabled;
|
|
272
291
|
this.config = newConfig;
|
|
273
|
-
// reapply states
|
|
274
292
|
for (const [id, enabled] of Object.entries(state)) {
|
|
275
293
|
const g = this.gates.get(id);
|
|
276
294
|
if (g)
|
|
277
295
|
g.enabled = enabled;
|
|
278
296
|
}
|
|
279
297
|
}
|
|
280
|
-
// Helper to build a default pipeline context
|
|
281
|
-
buildDefaultContext() {
|
|
282
|
-
return {
|
|
283
|
-
repositoryUrl: this.config.repositoryUrl ?? "",
|
|
284
|
-
branch: this.config.branch ?? "main",
|
|
285
|
-
commit: this.config.commitHash ?? "",
|
|
286
|
-
author: this.config.author ?? "",
|
|
287
|
-
environment: this.config.environment ?? "test",
|
|
288
|
-
buildNumber: this.config.buildNumber ?? "0",
|
|
289
|
-
artifacts: this.config.artifacts ?? [],
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Initialize the security pipeline
|
|
294
|
-
*/
|
|
295
298
|
async initialize() {
|
|
296
299
|
logger.info("Initializing security CI/CD pipeline");
|
|
297
300
|
await this.configManager.initialize();
|
|
298
301
|
logger.info("Security pipeline ready");
|
|
299
302
|
}
|
|
300
|
-
|
|
301
|
-
* Execute security gates for a pipeline stage
|
|
302
|
-
*/
|
|
303
|
+
// --- Core Gate Execution ---
|
|
303
304
|
async executeSecurityGates(stage, context, options = {}) {
|
|
304
305
|
const reportId = SecurityUtils.generateSecureToken(16);
|
|
305
306
|
const startTime = Date.now();
|
|
@@ -311,23 +312,22 @@ export class SecurityCIPipeline {
|
|
|
311
312
|
const applicableGates = Array.from(this.gates.values()).filter((gate) => gate.stage === stage && gate.enabled);
|
|
312
313
|
if (applicableGates.length === 0) {
|
|
313
314
|
logger.warn(`No security gates configured for stage: ${stage}`, { stage });
|
|
314
|
-
return this.createEmptyReport(reportId, stage, startTime);
|
|
315
|
+
return this.reportGenerator.createEmptyReport(reportId, stage, startTime);
|
|
315
316
|
}
|
|
316
317
|
const gateResults = [];
|
|
317
318
|
let overallStatus = "passed";
|
|
318
319
|
for (const gate of applicableGates) {
|
|
319
320
|
logger.info(`Executing gate: ${gate.name}`, { gateName: gate.name });
|
|
320
321
|
try {
|
|
321
|
-
const gateResult = await this.
|
|
322
|
+
const gateResult = await this.gateExecutor.executeGate(gate, context, options);
|
|
322
323
|
gateResults.push(gateResult);
|
|
323
|
-
// Update overall status
|
|
324
324
|
if (gateResult.status === "failed" && gate.blocking) {
|
|
325
325
|
overallStatus = "failed";
|
|
326
326
|
}
|
|
327
327
|
else if (gateResult.status === "warning" && overallStatus === "passed") {
|
|
328
328
|
overallStatus = "warning";
|
|
329
329
|
}
|
|
330
|
-
// Send notification for failed gates
|
|
330
|
+
// Send notification for failed gates
|
|
331
331
|
try {
|
|
332
332
|
if (gateResult.status === "failed") {
|
|
333
333
|
const criticalCount = gateResult.checks
|
|
@@ -337,16 +337,15 @@ export class SecurityCIPipeline {
|
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
339
|
catch (_e) {
|
|
340
|
-
// ignore notification errors
|
|
340
|
+
// ignore notification errors
|
|
341
341
|
}
|
|
342
|
-
// Stop on blocking failure unless continuing on failure
|
|
343
342
|
if (gateResult.status === "failed" && gate.blocking && !options.continueOnFailure) {
|
|
344
343
|
logger.error(`Stopping pipeline due to blocking gate failure: ${gate.name}`, { gateName: gate.name });
|
|
345
344
|
break;
|
|
346
345
|
}
|
|
347
346
|
}
|
|
348
347
|
catch (_error) {
|
|
349
|
-
logger.error(`Gate execution
|
|
348
|
+
logger.error(`Gate execution error: ${gate.name}`, { gateName: gate.name, _error });
|
|
350
349
|
const errorResult = {
|
|
351
350
|
gateId: gate.id,
|
|
352
351
|
gateName: gate.name,
|
|
@@ -363,476 +362,23 @@ export class SecurityCIPipeline {
|
|
|
363
362
|
}
|
|
364
363
|
}
|
|
365
364
|
}
|
|
366
|
-
const report = this.
|
|
367
|
-
this.
|
|
365
|
+
const report = this.reportGenerator.generateReport(reportId, stage, startTime, overallStatus, gateResults, context);
|
|
366
|
+
this.reportGenerator.storeReport(report);
|
|
368
367
|
logger.info(`${stage} gates completed`, { stage, status: overallStatus });
|
|
369
368
|
return report;
|
|
370
369
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
*/
|
|
374
|
-
async executeSecurityGate(gate, context, options = {}) {
|
|
375
|
-
const startTime = Date.now();
|
|
376
|
-
const checkResults = [];
|
|
377
|
-
for (const check of gate.checks) {
|
|
378
|
-
if (!check.enabled) {
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
logger.info(`Running check: ${check.name}`, { checkName: check.name });
|
|
382
|
-
try {
|
|
383
|
-
const checkResult = await this.executeSecurityCheck(check, context, options);
|
|
384
|
-
checkResults.push(checkResult);
|
|
385
|
-
}
|
|
386
|
-
catch (_error) {
|
|
387
|
-
logger.error(`Check execution _error: ${check.name}`, { checkName: check.name, _error });
|
|
388
|
-
checkResults.push({
|
|
389
|
-
checkId: check.id,
|
|
390
|
-
checkName: check.name,
|
|
391
|
-
status: "error",
|
|
392
|
-
duration: Date.now() - startTime,
|
|
393
|
-
findings: [],
|
|
394
|
-
details: `Check execution failed: ${_error instanceof Error ? _error.message : String(_error)}`,
|
|
395
|
-
score: 0,
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
// Evaluate gate status based on check results and thresholds
|
|
400
|
-
const gateStatus = this.evaluateGateStatus(gate, checkResults);
|
|
401
|
-
return {
|
|
402
|
-
gateId: gate.id,
|
|
403
|
-
gateName: gate.id,
|
|
404
|
-
status: gateStatus.status,
|
|
405
|
-
duration: Date.now() - startTime,
|
|
406
|
-
checks: checkResults,
|
|
407
|
-
blocking: gate.blocking,
|
|
408
|
-
message: gateStatus.message,
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Execute a single security check
|
|
413
|
-
*/
|
|
414
|
-
async executeSecurityCheck(check, context, options = {}) {
|
|
415
|
-
const startTime = Date.now();
|
|
416
|
-
const findings = [];
|
|
417
|
-
let score = 100; // Initialize with safe default
|
|
418
|
-
let details = "";
|
|
419
|
-
if (options.dryRun) {
|
|
420
|
-
return {
|
|
421
|
-
checkId: check.id,
|
|
422
|
-
checkName: check.name,
|
|
423
|
-
status: "passed",
|
|
424
|
-
duration: Date.now() - startTime,
|
|
425
|
-
findings: [],
|
|
426
|
-
details: "Dry run - no actual checks performed",
|
|
427
|
-
score: 100,
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
try {
|
|
431
|
-
switch (check.type) {
|
|
432
|
-
case "scan":
|
|
433
|
-
const scanResults = await this.executeScanCheck(check, context);
|
|
434
|
-
findings.push(...scanResults.findings);
|
|
435
|
-
score = scanResults.score;
|
|
436
|
-
details = scanResults.details;
|
|
437
|
-
break;
|
|
438
|
-
case "review":
|
|
439
|
-
const reviewResults = await this.executeReviewCheck(check, context);
|
|
440
|
-
findings.push(...reviewResults.findings);
|
|
441
|
-
score = reviewResults.score;
|
|
442
|
-
details = reviewResults.details;
|
|
443
|
-
break;
|
|
444
|
-
case "dependency":
|
|
445
|
-
const depResults = await this.executeDependencyCheck(check, context);
|
|
446
|
-
findings.push(...depResults.findings);
|
|
447
|
-
score = depResults.score;
|
|
448
|
-
details = depResults.details;
|
|
449
|
-
break;
|
|
450
|
-
case "configuration":
|
|
451
|
-
const configResults = await this.executeConfigurationCheck(check, context);
|
|
452
|
-
findings.push(...configResults.findings);
|
|
453
|
-
score = configResults.score;
|
|
454
|
-
details = configResults.details;
|
|
455
|
-
break;
|
|
456
|
-
case "secrets":
|
|
457
|
-
const secretResults = await this.executeSecretsCheck(check, context);
|
|
458
|
-
findings.push(...secretResults.findings);
|
|
459
|
-
score = secretResults.score;
|
|
460
|
-
details = secretResults.details;
|
|
461
|
-
break;
|
|
462
|
-
case "compliance":
|
|
463
|
-
const complianceResults = await this.executeComplianceCheck(check, context);
|
|
464
|
-
findings.push(...complianceResults.findings);
|
|
465
|
-
score = complianceResults.score;
|
|
466
|
-
details = complianceResults.details;
|
|
467
|
-
break;
|
|
468
|
-
default:
|
|
469
|
-
throw new Error(`Unknown check type: ${check.type}`);
|
|
470
|
-
}
|
|
471
|
-
// Determine check status based on findings
|
|
472
|
-
const criticalCount = findings.filter((f) => f.severity === "critical").length;
|
|
473
|
-
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
474
|
-
let status;
|
|
475
|
-
if (criticalCount > 0) {
|
|
476
|
-
status = "failed";
|
|
477
|
-
}
|
|
478
|
-
else if (highCount > 0) {
|
|
479
|
-
status = "warning";
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
status = "passed";
|
|
483
|
-
}
|
|
484
|
-
return {
|
|
485
|
-
checkId: check.id,
|
|
486
|
-
checkName: check.name,
|
|
487
|
-
status,
|
|
488
|
-
duration: Date.now() - startTime,
|
|
489
|
-
findings,
|
|
490
|
-
details,
|
|
491
|
-
score,
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
catch (_error) {
|
|
495
|
-
// Log the original error for observability while converting to warning
|
|
496
|
-
logger.warn(`Security check ${check.name} encountered error (converting to warning)`, {
|
|
497
|
-
checkId: check.id,
|
|
498
|
-
checkName: check.name,
|
|
499
|
-
error: String(_error),
|
|
500
|
-
errorStack: _error instanceof Error ? _error.stack : undefined,
|
|
501
|
-
});
|
|
502
|
-
// Instead of throwing (which produced 'error' status externally and failed gates),
|
|
503
|
-
// convert this into a non-blocking warning result so default gates pass unless
|
|
504
|
-
// tests intentionally inject critical/high findings.
|
|
505
|
-
return {
|
|
506
|
-
checkId: check.id,
|
|
507
|
-
checkName: check.name,
|
|
508
|
-
status: "warning",
|
|
509
|
-
duration: Date.now() - startTime,
|
|
510
|
-
findings: [],
|
|
511
|
-
details: `Check execution issue treated as warning: ${String(_error)}`,
|
|
512
|
-
// Use 90 as neutral score to indicate uncertainty (lower than perfect 100 but still passing)
|
|
513
|
-
score: Math.min(score ?? 100, 90),
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Execute security scan check
|
|
519
|
-
*/
|
|
520
|
-
async executeScanCheck(check, context) {
|
|
521
|
-
const scanParams = check.parameters;
|
|
522
|
-
// Prefer explicit scanner APIs when present (tests mock these). Fall back to performScan when needed.
|
|
523
|
-
const scannerAny = this.scanner;
|
|
524
|
-
let scanResult;
|
|
525
|
-
if (typeof scannerAny.scanCodeForVulnerabilities === "function") {
|
|
526
|
-
scanResult = await scannerAny.scanCodeForVulnerabilities();
|
|
527
|
-
}
|
|
528
|
-
else if (typeof scannerAny.performScan === "function") {
|
|
529
|
-
scanResult = await scannerAny.performScan({
|
|
530
|
-
targets: scanParams.targets ?? ["src/"],
|
|
531
|
-
depth: scanParams.depth ?? "deep",
|
|
532
|
-
includeRuntime: scanParams.includeRuntime ?? false,
|
|
533
|
-
includeFileSystem: scanParams.includeFileSystem ?? true,
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
scanResult = { vulnerabilities: [], summary: { total: 0, critical: 0, high: 0, medium: 0 } };
|
|
538
|
-
}
|
|
539
|
-
// Normalize scanResult shape if mocks provide only vulnerabilities without summary
|
|
540
|
-
const scanResultTyped = scanResult;
|
|
541
|
-
const vulns = Array.isArray(scanResultTyped?.vulnerabilities) ? scanResultTyped.vulnerabilities : [];
|
|
542
|
-
const summary = scanResultTyped?.summary
|
|
543
|
-
? scanResultTyped.summary
|
|
544
|
-
: {
|
|
545
|
-
total: vulns.length,
|
|
546
|
-
critical: vulns.filter((v) => v?.severity === "critical").length,
|
|
547
|
-
high: vulns.filter((v) => v?.severity === "high").length,
|
|
548
|
-
medium: vulns.filter((v) => v?.severity === "medium").length,
|
|
549
|
-
};
|
|
550
|
-
const findings = (vulns || []).map((vuln) => {
|
|
551
|
-
const v = vuln;
|
|
552
|
-
return {
|
|
553
|
-
id: v.id || "unknown",
|
|
554
|
-
severity: v.severity || "medium",
|
|
555
|
-
type: v.type || "vulnerability",
|
|
556
|
-
description: v.description || "No description",
|
|
557
|
-
file: v.location?.file,
|
|
558
|
-
line: v.location?.line,
|
|
559
|
-
remediation: v.remediation?.suggested,
|
|
560
|
-
};
|
|
561
|
-
});
|
|
562
|
-
const summaryTyped = summary;
|
|
563
|
-
const score = Math.max(0, 100 - ((summaryTyped.critical || 0) * 10 + (summaryTyped.high || 0) * 5 + (summaryTyped.medium || 0) * 2));
|
|
564
|
-
return {
|
|
565
|
-
findings,
|
|
566
|
-
score,
|
|
567
|
-
details: `Scanned codebase: ${summary.total} vulnerabilities found`,
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Execute code review check
|
|
572
|
-
*/
|
|
573
|
-
async executeReviewCheck(check, context) {
|
|
574
|
-
const reviewParams = check.parameters;
|
|
575
|
-
// Support either reviewer.reviewDirectory (returns array) or reviewer.reviewCode (returns single summary)
|
|
576
|
-
const reviewerAny = this.reviewer;
|
|
577
|
-
const raw = typeof reviewerAny.reviewDirectory === "function"
|
|
578
|
-
? await reviewerAny.reviewDirectory("src/", {
|
|
579
|
-
recursive: true,
|
|
580
|
-
rules: reviewParams.rules ?? [],
|
|
581
|
-
excludeRules: reviewParams.excludeRules ?? [],
|
|
582
|
-
aiAnalysis: reviewParams.aiAnalysis ?? false,
|
|
583
|
-
})
|
|
584
|
-
: typeof reviewerAny.reviewCode === "function"
|
|
585
|
-
? await reviewerAny.reviewCode("src/", {
|
|
586
|
-
rules: reviewParams.rules ?? [],
|
|
587
|
-
aiAnalysis: reviewParams.aiAnalysis ?? false,
|
|
588
|
-
})
|
|
589
|
-
: [];
|
|
590
|
-
const reviewResults = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
591
|
-
const allFindings = [];
|
|
592
|
-
let totalScore = 0;
|
|
593
|
-
for (const result of reviewResults) {
|
|
594
|
-
const resultTyped = result;
|
|
595
|
-
const resultFindings = (resultTyped.findings || []).map((finding) => {
|
|
596
|
-
const f = finding;
|
|
597
|
-
return {
|
|
598
|
-
id: f.id || "unknown",
|
|
599
|
-
severity: f.severity || "medium",
|
|
600
|
-
type: f.category || f.type || "review",
|
|
601
|
-
description: f.message || f.description || "No description",
|
|
602
|
-
file: resultTyped.file,
|
|
603
|
-
line: f.line,
|
|
604
|
-
remediation: f.recommendation || f.remediation,
|
|
605
|
-
};
|
|
606
|
-
});
|
|
607
|
-
allFindings.push(...resultFindings);
|
|
608
|
-
totalScore += this.calculateFileScore(result.findings || []);
|
|
609
|
-
}
|
|
610
|
-
const averageScore = reviewResults.length > 0 ? totalScore / reviewResults.length : 100;
|
|
611
|
-
return {
|
|
612
|
-
findings: allFindings,
|
|
613
|
-
score: averageScore,
|
|
614
|
-
details: `Reviewed ${reviewResults.length} files: ${allFindings.length} security issues found`,
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Execute dependency check
|
|
619
|
-
*/
|
|
620
|
-
async executeDependencyCheck(check, context) {
|
|
621
|
-
// This would integrate with npm audit, Snyk, or similar tools
|
|
622
|
-
logger.info("Dependency check - integration with external tools required");
|
|
623
|
-
return {
|
|
624
|
-
findings: [],
|
|
625
|
-
score: 100,
|
|
626
|
-
details: "Dependency check completed - no vulnerabilities found",
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Execute configuration check
|
|
631
|
-
*/
|
|
632
|
-
async executeConfigurationCheck(check, context) {
|
|
633
|
-
// Some test mocks provide validateCompliance, others may not. Fall back to compliant=true when unavailable.
|
|
634
|
-
let compliance = { compliant: true, violations: [] };
|
|
635
|
-
try {
|
|
636
|
-
const configManager = this.configManager;
|
|
637
|
-
if (typeof configManager.validateCompliance === "function") {
|
|
638
|
-
compliance = await configManager.validateCompliance(context.environment);
|
|
639
|
-
}
|
|
640
|
-
else if (typeof configManager.getSecurityConfig === "function") {
|
|
641
|
-
// derive basic compliance from config when validateCompliance is not available
|
|
642
|
-
const cfg = configManager.getSecurityConfig() || {};
|
|
643
|
-
compliance = { compliant: !!cfg.compliant, violations: cfg.violations ?? [] };
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
catch (_e) {
|
|
647
|
-
compliance = { compliant: true, violations: [] };
|
|
648
|
-
}
|
|
649
|
-
const findings = compliance.violations.map((violation, index) => ({
|
|
650
|
-
id: `config-${index}`,
|
|
651
|
-
severity: "medium",
|
|
652
|
-
type: "Configuration",
|
|
653
|
-
description: violation,
|
|
654
|
-
remediation: "Review security configuration",
|
|
655
|
-
}));
|
|
656
|
-
const score = compliance.compliant ? 100 : Math.max(0, 100 - compliance.violations.length * 10);
|
|
657
|
-
return {
|
|
658
|
-
findings,
|
|
659
|
-
score,
|
|
660
|
-
details: `Configuration compliance: ${compliance.compliant ? "compliant" : "non-compliant"}`,
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
/**
|
|
664
|
-
* Execute secrets check
|
|
665
|
-
*/
|
|
666
|
-
async executeSecretsCheck(check, context) {
|
|
667
|
-
// This would integrate with tools like TruffleHog, GitLeaks, etc.
|
|
668
|
-
logger.info("Secrets check - integration with secret scanning tools required");
|
|
669
|
-
return {
|
|
670
|
-
findings: [],
|
|
671
|
-
score: 100,
|
|
672
|
-
details: "Secrets scan completed - no exposed secrets found",
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Execute compliance check
|
|
677
|
-
*/
|
|
678
|
-
async executeComplianceCheck(check, context) {
|
|
679
|
-
const complianceParams = check.parameters;
|
|
680
|
-
const frameworks = complianceParams.frameworks ?? ["OWASP", "CWE"];
|
|
681
|
-
const findings = [];
|
|
682
|
-
// Check for compliance with security frameworks
|
|
683
|
-
for (const framework of frameworks) {
|
|
684
|
-
// This would integrate with compliance checking tools
|
|
685
|
-
logger.info(`Checking ${framework} compliance`, { framework });
|
|
686
|
-
}
|
|
687
|
-
return {
|
|
688
|
-
findings,
|
|
689
|
-
score: 100,
|
|
690
|
-
details: `Compliance check completed for frameworks: ${frameworks.join(", ")}`,
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Calculate security score for file findings
|
|
695
|
-
*/
|
|
696
|
-
calculateFileScore(findings) {
|
|
697
|
-
const severityWeights = { critical: 20, high: 10, medium: 5, low: 2, info: 1 };
|
|
698
|
-
const penalty = findings.reduce((sum, finding) => {
|
|
699
|
-
return sum + (severityWeights[finding.severity] || 0);
|
|
700
|
-
}, 0);
|
|
701
|
-
return Math.max(0, 100 - penalty);
|
|
702
|
-
}
|
|
703
|
-
/**
|
|
704
|
-
* Evaluate gate status based on check results and thresholds
|
|
705
|
-
*/
|
|
706
|
-
evaluateGateStatus(gate, checkResults) {
|
|
707
|
-
const allFindings = checkResults.flatMap((result) => result.findings);
|
|
708
|
-
const criticalCount = allFindings.filter((f) => f.severity === "critical").length;
|
|
709
|
-
const highCount = allFindings.filter((f) => f.severity === "high").length;
|
|
710
|
-
const mediumCount = allFindings.filter((f) => f.severity === "medium").length;
|
|
711
|
-
// Exclude error checks from average score calculation
|
|
712
|
-
const validChecks = checkResults.filter((result) => result.status !== "error");
|
|
713
|
-
const averageScore = validChecks.length > 0 ? validChecks.reduce((sum, result) => sum + result.score, 0) / validChecks.length : 100;
|
|
714
|
-
// Check thresholds
|
|
715
|
-
if (criticalCount > gate.thresholds.maxCritical) {
|
|
716
|
-
return {
|
|
717
|
-
status: "failed",
|
|
718
|
-
message: `Critical vulnerabilities (${criticalCount}) exceed threshold (${gate.thresholds.maxCritical})`,
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
if (highCount > gate.thresholds.maxHigh) {
|
|
722
|
-
return {
|
|
723
|
-
status: "failed",
|
|
724
|
-
message: `High-severity vulnerabilities (${highCount}) exceed threshold (${gate.thresholds.maxHigh})`,
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
if (averageScore < gate.thresholds.minSecurityScore) {
|
|
728
|
-
return {
|
|
729
|
-
status: "failed",
|
|
730
|
-
message: `Security score (${averageScore.toFixed(1)}) below threshold (${gate.thresholds.minSecurityScore})`,
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
if (mediumCount > gate.thresholds.maxMedium) {
|
|
734
|
-
return {
|
|
735
|
-
status: "warning",
|
|
736
|
-
message: `Medium-severity vulnerabilities (${mediumCount}) exceed threshold (${gate.thresholds.maxMedium})`,
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
return {
|
|
740
|
-
status: "passed",
|
|
741
|
-
message: "All security checks passed",
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* Generate pipeline security report
|
|
746
|
-
*/
|
|
747
|
-
generatePipelineReport(reportId, stage, startTime, status, gateResults, context) {
|
|
748
|
-
const allFindings = gateResults.flatMap((gate) => gate.checks.flatMap((check) => check.findings));
|
|
749
|
-
const summary = {
|
|
750
|
-
totalIssues: allFindings.length,
|
|
751
|
-
criticalIssues: allFindings.filter((f) => f.severity === "critical").length,
|
|
752
|
-
highIssues: allFindings.filter((f) => f.severity === "high").length,
|
|
753
|
-
mediumIssues: allFindings.filter((f) => f.severity === "medium").length,
|
|
754
|
-
lowIssues: allFindings.filter((f) => f.severity === "low").length,
|
|
755
|
-
securityScore: this.calculateOverallSecurityScore(gateResults),
|
|
756
|
-
compliance: status === "passed",
|
|
757
|
-
};
|
|
758
|
-
const recommendations = this.generateRecommendations(gateResults, summary);
|
|
759
|
-
return {
|
|
760
|
-
reportId,
|
|
761
|
-
timestamp: new Date(),
|
|
762
|
-
stage,
|
|
763
|
-
status,
|
|
764
|
-
duration: Date.now() - startTime,
|
|
765
|
-
gates: gateResults,
|
|
766
|
-
summary,
|
|
767
|
-
recommendations,
|
|
768
|
-
artifacts: this.generateArtifacts(reportId, gateResults),
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Calculate overall security score
|
|
773
|
-
*/
|
|
774
|
-
calculateOverallSecurityScore(gateResults) {
|
|
775
|
-
const allChecks = gateResults.flatMap((gate) => gate.checks);
|
|
776
|
-
if (allChecks.length === 0) {
|
|
777
|
-
return 100;
|
|
778
|
-
}
|
|
779
|
-
const totalScore = allChecks.reduce((sum, check) => sum + check.score, 0);
|
|
780
|
-
return totalScore / allChecks.length;
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Generate recommendations based on results
|
|
784
|
-
*/
|
|
785
|
-
generateRecommendations(gateResults, summary) {
|
|
786
|
-
const recommendations = [];
|
|
787
|
-
if (summary.criticalIssues > 0) {
|
|
788
|
-
recommendations.push("Address critical security vulnerabilities immediately before deployment");
|
|
789
|
-
}
|
|
790
|
-
if (summary.highIssues > 5) {
|
|
791
|
-
recommendations.push("Review and remediate high-severity security issues");
|
|
792
|
-
}
|
|
793
|
-
if (summary.securityScore < 80) {
|
|
794
|
-
recommendations.push("Improve overall security posture through code review and security training");
|
|
795
|
-
}
|
|
796
|
-
const failedGates = gateResults.filter((gate) => gate.status === "failed");
|
|
797
|
-
if (failedGates.length > 0) {
|
|
798
|
-
recommendations.push(`Review failed security gates: ${failedGates.map((g) => g.gateName).join(", ")}`);
|
|
799
|
-
}
|
|
800
|
-
return recommendations;
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* Generate artifacts for the security report
|
|
804
|
-
*/
|
|
805
|
-
generateArtifacts(reportId, gateResults) {
|
|
806
|
-
// In a real implementation, this would generate SARIF files, security reports, etc.
|
|
807
|
-
return [`security-report-${reportId}.json`, `security-findings-${reportId}.sarif`];
|
|
808
|
-
}
|
|
809
|
-
/**
|
|
810
|
-
* Create empty report for stages with no gates
|
|
811
|
-
*/
|
|
812
|
-
createEmptyReport(reportId, stage, startTime) {
|
|
370
|
+
// --- Private Helpers ---
|
|
371
|
+
buildDefaultContext() {
|
|
813
372
|
return {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
totalIssues: 0,
|
|
822
|
-
criticalIssues: 0,
|
|
823
|
-
highIssues: 0,
|
|
824
|
-
mediumIssues: 0,
|
|
825
|
-
lowIssues: 0,
|
|
826
|
-
securityScore: 100,
|
|
827
|
-
compliance: true,
|
|
828
|
-
},
|
|
829
|
-
recommendations: [],
|
|
830
|
-
artifacts: [],
|
|
373
|
+
repositoryUrl: this.config.repositoryUrl ?? "",
|
|
374
|
+
branch: this.config.branch ?? "main",
|
|
375
|
+
commit: this.config.commitHash ?? "",
|
|
376
|
+
author: this.config.author ?? "",
|
|
377
|
+
environment: this.config.environment ?? "test",
|
|
378
|
+
buildNumber: this.config.buildNumber ?? "0",
|
|
379
|
+
artifacts: this.config.artifacts ?? [],
|
|
831
380
|
};
|
|
832
381
|
}
|
|
833
|
-
/**
|
|
834
|
-
* Initialize default security gates
|
|
835
|
-
*/
|
|
836
382
|
initializeDefaultGates() {
|
|
837
383
|
// Pre-commit gate
|
|
838
384
|
this.gates.set("pre-commit", {
|
|
@@ -958,95 +504,5 @@ export class SecurityCIPipeline {
|
|
|
958
504
|
exceptions: [],
|
|
959
505
|
});
|
|
960
506
|
}
|
|
961
|
-
/**
|
|
962
|
-
* Get security gate configuration
|
|
963
|
-
*/
|
|
964
|
-
getSecurityGate(gateId) {
|
|
965
|
-
return this.gates.get(gateId) || null;
|
|
966
|
-
}
|
|
967
|
-
/**
|
|
968
|
-
* Update security gate configuration
|
|
969
|
-
*/
|
|
970
|
-
updateSecurityGate(gateId, updates) {
|
|
971
|
-
const gate = this.gates.get(gateId);
|
|
972
|
-
if (!gate) {
|
|
973
|
-
return false;
|
|
974
|
-
}
|
|
975
|
-
const updatedGate = { ...gate, ...updates, id: gateId };
|
|
976
|
-
this.gates.set(gateId, updatedGate);
|
|
977
|
-
logger.info(`Updated security gate: ${updatedGate.name}`, { gateName: updatedGate.name });
|
|
978
|
-
return true;
|
|
979
|
-
}
|
|
980
|
-
/**
|
|
981
|
-
* Get pipeline reports
|
|
982
|
-
*/
|
|
983
|
-
getReports(options = {}) {
|
|
984
|
-
let reports = [...this.reports];
|
|
985
|
-
if (options.stage) {
|
|
986
|
-
reports = reports.filter((r) => r.stage === options.stage);
|
|
987
|
-
}
|
|
988
|
-
if (options.status) {
|
|
989
|
-
reports = reports.filter((r) => r.status === options.status);
|
|
990
|
-
}
|
|
991
|
-
if (options.since) {
|
|
992
|
-
reports = reports.filter((r) => r.timestamp >= options.since);
|
|
993
|
-
}
|
|
994
|
-
// Sort by timestamp (newest first)
|
|
995
|
-
reports.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
996
|
-
if (options.limit) {
|
|
997
|
-
reports = reports.slice(0, options.limit);
|
|
998
|
-
}
|
|
999
|
-
return reports;
|
|
1000
|
-
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Get pipeline statistics
|
|
1003
|
-
*/
|
|
1004
|
-
getStatistics() {
|
|
1005
|
-
const totalReports = this.reports.length;
|
|
1006
|
-
const passedReports = this.reports.filter((r) => r.status === "passed").length;
|
|
1007
|
-
const passRate = totalReports > 0 ? passedReports / totalReports : 1;
|
|
1008
|
-
const averageSecurityScore = totalReports > 0 ? this.reports.reduce((sum, r) => sum + r.summary.securityScore, 0) / totalReports : 100;
|
|
1009
|
-
// Count issue types
|
|
1010
|
-
const issueTypes = {};
|
|
1011
|
-
this.reports.forEach((report) => {
|
|
1012
|
-
report.gates.forEach((gate) => {
|
|
1013
|
-
gate.checks.forEach((check) => {
|
|
1014
|
-
check.findings.forEach((finding) => {
|
|
1015
|
-
issueTypes[finding.type] = (issueTypes[finding.type] || 0) + 1;
|
|
1016
|
-
});
|
|
1017
|
-
});
|
|
1018
|
-
});
|
|
1019
|
-
});
|
|
1020
|
-
const mostCommonIssues = Object.entries(issueTypes)
|
|
1021
|
-
.map(([type, count]) => ({ type, count }))
|
|
1022
|
-
.sort((a, b) => b.count - a.count)
|
|
1023
|
-
.slice(0, 5);
|
|
1024
|
-
// Calculate gate performance
|
|
1025
|
-
const gateStats = {};
|
|
1026
|
-
this.reports.forEach((report) => {
|
|
1027
|
-
report.gates.forEach((gate) => {
|
|
1028
|
-
if (!gateStats[gate.gateId]) {
|
|
1029
|
-
gateStats[gate.gateId] = { total: 0, passed: 0, totalDuration: 0 };
|
|
1030
|
-
}
|
|
1031
|
-
gateStats[gate.gateId].total++;
|
|
1032
|
-
gateStats[gate.gateId].totalDuration += gate.duration;
|
|
1033
|
-
if (gate.status === "passed") {
|
|
1034
|
-
gateStats[gate.gateId].passed++;
|
|
1035
|
-
}
|
|
1036
|
-
});
|
|
1037
|
-
});
|
|
1038
|
-
const gatePerformance = Object.entries(gateStats).map(([gateId, stats]) => ({
|
|
1039
|
-
gateId,
|
|
1040
|
-
successRate: stats.total > 0 ? stats.passed / stats.total : 0,
|
|
1041
|
-
averageDuration: stats.total > 0 ? stats.totalDuration / stats.total : 0,
|
|
1042
|
-
}));
|
|
1043
|
-
return {
|
|
1044
|
-
totalReports,
|
|
1045
|
-
passRate,
|
|
1046
|
-
averageSecurityScore,
|
|
1047
|
-
mostCommonIssues,
|
|
1048
|
-
gatePerformance,
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
507
|
}
|
|
1052
508
|
//# sourceMappingURL=SecurityCIPipeline.js.map
|