@skillsmith/core 0.5.2 → 0.5.4

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 (175) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/activation/ActivationManager.d.ts +7 -0
  4. package/dist/src/activation/ActivationManager.d.ts.map +1 -1
  5. package/dist/src/activation/ActivationManager.js +13 -4
  6. package/dist/src/activation/ActivationManager.js.map +1 -1
  7. package/dist/src/analysis/adapters/python.d.ts +16 -11
  8. package/dist/src/analysis/adapters/python.d.ts.map +1 -1
  9. package/dist/src/analysis/adapters/python.js +46 -61
  10. package/dist/src/analysis/adapters/python.js.map +1 -1
  11. package/dist/src/analysis/router.test.d.ts +2 -0
  12. package/dist/src/analysis/router.test.d.ts.map +1 -0
  13. package/dist/src/analysis/router.test.js +411 -0
  14. package/dist/src/analysis/router.test.js.map +1 -0
  15. package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -1
  16. package/dist/src/analysis/tree-sitter/manager.js +12 -5
  17. package/dist/src/analysis/tree-sitter/manager.js.map +1 -1
  18. package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts +45 -0
  19. package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts.map +1 -0
  20. package/dist/src/analysis/tree-sitter/pythonExtractor.js +264 -0
  21. package/dist/src/analysis/tree-sitter/pythonExtractor.js.map +1 -0
  22. package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts +12 -0
  23. package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts.map +1 -0
  24. package/dist/src/analysis/tree-sitter/pythonExtractor.test.js +74 -0
  25. package/dist/src/analysis/tree-sitter/pythonExtractor.test.js.map +1 -0
  26. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +93 -0
  27. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -0
  28. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts +22 -0
  29. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts.map +1 -0
  30. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +229 -0
  31. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -0
  32. package/dist/src/analysis/tree-sitter/pythonIncremental.js +287 -0
  33. package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -0
  34. package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts +17 -0
  35. package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts.map +1 -0
  36. package/dist/src/analysis/tree-sitter/pythonIncremental.test.js +142 -0
  37. package/dist/src/analysis/tree-sitter/pythonIncremental.test.js.map +1 -0
  38. package/dist/src/analysis/tree-sitter/queries/python.d.ts +43 -0
  39. package/dist/src/analysis/tree-sitter/queries/python.d.ts.map +1 -0
  40. package/dist/src/analysis/tree-sitter/queries/python.js +88 -0
  41. package/dist/src/analysis/tree-sitter/queries/python.js.map +1 -0
  42. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts +13 -0
  43. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts.map +1 -0
  44. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +174 -0
  45. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -0
  46. package/dist/src/analytics/ROIDashboardService.csv.d.ts +11 -0
  47. package/dist/src/analytics/ROIDashboardService.csv.d.ts.map +1 -0
  48. package/dist/src/analytics/ROIDashboardService.csv.js +43 -0
  49. package/dist/src/analytics/ROIDashboardService.csv.js.map +1 -0
  50. package/dist/src/analytics/ROIDashboardService.d.ts +64 -3
  51. package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -1
  52. package/dist/src/analytics/ROIDashboardService.js +116 -45
  53. package/dist/src/analytics/ROIDashboardService.js.map +1 -1
  54. package/dist/src/api/schemas.d.ts +19 -4
  55. package/dist/src/api/schemas.d.ts.map +1 -1
  56. package/dist/src/api/schemas.js +8 -0
  57. package/dist/src/api/schemas.js.map +1 -1
  58. package/dist/src/benchmarks/incrementalParseBenchmark.d.ts +18 -0
  59. package/dist/src/benchmarks/incrementalParseBenchmark.d.ts.map +1 -0
  60. package/dist/src/benchmarks/incrementalParseBenchmark.js +121 -0
  61. package/dist/src/benchmarks/incrementalParseBenchmark.js.map +1 -0
  62. package/dist/src/billing/GDPRComplianceService.test.d.ts +2 -0
  63. package/dist/src/billing/GDPRComplianceService.test.d.ts.map +1 -0
  64. package/dist/src/billing/GDPRComplianceService.test.js +405 -0
  65. package/dist/src/billing/GDPRComplianceService.test.js.map +1 -0
  66. package/dist/src/index.d.ts +4 -3
  67. package/dist/src/index.d.ts.map +1 -1
  68. package/dist/src/index.js +2 -2
  69. package/dist/src/index.js.map +1 -1
  70. package/dist/src/indexer/SkillParser.coverage.test.d.ts +10 -0
  71. package/dist/src/indexer/SkillParser.coverage.test.d.ts.map +1 -0
  72. package/dist/src/indexer/SkillParser.coverage.test.js +76 -0
  73. package/dist/src/indexer/SkillParser.coverage.test.js.map +1 -0
  74. package/dist/src/indexer/SkillParser.test.d.ts +2 -0
  75. package/dist/src/indexer/SkillParser.test.d.ts.map +1 -0
  76. package/dist/src/indexer/SkillParser.test.js +375 -0
  77. package/dist/src/indexer/SkillParser.test.js.map +1 -0
  78. package/dist/src/sources/LocalFilesystemAdapter.d.ts +104 -10
  79. package/dist/src/sources/LocalFilesystemAdapter.d.ts.map +1 -1
  80. package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts +92 -0
  81. package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts.map +1 -0
  82. package/dist/src/sources/LocalFilesystemAdapter.helpers.js +157 -0
  83. package/dist/src/sources/LocalFilesystemAdapter.helpers.js.map +1 -0
  84. package/dist/src/sources/LocalFilesystemAdapter.js +218 -159
  85. package/dist/src/sources/LocalFilesystemAdapter.js.map +1 -1
  86. package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts +78 -0
  87. package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts.map +1 -0
  88. package/dist/src/sources/LocalFilesystemAdapter.scan.js +118 -0
  89. package/dist/src/sources/LocalFilesystemAdapter.scan.js.map +1 -0
  90. package/dist/src/sources/index.d.ts +1 -1
  91. package/dist/src/sources/index.d.ts.map +1 -1
  92. package/dist/src/sources/index.js.map +1 -1
  93. package/dist/src/sources/types.d.ts +28 -0
  94. package/dist/src/sources/types.d.ts.map +1 -1
  95. package/dist/src/telemetry/tracer-imports.d.ts +13 -0
  96. package/dist/src/telemetry/tracer-imports.d.ts.map +1 -0
  97. package/dist/src/telemetry/tracer-imports.js +26 -0
  98. package/dist/src/telemetry/tracer-imports.js.map +1 -0
  99. package/dist/src/telemetry/tracer.d.ts.map +1 -1
  100. package/dist/src/telemetry/tracer.js +18 -21
  101. package/dist/src/telemetry/tracer.js.map +1 -1
  102. package/dist/src/utils/rate-limit.d.ts +39 -0
  103. package/dist/src/utils/rate-limit.d.ts.map +1 -0
  104. package/dist/src/utils/rate-limit.js +48 -0
  105. package/dist/src/utils/rate-limit.js.map +1 -0
  106. package/dist/src/utils/rate-limit.test.d.ts +11 -0
  107. package/dist/src/utils/rate-limit.test.d.ts.map +1 -0
  108. package/dist/src/utils/rate-limit.test.js +86 -0
  109. package/dist/src/utils/rate-limit.test.js.map +1 -0
  110. package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts +178 -0
  111. package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts.map +1 -0
  112. package/dist/src/webhooks/WebhookDeadLetterRepository.js +196 -0
  113. package/dist/src/webhooks/WebhookDeadLetterRepository.js.map +1 -0
  114. package/dist/src/webhooks/WebhookQueue.d.ts +1 -0
  115. package/dist/src/webhooks/WebhookQueue.d.ts.map +1 -1
  116. package/dist/src/webhooks/WebhookQueue.js +19 -0
  117. package/dist/src/webhooks/WebhookQueue.js.map +1 -1
  118. package/dist/src/webhooks/WebhookQueue.types.d.ts +11 -0
  119. package/dist/src/webhooks/WebhookQueue.types.d.ts.map +1 -1
  120. package/dist/src/webhooks/index.d.ts +1 -0
  121. package/dist/src/webhooks/index.d.ts.map +1 -1
  122. package/dist/src/webhooks/index.js +2 -0
  123. package/dist/src/webhooks/index.js.map +1 -1
  124. package/dist/tests/ActivationManager.test.d.ts +13 -0
  125. package/dist/tests/ActivationManager.test.d.ts.map +1 -0
  126. package/dist/tests/ActivationManager.test.js +218 -0
  127. package/dist/tests/ActivationManager.test.js.map +1 -0
  128. package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts +13 -0
  129. package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts.map +1 -0
  130. package/dist/tests/LocalFilesystemAdapter.coverage.test.js +314 -0
  131. package/dist/tests/LocalFilesystemAdapter.coverage.test.js.map +1 -0
  132. package/dist/tests/LocalFilesystemAdapter.security.test.d.ts +18 -0
  133. package/dist/tests/LocalFilesystemAdapter.security.test.d.ts.map +1 -0
  134. package/dist/tests/LocalFilesystemAdapter.security.test.js +344 -0
  135. package/dist/tests/LocalFilesystemAdapter.security.test.js.map +1 -0
  136. package/dist/tests/LocalFilesystemAdapter.test.d.ts +12 -0
  137. package/dist/tests/LocalFilesystemAdapter.test.d.ts.map +1 -0
  138. package/dist/tests/LocalFilesystemAdapter.test.js +301 -0
  139. package/dist/tests/LocalFilesystemAdapter.test.js.map +1 -0
  140. package/dist/tests/ROIDashboardService.coverage.test.d.ts +9 -0
  141. package/dist/tests/ROIDashboardService.coverage.test.d.ts.map +1 -0
  142. package/dist/tests/ROIDashboardService.coverage.test.js +118 -0
  143. package/dist/tests/ROIDashboardService.coverage.test.js.map +1 -0
  144. package/dist/tests/ROIDashboardService.test.js +87 -0
  145. package/dist/tests/ROIDashboardService.test.js.map +1 -1
  146. package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts +14 -0
  147. package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts.map +1 -0
  148. package/dist/tests/ScraperAdapters.gitlab-coverage.test.js +169 -0
  149. package/dist/tests/ScraperAdapters.gitlab-coverage.test.js.map +1 -0
  150. package/dist/tests/ScraperAdapters.test.d.ts +5 -1
  151. package/dist/tests/ScraperAdapters.test.d.ts.map +1 -1
  152. package/dist/tests/ScraperAdapters.test.js +6 -336
  153. package/dist/tests/ScraperAdapters.test.js.map +1 -1
  154. package/dist/tests/WebhookDeadLetterRepository.test.d.ts +2 -0
  155. package/dist/tests/WebhookDeadLetterRepository.test.d.ts.map +1 -0
  156. package/dist/tests/WebhookDeadLetterRepository.test.js +333 -0
  157. package/dist/tests/WebhookDeadLetterRepository.test.js.map +1 -0
  158. package/dist/tests/WebhookHandler.test.js +93 -1
  159. package/dist/tests/WebhookHandler.test.js.map +1 -1
  160. package/dist/tests/WebhookQueue.coverage.test.d.ts +19 -0
  161. package/dist/tests/WebhookQueue.coverage.test.d.ts.map +1 -0
  162. package/dist/tests/WebhookQueue.coverage.test.js +190 -0
  163. package/dist/tests/WebhookQueue.coverage.test.js.map +1 -0
  164. package/dist/tests/api/client.validation.test.js +37 -0
  165. package/dist/tests/api/client.validation.test.js.map +1 -1
  166. package/dist/tests/billing/GDPRCompliance.test.d.ts +2 -2
  167. package/dist/tests/billing/GDPRCompliance.test.js +221 -36
  168. package/dist/tests/billing/GDPRCompliance.test.js.map +1 -1
  169. package/dist/tests/telemetry.test.js +126 -0
  170. package/dist/tests/telemetry.test.js.map +1 -1
  171. package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts +10 -0
  172. package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts.map +1 -0
  173. package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js +109 -0
  174. package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js.map +1 -0
  175. package/package.json +9 -5
