@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.
Files changed (138) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +6 -7
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/analysis/McpReferenceExtractor.d.ts +59 -5
  5. package/dist/src/analysis/McpReferenceExtractor.d.ts.map +1 -1
  6. package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts +2 -0
  7. package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts.map +1 -0
  8. package/dist/src/analysis/McpReferenceExtractor.hardening.test.js +278 -0
  9. package/dist/src/analysis/McpReferenceExtractor.hardening.test.js.map +1 -0
  10. package/dist/src/analysis/McpReferenceExtractor.js +276 -20
  11. package/dist/src/analysis/McpReferenceExtractor.js.map +1 -1
  12. package/dist/src/analysis/McpReferenceExtractor.test.js +4 -0
  13. package/dist/src/analysis/McpReferenceExtractor.test.js.map +1 -1
  14. package/dist/src/config/config-atomic-write.d.ts +58 -0
  15. package/dist/src/config/config-atomic-write.d.ts.map +1 -0
  16. package/dist/src/config/config-atomic-write.js +151 -0
  17. package/dist/src/config/config-atomic-write.js.map +1 -0
  18. package/dist/src/config/config-atomic-write.test.d.ts +9 -0
  19. package/dist/src/config/config-atomic-write.test.d.ts.map +1 -0
  20. package/dist/src/config/config-atomic-write.test.js +92 -0
  21. package/dist/src/config/config-atomic-write.test.js.map +1 -0
  22. package/dist/src/config/device-identity.d.ts +16 -0
  23. package/dist/src/config/device-identity.d.ts.map +1 -1
  24. package/dist/src/config/device-identity.js +82 -14
  25. package/dist/src/config/device-identity.js.map +1 -1
  26. package/dist/src/config/device-identity.test.js +158 -1
  27. package/dist/src/config/device-identity.test.js.map +1 -1
  28. package/dist/src/config/index.d.ts +14 -0
  29. package/dist/src/config/index.d.ts.map +1 -1
  30. package/dist/src/config/index.js +31 -15
  31. package/dist/src/config/index.js.map +1 -1
  32. package/dist/src/exports/services.d.ts +2 -1
  33. package/dist/src/exports/services.d.ts.map +1 -1
  34. package/dist/src/exports/services.js +1 -0
  35. package/dist/src/exports/services.js.map +1 -1
  36. package/dist/src/index.d.ts +2 -1
  37. package/dist/src/index.d.ts.map +1 -1
  38. package/dist/src/index.js +5 -1
  39. package/dist/src/index.js.map +1 -1
  40. package/dist/src/indexer/SwarmIndexer.d.ts +1 -1
  41. package/dist/src/indexer/SwarmIndexer.js +2 -2
  42. package/dist/src/indexer/SwarmIndexer.js.map +1 -1
  43. package/dist/src/install/paths.d.ts +1 -1
  44. package/dist/src/install/paths.d.ts.map +1 -1
  45. package/dist/src/install/paths.js +2 -0
  46. package/dist/src/install/paths.js.map +1 -1
  47. package/dist/src/install/paths.test.js +10 -1
  48. package/dist/src/install/paths.test.js.map +1 -1
  49. package/dist/src/journal/reader.d.ts.map +1 -1
  50. package/dist/src/journal/reader.js +4 -1
  51. package/dist/src/journal/reader.js.map +1 -1
  52. package/dist/src/journal/types.d.ts +13 -3
  53. package/dist/src/journal/types.d.ts.map +1 -1
  54. package/dist/src/journal/types.js +8 -2
  55. package/dist/src/journal/types.js.map +1 -1
  56. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +36 -0
  57. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  58. package/dist/src/security/audit-types.d.ts +1 -1
  59. package/dist/src/security/audit-types.d.ts.map +1 -1
  60. package/dist/src/security/audit-types.js.map +1 -1
  61. package/dist/src/security/scanner/SecurityScanner.exec.d.ts +7 -0
  62. package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
  63. package/dist/src/security/scanner/SecurityScanner.exec.js +10 -74
  64. package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
  65. package/dist/src/security/scanner/SecurityScanner.formatters.d.ts +14 -1
  66. package/dist/src/security/scanner/SecurityScanner.formatters.d.ts.map +1 -1
  67. package/dist/src/security/scanner/SecurityScanner.formatters.js +33 -0
  68. package/dist/src/security/scanner/SecurityScanner.formatters.js.map +1 -1
  69. package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts +9 -0
  70. package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts.map +1 -0
  71. package/dist/src/security/scanner/SecurityScanner.formatters.test.js +65 -0
  72. package/dist/src/security/scanner/SecurityScanner.formatters.test.js.map +1 -0
  73. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
  74. package/dist/src/security/scanner/SecurityScanner.helpers.js +15 -1
  75. package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
  76. package/dist/src/security/scanner/confusables.d.ts +28 -0
  77. package/dist/src/security/scanner/confusables.d.ts.map +1 -0
  78. package/dist/src/security/scanner/confusables.js +87 -0
  79. package/dist/src/security/scanner/confusables.js.map +1 -0
  80. package/dist/src/security/scanner/confusables.test.d.ts +10 -0
  81. package/dist/src/security/scanner/confusables.test.d.ts.map +1 -0
  82. package/dist/src/security/scanner/confusables.test.js +44 -0
  83. package/dist/src/security/scanner/confusables.test.js.map +1 -0
  84. package/dist/src/security/scanner/index.d.ts +7 -1
  85. package/dist/src/security/scanner/index.d.ts.map +1 -1
  86. package/dist/src/security/scanner/index.js +13 -0
  87. package/dist/src/security/scanner/index.js.map +1 -1
  88. package/dist/src/security/scanner/types.d.ts +14 -1
  89. package/dist/src/security/scanner/types.d.ts.map +1 -1
  90. package/dist/src/security/scanner/typosquat-reference-list.d.ts +54 -0
  91. package/dist/src/security/scanner/typosquat-reference-list.d.ts.map +1 -0
  92. package/dist/src/security/scanner/typosquat-reference-list.js +55 -0
  93. package/dist/src/security/scanner/typosquat-reference-list.js.map +1 -0
  94. package/dist/src/security/scanner/typosquat-reference-list.test.d.ts +5 -0
  95. package/dist/src/security/scanner/typosquat-reference-list.test.d.ts.map +1 -0
  96. package/dist/src/security/scanner/typosquat-reference-list.test.js +52 -0
  97. package/dist/src/security/scanner/typosquat-reference-list.test.js.map +1 -0
  98. package/dist/src/security/scanner/typosquat.d.ts +100 -0
  99. package/dist/src/security/scanner/typosquat.d.ts.map +1 -0
  100. package/dist/src/security/scanner/typosquat.js +256 -0
  101. package/dist/src/security/scanner/typosquat.js.map +1 -0
  102. package/dist/src/security/scanner/typosquat.test.d.ts +12 -0
  103. package/dist/src/security/scanner/typosquat.test.d.ts.map +1 -0
  104. package/dist/src/security/scanner/typosquat.test.js +140 -0
  105. package/dist/src/security/scanner/typosquat.test.js.map +1 -0
  106. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  107. package/dist/src/security/scanner/weights.js +10 -0
  108. package/dist/src/security/scanner/weights.js.map +1 -1
  109. package/dist/src/services/skill-installation.helpers.d.ts +21 -0
  110. package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
  111. package/dist/src/services/skill-installation.helpers.js +46 -2
  112. package/dist/src/services/skill-installation.helpers.js.map +1 -1
  113. package/dist/src/services/skill-installation.helpers.test.d.ts +2 -0
  114. package/dist/src/services/skill-installation.helpers.test.d.ts.map +1 -0
  115. package/dist/src/services/skill-installation.helpers.test.js +80 -0
  116. package/dist/src/services/skill-installation.helpers.test.js.map +1 -0
  117. package/dist/src/sync/inventory-collector.d.ts +15 -4
  118. package/dist/src/sync/inventory-collector.d.ts.map +1 -1
  119. package/dist/src/sync/inventory-collector.js +73 -24
  120. package/dist/src/sync/inventory-collector.js.map +1 -1
  121. package/dist/src/sync/inventory-collector.test.d.ts +15 -1
  122. package/dist/src/sync/inventory-collector.test.d.ts.map +1 -1
  123. package/dist/src/sync/inventory-collector.test.js +75 -5
  124. package/dist/src/sync/inventory-collector.test.js.map +1 -1
  125. package/dist/src/telemetry/tracer-imports.d.ts +1 -1
  126. package/dist/src/telemetry/tracer-imports.js +1 -1
  127. package/dist/src/telemetry/tracer.js +1 -1
  128. package/dist/tests/SecurityScanner.scoring.test.js +60 -0
  129. package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
  130. package/dist/tests/SwarmIndexer.test.js +4 -5
  131. package/dist/tests/SwarmIndexer.test.js.map +1 -1
  132. package/dist/tests/security/scanner-regression-guard.test.js +1 -0
  133. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  134. package/dist/tests/services/aidefence-feedback.test.js +1 -0
  135. package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
  136. package/dist/tests/skill-scanner/allowlist.test.js +8 -1
  137. package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
  138. 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 `mcp__<server>__<tool>` patterns,
7
- * tracking whether each reference appears inside a fenced code block.
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
- /** MCP tool name, e.g. "save_issue" */
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;;;;;;;GAOG;AAEH,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,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;CACpB;AAeD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAyEzE"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=McpReferenceExtractor.hardening.test.d.ts.map
@@ -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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"}