codegate-ai 0.16.2 → 1.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/dist/cli.d.ts +3 -1
- package/dist/cli.js +153 -44
- package/dist/commands/scan-command.d.ts +2 -1
- package/dist/commands/scan-command.js +6 -1
- package/dist/commands/trust.d.ts +28 -0
- package/dist/commands/trust.js +69 -0
- package/dist/config/inline-ignore.d.ts +10 -2
- package/dist/config/inline-ignore.js +5 -1
- package/dist/config/suppression-policy.d.ts +1 -1
- package/dist/config/suppression-policy.js +4 -1
- package/dist/config/trust.d.ts +2 -0
- package/dist/config/trust.js +19 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +45 -1
- package/dist/content/content-bundle.d.ts +27 -0
- package/dist/content/content-bundle.js +73 -0
- package/dist/content/content-store.d.ts +27 -0
- package/dist/content/content-store.js +0 -0
- package/dist/content/content-updater.d.ts +32 -0
- package/dist/content/content-updater.js +111 -0
- package/dist/content/known-bad.d.ts +26 -0
- package/dist/content/known-bad.js +100 -0
- package/dist/content/publisher-key.d.ts +10 -0
- package/dist/content/publisher-key.js +10 -0
- package/dist/layer1-discovery/knowledge-base.js +33 -1
- package/dist/layer2-static/data/popular-mcp-packages.d.ts +16 -0
- package/dist/layer2-static/data/popular-mcp-packages.js +83 -0
- package/dist/layer2-static/detectors/known-bad.d.ts +22 -0
- package/dist/layer2-static/detectors/known-bad.js +116 -0
- package/dist/layer2-static/detectors/mcp-package-hygiene.d.ts +28 -0
- package/dist/layer2-static/detectors/mcp-package-hygiene.js +191 -0
- package/dist/layer2-static/detectors/rule-file.js +128 -75
- package/dist/layer2-static/detectors/skill-frontmatter.d.ts +6 -0
- package/dist/layer2-static/detectors/skill-frontmatter.js +130 -0
- package/dist/layer2-static/engine.d.ts +2 -0
- package/dist/layer2-static/engine.js +0 -0
- package/dist/layer2-static/rule-pack-loader.js +24 -1
- package/dist/layer2-static/state/scan-state.d.ts +7 -2
- package/dist/layer2-static/state/scan-state.js +74 -30
- package/dist/layer2-static/text/confusables.d.ts +7 -0
- package/dist/layer2-static/text/confusables.js +70 -0
- package/dist/layer2-static/text/edit-distance.d.ts +6 -0
- package/dist/layer2-static/text/edit-distance.js +33 -0
- package/dist/layer2-static/text/encoded-payloads.d.ts +16 -0
- package/dist/layer2-static/text/encoded-payloads.js +116 -0
- package/dist/layer2-static/text/normalize.d.ts +11 -0
- package/dist/layer2-static/text/normalize.js +19 -0
- package/dist/layer2-static/text/override-phrases.d.ts +14 -0
- package/dist/layer2-static/text/override-phrases.js +49 -0
- package/dist/layer2-static/text/threat-patterns.d.ts +33 -0
- package/dist/layer2-static/text/threat-patterns.js +45 -0
- package/dist/layer2-static/text/unicode.d.ts +33 -0
- package/dist/layer2-static/text/unicode.js +83 -0
- package/dist/layer3-dynamic/deep-resource-executor.d.ts +21 -0
- package/dist/layer3-dynamic/deep-resource-executor.js +73 -0
- package/dist/layer3-dynamic/meta-agent.js +2 -1
- package/dist/layer3-dynamic/registry-client.d.ts +26 -0
- package/dist/layer3-dynamic/registry-client.js +138 -0
- package/dist/layer3-dynamic/registry-findings.d.ts +7 -0
- package/dist/layer3-dynamic/registry-findings.js +64 -0
- package/dist/layer3-dynamic/tool-description-scanner.js +22 -13
- package/dist/layer3-dynamic/toxic-flow.d.ts +4 -0
- package/dist/layer3-dynamic/toxic-flow.js +41 -8
- package/dist/pipeline.d.ts +5 -2
- package/dist/pipeline.js +53 -16
- package/dist/report-summary.d.ts +1 -1
- package/dist/report-summary.js +9 -1
- package/dist/scan.d.ts +13 -0
- package/dist/scan.js +291 -17
- package/dist/types/finding.d.ts +14 -0
- package/dist/types/finding.js +14 -0
- package/dist/types/report.d.ts +2 -0
- package/dist/wrapper.js +2 -19
- package/package.json +1 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const EXEC_PATTERN = /(run command|execute shell|bash -c|sh -c|powershell)/iu;
|
|
1
|
+
import { scanEncodedPayloads } from "../layer2-static/text/encoded-payloads.js";
|
|
2
|
+
import { normalizeForMatching } from "../layer2-static/text/normalize.js";
|
|
3
|
+
import { COMMAND_EXECUTION_PATTERN, EXFIL_PATTERN, SENSITIVE_FILE_PATTERN, findOverridePhrase, } from "../layer2-static/text/threat-patterns.js";
|
|
4
|
+
import { HIDDEN_UNICODE_CLASS, findHiddenUnicode } from "../layer2-static/text/unicode.js";
|
|
6
5
|
function makeFinding(input, tool, ruleId, severity, description) {
|
|
7
6
|
return {
|
|
8
7
|
rule_id: ruleId,
|
|
@@ -27,24 +26,34 @@ export function scanToolDescriptions(input) {
|
|
|
27
26
|
const findings = [];
|
|
28
27
|
for (const tool of input.tools) {
|
|
29
28
|
const text = tool.description;
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
29
|
+
const normalized = normalizeForMatching(text);
|
|
30
|
+
const hasSensitive = SENSITIVE_FILE_PATTERN.test(normalized);
|
|
31
|
+
const hasExfil = EXFIL_PATTERN.test(normalized);
|
|
32
|
+
const overrideMatch = findOverridePhrase(normalized);
|
|
33
|
+
const hasExec = COMMAND_EXECUTION_PATTERN.test(normalized);
|
|
34
|
+
const hiddenMatches = input.unicodeAnalysis === false ? [] : findHiddenUnicode(text);
|
|
35
|
+
const tagMatches = hiddenMatches.filter((match) => match.class === HIDDEN_UNICODE_CLASS.Tags);
|
|
36
|
+
const otherHiddenMatches = hiddenMatches.filter((match) => match.class !== HIDDEN_UNICODE_CLASS.Tags);
|
|
35
37
|
const isLong = text.length > 1000;
|
|
36
38
|
if (hasSensitive && hasExfil) {
|
|
37
39
|
findings.push(makeFinding(input, tool, "tool-description-sensitive-exfiltration", "CRITICAL", `Tool description references sensitive file access with exfiltration behavior: ${tool.name}`));
|
|
38
40
|
}
|
|
39
|
-
if (
|
|
40
|
-
findings.push(makeFinding(input, tool, "tool-description-instruction-override", "HIGH", `Tool description contains instruction-override language: ${tool.name}`));
|
|
41
|
+
if (overrideMatch) {
|
|
42
|
+
findings.push(makeFinding(input, tool, "tool-description-instruction-override", "HIGH", `Tool description contains instruction-override language ("${overrideMatch.phrase}"): ${tool.name}`));
|
|
41
43
|
}
|
|
42
44
|
if (hasExec) {
|
|
43
45
|
findings.push(makeFinding(input, tool, "tool-description-command-execution", "HIGH", `Tool description encourages command execution patterns: ${tool.name}`));
|
|
44
46
|
}
|
|
45
|
-
if (
|
|
47
|
+
if (otherHiddenMatches.length > 0) {
|
|
46
48
|
findings.push(makeFinding(input, tool, "tool-description-hidden-unicode", "MEDIUM", `Tool description includes hidden Unicode characters: ${tool.name}`));
|
|
47
49
|
}
|
|
50
|
+
if (tagMatches.length > 0) {
|
|
51
|
+
findings.push(makeFinding(input, tool, "tool-description-hidden-unicode-tags", "HIGH", `Tool description includes ${tagMatches.length} Unicode tag character(s) ` +
|
|
52
|
+
`(U+E0000-U+E007F, ASCII smuggling): ${tool.name}`));
|
|
53
|
+
}
|
|
54
|
+
for (const payload of scanEncodedPayloads(text)) {
|
|
55
|
+
findings.push(makeFinding(input, tool, "tool-description-encoded-payload", payload.matchesRemoteShell ? "CRITICAL" : "HIGH", `Tool description contains a ${payload.kind}-encoded payload with hidden instructions: ${tool.name}`));
|
|
56
|
+
}
|
|
48
57
|
if (isLong) {
|
|
49
58
|
findings.push(makeFinding(input, tool, "tool-description-unusually-long", "MEDIUM", `Tool description is unusually long and may hide instructions: ${tool.name}`));
|
|
50
59
|
}
|
|
@@ -8,5 +8,9 @@ export interface ToxicFlowInput {
|
|
|
8
8
|
scopeId: string;
|
|
9
9
|
tools: ToxicFlowTool[];
|
|
10
10
|
knownClassifications?: Record<string, ToxicToolClass[]>;
|
|
11
|
+
/** Maps tool name to its origin server, for cross-server workspace analysis. */
|
|
12
|
+
origins?: Record<string, string>;
|
|
13
|
+
/** Only report chains spanning at least two distinct origins. */
|
|
14
|
+
crossOriginOnly?: boolean;
|
|
11
15
|
}
|
|
12
16
|
export declare function detectToxicFlows(input: ToxicFlowInput): Finding[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const MAX_REPORTED_CHAINS = 10;
|
|
1
2
|
function classifyByDescription(description) {
|
|
2
3
|
const classes = new Set();
|
|
3
4
|
const text = description.toLowerCase();
|
|
@@ -25,16 +26,20 @@ function classifyTools(input) {
|
|
|
25
26
|
return { tool, classes };
|
|
26
27
|
});
|
|
27
28
|
}
|
|
29
|
+
function describeTool(input, toolName) {
|
|
30
|
+
const origin = input.origins?.[toolName];
|
|
31
|
+
return origin ? `${toolName} (server: ${origin})` : toolName;
|
|
32
|
+
}
|
|
28
33
|
function makeFinding(input, sourceTool, sensitiveTool, sinkTool) {
|
|
29
34
|
return {
|
|
30
35
|
rule_id: "toxic-flow-chain-detected",
|
|
31
|
-
finding_id: `TOXIC_FLOW-${sourceTool}-${sensitiveTool}-${sinkTool}`,
|
|
36
|
+
finding_id: `TOXIC_FLOW-${input.scopeId}-${sourceTool}-${sensitiveTool}-${sinkTool}`,
|
|
32
37
|
severity: "CRITICAL",
|
|
33
38
|
category: "TOXIC_FLOW",
|
|
34
39
|
layer: "L3",
|
|
35
40
|
file_path: input.scopeId,
|
|
36
41
|
location: { field: "tool_interaction_graph" },
|
|
37
|
-
description: `Toxic Flow detected: ${sourceTool} -> ${sensitiveTool} -> ${sinkTool}. This chain can propagate untrusted input into sensitive data access and external exfiltration.`,
|
|
42
|
+
description: `Toxic Flow detected: ${describeTool(input, sourceTool)} -> ${describeTool(input, sensitiveTool)} -> ${describeTool(input, sinkTool)}. This chain can propagate untrusted input into sensitive data access and external exfiltration.`,
|
|
38
43
|
affected_tools: [],
|
|
39
44
|
cve: null,
|
|
40
45
|
owasp: ["ASI08"],
|
|
@@ -51,13 +56,41 @@ function makeFinding(input, sourceTool, sensitiveTool, sinkTool) {
|
|
|
51
56
|
suppressed: false,
|
|
52
57
|
};
|
|
53
58
|
}
|
|
59
|
+
function distinctOriginCount(input, toolNames) {
|
|
60
|
+
const origins = new Set();
|
|
61
|
+
for (const name of toolNames) {
|
|
62
|
+
const origin = input.origins?.[name];
|
|
63
|
+
if (origin) {
|
|
64
|
+
origins.add(origin);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return origins.size;
|
|
68
|
+
}
|
|
54
69
|
export function detectToxicFlows(input) {
|
|
55
70
|
const classified = classifyTools(input);
|
|
56
|
-
const untrusted = classified
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
const untrusted = classified
|
|
72
|
+
.filter((entry) => entry.classes.has("untrusted_input"))
|
|
73
|
+
.map((entry) => entry.tool.name);
|
|
74
|
+
const sensitive = classified
|
|
75
|
+
.filter((entry) => entry.classes.has("sensitive_access"))
|
|
76
|
+
.map((entry) => entry.tool.name);
|
|
77
|
+
const exfil = classified
|
|
78
|
+
.filter((entry) => entry.classes.has("exfiltration_sink"))
|
|
79
|
+
.map((entry) => entry.tool.name);
|
|
80
|
+
const findings = [];
|
|
81
|
+
for (const source of untrusted) {
|
|
82
|
+
for (const sensitiveTool of sensitive) {
|
|
83
|
+
for (const sink of exfil) {
|
|
84
|
+
if (findings.length >= MAX_REPORTED_CHAINS) {
|
|
85
|
+
return findings;
|
|
86
|
+
}
|
|
87
|
+
if (input.crossOriginOnly &&
|
|
88
|
+
distinctOriginCount(input, [source, sensitiveTool, sink]) < 2) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
findings.push(makeFinding(input, source, sensitiveTool, sink));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
61
94
|
}
|
|
62
|
-
return
|
|
95
|
+
return findings;
|
|
63
96
|
}
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type CodeGateReport } from "./types/report.js";
|
|
|
3
3
|
import type { GitHookEntry } from "./layer2-static/detectors/git-hooks.js";
|
|
4
4
|
import type { SymlinkEscapeEntry } from "./layer2-static/detectors/symlink.js";
|
|
5
5
|
import type { ResourceFetchResult, ResourceRequest } from "./layer3-dynamic/resource-fetcher.js";
|
|
6
|
+
import { type RegistryHeuristicsOptions } from "./layer3-dynamic/registry-findings.js";
|
|
6
7
|
import type { Finding } from "./types/finding.js";
|
|
7
8
|
export interface StaticPipelineInput {
|
|
8
9
|
version: string;
|
|
@@ -27,8 +28,10 @@ export interface DeepScanOutcome {
|
|
|
27
28
|
result?: ResourceFetchResult;
|
|
28
29
|
}
|
|
29
30
|
export declare function runStaticPipeline(input: StaticPipelineInput): Promise<CodeGateReport>;
|
|
30
|
-
export
|
|
31
|
+
export interface Layer3FindingOptions {
|
|
31
32
|
unicodeAnalysis?: boolean;
|
|
32
|
-
|
|
33
|
+
registryHeuristics?: RegistryHeuristicsOptions;
|
|
34
|
+
}
|
|
35
|
+
export declare function layer3OutcomesToFindings(outcomes: DeepScanOutcome[], options?: Layer3FindingOptions): Finding[];
|
|
33
36
|
export declare function mergeLayer3Findings(baseReport: CodeGateReport, layer3Findings: Finding[]): CodeGateReport;
|
|
34
37
|
export declare function runDeepScanWithConsent(resources: DeepScanResource[], requestConsent: (resource: DeepScanResource) => Promise<boolean> | boolean, execute: (resource: DeepScanResource) => Promise<ResourceFetchResult>): Promise<DeepScanOutcome[]>;
|
package/dist/pipeline.js
CHANGED
|
@@ -2,6 +2,7 @@ import { runStaticEngine, } from "./layer2-static/engine.js";
|
|
|
2
2
|
import { createEmptyReport } from "./types/report.js";
|
|
3
3
|
import { scanToolDescriptions, } from "./layer3-dynamic/tool-description-scanner.js";
|
|
4
4
|
import { detectToxicFlows } from "./layer3-dynamic/toxic-flow.js";
|
|
5
|
+
import { deriveRegistryFindings, } from "./layer3-dynamic/registry-findings.js";
|
|
5
6
|
import { applyReportSummary } from "./report-summary.js";
|
|
6
7
|
import { withFindingFingerprint } from "./report/finding-fingerprint.js";
|
|
7
8
|
export async function runStaticPipeline(input) {
|
|
@@ -158,28 +159,32 @@ function parseToolClassifications(metadata, tools) {
|
|
|
158
159
|
}
|
|
159
160
|
return map;
|
|
160
161
|
}
|
|
161
|
-
function
|
|
162
|
+
function analyzeLayer3Tools(resourceId, metadata, options = {}) {
|
|
162
163
|
const toolEntries = parseToolEntries(metadata);
|
|
163
164
|
if (toolEntries.length === 0) {
|
|
164
|
-
return [];
|
|
165
|
+
return { findings: [], toolDescriptions: [], knownClassifications: {} };
|
|
165
166
|
}
|
|
166
167
|
const toolDescriptions = toolEntries.map((entry) => ({
|
|
167
168
|
name: entry.name,
|
|
168
169
|
description: entry.description,
|
|
169
170
|
}));
|
|
170
171
|
const knownClassifications = parseToolClassifications(metadata, toolEntries);
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
172
|
+
return {
|
|
173
|
+
findings: [
|
|
174
|
+
...scanToolDescriptions({
|
|
175
|
+
serverId: resourceId,
|
|
176
|
+
tools: toolDescriptions,
|
|
177
|
+
unicodeAnalysis: options.unicodeAnalysis,
|
|
178
|
+
}).map(withFindingFingerprint),
|
|
179
|
+
...detectToxicFlows({
|
|
180
|
+
scopeId: resourceId,
|
|
181
|
+
tools: toolDescriptions,
|
|
182
|
+
knownClassifications,
|
|
183
|
+
}).map(withFindingFingerprint),
|
|
184
|
+
],
|
|
185
|
+
toolDescriptions,
|
|
186
|
+
knownClassifications,
|
|
187
|
+
};
|
|
183
188
|
}
|
|
184
189
|
function layer3ErrorFinding(resourceId, status, description) {
|
|
185
190
|
const severity = status === "timeout" ? "MEDIUM" : status === "skipped_without_consent" ? "INFO" : "LOW";
|
|
@@ -204,6 +209,25 @@ function layer3ErrorFinding(resourceId, status, description) {
|
|
|
204
209
|
}
|
|
205
210
|
export function layer3OutcomesToFindings(outcomes, options = {}) {
|
|
206
211
|
const findings = [];
|
|
212
|
+
const workspaceTools = [];
|
|
213
|
+
const workspaceClassifications = {};
|
|
214
|
+
const workspaceOrigins = {};
|
|
215
|
+
const contributingServers = new Set();
|
|
216
|
+
const addWorkspaceTools = (resourceId, analysis) => {
|
|
217
|
+
for (const tool of analysis.toolDescriptions) {
|
|
218
|
+
// Keep workspace tool names unique across servers so origins stay unambiguous.
|
|
219
|
+
const uniqueName = workspaceOrigins[tool.name] === undefined ? tool.name : `${tool.name}@${resourceId}`;
|
|
220
|
+
workspaceTools.push({ name: uniqueName, description: tool.description });
|
|
221
|
+
workspaceOrigins[uniqueName] = resourceId;
|
|
222
|
+
const classifications = analysis.knownClassifications[tool.name];
|
|
223
|
+
if (classifications && classifications.length > 0) {
|
|
224
|
+
workspaceClassifications[uniqueName] = classifications;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (analysis.toolDescriptions.length > 0) {
|
|
228
|
+
contributingServers.add(resourceId);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
207
231
|
for (const outcome of outcomes) {
|
|
208
232
|
if (!outcome.approved || outcome.status === "skipped_without_consent") {
|
|
209
233
|
findings.push(layer3ErrorFinding(outcome.resourceId, "skipped_without_consent", "Deep scan skipped because consent was not granted"));
|
|
@@ -214,8 +238,10 @@ export function layer3OutcomesToFindings(outcomes, options = {}) {
|
|
|
214
238
|
continue;
|
|
215
239
|
}
|
|
216
240
|
const parsed = parseLayer3Response(outcome.resourceId, outcome.result.metadata);
|
|
217
|
-
const
|
|
218
|
-
|
|
241
|
+
const analysis = analyzeLayer3Tools(outcome.resourceId, outcome.result.metadata, options);
|
|
242
|
+
addWorkspaceTools(outcome.resourceId, analysis);
|
|
243
|
+
const registryFindings = deriveRegistryFindings(outcome.resourceId, outcome.result.metadata, options.registryHeuristics).map(withFindingFingerprint);
|
|
244
|
+
const combined = [...parsed, ...analysis.findings, ...registryFindings];
|
|
219
245
|
// If a Layer 3 resource was fetched successfully but carries no
|
|
220
246
|
// actionable metadata (no `findings[]`, no `tools[]`), that is not an
|
|
221
247
|
// issue with the scan target itself — it usually means the default
|
|
@@ -231,6 +257,17 @@ export function layer3OutcomesToFindings(outcomes, options = {}) {
|
|
|
231
257
|
}
|
|
232
258
|
findings.push(...combined);
|
|
233
259
|
}
|
|
260
|
+
// Cross-server pass: a toxic chain whose links live on different servers
|
|
261
|
+
// is invisible to the per-server analysis above.
|
|
262
|
+
if (contributingServers.size >= 2) {
|
|
263
|
+
findings.push(...detectToxicFlows({
|
|
264
|
+
scopeId: "workspace",
|
|
265
|
+
tools: workspaceTools,
|
|
266
|
+
knownClassifications: workspaceClassifications,
|
|
267
|
+
origins: workspaceOrigins,
|
|
268
|
+
crossOriginOnly: true,
|
|
269
|
+
}).map(withFindingFingerprint));
|
|
270
|
+
}
|
|
234
271
|
return findings;
|
|
235
272
|
}
|
|
236
273
|
export function mergeLayer3Findings(baseReport, layer3Findings) {
|
package/dist/report-summary.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Finding } from "./types/finding.js";
|
|
2
2
|
import type { CodeGateReport, ReportSummary } from "./types/report.js";
|
|
3
3
|
export type ReportThreshold = "critical" | "high" | "medium" | "low" | "info";
|
|
4
4
|
export declare function computeExitCode(findings: Finding[], threshold?: ReportThreshold): number;
|
package/dist/report-summary.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isUntrustedInlineSuppression } from "./types/finding.js";
|
|
1
2
|
const SEVERITY_LEVEL = {
|
|
2
3
|
CRITICAL: 4,
|
|
3
4
|
HIGH: 3,
|
|
@@ -12,8 +13,13 @@ const THRESHOLD_LEVEL = {
|
|
|
12
13
|
low: 1,
|
|
13
14
|
info: 0,
|
|
14
15
|
};
|
|
16
|
+
function countsTowardExitCode(finding) {
|
|
17
|
+
// Inline suppressions from untrusted content stay visible as suppressed
|
|
18
|
+
// but must not weaken gating.
|
|
19
|
+
return !finding.suppressed || isUntrustedInlineSuppression(finding);
|
|
20
|
+
}
|
|
15
21
|
export function computeExitCode(findings, threshold = "high") {
|
|
16
|
-
const unsuppressed = findings.filter(
|
|
22
|
+
const unsuppressed = findings.filter(countsTowardExitCode);
|
|
17
23
|
if (unsuppressed.length === 0) {
|
|
18
24
|
return 0;
|
|
19
25
|
}
|
|
@@ -32,11 +38,13 @@ export function summarizeFindings(findings, threshold = "high") {
|
|
|
32
38
|
for (const finding of findings) {
|
|
33
39
|
bySeverity[finding.severity] = (bySeverity[finding.severity] ?? 0) + 1;
|
|
34
40
|
}
|
|
41
|
+
const suppressedUntrusted = findings.filter(isUntrustedInlineSuppression).length;
|
|
35
42
|
return {
|
|
36
43
|
total: findings.length,
|
|
37
44
|
by_severity: bySeverity,
|
|
38
45
|
fixable: findings.filter((finding) => finding.fixable).length,
|
|
39
46
|
suppressed: findings.filter((finding) => finding.suppressed).length,
|
|
47
|
+
...(suppressedUntrusted > 0 ? { suppressed_untrusted: suppressedUntrusted } : {}),
|
|
40
48
|
exit_code: computeExitCode(findings, threshold),
|
|
41
49
|
};
|
|
42
50
|
}
|
package/dist/scan.d.ts
CHANGED
|
@@ -6,6 +6,18 @@ import type { DiscoveryFormat } from "./types/discovery.js";
|
|
|
6
6
|
import type { CodeGateReport } from "./types/report.js";
|
|
7
7
|
import type { CodeGateConfig, ScanCollectionKind, ScanCollectionMode } from "./config.js";
|
|
8
8
|
import type { DeepScanResource } from "./pipeline.js";
|
|
9
|
+
export declare const SKILL_BINARY_KIND: {
|
|
10
|
+
readonly Elf: "elf";
|
|
11
|
+
readonly MachO: "mach-o";
|
|
12
|
+
readonly Pe: "pe";
|
|
13
|
+
readonly Unknown: "binary";
|
|
14
|
+
};
|
|
15
|
+
export type SkillBinaryKind = (typeof SKILL_BINARY_KIND)[keyof typeof SKILL_BINARY_KIND];
|
|
16
|
+
export interface SkillBinaryArtifact {
|
|
17
|
+
reportPath: string;
|
|
18
|
+
kind: SkillBinaryKind;
|
|
19
|
+
executable: boolean;
|
|
20
|
+
}
|
|
9
21
|
export interface ScanEngineInput {
|
|
10
22
|
version: string;
|
|
11
23
|
scanTarget: string;
|
|
@@ -43,6 +55,7 @@ export interface ScanDiscoveryContext {
|
|
|
43
55
|
walked: WalkResult;
|
|
44
56
|
selected: ScanDiscoveryCandidate[];
|
|
45
57
|
parsedCandidates?: ParsedScanDiscoveryCandidate[];
|
|
58
|
+
skillBinaries?: SkillBinaryArtifact[];
|
|
46
59
|
}
|
|
47
60
|
export interface ScanDiscoveryContextOptions {
|
|
48
61
|
includeUserScope?: boolean;
|