@@ -1,20 +1,45 @@
1
1
  /**
2
- * Local Filesystem Source Adapter (SMI-591)
2
+ * Local Filesystem Source Adapter (SMI-591, SMI-4287, SMI-4319, SMI-4320)
3
3
  *
4
4
  * Scans local directories for SKILL.md files.
5
5
  * Useful for local development and testing.
6
+ *
7
+ * SMI-4287 hardening:
8
+ * - Symlink targets are resolved via `fs.realpath` and checked against the
9
+ * adapter's `rootDir`. Targets outside root are skipped with a
10
+ * `symlink-escape` warning (unless `allowSymlinksOutsideRoot` is `true`).
11
+ * - Permission (EACCES/EPERM), not-found (ENOENT), and loop (ELOOP) errors
12
+ * are surfaced as `AdapterError` entries on `SourceSearchResult.warnings`
13
+ * instead of throwing, so siblings continue to be scanned.
14
+ * - All `fs.*` calls route through the typed `safeFs` helpers; the historic
15
+ * bare `try/catch` for EACCES in `scanDirectory` is removed.
16
+ *
17
+ * SMI-4319 hardening:
18
+ * - `runScan` allocates a fresh `visitedRealpaths: Set<string>` per
19
+ * invocation so mutually-recursive / self-looping directory symlinks are
20
+ * detected and skipped with a `loop` warning instead of silently wasting
21
+ * `maxDepth` traversals.
22
+ *
23
+ * SMI-4320 hardening:
24
+ * - `resolveSkillPath` is now async and routes through `resolveSafeRealpath`
25
+ * (byte-wise `startsWith(rootReal + sep)` on realpath outputs — no
26
+ * platform lowercasing). Direct-access methods (`getRepository`,
27
+ * `fetchSkillContent`, `skillExists`) inherit containment instead of
28
+ * relying solely on lexical `validatePath`. This closes the scan-to-fetch
29
+ * TOCTOU window where an indexed-then-swapped symlink previously escaped
30
+ * containment. `allowSymlinksOutsideRoot` is honoured at every realpath
31
+ * callsite. Residual TOCTOU between `resolveSkillPath` and the subsequent
32
+ * `fs.readFile` is documented; closing it requires fd-based I/O and is
33
+ * tracked as a separate follow-up.
6
34
  */
