ferret-scan 2.2.0 → 2.4.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/CHANGELOG.md +17 -0
- package/README.md +15 -11
- package/bin/ferret.js +104 -8
- package/dist/__tests__/AgentMonitor.test.d.ts +6 -0
- package/dist/__tests__/AgentMonitor.test.js +235 -0
- package/dist/__tests__/AtlasNavigatorReporter.test.d.ts +6 -0
- package/dist/__tests__/AtlasNavigatorReporter.test.js +193 -0
- package/dist/__tests__/CorrelationAnalyzer.test.d.ts +6 -0
- package/dist/__tests__/CorrelationAnalyzer.test.js +211 -0
- package/dist/__tests__/IndicatorMatcher.test.d.ts +6 -0
- package/dist/__tests__/IndicatorMatcher.test.js +245 -0
- package/dist/__tests__/MarketplaceScanner.test.d.ts +5 -0
- package/dist/__tests__/MarketplaceScanner.test.js +212 -0
- package/dist/__tests__/RuleGenerator.test.d.ts +6 -0
- package/dist/__tests__/RuleGenerator.test.js +207 -0
- package/dist/__tests__/ThreatFeed.test.d.ts +6 -0
- package/dist/__tests__/ThreatFeed.test.js +359 -0
- package/dist/__tests__/WatchMode.test.d.ts +6 -0
- package/dist/__tests__/WatchMode.test.js +104 -0
- package/dist/__tests__/astAnalyzerExtra.test.d.ts +6 -0
- package/dist/__tests__/astAnalyzerExtra.test.js +67 -0
- package/dist/__tests__/astAnalyzerFull.test.d.ts +6 -0
- package/dist/__tests__/astAnalyzerFull.test.js +138 -0
- package/dist/__tests__/astAnalyzerPatterns.test.d.ts +6 -0
- package/dist/__tests__/astAnalyzerPatterns.test.js +143 -0
- package/dist/__tests__/atlas.test.d.ts +6 -0
- package/dist/__tests__/atlas.test.js +319 -0
- package/dist/__tests__/atlasCatalog.test.d.ts +6 -0
- package/dist/__tests__/atlasCatalog.test.js +200 -0
- package/dist/__tests__/atlasCatalogExtra.test.d.ts +6 -0
- package/dist/__tests__/atlasCatalogExtra.test.js +215 -0
- package/dist/__tests__/baseline.test.d.ts +6 -0
- package/dist/__tests__/baseline.test.js +321 -0
- package/dist/__tests__/baselineExtra.test.d.ts +6 -0
- package/dist/__tests__/baselineExtra.test.js +317 -0
- package/dist/__tests__/capabilityMapping.test.d.ts +5 -0
- package/dist/__tests__/capabilityMapping.test.js +49 -0
- package/dist/__tests__/capabilityMappingExtra.test.d.ts +5 -0
- package/dist/__tests__/capabilityMappingExtra.test.js +200 -0
- package/dist/__tests__/complianceExtra.test.d.ts +6 -0
- package/dist/__tests__/complianceExtra.test.js +121 -0
- package/dist/__tests__/config.test.js +1 -1
- package/dist/__tests__/configLoader.test.d.ts +6 -0
- package/dist/__tests__/configLoader.test.js +225 -0
- package/dist/__tests__/configLoaderExtra.test.d.ts +6 -0
- package/dist/__tests__/configLoaderExtra.test.js +186 -0
- package/dist/__tests__/correlationAnalyzerExtra.test.d.ts +5 -0
- package/dist/__tests__/correlationAnalyzerExtra.test.js +98 -0
- package/dist/__tests__/correlationAnalyzerFull.test.d.ts +6 -0
- package/dist/__tests__/correlationAnalyzerFull.test.js +154 -0
- package/dist/__tests__/customRules.extra.test.d.ts +6 -0
- package/dist/__tests__/customRules.extra.test.js +245 -0
- package/dist/__tests__/customRules.test.d.ts +7 -0
- package/dist/__tests__/customRules.test.js +347 -0
- package/dist/__tests__/dependencyRisk.test.d.ts +5 -0
- package/dist/__tests__/dependencyRisk.test.js +248 -0
- package/dist/__tests__/dependencyRiskExtra.test.d.ts +6 -0
- package/dist/__tests__/dependencyRiskExtra.test.js +177 -0
- package/dist/__tests__/featureExitCodes.test.d.ts +7 -0
- package/dist/__tests__/featureExitCodes.test.js +332 -0
- package/dist/__tests__/fileDiscoveryConfigOnly.test.d.ts +6 -0
- package/dist/__tests__/fileDiscoveryConfigOnly.test.js +195 -0
- package/dist/__tests__/fileDiscoveryExtra.test.d.ts +6 -0
- package/dist/__tests__/fileDiscoveryExtra.test.js +149 -0
- package/dist/__tests__/fixer.extra.test.d.ts +6 -0
- package/dist/__tests__/fixer.extra.test.js +135 -0
- package/dist/__tests__/fixerApply.test.d.ts +6 -0
- package/dist/__tests__/fixerApply.test.js +132 -0
- package/dist/__tests__/gitHooks.test.d.ts +7 -0
- package/dist/__tests__/gitHooks.test.js +188 -0
- package/dist/__tests__/htmlReporter.extra.test.d.ts +5 -0
- package/dist/__tests__/htmlReporter.extra.test.js +126 -0
- package/dist/__tests__/interactiveTui.test.d.ts +6 -0
- package/dist/__tests__/interactiveTui.test.js +180 -0
- package/dist/__tests__/interactiveTuiCommands.test.d.ts +6 -0
- package/dist/__tests__/interactiveTuiCommands.test.js +187 -0
- package/dist/__tests__/interactiveTuiMore.test.d.ts +6 -0
- package/dist/__tests__/interactiveTuiMore.test.js +194 -0
- package/dist/__tests__/interactiveTuiSession.test.d.ts +6 -0
- package/dist/__tests__/interactiveTuiSession.test.js +173 -0
- package/dist/__tests__/llmAnalysis.test.d.ts +6 -0
- package/dist/__tests__/llmAnalysis.test.js +229 -0
- package/dist/__tests__/llmAnalysisBuildExcerpt.test.d.ts +6 -0
- package/dist/__tests__/llmAnalysisBuildExcerpt.test.js +132 -0
- package/dist/__tests__/llmAnalysisExtra.test.d.ts +6 -0
- package/dist/__tests__/llmAnalysisExtra.test.js +214 -0
- package/dist/__tests__/llmAnalysisFilters.test.d.ts +6 -0
- package/dist/__tests__/llmAnalysisFilters.test.js +181 -0
- package/dist/__tests__/llmAnalysisMitre.test.d.ts +6 -0
- package/dist/__tests__/llmAnalysisMitre.test.js +192 -0
- package/dist/__tests__/llmGroqTPM.test.d.ts +6 -0
- package/dist/__tests__/llmGroqTPM.test.js +89 -0
- package/dist/__tests__/llmProviderRetry.test.d.ts +6 -0
- package/dist/__tests__/llmProviderRetry.test.js +172 -0
- package/dist/__tests__/mcpValidator.extra.test.d.ts +5 -0
- package/dist/__tests__/mcpValidator.extra.test.js +270 -0
- package/dist/__tests__/patternMatcherExtra.test.d.ts +7 -0
- package/dist/__tests__/patternMatcherExtra.test.js +198 -0
- package/dist/__tests__/patternsCommon.test.d.ts +6 -0
- package/dist/__tests__/patternsCommon.test.js +107 -0
- package/dist/__tests__/policyEnforcement.test.d.ts +5 -0
- package/dist/__tests__/policyEnforcement.test.js +510 -0
- package/dist/__tests__/quarantineExtra.test.d.ts +5 -0
- package/dist/__tests__/quarantineExtra.test.js +214 -0
- package/dist/__tests__/redactionExtra.test.d.ts +6 -0
- package/dist/__tests__/redactionExtra.test.js +228 -0
- package/dist/__tests__/scanDiff.test.d.ts +7 -0
- package/dist/__tests__/scanDiff.test.js +266 -0
- package/dist/__tests__/scanFull.test.d.ts +6 -0
- package/dist/__tests__/scanFull.test.js +158 -0
- package/dist/__tests__/scannerDampening.test.d.ts +6 -0
- package/dist/__tests__/scannerDampening.test.js +160 -0
- package/dist/__tests__/scannerExtra.test.d.ts +6 -0
- package/dist/__tests__/scannerExtra.test.js +194 -0
- package/dist/__tests__/scannerMitre.test.d.ts +5 -0
- package/dist/__tests__/scannerMitre.test.js +141 -0
- package/dist/__tests__/scannerSSRF.test.d.ts +5 -0
- package/dist/__tests__/scannerSSRF.test.js +149 -0
- package/dist/__tests__/schemas.test.d.ts +6 -0
- package/dist/__tests__/schemas.test.js +125 -0
- package/dist/__tests__/webhooks.extra.test.d.ts +6 -0
- package/dist/__tests__/webhooks.extra.test.js +144 -0
- package/dist/__tests__/webhooks.test.d.ts +6 -0
- package/dist/__tests__/webhooks.test.js +154 -0
- package/dist/features/customRules.js +22 -29
- package/dist/features/mcpTrustScore.d.ts +17 -0
- package/dist/features/mcpTrustScore.js +74 -0
- package/dist/features/mcpValidator.d.ts +2 -0
- package/dist/features/mcpValidator.js +13 -0
- package/dist/features/policyEnforcement.d.ts +22 -22
- package/dist/intelligence/ThreatFeed.js +207 -62
- package/dist/remediation/Quarantine.js +24 -6
- package/dist/reporters/ConsoleReporter.js +10 -0
- package/dist/reporters/HtmlReporter.js +5 -0
- package/dist/reporters/SarifReporter.d.ts +1 -0
- package/dist/reporters/SarifReporter.js +1 -0
- package/dist/scanner/IAnalyzer.d.ts +19 -0
- package/dist/scanner/IAnalyzer.js +5 -0
- package/dist/scanner/Scanner.js +64 -125
- package/dist/scanner/analyzers/CapabilityAnalyzer.d.ts +8 -0
- package/dist/scanner/analyzers/CapabilityAnalyzer.js +19 -0
- package/dist/scanner/analyzers/DependencyAnalyzer.d.ts +8 -0
- package/dist/scanner/analyzers/DependencyAnalyzer.js +18 -0
- package/dist/scanner/analyzers/EntropyAnalyzer.d.ts +8 -0
- package/dist/scanner/analyzers/EntropyAnalyzer.js +12 -0
- package/dist/scanner/analyzers/LlmAnalyzer.d.ts +17 -0
- package/dist/scanner/analyzers/LlmAnalyzer.js +36 -0
- package/dist/scanner/analyzers/McpAnalyzer.d.ts +8 -0
- package/dist/scanner/analyzers/McpAnalyzer.js +19 -0
- package/dist/scanner/analyzers/SemanticAnalyzer.d.ts +8 -0
- package/dist/scanner/analyzers/SemanticAnalyzer.js +21 -0
- package/dist/scanner/analyzers/ThreatIntelAnalyzer.d.ts +8 -0
- package/dist/scanner/analyzers/ThreatIntelAnalyzer.js +21 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.js +1 -1
- package/dist/utils/safeRegex.d.ts +12 -51
- package/dist/utils/safeRegex.js +45 -62
- package/dist/utils/schemas.d.ts +64 -64
- package/package.json +25 -19
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Additional Webhook Tests
|
|
3
|
+
* Covers sendWebhook with slack/discord/teams includeDetails formatting
|
|
4
|
+
*/
|
|
5
|
+
import { sendWebhook } from '../features/webhooks.js';
|
|
6
|
+
function makeFinding(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
ruleId: 'INJ-001',
|
|
9
|
+
ruleName: 'Test Rule',
|
|
10
|
+
severity: 'HIGH',
|
|
11
|
+
category: 'injection',
|
|
12
|
+
file: '/test.md',
|
|
13
|
+
relativePath: 'test.md',
|
|
14
|
+
line: 5,
|
|
15
|
+
match: 'bad content',
|
|
16
|
+
context: [],
|
|
17
|
+
remediation: 'fix it',
|
|
18
|
+
timestamp: new Date(),
|
|
19
|
+
riskScore: 50,
|
|
20
|
+
...overrides,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function makeScanResult(findings = [], overrides = {}) {
|
|
24
|
+
return {
|
|
25
|
+
success: true,
|
|
26
|
+
startTime: new Date(),
|
|
27
|
+
endTime: new Date(),
|
|
28
|
+
duration: 1000,
|
|
29
|
+
scannedPaths: ['/project'],
|
|
30
|
+
totalFiles: 5,
|
|
31
|
+
analyzedFiles: 4,
|
|
32
|
+
skippedFiles: 1,
|
|
33
|
+
findings,
|
|
34
|
+
findingsBySeverity: {
|
|
35
|
+
CRITICAL: findings.filter(f => f.severity === 'CRITICAL'),
|
|
36
|
+
HIGH: findings.filter(f => f.severity === 'HIGH'),
|
|
37
|
+
MEDIUM: findings.filter(f => f.severity === 'MEDIUM'),
|
|
38
|
+
LOW: findings.filter(f => f.severity === 'LOW'),
|
|
39
|
+
INFO: findings.filter(f => f.severity === 'INFO'),
|
|
40
|
+
},
|
|
41
|
+
findingsByCategory: {},
|
|
42
|
+
overallRiskScore: 50,
|
|
43
|
+
summary: {
|
|
44
|
+
critical: findings.filter(f => f.severity === 'CRITICAL').length,
|
|
45
|
+
high: findings.filter(f => f.severity === 'HIGH').length,
|
|
46
|
+
medium: findings.filter(f => f.severity === 'MEDIUM').length,
|
|
47
|
+
low: findings.filter(f => f.severity === 'LOW').length,
|
|
48
|
+
info: 0,
|
|
49
|
+
total: findings.length,
|
|
50
|
+
},
|
|
51
|
+
errors: [],
|
|
52
|
+
...overrides,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
describe('sendWebhook with includeDetails', () => {
|
|
56
|
+
let originalFetch;
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
originalFetch = globalThis.fetch;
|
|
59
|
+
globalThis.fetch = jest.fn().mockResolvedValue({
|
|
60
|
+
ok: true,
|
|
61
|
+
status: 200,
|
|
62
|
+
text: () => Promise.resolve(''),
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
globalThis.fetch = originalFetch;
|
|
67
|
+
});
|
|
68
|
+
it('sends slack with includeDetails and CRITICAL findings', async () => {
|
|
69
|
+
const result = await sendWebhook(makeScanResult([makeFinding({ severity: 'CRITICAL' })]), {
|
|
70
|
+
url: 'https://hooks.slack.com/services/xxx',
|
|
71
|
+
type: 'slack',
|
|
72
|
+
includeDetails: true,
|
|
73
|
+
});
|
|
74
|
+
expect(result.success).toBe(true);
|
|
75
|
+
expect(globalThis.fetch).toHaveBeenCalled();
|
|
76
|
+
const body = JSON.parse(globalThis.fetch.mock.calls[0][1]?.body);
|
|
77
|
+
expect(body.attachments).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
it('sends discord with includeDetails', async () => {
|
|
80
|
+
const result = await sendWebhook(makeScanResult([makeFinding({ severity: 'HIGH' })]), {
|
|
81
|
+
url: 'https://discord.com/api/webhooks/x/y',
|
|
82
|
+
type: 'discord',
|
|
83
|
+
includeDetails: true,
|
|
84
|
+
});
|
|
85
|
+
expect(result.success).toBe(true);
|
|
86
|
+
const body = JSON.parse(globalThis.fetch.mock.calls[0][1]?.body);
|
|
87
|
+
expect(body.embeds).toBeDefined();
|
|
88
|
+
});
|
|
89
|
+
it('sends teams with includeDetails', async () => {
|
|
90
|
+
const result = await sendWebhook(makeScanResult([makeFinding({ severity: 'MEDIUM' })]), {
|
|
91
|
+
url: 'https://org.webhook.office.com/webhook',
|
|
92
|
+
type: 'teams',
|
|
93
|
+
includeDetails: true,
|
|
94
|
+
});
|
|
95
|
+
expect(result.success).toBe(true);
|
|
96
|
+
const body = JSON.parse(globalThis.fetch.mock.calls[0][1]?.body);
|
|
97
|
+
expect(body['@type']).toBe('MessageCard');
|
|
98
|
+
});
|
|
99
|
+
it('sends generic webhook without error', async () => {
|
|
100
|
+
const result = await sendWebhook(makeScanResult([makeFinding()]), {
|
|
101
|
+
url: 'https://custom-webhook.example.com/hook',
|
|
102
|
+
type: 'generic',
|
|
103
|
+
includeDetails: true,
|
|
104
|
+
headers: { 'X-Custom-Token': 'abc123' },
|
|
105
|
+
});
|
|
106
|
+
expect(result.success).toBe(true);
|
|
107
|
+
const [, options] = globalThis.fetch.mock.calls[0];
|
|
108
|
+
expect(options.headers['X-Custom-Token']).toBe('abc123');
|
|
109
|
+
});
|
|
110
|
+
it('sends with medium severity findings triggering yellow color', async () => {
|
|
111
|
+
const result = await sendWebhook(makeScanResult([makeFinding({ severity: 'MEDIUM' })]), {
|
|
112
|
+
url: 'https://hooks.slack.com/services/xxx',
|
|
113
|
+
type: 'slack',
|
|
114
|
+
includeDetails: false,
|
|
115
|
+
});
|
|
116
|
+
expect(result.success).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
it('sends with no findings (green color path)', async () => {
|
|
119
|
+
const result = await sendWebhook(makeScanResult([]), {
|
|
120
|
+
url: 'https://hooks.slack.com/services/xxx',
|
|
121
|
+
type: 'slack',
|
|
122
|
+
includeDetails: false,
|
|
123
|
+
});
|
|
124
|
+
expect(result.success).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
it('respects custom timeout option', async () => {
|
|
127
|
+
const result = await sendWebhook(makeScanResult(), {
|
|
128
|
+
url: 'https://hooks.slack.com/services/xxx',
|
|
129
|
+
type: 'slack',
|
|
130
|
+
timeout: 5000,
|
|
131
|
+
});
|
|
132
|
+
expect(result.success).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
it('skips sending when all findings are below minSeverity', async () => {
|
|
135
|
+
const result = await sendWebhook(makeScanResult([makeFinding({ severity: 'LOW' }), makeFinding({ severity: 'INFO' })]), {
|
|
136
|
+
url: 'https://hooks.slack.com/services/xxx',
|
|
137
|
+
type: 'slack',
|
|
138
|
+
minSeverity: 'HIGH',
|
|
139
|
+
});
|
|
140
|
+
expect(result.success).toBe(true);
|
|
141
|
+
expect(globalThis.fetch).not.toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=webhooks.extra.test.js.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhooks Tests
|
|
3
|
+
* Tests for detectWebhookType and sendWebhook (mocking fetch).
|
|
4
|
+
*/
|
|
5
|
+
import { detectWebhookType, sendWebhook, } from '../features/webhooks.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
function makeFinding(overrides = {}) {
|
|
10
|
+
return {
|
|
11
|
+
ruleId: 'INJ-001',
|
|
12
|
+
ruleName: 'Test Rule',
|
|
13
|
+
severity: 'HIGH',
|
|
14
|
+
category: 'injection',
|
|
15
|
+
file: '/test.md',
|
|
16
|
+
relativePath: 'test.md',
|
|
17
|
+
line: 5,
|
|
18
|
+
match: 'bad content',
|
|
19
|
+
context: [],
|
|
20
|
+
remediation: 'fix it',
|
|
21
|
+
timestamp: new Date(),
|
|
22
|
+
riskScore: 50,
|
|
23
|
+
...overrides,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function makeScanResult(findings = []) {
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
startTime: new Date(),
|
|
30
|
+
endTime: new Date(),
|
|
31
|
+
duration: 1000,
|
|
32
|
+
scannedPaths: ['/project'],
|
|
33
|
+
totalFiles: 5,
|
|
34
|
+
analyzedFiles: 4,
|
|
35
|
+
skippedFiles: 1,
|
|
36
|
+
findings,
|
|
37
|
+
findingsBySeverity: {
|
|
38
|
+
CRITICAL: findings.filter(f => f.severity === 'CRITICAL'),
|
|
39
|
+
HIGH: findings.filter(f => f.severity === 'HIGH'),
|
|
40
|
+
MEDIUM: findings.filter(f => f.severity === 'MEDIUM'),
|
|
41
|
+
LOW: findings.filter(f => f.severity === 'LOW'),
|
|
42
|
+
INFO: findings.filter(f => f.severity === 'INFO'),
|
|
43
|
+
},
|
|
44
|
+
findingsByCategory: {},
|
|
45
|
+
overallRiskScore: 50,
|
|
46
|
+
summary: {
|
|
47
|
+
critical: 0,
|
|
48
|
+
high: findings.filter(f => f.severity === 'HIGH').length,
|
|
49
|
+
medium: 0, low: 0, info: 0,
|
|
50
|
+
total: findings.length,
|
|
51
|
+
},
|
|
52
|
+
errors: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function makeWebhookConfig(overrides = {}) {
|
|
56
|
+
return {
|
|
57
|
+
url: 'https://hooks.example.com/webhook',
|
|
58
|
+
type: 'generic',
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// detectWebhookType
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
describe('detectWebhookType', () => {
|
|
66
|
+
it('detects Slack URL', () => {
|
|
67
|
+
expect(detectWebhookType('https://hooks.slack.com/services/xxx')).toBe('slack');
|
|
68
|
+
});
|
|
69
|
+
it('detects Discord URL', () => {
|
|
70
|
+
expect(detectWebhookType('https://discord.com/api/webhooks/123/abc')).toBe('discord');
|
|
71
|
+
});
|
|
72
|
+
it('detects Teams from webhook.office.com', () => {
|
|
73
|
+
expect(detectWebhookType('https://myorg.webhook.office.com/webhookb2/xxx')).toBe('teams');
|
|
74
|
+
});
|
|
75
|
+
it('detects Teams from outlook.office.com', () => {
|
|
76
|
+
expect(detectWebhookType('https://myorg.outlook.office.com/webhook/xxx')).toBe('teams');
|
|
77
|
+
});
|
|
78
|
+
it('returns generic for unknown URLs', () => {
|
|
79
|
+
expect(detectWebhookType('https://my-custom-webhook.example.com/hook')).toBe('generic');
|
|
80
|
+
});
|
|
81
|
+
it('returns generic for empty string', () => {
|
|
82
|
+
expect(detectWebhookType('')).toBe('generic');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// sendWebhook — with mocked fetch
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
describe('sendWebhook', () => {
|
|
89
|
+
let originalFetch;
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
originalFetch = globalThis.fetch;
|
|
92
|
+
});
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
globalThis.fetch = originalFetch;
|
|
95
|
+
});
|
|
96
|
+
function mockFetch(status, ok, body = '') {
|
|
97
|
+
globalThis.fetch = jest.fn().mockResolvedValue({
|
|
98
|
+
ok,
|
|
99
|
+
status,
|
|
100
|
+
text: () => Promise.resolve(body),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
it('returns success when fetch succeeds with 200', async () => {
|
|
104
|
+
mockFetch(200, true);
|
|
105
|
+
const result = await sendWebhook(makeScanResult(), makeWebhookConfig());
|
|
106
|
+
expect(result.success).toBe(true);
|
|
107
|
+
expect(result.statusCode).toBe(200);
|
|
108
|
+
});
|
|
109
|
+
it('returns failure when fetch returns non-ok status', async () => {
|
|
110
|
+
mockFetch(500, false, 'Internal Server Error');
|
|
111
|
+
const result = await sendWebhook(makeScanResult(), makeWebhookConfig());
|
|
112
|
+
expect(result.success).toBe(false);
|
|
113
|
+
expect(result.statusCode).toBe(500);
|
|
114
|
+
});
|
|
115
|
+
it('returns failure when fetch throws', async () => {
|
|
116
|
+
globalThis.fetch = jest.fn().mockRejectedValue(new Error('network error'));
|
|
117
|
+
const result = await sendWebhook(makeScanResult(), makeWebhookConfig());
|
|
118
|
+
expect(result.success).toBe(false);
|
|
119
|
+
expect(result.error).toContain('network error');
|
|
120
|
+
});
|
|
121
|
+
it('sends to slack type without error', async () => {
|
|
122
|
+
mockFetch(200, true);
|
|
123
|
+
const result = await sendWebhook(makeScanResult([makeFinding()]), makeWebhookConfig({ type: 'slack', url: 'https://hooks.slack.com/services/xxx' }));
|
|
124
|
+
expect(result.success).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
it('sends to discord type without error', async () => {
|
|
127
|
+
mockFetch(200, true);
|
|
128
|
+
const result = await sendWebhook(makeScanResult([makeFinding()]), makeWebhookConfig({ type: 'discord', url: 'https://discord.com/api/webhooks/x/y' }));
|
|
129
|
+
expect(result.success).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
it('sends to teams type without error', async () => {
|
|
132
|
+
mockFetch(200, true);
|
|
133
|
+
const result = await sendWebhook(makeScanResult([makeFinding()]), makeWebhookConfig({ type: 'teams', url: 'https://org.webhook.office.com/webhook' }));
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
it('skips when minSeverity not met and findings exist', async () => {
|
|
137
|
+
mockFetch(200, true);
|
|
138
|
+
const result = await sendWebhook(makeScanResult([makeFinding({ severity: 'LOW' })]), makeWebhookConfig({ minSeverity: 'HIGH' }));
|
|
139
|
+
// Should return success=true without sending
|
|
140
|
+
expect(result.success).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
it('sends when minSeverity is met', async () => {
|
|
143
|
+
mockFetch(200, true);
|
|
144
|
+
const result = await sendWebhook(makeScanResult([makeFinding({ severity: 'CRITICAL' })]), makeWebhookConfig({ minSeverity: 'HIGH' }));
|
|
145
|
+
expect(result.success).toBe(true);
|
|
146
|
+
expect(globalThis.fetch).toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
it('includes details when includeDetails is true', async () => {
|
|
149
|
+
mockFetch(200, true);
|
|
150
|
+
const result = await sendWebhook(makeScanResult([makeFinding()]), makeWebhookConfig({ includeDetails: true }));
|
|
151
|
+
expect(result.success).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=webhooks.test.js.map
|
|
@@ -7,6 +7,7 @@ import { createHash } from 'node:crypto';
|
|
|
7
7
|
import { resolve, extname } from 'node:path';
|
|
8
8
|
import { parse as parseYaml } from 'yaml';
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
+
import { compileSafePattern } from '../utils/safeRegex.js';
|
|
10
11
|
import logger from '../utils/logger.js';
|
|
11
12
|
/**
|
|
12
13
|
* Schema for custom rule definition in YAML/JSON
|
|
@@ -139,14 +140,15 @@ function parseCustomRulesContent(content, sourceExt, sourceLabel) {
|
|
|
139
140
|
* Convert custom rule definition to Rule object
|
|
140
141
|
*/
|
|
141
142
|
function definitionToRule(def) {
|
|
142
|
-
// Compile regex patterns
|
|
143
|
+
// Compile regex patterns — reject unsafe patterns via compileSafePattern
|
|
143
144
|
const patterns = [];
|
|
144
145
|
for (const pattern of def.patterns) {
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
const compiled = compileSafePattern(pattern, 'gi');
|
|
147
|
+
if (compiled === null) {
|
|
148
|
+
logger.warn(`Unsafe or invalid regex pattern in rule ${def.id} (skipped): ${pattern}`);
|
|
147
149
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
else {
|
|
151
|
+
patterns.push(compiled);
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
if (patterns.length === 0) {
|
|
@@ -154,33 +156,27 @@ function definitionToRule(def) {
|
|
|
154
156
|
}
|
|
155
157
|
// Compile exclude patterns
|
|
156
158
|
const excludePatterns = def.excludePatterns?.map((p) => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
catch {
|
|
161
|
-
logger.warn(`Invalid exclude pattern in rule ${def.id}: ${p}`);
|
|
162
|
-
return null;
|
|
159
|
+
const compiled = compileSafePattern(p, 'gi');
|
|
160
|
+
if (compiled === null) {
|
|
161
|
+
logger.warn(`Unsafe or invalid exclude pattern in rule ${def.id} (skipped): ${p}`);
|
|
163
162
|
}
|
|
163
|
+
return compiled;
|
|
164
164
|
}).filter((p) => p !== null);
|
|
165
165
|
// Compile require context patterns
|
|
166
166
|
const requireContext = def.requireContext?.map((p) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
catch {
|
|
171
|
-
logger.warn(`Invalid requireContext pattern in rule ${def.id}: ${p}`);
|
|
172
|
-
return null;
|
|
167
|
+
const compiled = compileSafePattern(p, 'gi');
|
|
168
|
+
if (compiled === null) {
|
|
169
|
+
logger.warn(`Unsafe or invalid requireContext pattern in rule ${def.id} (skipped): ${p}`);
|
|
173
170
|
}
|
|
171
|
+
return compiled;
|
|
174
172
|
}).filter((p) => p !== null);
|
|
175
173
|
// Compile exclude context patterns
|
|
176
174
|
const excludeContext = def.excludeContext?.map((p) => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
catch {
|
|
181
|
-
logger.warn(`Invalid excludeContext pattern in rule ${def.id}: ${p}`);
|
|
182
|
-
return null;
|
|
175
|
+
const compiled = compileSafePattern(p, 'gi');
|
|
176
|
+
if (compiled === null) {
|
|
177
|
+
logger.warn(`Unsafe or invalid excludeContext pattern in rule ${def.id} (skipped): ${p}`);
|
|
183
178
|
}
|
|
179
|
+
return compiled;
|
|
184
180
|
}).filter((p) => p !== null);
|
|
185
181
|
const rule = {
|
|
186
182
|
id: def.id,
|
|
@@ -458,14 +454,11 @@ export function validateCustomRulesFile(filePath) {
|
|
|
458
454
|
warnings,
|
|
459
455
|
};
|
|
460
456
|
}
|
|
461
|
-
// Validate regex patterns
|
|
457
|
+
// Validate regex patterns — also screen for ReDoS risks
|
|
462
458
|
for (const rule of result.data.rules) {
|
|
463
459
|
for (const pattern of rule.patterns) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
catch {
|
|
468
|
-
errors.push(`Rule ${rule.id}: Invalid regex pattern "${pattern}"`);
|
|
460
|
+
if (compileSafePattern(pattern, 'gi') === null) {
|
|
461
|
+
errors.push(`Rule ${rule.id}: Unsafe or invalid regex pattern "${pattern}"`);
|
|
469
462
|
}
|
|
470
463
|
}
|
|
471
464
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Trust Scoring
|
|
3
|
+
* Evaluates the security posture of an MCP server configuration.
|
|
4
|
+
*/
|
|
5
|
+
export interface McpTrustResult {
|
|
6
|
+
score: number;
|
|
7
|
+
trustLevel: 'HIGH' | 'MEDIUM' | 'LOW' | 'CRITICAL';
|
|
8
|
+
flags: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Score an MCP server configuration entry.
|
|
12
|
+
*
|
|
13
|
+
* @param serverConfig - A single MCP server config object (value from `mcpServers` map)
|
|
14
|
+
* @returns Trust score (0-100), trust level, and list of flags
|
|
15
|
+
*/
|
|
16
|
+
export declare function scoreMcpServer(serverConfig: unknown): McpTrustResult;
|
|
17
|
+
//# sourceMappingURL=mcpTrustScore.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Trust Scoring
|
|
3
|
+
* Evaluates the security posture of an MCP server configuration.
|
|
4
|
+
*/
|
|
5
|
+
// Known suspicious package names / name fragments
|
|
6
|
+
const SUSPICIOUS_NAMES = [
|
|
7
|
+
'shadow', 'stealer', 'exfil', 'beacon', 'c2-', '-c2',
|
|
8
|
+
'keylog', 'implant', 'dropper', 'exploit',
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* Score an MCP server configuration entry.
|
|
12
|
+
*
|
|
13
|
+
* @param serverConfig - A single MCP server config object (value from `mcpServers` map)
|
|
14
|
+
* @returns Trust score (0-100), trust level, and list of flags
|
|
15
|
+
*/
|
|
16
|
+
export function scoreMcpServer(serverConfig) {
|
|
17
|
+
const flags = [];
|
|
18
|
+
let score = 100;
|
|
19
|
+
if (typeof serverConfig !== 'object' || serverConfig === null) {
|
|
20
|
+
return { score: 0, trustLevel: 'CRITICAL', flags: ['Invalid config object'] };
|
|
21
|
+
}
|
|
22
|
+
const cfg = serverConfig;
|
|
23
|
+
// Insecure transport
|
|
24
|
+
const transport = cfg['transport'];
|
|
25
|
+
if (transport === 'http' || transport === 'sse') {
|
|
26
|
+
score -= 30;
|
|
27
|
+
flags.push(`Insecure transport: '${transport}' — prefer stdio or wss`);
|
|
28
|
+
}
|
|
29
|
+
// Plain HTTP URL
|
|
30
|
+
const url = typeof cfg['url'] === 'string' ? cfg['url'] : '';
|
|
31
|
+
if (url.startsWith('http://')) {
|
|
32
|
+
score -= 25;
|
|
33
|
+
flags.push('Plain HTTP URL — credentials and tool calls are transmitted in cleartext');
|
|
34
|
+
}
|
|
35
|
+
// Unpinned npx command
|
|
36
|
+
const command = typeof cfg['command'] === 'string' ? cfg['command'] : '';
|
|
37
|
+
if (command === 'npx' || command.endsWith('/npx')) {
|
|
38
|
+
const args = Array.isArray(cfg['args']) ? cfg['args'] : [];
|
|
39
|
+
const firstArg = args[0] ?? '';
|
|
40
|
+
if (firstArg && !firstArg.includes('@') && !firstArg.startsWith('-')) {
|
|
41
|
+
score -= 20;
|
|
42
|
+
flags.push(`Unpinned npx package '${firstArg}' — pin to a specific version to prevent rug pulls`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Dangerous flags in args
|
|
46
|
+
const args = Array.isArray(cfg['args']) ? cfg['args'] : [];
|
|
47
|
+
for (const arg of args) {
|
|
48
|
+
if (typeof arg === 'string' && (arg.includes('--allow-all') || arg.includes('--dangerously-skip'))) {
|
|
49
|
+
score -= 30;
|
|
50
|
+
flags.push(`Dangerous arg '${arg}' — bypasses MCP safety checks`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Suspicious name
|
|
54
|
+
const name = typeof cfg['name'] === 'string' ? cfg['name'].toLowerCase() : '';
|
|
55
|
+
const pkg = args.find(a => typeof a === 'string' && !a.startsWith('-')) ?? '';
|
|
56
|
+
const combined = `${name} ${pkg}`.toLowerCase();
|
|
57
|
+
for (const pattern of SUSPICIOUS_NAMES) {
|
|
58
|
+
if (combined.includes(pattern)) {
|
|
59
|
+
score -= 50;
|
|
60
|
+
flags.push(`Name matches suspicious pattern '${pattern}'`);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const clampedScore = Math.max(0, Math.min(100, score));
|
|
65
|
+
return {
|
|
66
|
+
score: clampedScore,
|
|
67
|
+
trustLevel: clampedScore >= 80 ? 'HIGH'
|
|
68
|
+
: clampedScore >= 60 ? 'MEDIUM'
|
|
69
|
+
: clampedScore >= 40 ? 'LOW'
|
|
70
|
+
: 'CRITICAL',
|
|
71
|
+
flags,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=mcpTrustScore.js.map
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Validates .mcp.json files for dangerous permissions, untrusted sources, etc.
|
|
4
4
|
*/
|
|
5
5
|
import type { Finding, Severity } from '../types.js';
|
|
6
|
+
import { type McpTrustResult } from './mcpTrustScore.js';
|
|
6
7
|
/**
|
|
7
8
|
* Risk assessment for MCP servers
|
|
8
9
|
*/
|
|
@@ -18,6 +19,7 @@ export interface McpRiskAssessment {
|
|
|
18
19
|
capabilities: string[];
|
|
19
20
|
command?: string | undefined;
|
|
20
21
|
url?: string | undefined;
|
|
22
|
+
trustScore?: McpTrustResult | undefined;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Validate MCP configuration JSON content
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { readFileSync, existsSync } from 'node:fs';
|
|
8
8
|
import { resolve, basename } from 'node:path';
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
+
import { scoreMcpServer } from './mcpTrustScore.js';
|
|
10
11
|
/**
|
|
11
12
|
* MCP Server configuration schema
|
|
12
13
|
*/
|
|
@@ -282,6 +283,18 @@ export function validateMcpConfigContent(content) {
|
|
|
282
283
|
for (const [name, config] of Object.entries(servers)) {
|
|
283
284
|
if (typeof config === 'object' && config !== null) {
|
|
284
285
|
const assessment = analyzeServer(name, config);
|
|
286
|
+
// Augment with trust score; surface CRITICAL/LOW trust as issues
|
|
287
|
+
assessment.trustScore = scoreMcpServer({ ...config, name });
|
|
288
|
+
if (assessment.trustScore.trustLevel === 'CRITICAL' || assessment.trustScore.trustLevel === 'LOW') {
|
|
289
|
+
for (const flag of assessment.trustScore.flags) {
|
|
290
|
+
assessment.issues.push({
|
|
291
|
+
type: 'trust-score',
|
|
292
|
+
severity: assessment.trustScore.trustLevel === 'CRITICAL' ? 'CRITICAL' : 'HIGH',
|
|
293
|
+
description: `Trust score ${assessment.trustScore.score}/100: ${flag}`,
|
|
294
|
+
remediation: 'Review MCP server configuration and address the flagged concern.',
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
285
298
|
assessments.push(assessment);
|
|
286
299
|
}
|
|
287
300
|
}
|
|
@@ -24,17 +24,17 @@ declare const PolicyRuleSchema: z.ZodObject<{
|
|
|
24
24
|
minRiskScore: z.ZodOptional<z.ZodNumber>;
|
|
25
25
|
maxFindings: z.ZodOptional<z.ZodNumber>;
|
|
26
26
|
}, "strip", z.ZodTypeAny, {
|
|
27
|
-
categories?: string[] | undefined;
|
|
28
27
|
ruleIds?: string[] | undefined;
|
|
29
|
-
|
|
28
|
+
categories?: string[] | undefined;
|
|
30
29
|
filePatterns?: string[] | undefined;
|
|
30
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
31
31
|
minRiskScore?: number | undefined;
|
|
32
32
|
maxFindings?: number | undefined;
|
|
33
33
|
}, {
|
|
34
|
-
categories?: string[] | undefined;
|
|
35
34
|
ruleIds?: string[] | undefined;
|
|
36
|
-
|
|
35
|
+
categories?: string[] | undefined;
|
|
37
36
|
filePatterns?: string[] | undefined;
|
|
37
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
38
38
|
minRiskScore?: number | undefined;
|
|
39
39
|
maxFindings?: number | undefined;
|
|
40
40
|
}>;
|
|
@@ -44,10 +44,10 @@ declare const PolicyRuleSchema: z.ZodObject<{
|
|
|
44
44
|
enabled: boolean;
|
|
45
45
|
action: "warn" | "ignore" | "block";
|
|
46
46
|
conditions: {
|
|
47
|
-
categories?: string[] | undefined;
|
|
48
47
|
ruleIds?: string[] | undefined;
|
|
49
|
-
|
|
48
|
+
categories?: string[] | undefined;
|
|
50
49
|
filePatterns?: string[] | undefined;
|
|
50
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
51
51
|
minRiskScore?: number | undefined;
|
|
52
52
|
maxFindings?: number | undefined;
|
|
53
53
|
};
|
|
@@ -56,10 +56,10 @@ declare const PolicyRuleSchema: z.ZodObject<{
|
|
|
56
56
|
}, {
|
|
57
57
|
id: string;
|
|
58
58
|
conditions: {
|
|
59
|
-
categories?: string[] | undefined;
|
|
60
59
|
ruleIds?: string[] | undefined;
|
|
61
|
-
|
|
60
|
+
categories?: string[] | undefined;
|
|
62
61
|
filePatterns?: string[] | undefined;
|
|
62
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
63
63
|
minRiskScore?: number | undefined;
|
|
64
64
|
maxFindings?: number | undefined;
|
|
65
65
|
};
|
|
@@ -88,17 +88,17 @@ declare const PolicyConfigSchema: z.ZodObject<{
|
|
|
88
88
|
minRiskScore: z.ZodOptional<z.ZodNumber>;
|
|
89
89
|
maxFindings: z.ZodOptional<z.ZodNumber>;
|
|
90
90
|
}, "strip", z.ZodTypeAny, {
|
|
91
|
-
categories?: string[] | undefined;
|
|
92
91
|
ruleIds?: string[] | undefined;
|
|
93
|
-
|
|
92
|
+
categories?: string[] | undefined;
|
|
94
93
|
filePatterns?: string[] | undefined;
|
|
94
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
95
95
|
minRiskScore?: number | undefined;
|
|
96
96
|
maxFindings?: number | undefined;
|
|
97
97
|
}, {
|
|
98
|
-
categories?: string[] | undefined;
|
|
99
98
|
ruleIds?: string[] | undefined;
|
|
100
|
-
|
|
99
|
+
categories?: string[] | undefined;
|
|
101
100
|
filePatterns?: string[] | undefined;
|
|
101
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
102
102
|
minRiskScore?: number | undefined;
|
|
103
103
|
maxFindings?: number | undefined;
|
|
104
104
|
}>;
|
|
@@ -108,10 +108,10 @@ declare const PolicyConfigSchema: z.ZodObject<{
|
|
|
108
108
|
enabled: boolean;
|
|
109
109
|
action: "warn" | "ignore" | "block";
|
|
110
110
|
conditions: {
|
|
111
|
-
categories?: string[] | undefined;
|
|
112
111
|
ruleIds?: string[] | undefined;
|
|
113
|
-
|
|
112
|
+
categories?: string[] | undefined;
|
|
114
113
|
filePatterns?: string[] | undefined;
|
|
114
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
115
115
|
minRiskScore?: number | undefined;
|
|
116
116
|
maxFindings?: number | undefined;
|
|
117
117
|
};
|
|
@@ -120,10 +120,10 @@ declare const PolicyConfigSchema: z.ZodObject<{
|
|
|
120
120
|
}, {
|
|
121
121
|
id: string;
|
|
122
122
|
conditions: {
|
|
123
|
-
categories?: string[] | undefined;
|
|
124
123
|
ruleIds?: string[] | undefined;
|
|
125
|
-
|
|
124
|
+
categories?: string[] | undefined;
|
|
126
125
|
filePatterns?: string[] | undefined;
|
|
126
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
127
127
|
minRiskScore?: number | undefined;
|
|
128
128
|
maxFindings?: number | undefined;
|
|
129
129
|
};
|
|
@@ -182,10 +182,10 @@ declare const PolicyConfigSchema: z.ZodObject<{
|
|
|
182
182
|
enabled: boolean;
|
|
183
183
|
action: "warn" | "ignore" | "block";
|
|
184
184
|
conditions: {
|
|
185
|
-
categories?: string[] | undefined;
|
|
186
185
|
ruleIds?: string[] | undefined;
|
|
187
|
-
|
|
186
|
+
categories?: string[] | undefined;
|
|
188
187
|
filePatterns?: string[] | undefined;
|
|
188
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
189
189
|
minRiskScore?: number | undefined;
|
|
190
190
|
maxFindings?: number | undefined;
|
|
191
191
|
};
|
|
@@ -198,10 +198,10 @@ declare const PolicyConfigSchema: z.ZodObject<{
|
|
|
198
198
|
rules: {
|
|
199
199
|
id: string;
|
|
200
200
|
conditions: {
|
|
201
|
-
categories?: string[] | undefined;
|
|
202
201
|
ruleIds?: string[] | undefined;
|
|
203
|
-
|
|
202
|
+
categories?: string[] | undefined;
|
|
204
203
|
filePatterns?: string[] | undefined;
|
|
204
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
205
205
|
minRiskScore?: number | undefined;
|
|
206
206
|
maxFindings?: number | undefined;
|
|
207
207
|
};
|
|
@@ -313,10 +313,10 @@ declare const _default: {
|
|
|
313
313
|
enabled: boolean;
|
|
314
314
|
action: "warn" | "ignore" | "block";
|
|
315
315
|
conditions: {
|
|
316
|
-
categories?: string[] | undefined;
|
|
317
316
|
ruleIds?: string[] | undefined;
|
|
318
|
-
|
|
317
|
+
categories?: string[] | undefined;
|
|
319
318
|
filePatterns?: string[] | undefined;
|
|
319
|
+
severities?: ("CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO")[] | undefined;
|
|
320
320
|
minRiskScore?: number | undefined;
|
|
321
321
|
maxFindings?: number | undefined;
|
|
322
322
|
};
|