@skillsmith/core 0.11.1 → 0.11.3
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 +20 -0
- package/README.md +6 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/McpReferenceExtractor.d.ts +59 -5
- package/dist/src/analysis/McpReferenceExtractor.d.ts.map +1 -1
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts +2 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts.map +1 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.js +278 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.js.map +1 -0
- package/dist/src/analysis/McpReferenceExtractor.js +276 -20
- package/dist/src/analysis/McpReferenceExtractor.js.map +1 -1
- package/dist/src/analysis/McpReferenceExtractor.test.js +4 -0
- package/dist/src/analysis/McpReferenceExtractor.test.js.map +1 -1
- package/dist/src/config/config-atomic-write.d.ts +58 -0
- package/dist/src/config/config-atomic-write.d.ts.map +1 -0
- package/dist/src/config/config-atomic-write.js +151 -0
- package/dist/src/config/config-atomic-write.js.map +1 -0
- package/dist/src/config/config-atomic-write.test.d.ts +9 -0
- package/dist/src/config/config-atomic-write.test.d.ts.map +1 -0
- package/dist/src/config/config-atomic-write.test.js +92 -0
- package/dist/src/config/config-atomic-write.test.js.map +1 -0
- package/dist/src/config/device-identity.d.ts +16 -0
- package/dist/src/config/device-identity.d.ts.map +1 -1
- package/dist/src/config/device-identity.js +82 -14
- package/dist/src/config/device-identity.js.map +1 -1
- package/dist/src/config/device-identity.test.js +158 -1
- package/dist/src/config/device-identity.test.js.map +1 -1
- package/dist/src/config/index.d.ts +14 -0
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +31 -15
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/exports/services.d.ts +2 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +1 -0
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/SwarmIndexer.d.ts +1 -1
- package/dist/src/indexer/SwarmIndexer.js +2 -2
- package/dist/src/indexer/SwarmIndexer.js.map +1 -1
- package/dist/src/install/paths.d.ts +1 -1
- package/dist/src/install/paths.d.ts.map +1 -1
- package/dist/src/install/paths.js +2 -0
- package/dist/src/install/paths.js.map +1 -1
- package/dist/src/install/paths.test.js +10 -1
- package/dist/src/install/paths.test.js.map +1 -1
- package/dist/src/journal/reader.d.ts.map +1 -1
- package/dist/src/journal/reader.js +4 -1
- package/dist/src/journal/reader.js.map +1 -1
- package/dist/src/journal/types.d.ts +13 -3
- package/dist/src/journal/types.d.ts.map +1 -1
- package/dist/src/journal/types.js +8 -2
- package/dist/src/journal/types.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +36 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/security/audit-types.d.ts +1 -1
- package/dist/src/security/audit-types.d.ts.map +1 -1
- package/dist/src/security/audit-types.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts +7 -0
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.js +10 -74
- package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.d.ts +14 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.js +33 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts +9 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.js +65 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js +15 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
- package/dist/src/security/scanner/confusables.d.ts +28 -0
- package/dist/src/security/scanner/confusables.d.ts.map +1 -0
- package/dist/src/security/scanner/confusables.js +87 -0
- package/dist/src/security/scanner/confusables.js.map +1 -0
- package/dist/src/security/scanner/confusables.test.d.ts +10 -0
- package/dist/src/security/scanner/confusables.test.d.ts.map +1 -0
- package/dist/src/security/scanner/confusables.test.js +44 -0
- package/dist/src/security/scanner/confusables.test.js.map +1 -0
- package/dist/src/security/scanner/index.d.ts +7 -1
- package/dist/src/security/scanner/index.d.ts.map +1 -1
- package/dist/src/security/scanner/index.js +13 -0
- package/dist/src/security/scanner/index.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +14 -1
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/security/scanner/typosquat-reference-list.d.ts +54 -0
- package/dist/src/security/scanner/typosquat-reference-list.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.js +55 -0
- package/dist/src/security/scanner/typosquat-reference-list.js.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.d.ts +5 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.js +52 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.js.map +1 -0
- package/dist/src/security/scanner/typosquat.d.ts +100 -0
- package/dist/src/security/scanner/typosquat.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat.js +256 -0
- package/dist/src/security/scanner/typosquat.js.map +1 -0
- package/dist/src/security/scanner/typosquat.test.d.ts +12 -0
- package/dist/src/security/scanner/typosquat.test.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat.test.js +140 -0
- package/dist/src/security/scanner/typosquat.test.js.map +1 -0
- package/dist/src/security/scanner/weights.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.js +10 -0
- package/dist/src/security/scanner/weights.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.d.ts +21 -0
- package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.js +46 -2
- package/dist/src/services/skill-installation.helpers.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.test.d.ts +2 -0
- package/dist/src/services/skill-installation.helpers.test.d.ts.map +1 -0
- package/dist/src/services/skill-installation.helpers.test.js +80 -0
- package/dist/src/services/skill-installation.helpers.test.js.map +1 -0
- package/dist/src/sync/inventory-collector.d.ts +15 -4
- package/dist/src/sync/inventory-collector.d.ts.map +1 -1
- package/dist/src/sync/inventory-collector.js +73 -24
- package/dist/src/sync/inventory-collector.js.map +1 -1
- package/dist/src/sync/inventory-collector.test.d.ts +15 -1
- package/dist/src/sync/inventory-collector.test.d.ts.map +1 -1
- package/dist/src/sync/inventory-collector.test.js +75 -5
- package/dist/src/sync/inventory-collector.test.js.map +1 -1
- package/dist/src/telemetry/tracer-imports.d.ts +1 -1
- package/dist/src/telemetry/tracer-imports.js +1 -1
- package/dist/src/telemetry/tracer.js +1 -1
- package/dist/tests/SecurityScanner.scoring.test.js +60 -0
- package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
- package/dist/tests/SwarmIndexer.test.js +4 -5
- package/dist/tests/SwarmIndexer.test.js.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.js +1 -0
- package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
- package/dist/tests/services/aidefence-feedback.test.js +1 -0
- package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
- package/dist/tests/skill-scanner/allowlist.test.js +8 -1
- package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
- package/package.json +13 -8
|
@@ -2,21 +2,56 @@
|
|
|
2
2
|
* @fileoverview Extracts MCP tool references from SKILL.md content
|
|
3
3
|
* @module @skillsmith/core/analysis/McpReferenceExtractor
|
|
4
4
|
* @see SMI-3145: Build McpReferenceExtractor
|
|
5
|
+
* @see SMI-5676: Wave 1 Step 3b — harden extraction (26-file validation found a
|
|
6
|
+
* 71% false-negative rate against real SKILL.md files)
|
|
5
7
|
*
|
|
6
|
-
* Scans skill content for
|
|
7
|
-
*
|
|
8
|
+
* Scans skill content for three kinds of MCP dependency signal:
|
|
9
|
+
* 1. Inline `mcp__<server>__<tool>` references in prose/code (original
|
|
10
|
+
* SMI-3145 behavior), tracking fenced-code-block state for confidence.
|
|
11
|
+
* 2. Frontmatter `allowed-tools`/`tools` YAML fields, including the
|
|
12
|
+
* bare-server (`mcp__linear`) and wildcard (`mcp__linear__*`) forms.
|
|
13
|
+
* 3. Embedded `mcpServers` JSON registration blocks (e.g. a fenced code
|
|
14
|
+
* block showing how to add the skill's server to `.mcp.json`).
|
|
15
|
+
*
|
|
16
|
+
* All three feed the same `references`/`servers`/`highConfidenceServers`
|
|
17
|
+
* output — hardening changes what gets *extracted*, not how extracted
|
|
18
|
+
* results get scored (see `DependencyMerger.ts`, unchanged).
|
|
19
|
+
*
|
|
20
|
+
* A fourth signal, `registeredServers`, lets a caller cross-check every
|
|
21
|
+
* candidate server name against the consuming project's own `.mcp.json`.
|
|
22
|
+
* This is advisory tagging only (`serverResolutions`) — a name is NEVER
|
|
23
|
+
* excluded from `references`/`servers` just because it doesn't resolve,
|
|
24
|
+
* since a real (but not-yet-installed) dependency looks identical to a
|
|
25
|
+
* stale/renamed one from here. See `serverResolutions` doc below.
|
|
8
26
|
*/
|
|
9
27
|
/** A single MCP tool reference found in content */
|
|
10
28
|
export interface McpReference {
|
|
11
29
|
/** MCP server name, e.g. "linear" */
|
|
12
30
|
server: string;
|
|
13
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* MCP tool name, e.g. "save_issue". `'*'` marks a server-level reference
|
|
33
|
+
* with no specific tool named — the bare-server frontmatter form
|
|
34
|
+
* (`mcp__linear`), the wildcard frontmatter form (`mcp__linear__*`), or an
|
|
35
|
+
* `mcpServers` JSON registration entry (which names a server, not a tool).
|
|
36
|
+
*/
|
|
14
37
|
tool: string;
|
|
15
38
|
/** 1-indexed line number where the reference appears */
|
|
16
39
|
line: number;
|
|
17
40
|
/** true if the reference is inside a fenced code block */
|
|
18
41
|
inCodeBlock: boolean;
|
|
19
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolution of a candidate server name against the consuming project's
|
|
45
|
+
* `.mcp.json` (SMI-5676's `registeredServers` cross-check).
|
|
46
|
+
* - `registered`: found in the `registeredServers` list passed in
|
|
47
|
+
* - `unregistered`: `registeredServers` was provided but didn't contain this
|
|
48
|
+
* name (e.g. the `claude-flow` -> `ruflo` rename: bundled skill docs still
|
|
49
|
+
* say `mcp__claude-flow__*`, but this project's `.mcp.json` only registers
|
|
50
|
+
* `ruflo`)
|
|
51
|
+
* - `unknown`: no `registeredServers` list was available to cross-check
|
|
52
|
+
* (caller passed nothing — e.g. `.mcp.json` missing/unparseable; fail open)
|
|
53
|
+
*/
|
|
54
|
+
export type McpServerResolution = 'registered' | 'unregistered' | 'unknown';
|
|
20
55
|
/** Aggregated extraction result */
|
|
21
56
|
export interface McpExtractionResult {
|
|
22
57
|
/** All individual references found */
|
|
@@ -27,16 +62,35 @@ export interface McpExtractionResult {
|
|
|
27
62
|
highConfidenceServers: string[];
|
|
28
63
|
/** true if input exceeded the 100KB cap and was truncated */
|
|
29
64
|
truncated?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Resolution state for every name in `servers`, keyed by server name. See
|
|
67
|
+
* {@link McpServerResolution}. Optional only for backward compatibility
|
|
68
|
+
* with hand-constructed `McpExtractionResult` fixtures predating SMI-5676
|
|
69
|
+
* (e.g. `DependencyMerger.test.ts`'s helpers) — {@link extractMcpReferences}
|
|
70
|
+
* itself always populates this (with `'unknown'` entries when
|
|
71
|
+
* `registeredServers` wasn't passed), so a real caller can always rely on
|
|
72
|
+
* it being present.
|
|
73
|
+
*/
|
|
74
|
+
serverResolutions?: Record<string, McpServerResolution>;
|
|
30
75
|
}
|
|
31
76
|
/**
|
|
32
77
|
* Extract all MCP tool references from skill content.
|
|
33
78
|
*
|
|
34
79
|
* Scans each line for `mcp__server__tool` patterns, tracking fenced
|
|
35
80
|
* code block state to distinguish high-confidence (prose) references
|
|
36
|
-
* from low-confidence (code example) references.
|
|
81
|
+
* from low-confidence (code example) references. Also parses frontmatter
|
|
82
|
+
* `allowed-tools`/`tools` fields and embedded `mcpServers` JSON blocks
|
|
83
|
+
* (SMI-5676).
|
|
37
84
|
*
|
|
38
85
|
* @param content - Raw SKILL.md content (markdown)
|
|
86
|
+
* @param registeredServers - Optional list of MCP server names actually
|
|
87
|
+
* registered in the consuming project's `.mcp.json`. When provided
|
|
88
|
+
* (even as `[]`), every candidate server name is tagged `registered` or
|
|
89
|
+
* `unregistered` in the result's `serverResolutions` map — never filtered
|
|
90
|
+
* out. Omit (or pass `undefined`) when the caller couldn't determine this
|
|
91
|
+
* (e.g. `.mcp.json` missing/unparseable); every name is then tagged
|
|
92
|
+
* `unknown` rather than assumed unregistered.
|
|
39
93
|
* @returns Extraction result with references, servers, and confidence info
|
|
40
94
|
*/
|
|
41
|
-
export declare function extractMcpReferences(content: string): McpExtractionResult;
|
|
95
|
+
export declare function extractMcpReferences(content: string, registeredServers?: string[]): McpExtractionResult;
|
|
42
96
|
//# sourceMappingURL=McpReferenceExtractor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpReferenceExtractor.d.ts","sourceRoot":"","sources":["../../../src/analysis/McpReferenceExtractor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"McpReferenceExtractor.d.ts","sourceRoot":"","sources":["../../../src/analysis/McpReferenceExtractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,WAAW,EAAE,OAAO,CAAA;CACrB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,cAAc,GAAG,SAAS,CAAA;AAE3E,mCAAmC;AACnC,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,UAAU,EAAE,YAAY,EAAE,CAAA;IAC1B,gDAAgD;IAChD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,4DAA4D;IAC5D,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;CACxD;AA0CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAC3B,mBAAmB,CA+HrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpReferenceExtractor.hardening.test.d.ts","sourceRoot":"","sources":["../../../src/analysis/McpReferenceExtractor.hardening.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the SMI-5676 extraction-hardening additions to
|
|
3
|
+
* McpReferenceExtractor.ts (frontmatter parsing, mcpServers JSON-block
|
|
4
|
+
* detection, .mcp.json cross-check tagging). Split from
|
|
5
|
+
* McpReferenceExtractor.test.ts to stay under the 500-line file gate.
|
|
6
|
+
* @module @skillsmith/core/analysis/McpReferenceExtractor.hardening.test
|
|
7
|
+
* @see SMI-5676: Wave 1 Step 3b — harden extractMcpReferences
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { extractMcpReferences } from './McpReferenceExtractor.js';
|
|
11
|
+
describe('McpReferenceExtractor — SMI-5676 hardening', () => {
|
|
12
|
+
describe('extractMcpReferences', () => {
|
|
13
|
+
describe('frontmatter allowed-tools/tools parsing (SMI-5676)', () => {
|
|
14
|
+
it('should detect the bare-server form in a YAML block list (real ~/.claude/skills/linear/SKILL.md shape)', () => {
|
|
15
|
+
const content = [
|
|
16
|
+
'---',
|
|
17
|
+
'name: Linear',
|
|
18
|
+
'description: Managing Linear issues, projects, and teams.',
|
|
19
|
+
'version: 3.2.0',
|
|
20
|
+
'author: Ryan Smith <ryan@smithhorn.ca>',
|
|
21
|
+
'tags:',
|
|
22
|
+
' - linear',
|
|
23
|
+
' - issue-tracking',
|
|
24
|
+
' - mcp',
|
|
25
|
+
'allowed-tools:',
|
|
26
|
+
' - mcp__linear',
|
|
27
|
+
' - WebFetch(domain:linear.app)',
|
|
28
|
+
' - Bash',
|
|
29
|
+
'---',
|
|
30
|
+
'',
|
|
31
|
+
'# Linear',
|
|
32
|
+
'',
|
|
33
|
+
'Tools and workflows for managing issues, projects, and teams in Linear.',
|
|
34
|
+
].join('\n');
|
|
35
|
+
const result = extractMcpReferences(content);
|
|
36
|
+
expect(result.servers).toContain('linear');
|
|
37
|
+
expect(result.highConfidenceServers).toContain('linear');
|
|
38
|
+
const frontmatterRef = result.references.find((r) => r.server === 'linear' && r.tool === '*');
|
|
39
|
+
expect(frontmatterRef).toBeDefined();
|
|
40
|
+
expect(frontmatterRef?.inCodeBlock).toBe(false);
|
|
41
|
+
expect(frontmatterRef?.line).toBe(11); // the "- mcp__linear" line
|
|
42
|
+
});
|
|
43
|
+
it('should detect the wildcard form (mcp__server__*) and only from the allowed-tools/tools fields', () => {
|
|
44
|
+
const content = [
|
|
45
|
+
'---',
|
|
46
|
+
'name: SPARC',
|
|
47
|
+
'tools_required:', // wrong field name (real sparc-methodology.md shape) — must NOT be picked up
|
|
48
|
+
' - mcp__wrongfield__*',
|
|
49
|
+
'tools:',
|
|
50
|
+
' - mcp__claude-flow__*',
|
|
51
|
+
' - Bash',
|
|
52
|
+
'---',
|
|
53
|
+
'',
|
|
54
|
+
'# SPARC',
|
|
55
|
+
].join('\n');
|
|
56
|
+
const result = extractMcpReferences(content);
|
|
57
|
+
expect(result.servers).toContain('claude-flow');
|
|
58
|
+
expect(result.servers).not.toContain('wrongfield');
|
|
59
|
+
const ref = result.references.find((r) => r.server === 'claude-flow');
|
|
60
|
+
expect(ref?.tool).toBe('*');
|
|
61
|
+
});
|
|
62
|
+
it('should detect the full mcp__server__tool form inside a frontmatter list exactly once (not double-counted by the base inline scan)', () => {
|
|
63
|
+
const content = [
|
|
64
|
+
'---',
|
|
65
|
+
'name: X',
|
|
66
|
+
'allowed-tools:',
|
|
67
|
+
' - mcp__linear__save_issue',
|
|
68
|
+
'---',
|
|
69
|
+
].join('\n');
|
|
70
|
+
const result = extractMcpReferences(content);
|
|
71
|
+
// Regression guard (SMI-5676 review finding): the full mcp__server__tool
|
|
72
|
+
// form matches the base inline MCP_PATTERN regex too, so without
|
|
73
|
+
// excluding the frontmatter block from that base scan this entry was
|
|
74
|
+
// counted twice — once by the base scan, once by the frontmatter
|
|
75
|
+
// parser. Use .filter() + toHaveLength, not .find(), so a duplicate
|
|
76
|
+
// can't hide behind "first match wins".
|
|
77
|
+
const linearRefs = result.references.filter((r) => r.server === 'linear');
|
|
78
|
+
expect(linearRefs).toHaveLength(1);
|
|
79
|
+
expect(linearRefs[0].tool).toBe('save_issue');
|
|
80
|
+
expect(linearRefs[0].inCodeBlock).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
it('should support the YAML flow-list form (allowed-tools: [mcp__linear, Bash])', () => {
|
|
83
|
+
const content = ['---', 'name: X', 'allowed-tools: [mcp__linear, Bash]', '---'].join('\n');
|
|
84
|
+
const result = extractMcpReferences(content);
|
|
85
|
+
expect(result.servers).toContain('linear');
|
|
86
|
+
});
|
|
87
|
+
it('should support quoted flow-list entries', () => {
|
|
88
|
+
const content = [
|
|
89
|
+
'---',
|
|
90
|
+
'name: X',
|
|
91
|
+
'tools: ["mcp__stripe__create_customer", "Bash"]',
|
|
92
|
+
'---',
|
|
93
|
+
].join('\n');
|
|
94
|
+
const result = extractMcpReferences(content);
|
|
95
|
+
const ref = result.references.find((r) => r.server === 'stripe');
|
|
96
|
+
expect(ref?.tool).toBe('create_customer');
|
|
97
|
+
});
|
|
98
|
+
it('should support a single bare scalar value (no brackets, no dash)', () => {
|
|
99
|
+
const content = ['---', 'name: X', 'allowed-tools: mcp__linear', '---'].join('\n');
|
|
100
|
+
const result = extractMcpReferences(content);
|
|
101
|
+
expect(result.servers).toContain('linear');
|
|
102
|
+
});
|
|
103
|
+
it('should ignore non-mcp__ tool names in the list', () => {
|
|
104
|
+
const content = [
|
|
105
|
+
'---',
|
|
106
|
+
'allowed-tools:',
|
|
107
|
+
' - Bash',
|
|
108
|
+
' - WebFetch(domain:example.com)',
|
|
109
|
+
'---',
|
|
110
|
+
].join('\n');
|
|
111
|
+
const result = extractMcpReferences(content);
|
|
112
|
+
expect(result.servers).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
it('should fail open (skip frontmatter refs, keep scanning body) on malformed YAML frontmatter', () => {
|
|
115
|
+
const content = [
|
|
116
|
+
'---',
|
|
117
|
+
'allowed-tools: [unterminated',
|
|
118
|
+
' bash: {',
|
|
119
|
+
'---',
|
|
120
|
+
'mcp__linear__save_issue in body still works',
|
|
121
|
+
].join('\n');
|
|
122
|
+
const result = extractMcpReferences(content);
|
|
123
|
+
// Frontmatter YAML fails to parse, but the base body regex scan is
|
|
124
|
+
// entirely independent and still finds the prose reference.
|
|
125
|
+
expect(result.servers).toContain('linear');
|
|
126
|
+
});
|
|
127
|
+
it('should return no frontmatter refs when there is no frontmatter block', () => {
|
|
128
|
+
const content = '# No frontmatter\n\nJust prose.';
|
|
129
|
+
const result = extractMcpReferences(content);
|
|
130
|
+
expect(result.references).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('mcpServers JSON registration block detection (SMI-5676)', () => {
|
|
134
|
+
it('should detect a server name from an mcpServers JSON block (real ~/.claude/skills/stripe-mcp/SKILL.md Option 1 shape)', () => {
|
|
135
|
+
const content = [
|
|
136
|
+
'### Option 1: Remote HTTP (Recommended)',
|
|
137
|
+
'',
|
|
138
|
+
'Add to `~/.claude/settings.json`:',
|
|
139
|
+
'',
|
|
140
|
+
'```json',
|
|
141
|
+
'{',
|
|
142
|
+
' "mcpServers": {',
|
|
143
|
+
' "stripe": {',
|
|
144
|
+
' "type": "http",',
|
|
145
|
+
' "url": "https://mcp.stripe.com/v1/sse"',
|
|
146
|
+
' }',
|
|
147
|
+
' }',
|
|
148
|
+
'}',
|
|
149
|
+
'```',
|
|
150
|
+
].join('\n');
|
|
151
|
+
const result = extractMcpReferences(content);
|
|
152
|
+
expect(result.servers).toContain('stripe');
|
|
153
|
+
const ref = result.references.find((r) => r.server === 'stripe');
|
|
154
|
+
expect(ref?.tool).toBe('*');
|
|
155
|
+
expect(ref?.inCodeBlock).toBe(true); // it's inside a fenced ```json block
|
|
156
|
+
});
|
|
157
|
+
it('should detect a server name from the Option 3 project .mcp.json shape (nested env object)', () => {
|
|
158
|
+
const content = [
|
|
159
|
+
'### Option 3: Project .mcp.json',
|
|
160
|
+
'',
|
|
161
|
+
'Create `.mcp.json` in your project root:',
|
|
162
|
+
'',
|
|
163
|
+
'```json',
|
|
164
|
+
'{',
|
|
165
|
+
' "mcpServers": {',
|
|
166
|
+
' "stripe": {',
|
|
167
|
+
' "command": "npx",',
|
|
168
|
+
' "args": ["-y", "@stripe/mcp"],',
|
|
169
|
+
' "env": {',
|
|
170
|
+
' "STRIPE_SECRET_KEY": "${STRIPE_SECRET_KEY}"',
|
|
171
|
+
' }',
|
|
172
|
+
' }',
|
|
173
|
+
' }',
|
|
174
|
+
'}',
|
|
175
|
+
'```',
|
|
176
|
+
].join('\n');
|
|
177
|
+
const result = extractMcpReferences(content);
|
|
178
|
+
expect(result.servers).toEqual(['stripe']);
|
|
179
|
+
// Nested keys inside the server's config object must NOT be picked
|
|
180
|
+
// up as server names.
|
|
181
|
+
expect(result.servers).not.toContain('env');
|
|
182
|
+
expect(result.servers).not.toContain('STRIPE_SECRET_KEY');
|
|
183
|
+
});
|
|
184
|
+
it('should detect multiple servers registered in one mcpServers block', () => {
|
|
185
|
+
const content = [
|
|
186
|
+
'```json',
|
|
187
|
+
'{ "mcpServers": { "stripe": { "url": "x" }, "linear": { "url": "y" } } }',
|
|
188
|
+
'```',
|
|
189
|
+
].join('\n');
|
|
190
|
+
const result = extractMcpReferences(content);
|
|
191
|
+
expect(result.servers).toEqual(['linear', 'stripe']);
|
|
192
|
+
});
|
|
193
|
+
it('should not throw and should skip invalid JSON after an mcpServers marker', () => {
|
|
194
|
+
const content = '"mcpServers": { not valid json here';
|
|
195
|
+
const result = extractMcpReferences(content);
|
|
196
|
+
expect(result.references).toEqual([]);
|
|
197
|
+
});
|
|
198
|
+
it('should not hang on adversarial input with many unclosed mcpServers markers (SMI-5676 review finding, DoS guard)', () => {
|
|
199
|
+
// Each occurrence is unclosed (no matching "}" anywhere in the
|
|
200
|
+
// document), so without a cap on markers processed, every one of
|
|
201
|
+
// 1000+ occurrences would trigger its own O(remaining-document-length)
|
|
202
|
+
// brace scan — O(n^2) worst case. Stays under the 100KB input cap so
|
|
203
|
+
// this exercises the NEW marker-count cap, not input truncation.
|
|
204
|
+
const content = '"mcpServers": { '.repeat(3000); // ~51KB, well under 100KB
|
|
205
|
+
expect(content.length).toBeLessThan(100 * 1024);
|
|
206
|
+
const start = Date.now();
|
|
207
|
+
const result = extractMcpReferences(content);
|
|
208
|
+
const elapsedMs = Date.now() - start;
|
|
209
|
+
expect(result.references).toBeDefined();
|
|
210
|
+
expect(elapsedMs).toBeLessThan(2000);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe('.mcp.json cross-check tagging (SMI-5676) — keep-and-tag, never exclude', () => {
|
|
214
|
+
it('should tag every server "unknown" when registeredServers is not passed', () => {
|
|
215
|
+
const content = 'Use mcp__linear__save_issue and mcp__claude-flow__agent_spawn.';
|
|
216
|
+
const result = extractMcpReferences(content);
|
|
217
|
+
expect(result.servers).toEqual(['claude-flow', 'linear']);
|
|
218
|
+
expect(result.serverResolutions).toEqual({
|
|
219
|
+
'claude-flow': 'unknown',
|
|
220
|
+
linear: 'unknown',
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
it('should tag registered vs unregistered without excluding either from servers/references', () => {
|
|
224
|
+
const content = 'Use mcp__linear__save_issue and mcp__claude-flow__agent_spawn.';
|
|
225
|
+
const result = extractMcpReferences(content, ['linear', 'ruflo', 'skillsmith']);
|
|
226
|
+
// Neither candidate is dropped — both remain in servers/references.
|
|
227
|
+
expect(result.servers).toEqual(['claude-flow', 'linear']);
|
|
228
|
+
expect(result.references).toHaveLength(2);
|
|
229
|
+
expect(result.serverResolutions?.linear).toBe('registered');
|
|
230
|
+
expect(result.serverResolutions?.['claude-flow']).toBe('unregistered');
|
|
231
|
+
});
|
|
232
|
+
it('should tag every candidate "unregistered" when registeredServers is an empty array (explicitly checked, found nothing)', () => {
|
|
233
|
+
const content = 'Use mcp__linear__save_issue.';
|
|
234
|
+
const result = extractMcpReferences(content, []);
|
|
235
|
+
expect(result.servers).toEqual(['linear']);
|
|
236
|
+
expect(result.serverResolutions?.linear).toBe('unregistered');
|
|
237
|
+
});
|
|
238
|
+
it('should reproduce the claude-flow -> ruflo stale-rename class: unregistered, but never excluded', () => {
|
|
239
|
+
// Representative excerpt of the real shape found in
|
|
240
|
+
// .claude/skills/{swarm-advanced,hive-mind-advanced,sparc-methodology,
|
|
241
|
+
// hooks-automation,github-release-management,github-project-management,
|
|
242
|
+
// github-multi-repo,performance-analysis}/SKILL.md: this project's own
|
|
243
|
+
// .mcp.json registers "ruflo", not "claude-flow" (SMI-4881 rename).
|
|
244
|
+
const content = [
|
|
245
|
+
'```bash',
|
|
246
|
+
'mcp__claude-flow__swarm_init({ topology: "mesh", maxAgents: 6 })',
|
|
247
|
+
'mcp__claude-flow__agent_spawn({ type: "researcher" })',
|
|
248
|
+
'```',
|
|
249
|
+
].join('\n');
|
|
250
|
+
const registeredServers = ['skillsmith', 'skillsmith-doc-retrieval', 'ruflo'];
|
|
251
|
+
const result = extractMcpReferences(content, registeredServers);
|
|
252
|
+
expect(result.servers).toEqual(['claude-flow']);
|
|
253
|
+
expect(result.serverResolutions?.['claude-flow']).toBe('unregistered');
|
|
254
|
+
// Still present, still counted — DependencyMerger (unchanged) decides
|
|
255
|
+
// advisory-vs-promoted; this extractor only tags, never drops.
|
|
256
|
+
expect(result.references).toHaveLength(2);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
describe('bare tool-name prose without any mcp__ prefix — documented out of scope (SMI-5676)', () => {
|
|
260
|
+
it('does not detect a known ruflo/claude-flow tool name with zero mcp__ prefix (real .claude/skills/hive-mind-execution/SKILL.md shape)', () => {
|
|
261
|
+
// Real "Quick Start" shape from hive-mind-execution/SKILL.md: natural
|
|
262
|
+
// prose invoking a known tool name with no mcp__ prefix at all.
|
|
263
|
+
// Catching this would require matching arbitrary prose against a
|
|
264
|
+
// known-tool-name list (semantic/NLP-ish matching) — out of scope for
|
|
265
|
+
// this hardening pass, whose three acceptance criteria are all
|
|
266
|
+
// mcp__-token-based or JSON-key-based.
|
|
267
|
+
const content = [
|
|
268
|
+
'```',
|
|
269
|
+
'2. Initialize hive mind: swarm_init({ topology: "hierarchical" })',
|
|
270
|
+
'```',
|
|
271
|
+
].join('\n');
|
|
272
|
+
const result = extractMcpReferences(content);
|
|
273
|
+
expect(result.servers).toEqual([]);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
//# sourceMappingURL=McpReferenceExtractor.hardening.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpReferenceExtractor.hardening.test.js","sourceRoot":"","sources":["../../../src/analysis/McpReferenceExtractor.hardening.test.ts"],"names":[],"mappings":"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"}
|