7
35
  import { BaseSourceAdapter } from './BaseSourceAdapter.js';
8
36
  import { createHash } from 'crypto';
9
- import { promises as fs } from 'fs';
10
- import { join, basename, dirname, relative, resolve } from 'path';
37
+ import { basename, dirname, resolve, join } from 'path';
11
38
  import { createLogger } from '../utils/logger.js';
12
39
  import { validatePath, safePatternMatch } from '../validation/index.js';
40
+ import { safeFs, resolveSafeRealpath } from './LocalFilesystemAdapter.helpers.js';
41
+ import { scanDirectoryRecursive, } from './LocalFilesystemAdapter.scan.js';
13
42
  const log = createLogger('LocalFilesystemAdapter');
14
- /**
15
- * Default skill file names to search for
16
- */
17
- const SKILL_FILE_NAMES = ['SKILL.md', 'skill.md'];
18
43
  /**
19
44
  * Local Filesystem Source Adapter
20
45
  *
@@ -33,6 +58,9 @@ const SKILL_FILE_NAMES = ['SKILL.md', 'skill.md'];
33
58
  *
34
59
  * await adapter.initialize()
35
60
  * const result = await adapter.search({})
61
+ * for (const warning of result.warnings ?? []) {
62
+ * console.warn(`[${warning.code}] ${warning.message}`)
63
+ * }
36
64
  * ```
37
65
  */
