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.
Files changed (74) hide show
  1. package/dist/cli.d.ts +3 -1
  2. package/dist/cli.js +153 -44
  3. package/dist/commands/scan-command.d.ts +2 -1
  4. package/dist/commands/scan-command.js +6 -1
  5. package/dist/commands/trust.d.ts +28 -0
  6. package/dist/commands/trust.js +69 -0
  7. package/dist/config/inline-ignore.d.ts +10 -2
  8. package/dist/config/inline-ignore.js +5 -1
  9. package/dist/config/suppression-policy.d.ts +1 -1
  10. package/dist/config/suppression-policy.js +4 -1
  11. package/dist/config/trust.d.ts +2 -0
  12. package/dist/config/trust.js +19 -0
  13. package/dist/config.d.ts +14 -0
  14. package/dist/config.js +45 -1
  15. package/dist/content/content-bundle.d.ts +27 -0
  16. package/dist/content/content-bundle.js +73 -0
  17. package/dist/content/content-store.d.ts +27 -0
  18. package/dist/content/content-store.js +0 -0
  19. package/dist/content/content-updater.d.ts +32 -0
  20. package/dist/content/content-updater.js +111 -0
  21. package/dist/content/known-bad.d.ts +26 -0
  22. package/dist/content/known-bad.js +100 -0
  23. package/dist/content/publisher-key.d.ts +10 -0
  24. package/dist/content/publisher-key.js +10 -0
  25. package/dist/layer1-discovery/knowledge-base.js +33 -1
  26. package/dist/layer2-static/data/popular-mcp-packages.d.ts +16 -0
  27. package/dist/layer2-static/data/popular-mcp-packages.js +83 -0
  28. package/dist/layer2-static/detectors/known-bad.d.ts +22 -0
  29. package/dist/layer2-static/detectors/known-bad.js +116 -0
  30. package/dist/layer2-static/detectors/mcp-package-hygiene.d.ts +28 -0
  31. package/dist/layer2-static/detectors/mcp-package-hygiene.js +191 -0
  32. package/dist/layer2-static/detectors/rule-file.js +128 -75
  33. package/dist/layer2-static/detectors/skill-frontmatter.d.ts +6 -0
  34. package/dist/layer2-static/detectors/skill-frontmatter.js +130 -0
  35. package/dist/layer2-static/engine.d.ts +2 -0
  36. package/dist/layer2-static/engine.js +0 -0
  37. package/dist/layer2-static/rule-pack-loader.js +24 -1
  38. package/dist/layer2-static/state/scan-state.d.ts +7 -2
  39. package/dist/layer2-static/state/scan-state.js +74 -30
  40. package/dist/layer2-static/text/confusables.d.ts +7 -0
  41. package/dist/layer2-static/text/confusables.js +70 -0
  42. package/dist/layer2-static/text/edit-distance.d.ts +6 -0
  43. package/dist/layer2-static/text/edit-distance.js +33 -0
  44. package/dist/layer2-static/text/encoded-payloads.d.ts +16 -0
  45. package/dist/layer2-static/text/encoded-payloads.js +116 -0
  46. package/dist/layer2-static/text/normalize.d.ts +11 -0
  47. package/dist/layer2-static/text/normalize.js +19 -0
  48. package/dist/layer2-static/text/override-phrases.d.ts +14 -0
  49. package/dist/layer2-static/text/override-phrases.js +49 -0
  50. package/dist/layer2-static/text/threat-patterns.d.ts +33 -0
  51. package/dist/layer2-static/text/threat-patterns.js +45 -0
  52. package/dist/layer2-static/text/unicode.d.ts +33 -0
  53. package/dist/layer2-static/text/unicode.js +83 -0
  54. package/dist/layer3-dynamic/deep-resource-executor.d.ts +21 -0
  55. package/dist/layer3-dynamic/deep-resource-executor.js +73 -0
  56. package/dist/layer3-dynamic/meta-agent.js +2 -1
  57. package/dist/layer3-dynamic/registry-client.d.ts +26 -0
  58. package/dist/layer3-dynamic/registry-client.js +138 -0
  59. package/dist/layer3-dynamic/registry-findings.d.ts +7 -0
  60. package/dist/layer3-dynamic/registry-findings.js +64 -0
  61. package/dist/layer3-dynamic/tool-description-scanner.js +22 -13
  62. package/dist/layer3-dynamic/toxic-flow.d.ts +4 -0
  63. package/dist/layer3-dynamic/toxic-flow.js +41 -8
  64. package/dist/pipeline.d.ts +5 -2
  65. package/dist/pipeline.js +53 -16
  66. package/dist/report-summary.d.ts +1 -1
  67. package/dist/report-summary.js +9 -1
  68. package/dist/scan.d.ts +13 -0
  69. package/dist/scan.js +291 -17
  70. package/dist/types/finding.d.ts +14 -0
  71. package/dist/types/finding.js +14 -0
  72. package/dist/types/report.d.ts +2 -0
  73. package/dist/wrapper.js +2 -19
  74. package/package.json +1 -1
@@ -1,8 +1,7 @@
1
- const HIDDEN_UNICODE = /[\u200B-\u200D\u2060\uFEFF]/u;
2
- const SENSITIVE_FILE_PATTERN = /(~\/\.ssh|~\/\.aws|id_rsa|\.env|credentials|\.git-credentials)/iu;
3
- const EXFIL_PATTERN = /(send .*https?:\/\/|upload|webhook|post to|exfiltrat)/iu;
4
- const OVERRIDE_PATTERN = /(ignore previous instructions|bypass safety|disable guardrails)/iu;
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 hasSensitive = SENSITIVE_FILE_PATTERN.test(text);
31
- const hasExfil = EXFIL_PATTERN.test(text);
32
- const hasOverride = OVERRIDE_PATTERN.test(text);
33
- const hasExec = EXEC_PATTERN.test(text);
34
- const hasUnicode = input.unicodeAnalysis === false ? false : HIDDEN_UNICODE.test(text);
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 (hasOverride) {
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 (hasUnicode) {
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.find((entry) => entry.classes.has("untrusted_input"))?.tool.name;
57
- const sensitive = classified.find((entry) => entry.classes.has("sensitive_access"))?.tool.name;
58
- const exfil = classified.find((entry) => entry.classes.has("exfiltration_sink"))?.tool.name;
59
- if (!untrusted || !sensitive || !exfil) {
60
- return [];
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 [makeFinding(input, untrusted, sensitive, exfil)];
95
+ return findings;
63
96
  }
@@ -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 declare function layer3OutcomesToFindings(outcomes: DeepScanOutcome[], options?: {
31
+ export interface Layer3FindingOptions {
31
32
  unicodeAnalysis?: boolean;
32
- }): Finding[];
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 deriveLayer3ToolFindings(resourceId, metadata, options = {}) {
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
- ...scanToolDescriptions({
173
- serverId: resourceId,
174
- tools: toolDescriptions,
175
- unicodeAnalysis: options.unicodeAnalysis,
176
- }).map(withFindingFingerprint),
177
- ...detectToxicFlows({
178
- scopeId: resourceId,
179
- tools: toolDescriptions,
180
- knownClassifications,
181
- }).map(withFindingFingerprint),
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 derived = deriveLayer3ToolFindings(outcome.resourceId, outcome.result.metadata, options);
218
- const combined = [...parsed, ...derived];
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) {
@@ -1,4 +1,4 @@
1
- import type { Finding } from "./types/finding.js";
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;
@@ -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((finding) => !finding.suppressed);
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;