38
66
  export class LocalFilesystemAdapter extends BaseSourceAdapter {
@@ -40,140 +68,167 @@ export class LocalFilesystemAdapter extends BaseSourceAdapter {
40
68
  maxDepth;
41
69
  excludePatterns;
42
70
  followSymlinks;
71
+ allowSymlinksOutsideRoot;
43
72
  discoveredSkills = [];
73
+ /**
74
+ * Warnings accumulated during the most recent scan. Consumed and cleared
75
+ * by `search()` so each caller sees only the warnings from that call's
76
+ * underlying scan.
77
+ */
78
+ scanWarnings = [];
44
79
  constructor(config) {
45
80
  super(config);
46
81
  this.rootDir = config.rootDir;
47
82
  this.maxDepth = config.maxDepth ?? 5;
48
83
  this.excludePatterns = config.excludePatterns ?? ['node_modules', '.git', '.svn', 'dist'];
49
84
  this.followSymlinks = config.followSymlinks ?? false;
85
+ this.allowSymlinksOutsideRoot = config.allowSymlinksOutsideRoot ?? false;
50
86
  }
51
87
  /**
52
88
  * Initialize by scanning the filesystem
53
89
  */
54
90
  async doInitialize() {
55
- await this.scanDirectory(this.rootDir, 0);
91
+ this.scanWarnings = [];
92
+ await this.runScan();
56
93
  }
57
94
  /**
58
- * Check if root directory exists and is accessible
95
+ * Check if root directory exists and is accessible.
96
+ *
97
+ * SMI-4287: routes `fs.stat(rootDir)` through `safeFs` so the raw Node
98
+ * error is translated to a typed `AdapterError` message.
59
99
  */
60
100
  async doHealthCheck() {
61
- try {
62
- const stats = await fs.stat(this.rootDir);
63
- return {
64
- healthy: stats.isDirectory(),
65
- error: stats.isDirectory() ? undefined : 'Root path is not a directory',
66
- };
67
- }
68
- catch (error) {
101
+ const statResult = await safeFs.stat(this.rootDir);
102
+ if (!statResult.ok) {
69
103
  return {
70
104
  healthy: false,
71
- error: error instanceof Error ? error.message : 'Directory not accessible',
105
+ error: statResult.error.message,
72
106
  };
73
107
  }
108
+ return {
109
+ healthy: statResult.value.isDirectory(),
110
+ error: statResult.value.isDirectory() ? undefined : 'Root path is not a directory',
111
+ };
74
112
  }
75
113
  /**
76
- * Search for skills in the scanned directories
114
+ * Search for skills in the scanned directories.
115
+ *
116
+ * SMI-4287: `warnings` collects non-fatal `AdapterError` entries from the
117
+ * scan (symlink escapes, permission denials, loops). An empty array is
118
+ * returned as `undefined` to keep the field strictly optional.
77
119
  */
78
120
  async search(options = {}) {
79
- // Re-scan if needed
80
121
  if (this.discoveredSkills.length === 0) {
81
- await this.scanDirectory(this.rootDir, 0);
122
+ this.scanWarnings = [];
123
+ await this.runScan();
82
124
  }
83
125
  let filtered = [...this.discoveredSkills];
84
- // Filter by query (search in path/directory name)
85
126
  if (options.query) {
86
127
  const query = options.query.toLowerCase();
87
128
  filtered = filtered.filter((skill) => skill.relativePath.toLowerCase().includes(query) ||
88
129
  skill.directory.toLowerCase().includes(query));
89
130
  }
90
- // Apply limit
91
131
  const limit = options.limit ?? 100;
92
132
  const limitedResults = filtered.slice(0, limit);
93
- // Convert to SourceRepository format
94
- const repositories = await Promise.all(limitedResults.map((skill) => this.skillToRepository(skill)));
133
+ const repositoriesWithWarnings = await Promise.all(limitedResults.map((skill) => this.skillToRepository(skill)));
134
+ const repositories = repositoriesWithWarnings.map((r) => r.repo);
135
+ const repoWarnings = repositoriesWithWarnings.flatMap((r) => r.warnings);
136
+ const allWarnings = [...this.scanWarnings, ...repoWarnings];
95
137
  return {
96
138
  repositories,
97
139
  totalCount: filtered.length,
98
140
  hasMore: filtered.length > limit,
141
+ warnings: allWarnings.length > 0 ? allWarnings : undefined,
99
142
  };
100
143
  }
101
144
  /**
102
- * Get repository info for a skill location
145
+ * Get repository info for a skill location.
146
+ *
147
+ * SMI-4287: `fs.stat` is routed through `safeFs`; permission errors are
148
+ * converted to typed Error messages instead of raw Node throws.
103
149
  */
104
150
  async getRepository(location) {
105
- const skillPath = this.resolveSkillPath(location);
151
+ const skillPath = await this.resolveSkillPath(location);
106
152
  const skill = this.discoveredSkills.find((s) => s.path === skillPath);
107
153
  if (skill) {
108
- return this.skillToRepository(skill);
154
+ const { repo } = await this.skillToRepository(skill);
155
+ return repo;
109
156
  }
110
- // Try to get info even if not in discovered list
111
- try {
112
- const stats = await fs.stat(skillPath);
113
- return {
114
- id: this.generateId(skillPath),
115
- name: basename(dirname(skillPath)),
116
- url: `file://${skillPath}`,
117
- description: null,
118
- owner: 'local',
119
- defaultBranch: 'main',
120
- stars: 0,
121
- forks: 0,
122
- topics: ['local'],
123
- updatedAt: stats.mtime.toISOString(),
124
- createdAt: stats.ctime.toISOString(),
125
- license: null,
126
- metadata: {
127
- sourceType: 'local',
128
- path: skillPath,
129
- },
130
- };
131
- }
132
- catch (error) {
133
- throw new Error(`Skill not found at ${skillPath}: ${error instanceof Error ? error.message : String(error)}`);
157
+ const statResult = await safeFs.stat(skillPath);
158
+ if (!statResult.ok) {
159
+ throw new Error(`Skill not found at ${skillPath}: ${statResult.error.message}`);
134
160
  }
161
+ const stats = statResult.value;
162
+ return {
163
+ id: this.generateId(skillPath),
164
+ name: basename(dirname(skillPath)),
165
+ url: `file://${skillPath}`,
166
+ description: null,
167
+ owner: 'local',
168
+ defaultBranch: 'main',
169
+ stars: 0,
170
+ forks: 0,
171
+ topics: ['local'],
172
+ updatedAt: stats.mtime.toISOString(),
173
+ createdAt: stats.ctime.toISOString(),
174
+ license: null,
175
+ metadata: {
176
+ sourceType: 'local',
177
+ path: skillPath,
178
+ },
179
+ };
135
180
  }
136
181
  /**
137
- * Fetch skill content from local file
182
+ * Fetch skill content from local file.
183
+ *
184
+ * SMI-4287: both `fs.readFile` and `fs.stat` route through `safeFs`, so
185
+ * permission errors (EACCES/EPERM) raise typed Errors with path context
186
+ * instead of raw Node errors.
138
187
  */
139
188
  async fetchSkillContent(location) {
140
- const skillPath = this.resolveSkillPath(location);
141
- try {
142
- const rawContent = await fs.readFile(skillPath, 'utf-8');
143
- const stats = await fs.stat(skillPath);
144
- const sha = this.generateSha(rawContent);
145
- return {
146
- rawContent,
147
- sha,
148
- location,
149
- filePath: skillPath,
150
- encoding: 'utf-8',
151
- lastModified: stats.mtime.toISOString(),
152
- };
189
+ const skillPath = await this.resolveSkillPath(location);
190
+ const contentResult = await safeFs.readFile(skillPath);
191
+ if (!contentResult.ok) {
192
+ throw new Error(`Failed to read skill file at ${skillPath}: ${contentResult.error.message}`);
153
193
  }
154
- catch (error) {
155
- throw new Error(`Failed to read skill file at ${skillPath}: ${error instanceof Error ? error.message : String(error)}`);
194
+ const statResult = await safeFs.stat(skillPath);
195
+ if (!statResult.ok) {
196
+ throw new Error(`Failed to stat skill file at ${skillPath}: ${statResult.error.message}`);
156
197
  }
198
+ const rawContent = contentResult.value;
199
+ const stats = statResult.value;
200
+ const sha = this.generateSha(rawContent);
201
+ return {
202
+ rawContent,
203
+ sha,
204
+ location,
205
+ filePath: skillPath,
206
+ encoding: 'utf-8',
207
+ lastModified: stats.mtime.toISOString(),
208
+ };
157
209
  }
158
210
  /**
159
211
  * Check if skill exists at location
160
212
  */
161
213
  async skillExists(location) {
162
- const skillPath = this.resolveSkillPath(location);
163
- try {
164
- await fs.access(skillPath);
165
- return true;
166
- }
167
- catch {
168
- return false;
169
- }
214
+ // SMI-720 contract: lexical path-traversal attempts throw
215
+ // `ValidationError('Path traversal detected: ...')` from `resolveSkillPath`
216
+ // — callers (and the SMI-720 test suite) rely on this. Only absence on
217
+ // disk should return `false`; traversal attempts surface as exceptions.
218
+ const skillPath = await this.resolveSkillPath(location);
219
+ const statResult = await safeFs.stat(skillPath);
220
+ return statResult.ok;
170
221
  }
171
222
  /**
172
- * Rescan the filesystem for new skills
223
+ * Rescan the filesystem for new skills.
224
+ *
225
+ * Returns the count of discovered skills. Warnings from the rescan are
226
+ * available via the next call to `search()`.
173
227
  */
174
228
  async rescan() {
175
229
  this.discoveredSkills = [];
176
- await this.scanDirectory(this.rootDir, 0);
230
+ this.scanWarnings = [];
231
+ await this.runScan();
177
232
  return this.discoveredSkills.length;
178
233
  }
179
234
  /**
@@ -183,59 +238,27 @@ export class LocalFilesystemAdapter extends BaseSourceAdapter {
183
238
  return this.discoveredSkills.length;
184
239
  }
185
240
  /**
186
- * Scan a directory recursively for skill files
241
+ * Run the recursive scan starting at `rootDir`. Delegates to the extracted
242
+ * `scanDirectoryRecursive` helper (see `LocalFilesystemAdapter.scan.ts`).
243
+ *
244
+ * SMI-4319: allocates a fresh `visitedRealpaths` set per invocation so
245
+ * back-to-back scans don't share state. Sibling directories within a
246
+ * single scan share the set (they're in the same call tree), so
247
+ * cross-linked loops (A↔B) are caught even when the loop isn't on the
248
+ * descent path from `rootDir`.
187
249
  */
188
- async scanDirectory(dirPath, depth) {
189
- if (depth > this.maxDepth)
190
- return;
191
- try {
192
- const entries = await fs.readdir(dirPath, { withFileTypes: true });
193
- for (const entry of entries) {
194
- const fullPath = join(dirPath, entry.name);
195
- // Skip excluded patterns
196
- if (this.isExcluded(entry.name))
197
- continue;
198
- // Handle symlinks
199
- let isDirectory = entry.isDirectory();
200
- let isFile = entry.isFile();
201
- if (entry.isSymbolicLink()) {
202
- if (!this.followSymlinks)
203
- continue;
204
- try {
205
- const stats = await fs.stat(fullPath);
206
- isDirectory = stats.isDirectory();
207
- isFile = stats.isFile();
208
- }
209
- catch {
210
- continue; // Skip broken symlinks
211
- }
212
- }
213
- // Check for skill files
214
- if (isFile && SKILL_FILE_NAMES.includes(entry.name)) {
215
- const stats = await fs.stat(fullPath);
216
- this.discoveredSkills.push({
217
- path: fullPath,
218
- relativePath: relative(this.rootDir, fullPath),
219
- directory: dirname(fullPath),
220
- stats: {
221
- size: stats.size,
222
- mtime: stats.mtime,
223
- ctime: stats.ctime,
224
- },
225
- });
226
- }
227
- // Recurse into directories
228
- if (isDirectory) {
229
- await this.scanDirectory(fullPath, depth + 1);
230
- }
231
- }
232
- }
233
- catch (error) {
234
- // Ignore permission errors and continue scanning
235
- if (error.code !== 'EACCES') {
236
- log.warn(`Error scanning directory ${dirPath}: ${error}`);
237
- }
238
- }
250
+ async runScan() {
251
+ await scanDirectoryRecursive(this.rootDir, 0, {
252
+ rootDir: this.rootDir,
253
+ maxDepth: this.maxDepth,
254
+ followSymlinks: this.followSymlinks,
255
+ allowSymlinksOutsideRoot: this.allowSymlinksOutsideRoot,
256
+ isExcluded: (name) => this.isExcluded(name),
257
+ discovered: this.discoveredSkills,
258
+ warnings: this.scanWarnings,
259
+ visitedRealpaths: new Set(),
260
+ log,
261
+ });
239
262
  }
240
263
  /**
241
264
  * Check if a path/name should be excluded (SMI-722, SMI-726)
@@ -246,9 +269,22 @@ export class LocalFilesystemAdapter extends BaseSourceAdapter {
246
269
  }
247
270
  /**
248
271
  * Resolve a skill location to a full filesystem path
249
- * Validates that the resolved path remains within rootDir to prevent path traversal attacks (SMI-720, SMI-726)
272
+ * (SMI-720, SMI-726, SMI-4287, SMI-4320).
273
+ *
274
+ * Two-stage containment: (1) lexical `validatePath` fast-fails
275
+ * `../`-style traversal (SMI-720 contract — callers assert the
276
+ * "Path traversal detected" message), then (2) `resolveSafeRealpath`
277
+ * enforces realpath byte-wise containment so symlinks can't escape
278
+ * `rootDir` even when the lexical path is clean. Honours the SMI-4287
279
+ * `allowSymlinksOutsideRoot` opt-in.
280
+ *
281
+ * Not-found behaviour: realpath ENOENT falls back to the lexically
282
+ * resolved path so downstream `stat` / `readFile` produce the canonical
283
+ * caller-visible error. TOCTOU caveat: the window between this resolve
284
+ * and the caller's subsequent read remains open; closing it requires
285
+ * fd-based I/O and is tracked separately.
250
286
  */
251
- resolveSkillPath(location) {
287
+ async resolveSkillPath(location) {
252
288
  let resolvedPath;
253
289
  if (location.path?.startsWith('/')) {
254
290
  resolvedPath = location.path;
@@ -265,53 +301,76 @@ export class LocalFilesystemAdapter extends BaseSourceAdapter {
265
301
  else {
266
302
  throw new Error('Invalid location: must specify path or repo');
267
303
  }
268
- // Use centralized path validation to prevent path traversal attacks
304
+ // Lexical fast-fail (SMI-720 contract callers test for
305
+ // "Path traversal detected" on obvious traversal attempts).
269
306
  validatePath(resolvedPath, this.rootDir);
270
- return resolve(resolvedPath);
307
+ const absolutePath = resolve(resolvedPath);
308
+ // SMI-4320: realpath-based containment. Honours SMI-4287 opt-in.
309
+ const realResult = await resolveSafeRealpath(absolutePath, this.rootDir, {
310
+ allowSymlinksOutsideRoot: this.allowSymlinksOutsideRoot,
311
+ });
312
+ if (realResult.ok) {
313
+ return realResult.value;
314
+ }
315
+ // `not-found` is expected for skillExists / fetchSkillContent on paths
316
+ // that the caller hasn't verified exist. Fall back to the lexically
317
+ // resolved path so downstream `stat` / `readFile` produce the canonical
318
+ // error message callers already depend on.
319
+ if (realResult.error.code === 'not-found') {
320
+ return absolutePath;
321
+ }
322
+ // `symlink-escape`, `loop`, `permission`, `io` all throw — the caller
323
+ // asked for a path that either escapes root or can't be validated.
324
+ throw new Error(`Path rejected by realpath containment: ${realResult.error.message}`);
271
325
  }
272
326
  /**
273
- * Convert discovered skill to SourceRepository
327
+ * Convert discovered skill to SourceRepository.
328
+ *
329
+ * Returns both the repository and any warnings encountered while reading
330
+ * the file (typically permission errors on SKILL.md after discovery).
274
331
  */
275
332
  async skillToRepository(skill) {
276
333
  const dirName = basename(skill.directory);
277
- // Try to extract metadata from the skill file
334
+ const warnings = [];
278
335
  let description = null;
279
336
  let name = dirName;
280
- try {
281
- const content = await fs.readFile(skill.path, 'utf-8');
282
- // Try to extract name from frontmatter
337
+ const contentResult = await safeFs.readFile(skill.path);
338
+ if (contentResult.ok) {
339
+ const content = contentResult.value;
283
340
  const nameMatch = content.match(/^---[\s\S]*?name:\s*["']?([^"'\n]+)["']?/m);
284
341
  if (nameMatch) {
285
342
  name = nameMatch[1].trim();
286
343
  }
287
- // Try to extract description from frontmatter
288
344
  const descMatch = content.match(/^---[\s\S]*?description:\s*["']?([^"'\n]+)["']?/m);
289
345
  if (descMatch) {
290
346
  description = descMatch[1].trim();
291
347
  }
292
348
  }
293
- catch {
294
- // Use defaults
349
+ else {
350
+ warnings.push(contentResult.error);
295
351
  }
296
352
  return {
297
- id: this.generateId(skill.path),
298
- name,
299
- url: `file://${skill.path}`,
300
- description,
301
- owner: 'local',
302
- defaultBranch: 'main',
303
- stars: 0,
304
- forks: 0,
305
- topics: ['local'],
306
- updatedAt: skill.stats.mtime.toISOString(),
307
- createdAt: skill.stats.ctime.toISOString(),
308
- license: null,
309
- metadata: {
310
- sourceType: 'local',
311
- path: skill.path,
312
- relativePath: skill.relativePath,
313
- size: skill.stats.size,
353
+ repo: {
354
+ id: this.generateId(skill.path),
355
+ name,
356
+ url: `file://${skill.path}`,
357
+ description,
358
+ owner: 'local',
359
+ defaultBranch: 'main',
360
+ stars: 0,
361
+ forks: 0,
362
+ topics: ['local'],
363
+ updatedAt: skill.stats.mtime.toISOString(),
364
+ createdAt: skill.stats.ctime.toISOString(),
365
+ license: null,
366
+ metadata: {
367
+ sourceType: 'local',
368
+ path: skill.path,
369
+ relativePath: skill.relativePath,
370
+ size: skill.stats.size,
371
+ },
314
372
  },
373
+ warnings,
315
374
  };
316
375
  }
317
376
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"LocalFilesystemAdapter.js","sourceRoot":"","sources":["../../../src/sources/LocalFilesystemAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LocalFilesystemAdapter.js","sourceRoot":"","sources":["../../../src/sources/LocalFilesystemAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * LocalFilesystemAdapter scan loop (SMI-4287, SMI-4319)
3
+ *
4
+ * Extracted from `LocalFilesystemAdapter.ts` to keep the main adapter file
5
+ * under the 500-line governance ceiling. Pure function of its inputs — no
6
+ * shared instance state other than the `warnings` sink.
7
+ *
8
+ * SMI-4319: tracks visited directory realpaths in a per-scan `Set<string>`
9
+ * so mutually-recursive symlinks (A→B, B→A) are detected and skipped with a
10
+ * `loop` warning. Each `scanDirectoryRecursive` invocation checks the set
11
+ * BEFORE recursing; the set is keyed on the raw realpath (no
12
+ * `normaliseForFs`) so legitimate case-sensitive distinctions are preserved.
13
+ * Loop detection runs regardless of `allowSymlinksOutsideRoot` — loops are a
14
+ * correctness issue (infinite work), not a security issue.
15
+ */
16
+ import type { AdapterError } from './types.js';
17
+ import type { createLogger } from '../utils/logger.js';
18
+ /**
19
+ * File names that identify a skill definition.
20
+ */
21
+ export declare const SKILL_FILE_NAMES: string[];
22
+ /**
23
+ * Shape shared with `LocalFilesystemAdapter.DiscoveredSkill`.
24
+ */
25
+ export interface DiscoveredSkillRecord {
26
+ path: string;
27
+ relativePath: string;
28
+ directory: string;
29
+ stats: {
30
+ size: number;
31
+ mtime: Date;
32
+ ctime: Date;
33
+ };
34
+ }
35
+ /**
36
+ * Configuration consumed by the scan loop.
37
+ */
38
+ export interface ScanDirectoryOptions {
39
+ /** Root directory (used for relative path calculations). */
40
+ rootDir: string;
41
+ /** Maximum recursion depth. */
42
+ maxDepth: number;
43
+ /** If false, symlinks are skipped entirely. */
44
+ followSymlinks: boolean;
45
+ /** If true, skip the symlink-containment check. */
46
+ allowSymlinksOutsideRoot: boolean;
47
+ /** Predicate for name-based exclusion. */
48
+ isExcluded: (name: string) => boolean;
49
+ /** Destination array for discovered skills (mutated in place). */
50
+ discovered: DiscoveredSkillRecord[];
51
+ /** Destination array for non-fatal errors (mutated in place). */
52
+ warnings: AdapterError[];
53
+ /**
54
+ * Visited directory realpaths for this scan (SMI-4319). Mutated in place.
55
+ * Keyed on the raw realpath string (no platform lowercasing). Must be a
56
+ * fresh `Set` per `runScan` invocation so back-to-back scans don't share
57
+ * state.
58
+ */
59
+ visitedRealpaths: Set<string>;
60
+ /** Logger instance from the parent adapter. */
61
+ log: ReturnType<typeof createLogger>;
62
+ }
63
+ /**
64
+ * Recursively scan `dirPath` for `SKILL.md` files, populating
65
+ * `options.discovered` and `options.warnings` in place.
66
+ *
67
+ * SMI-4287: all filesystem access routes through `safeFs`. Per-entry errors
68
+ * are recorded on `warnings` and the scan continues for siblings.
69
+ *
70
+ * SMI-4319: before descending, realpath `dirPath` and check
71
+ * `options.visitedRealpaths` for a prior visit. On hit, push a `loop` warning
72
+ * and return — prevents A↔B / self-loop directory symlinks from wasting
73
+ * `maxDepth` traversals and surfacing the same SKILL.md under multiple
74
+ * lexical paths. Realpath errors (permission, ENOENT, ELOOP on the dir
75
+ * itself) are recorded and the subtree is skipped.
76
+ */
77
+ export declare function scanDirectoryRecursive(dirPath: string, depth: number, options: ScanDirectoryOptions): Promise<void>;
78
+ //# sourceMappingURL=LocalFilesystemAdapter.scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalFilesystemAdapter.scan.d.ts","sourceRoot":"","sources":["../../../src/sources/LocalFilesystemAdapter.scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD;;GAEG;AACH,eAAO,MAAM,gBAAgB,UAA2B,CAAA;AAExD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE,IAAI,CAAA;QACX,KAAK,EAAE,IAAI,CAAA;KACZ,CAAA;CACF;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,+CAA+C;IAC/C,cAAc,EAAE,OAAO,CAAA;IACvB,mDAAmD;IACnD,wBAAwB,EAAE,OAAO,CAAA;IACjC,0CAA0C;IAC1C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACrC,kEAAkE;IAClE,UAAU,EAAE,qBAAqB,EAAE,CAAA;IACnC,iEAAiE;IACjE,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB;;;;;OAKG;IACH,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC7B,+CAA+C;IAC/C,GAAG,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAA;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAuFf"}