@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.
- package/CHANGELOG.md +14 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/activation/ActivationManager.d.ts +7 -0
- package/dist/src/activation/ActivationManager.d.ts.map +1 -1
- package/dist/src/activation/ActivationManager.js +13 -4
- package/dist/src/activation/ActivationManager.js.map +1 -1
- package/dist/src/analysis/adapters/python.d.ts +16 -11
- package/dist/src/analysis/adapters/python.d.ts.map +1 -1
- package/dist/src/analysis/adapters/python.js +46 -61
- package/dist/src/analysis/adapters/python.js.map +1 -1
- package/dist/src/analysis/router.test.d.ts +2 -0
- package/dist/src/analysis/router.test.d.ts.map +1 -0
- package/dist/src/analysis/router.test.js +411 -0
- package/dist/src/analysis/router.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -1
- package/dist/src/analysis/tree-sitter/manager.js +12 -5
- package/dist/src/analysis/tree-sitter/manager.js.map +1 -1
- package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts +45 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.js +264 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts +12 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.js +74 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +93 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts +22 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +229 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.js +287 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts +17 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.js +142 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/queries/python.d.ts +43 -0
- package/dist/src/analysis/tree-sitter/queries/python.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/queries/python.js +88 -0
- package/dist/src/analysis/tree-sitter/queries/python.js.map +1 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts +13 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +174 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.csv.d.ts +11 -0
- package/dist/src/analytics/ROIDashboardService.csv.d.ts.map +1 -0
- package/dist/src/analytics/ROIDashboardService.csv.js +43 -0
- package/dist/src/analytics/ROIDashboardService.csv.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.d.ts +64 -3
- package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -1
- package/dist/src/analytics/ROIDashboardService.js +116 -45
- package/dist/src/analytics/ROIDashboardService.js.map +1 -1
- package/dist/src/api/schemas.d.ts +19 -4
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/api/schemas.js +8 -0
- package/dist/src/api/schemas.js.map +1 -1
- package/dist/src/benchmarks/incrementalParseBenchmark.d.ts +18 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.d.ts.map +1 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.js +121 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.js.map +1 -0
- package/dist/src/billing/GDPRComplianceService.test.d.ts +2 -0
- package/dist/src/billing/GDPRComplianceService.test.d.ts.map +1 -0
- package/dist/src/billing/GDPRComplianceService.test.js +405 -0
- package/dist/src/billing/GDPRComplianceService.test.js.map +1 -0
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/SkillParser.coverage.test.d.ts +10 -0
- package/dist/src/indexer/SkillParser.coverage.test.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.coverage.test.js +76 -0
- package/dist/src/indexer/SkillParser.coverage.test.js.map +1 -0
- package/dist/src/indexer/SkillParser.test.d.ts +2 -0
- package/dist/src/indexer/SkillParser.test.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.test.js +375 -0
- package/dist/src/indexer/SkillParser.test.js.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.d.ts +104 -10
- package/dist/src/sources/LocalFilesystemAdapter.d.ts.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts +92 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.js +157 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.js.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.js +218 -159
- package/dist/src/sources/LocalFilesystemAdapter.js.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts +78 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.js +118 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.js.map +1 -0
- package/dist/src/sources/index.d.ts +1 -1
- package/dist/src/sources/index.d.ts.map +1 -1
- package/dist/src/sources/index.js.map +1 -1
- package/dist/src/sources/types.d.ts +28 -0
- package/dist/src/sources/types.d.ts.map +1 -1
- package/dist/src/telemetry/tracer-imports.d.ts +13 -0
- package/dist/src/telemetry/tracer-imports.d.ts.map +1 -0
- package/dist/src/telemetry/tracer-imports.js +26 -0
- package/dist/src/telemetry/tracer-imports.js.map +1 -0
- package/dist/src/telemetry/tracer.d.ts.map +1 -1
- package/dist/src/telemetry/tracer.js +18 -21
- package/dist/src/telemetry/tracer.js.map +1 -1
- package/dist/src/utils/rate-limit.d.ts +39 -0
- package/dist/src/utils/rate-limit.d.ts.map +1 -0
- package/dist/src/utils/rate-limit.js +48 -0
- package/dist/src/utils/rate-limit.js.map +1 -0
- package/dist/src/utils/rate-limit.test.d.ts +11 -0
- package/dist/src/utils/rate-limit.test.d.ts.map +1 -0
- package/dist/src/utils/rate-limit.test.js +86 -0
- package/dist/src/utils/rate-limit.test.js.map +1 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts +178 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts.map +1 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.js +196 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.js.map +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts.map +1 -1
- package/dist/src/webhooks/WebhookQueue.js +19 -0
- package/dist/src/webhooks/WebhookQueue.js.map +1 -1
- package/dist/src/webhooks/WebhookQueue.types.d.ts +11 -0
- package/dist/src/webhooks/WebhookQueue.types.d.ts.map +1 -1
- package/dist/src/webhooks/index.d.ts +1 -0
- package/dist/src/webhooks/index.d.ts.map +1 -1
- package/dist/src/webhooks/index.js +2 -0
- package/dist/src/webhooks/index.js.map +1 -1
- package/dist/tests/ActivationManager.test.d.ts +13 -0
- package/dist/tests/ActivationManager.test.d.ts.map +1 -0
- package/dist/tests/ActivationManager.test.js +218 -0
- package/dist/tests/ActivationManager.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts +13 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.js +314 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.d.ts +18 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.js +344 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.test.d.ts +12 -0
- package/dist/tests/LocalFilesystemAdapter.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.test.js +301 -0
- package/dist/tests/LocalFilesystemAdapter.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.coverage.test.d.ts +9 -0
- package/dist/tests/ROIDashboardService.coverage.test.d.ts.map +1 -0
- package/dist/tests/ROIDashboardService.coverage.test.js +118 -0
- package/dist/tests/ROIDashboardService.coverage.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.test.js +87 -0
- package/dist/tests/ROIDashboardService.test.js.map +1 -1
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts +14 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts.map +1 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.js +169 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.js.map +1 -0
- package/dist/tests/ScraperAdapters.test.d.ts +5 -1
- package/dist/tests/ScraperAdapters.test.d.ts.map +1 -1
- package/dist/tests/ScraperAdapters.test.js +6 -336
- package/dist/tests/ScraperAdapters.test.js.map +1 -1
- package/dist/tests/WebhookDeadLetterRepository.test.d.ts +2 -0
- package/dist/tests/WebhookDeadLetterRepository.test.d.ts.map +1 -0
- package/dist/tests/WebhookDeadLetterRepository.test.js +333 -0
- package/dist/tests/WebhookDeadLetterRepository.test.js.map +1 -0
- package/dist/tests/WebhookHandler.test.js +93 -1
- package/dist/tests/WebhookHandler.test.js.map +1 -1
- package/dist/tests/WebhookQueue.coverage.test.d.ts +19 -0
- package/dist/tests/WebhookQueue.coverage.test.d.ts.map +1 -0
- package/dist/tests/WebhookQueue.coverage.test.js +190 -0
- package/dist/tests/WebhookQueue.coverage.test.js.map +1 -0
- package/dist/tests/api/client.validation.test.js +37 -0
- package/dist/tests/api/client.validation.test.js.map +1 -1
- package/dist/tests/billing/GDPRCompliance.test.d.ts +2 -2
- package/dist/tests/billing/GDPRCompliance.test.js +221 -36
- package/dist/tests/billing/GDPRCompliance.test.js.map +1 -1
- package/dist/tests/telemetry.test.js +126 -0
- package/dist/tests/telemetry.test.js.map +1 -1
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts +10 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts.map +1 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js +109 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js.map +1 -0
- package/package.json +9 -5
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalFilesystemAdapter security sidecar (SMI-4319, SMI-4320)
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - SMI-4319 symlink loop detection via per-scan visited-realpath set
|
|
6
|
+
* (A↔B, self-loops, `allowSymlinksOutsideRoot` does NOT suppress the
|
|
7
|
+
* loop warning, per-scan isolation, happy deep tree).
|
|
8
|
+
* - SMI-4320 realpath-based containment for direct-access methods
|
|
9
|
+
* (`fetchSkillContent` / `getRepository` / `skillExists`), byte-wise
|
|
10
|
+
* `startsWith(root + sep)` correctness including the trailing-separator
|
|
11
|
+
* guard, and the `allowSymlinksOutsideRoot` regression guard.
|
|
12
|
+
*
|
|
13
|
+
* Kept in its own file because the pre-commit `check-file-length.mjs`
|
|
14
|
+
* enforces < 500 lines on `.test.ts` even though `audit:standards` exempts
|
|
15
|
+
* tests (see memory feedback: "File-length enforcement asymmetry").
|
|
16
|
+
*/
|
|
17
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
18
|
+
import { LocalFilesystemAdapter } from '../src/sources/LocalFilesystemAdapter.js';
|
|
19
|
+
import { isRealpathContained } from '../src/sources/LocalFilesystemAdapter.helpers.js';
|
|
20
|
+
import { promises as fs } from 'fs';
|
|
21
|
+
import { join, sep } from 'path';
|
|
22
|
+
import { tmpdir, platform } from 'os';
|
|
23
|
+
function uniq(prefix) {
|
|
24
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
25
|
+
}
|
|
26
|
+
describe('LocalFilesystemAdapter SMI-4319 loop detection', () => {
|
|
27
|
+
let testDir;
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
testDir = join(tmpdir(), uniq('skillsmith-4319'));
|
|
30
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
31
|
+
});
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
await fs.rm(testDir, { recursive: true, force: true }).catch(() => undefined);
|
|
34
|
+
});
|
|
35
|
+
async function trySymlink(target, link) {
|
|
36
|
+
try {
|
|
37
|
+
await fs.symlink(target, link);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
it('should emit a loop warning for A↔B directory symlinks and not hang', async () => {
|
|
45
|
+
// Build two directories that symlink into each other. maxDepth is the
|
|
46
|
+
// safety net — without the visited-realpath set it would still waste
|
|
47
|
+
// work up to maxDepth. With the set, we get a `loop` warning and
|
|
48
|
+
// short-circuit.
|
|
49
|
+
await fs.mkdir(join(testDir, 'dir-a'), { recursive: true });
|
|
50
|
+
await fs.mkdir(join(testDir, 'dir-b'), { recursive: true });
|
|
51
|
+
await fs.writeFile(join(testDir, 'dir-a', 'SKILL.md'), '# A Skill');
|
|
52
|
+
await fs.writeFile(join(testDir, 'dir-b', 'SKILL.md'), '# B Skill');
|
|
53
|
+
if (!(await trySymlink(join(testDir, 'dir-b'), join(testDir, 'dir-a', 'link-to-b'))))
|
|
54
|
+
return;
|
|
55
|
+
if (!(await trySymlink(join(testDir, 'dir-a'), join(testDir, 'dir-b', 'link-to-a'))))
|
|
56
|
+
return;
|
|
57
|
+
const adapter = new LocalFilesystemAdapter({
|
|
58
|
+
id: 'loop-ab',
|
|
59
|
+
name: 'Loop A↔B',
|
|
60
|
+
type: 'local',
|
|
61
|
+
baseUrl: 'file://',
|
|
62
|
+
enabled: true,
|
|
63
|
+
rootDir: testDir,
|
|
64
|
+
followSymlinks: true,
|
|
65
|
+
maxDepth: 10,
|
|
66
|
+
rateLimit: { maxRequests: 100, windowMs: 60000, minDelayMs: 0 },
|
|
67
|
+
});
|
|
68
|
+
await adapter.initialize();
|
|
69
|
+
const result = await adapter.search({});
|
|
70
|
+
const loopWarnings = (result.warnings ?? []).filter((w) => w.code === 'loop');
|
|
71
|
+
expect(loopWarnings.length).toBeGreaterThan(0);
|
|
72
|
+
// SKILL.md under dir-a and dir-b should each appear exactly once,
|
|
73
|
+
// not multiplied by the symlink traversal depth.
|
|
74
|
+
expect(adapter.skillCount).toBe(2);
|
|
75
|
+
});
|
|
76
|
+
it('should emit a loop warning for self-referencing directory symlinks', async () => {
|
|
77
|
+
if (!(await trySymlink(join(testDir, 'self'), join(testDir, 'self'))))
|
|
78
|
+
return;
|
|
79
|
+
const adapter = new LocalFilesystemAdapter({
|
|
80
|
+
id: 'self-loop',
|
|
81
|
+
name: 'Self Loop',
|
|
82
|
+
type: 'local',
|
|
83
|
+
baseUrl: 'file://',
|
|
84
|
+
enabled: true,
|
|
85
|
+
rootDir: testDir,
|
|
86
|
+
followSymlinks: true,
|
|
87
|
+
rateLimit: { maxRequests: 100, windowMs: 60000, minDelayMs: 0 },
|
|
88
|
+
});
|
|
89
|
+
await expect(adapter.initialize()).resolves.not.toThrow();
|
|
90
|
+
const result = await adapter.search({});
|
|
91
|
+
// Self-link produces either a `loop` (from the visited-set guard on a
|
|
92
|
+
// repeat realpath) or `symlink-escape` / `io` depending on how the FS
|
|
93
|
+
// resolves it. We only require the scan to terminate and either not
|
|
94
|
+
// crash or record a typed warning — both outcomes are safe.
|
|
95
|
+
expect(() => result.warnings ?? []).not.toThrow();
|
|
96
|
+
});
|
|
97
|
+
it('should allocate a fresh visited-realpath set per scan', async () => {
|
|
98
|
+
// Two back-to-back rescans must each produce the loop warning; if the
|
|
99
|
+
// set leaked between scans, the second rescan would short-circuit on
|
|
100
|
+
// the rootDir itself (already "visited" from the prior scan) and
|
|
101
|
+
// silently find zero skills.
|
|
102
|
+
await fs.mkdir(join(testDir, 'dir-a'), { recursive: true });
|
|
103
|
+
await fs.writeFile(join(testDir, 'dir-a', 'SKILL.md'), '# A');
|
|
104
|
+
await fs.mkdir(join(testDir, 'dir-b'), { recursive: true });
|
|
105
|
+
if (!(await trySymlink(join(testDir, 'dir-b'), join(testDir, 'dir-a', 'link-to-b'))))
|
|
106
|
+
return;
|
|
107
|
+
if (!(await trySymlink(join(testDir, 'dir-a'), join(testDir, 'dir-b', 'link-to-a'))))
|
|
108
|
+
return;
|
|
109
|
+
const adapter = new LocalFilesystemAdapter({
|
|
110
|
+
id: 'per-scan-reset',
|
|
111
|
+
name: 'Per-Scan Reset',
|
|
112
|
+
type: 'local',
|
|
113
|
+
baseUrl: 'file://',
|
|
114
|
+
enabled: true,
|
|
115
|
+
rootDir: testDir,
|
|
116
|
+
followSymlinks: true,
|
|
117
|
+
rateLimit: { maxRequests: 100, windowMs: 60000, minDelayMs: 0 },
|
|
118
|
+
});
|
|
119
|
+
await adapter.initialize();
|
|
120
|
+
const firstCount = adapter.skillCount;
|
|
121
|
+
const secondCount = await adapter.rescan();
|
|
122
|
+
expect(secondCount).toBe(firstCount);
|
|
123
|
+
expect(secondCount).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
it('should detect loops even when allowSymlinksOutsideRoot is true', async () => {
|
|
126
|
+
// Loops are a correctness issue, not a security one. The opt-in that
|
|
127
|
+
// relaxes containment must NOT relax loop detection.
|
|
128
|
+
await fs.mkdir(join(testDir, 'dir-a'), { recursive: true });
|
|
129
|
+
await fs.mkdir(join(testDir, 'dir-b'), { recursive: true });
|
|
130
|
+
await fs.writeFile(join(testDir, 'dir-a', 'SKILL.md'), '# A');
|
|
131
|
+
if (!(await trySymlink(join(testDir, 'dir-b'), join(testDir, 'dir-a', 'link-to-b'))))
|
|
132
|
+
return;
|
|
133
|
+
if (!(await trySymlink(join(testDir, 'dir-a'), join(testDir, 'dir-b', 'link-to-a'))))
|
|
134
|
+
return;
|
|
135
|
+
const adapter = new LocalFilesystemAdapter({
|
|
136
|
+
id: 'loop-with-opt-in',
|
|
137
|
+
name: 'Loop With Opt-In',
|
|
138
|
+
type: 'local',
|
|
139
|
+
baseUrl: 'file://',
|
|
140
|
+
enabled: true,
|
|
141
|
+
rootDir: testDir,
|
|
142
|
+
followSymlinks: true,
|
|
143
|
+
allowSymlinksOutsideRoot: true,
|
|
144
|
+
rateLimit: { maxRequests: 100, windowMs: 60000, minDelayMs: 0 },
|
|
145
|
+
});
|
|
146
|
+
await adapter.initialize();
|
|
147
|
+
const result = await adapter.search({});
|
|
148
|
+
const loopWarnings = (result.warnings ?? []).filter((w) => w.code === 'loop');
|
|
149
|
+
expect(loopWarnings.length).toBeGreaterThan(0);
|
|
150
|
+
});
|
|
151
|
+
it('should scan a legitimate deep non-looping tree without loop warnings', async () => {
|
|
152
|
+
// 5-level non-looping tree; each level has a SKILL.md. No symlinks.
|
|
153
|
+
let dir = testDir;
|
|
154
|
+
for (const part of ['l1', 'l2', 'l3', 'l4', 'l5']) {
|
|
155
|
+
dir = join(dir, part);
|
|
156
|
+
await fs.mkdir(dir, { recursive: true });
|
|
157
|
+
await fs.writeFile(join(dir, 'SKILL.md'), `# ${part}`);
|
|
158
|
+
}
|
|
159
|
+
const adapter = new LocalFilesystemAdapter({
|
|
160
|
+
id: 'deep-tree',
|
|
161
|
+
name: 'Deep Tree',
|
|
162
|
+
type: 'local',
|
|
163
|
+
baseUrl: 'file://',
|
|
164
|
+
enabled: true,
|
|
165
|
+
rootDir: testDir,
|
|
166
|
+
maxDepth: 10,
|
|
167
|
+
followSymlinks: true,
|
|
168
|
+
rateLimit: { maxRequests: 100, windowMs: 60000, minDelayMs: 0 },
|
|
169
|
+
});
|
|
170
|
+
await adapter.initialize();
|
|
171
|
+
const result = await adapter.search({});
|
|
172
|
+
const loopWarnings = (result.warnings ?? []).filter((w) => w.code === 'loop');
|
|
173
|
+
expect(loopWarnings).toHaveLength(0);
|
|
174
|
+
expect(adapter.skillCount).toBe(5);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe('LocalFilesystemAdapter SMI-4320 byte-wise containment', () => {
|
|
178
|
+
it('accepts a candidate equal to root', () => {
|
|
179
|
+
expect(isRealpathContained('/a/root', '/a/root')).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
it('accepts a candidate strictly inside root', () => {
|
|
182
|
+
expect(isRealpathContained(`/a/root${sep}skill${sep}SKILL.md`, '/a/root')).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
it('rejects trailing-separator-confused neighbour (rootfoo not inside root)', () => {
|
|
185
|
+
// THE regression test for the SMI-4320 trailing-separator guard: without
|
|
186
|
+
// the `+ sep`, `startsWith('/a/root')` accepts `/a/rootfoo` — which is a
|
|
187
|
+
// completely unrelated directory.
|
|
188
|
+
expect(isRealpathContained('/a/rootfoo', '/a/root')).toBe(false);
|
|
189
|
+
expect(isRealpathContained('/a/rootfoo/file', '/a/root')).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
it('rejects candidates outside root', () => {
|
|
192
|
+
expect(isRealpathContained('/b/other', '/a/root')).toBe(false);
|
|
193
|
+
expect(isRealpathContained('/etc/passwd', '/a/root')).toBe(false);
|
|
194
|
+
});
|
|
195
|
+
it('treats case-differing paths as distinct on a case-sensitive volume', () => {
|
|
196
|
+
// Byte-wise, not platform-normalised. This is the SMI-4320 correctness
|
|
197
|
+
// target: removing `normaliseForFs` means `/a/Root` and `/a/root` are
|
|
198
|
+
// genuinely different strings. On macOS APFS default (case-insensitive),
|
|
199
|
+
// `realpath` itself would canonicalise both to the same bytes before
|
|
200
|
+
// this helper runs.
|
|
201
|
+
expect(isRealpathContained(`/a/root${sep}skill`, '/a/Root')).toBe(false);
|
|
202
|
+
expect(isRealpathContained(`/a/Root${sep}skill`, '/a/root')).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
describe('LocalFilesystemAdapter SMI-4320 direct-access containment', () => {
|
|
206
|
+
let testDir;
|
|
207
|
+
let adapter;
|
|
208
|
+
beforeEach(async () => {
|
|
209
|
+
testDir = join(tmpdir(), uniq('skillsmith-4320'));
|
|
210
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
211
|
+
await fs.mkdir(join(testDir, 'skill-one'), { recursive: true });
|
|
212
|
+
await fs.writeFile(join(testDir, 'skill-one', 'SKILL.md'), '---\nname: Skill One\n---\n# Skill One');
|
|
213
|
+
adapter = new LocalFilesystemAdapter({
|
|
214
|
+
id: 'security-direct',
|
|
215
|
+
name: 'Security Direct',
|
|
216
|
+
type: 'local',
|
|
217
|
+
baseUrl: 'file://',
|
|
218
|
+
enabled: true,
|
|
219
|
+
rootDir: testDir,
|
|
220
|
+
followSymlinks: true,
|
|
221
|
+
rateLimit: { maxRequests: 100, windowMs: 60000, minDelayMs: 0 },
|
|
222
|
+
});
|
|
223
|
+
await adapter.initialize();
|
|
224
|
+
});
|
|
225
|
+
afterEach(async () => {
|
|
226
|
+
await fs.rm(testDir, { recursive: true, force: true }).catch(() => undefined);
|
|
227
|
+
});
|
|
228
|
+
it('rejects fetchSkillContent on a symlink pointing outside root (post-scan swap)', async () => {
|
|
229
|
+
if (platform() === 'win32')
|
|
230
|
+
return;
|
|
231
|
+
// Simulate the TOCTOU scenario: legitimate file discovered during scan,
|
|
232
|
+
// replaced between scan and fetch with a symlink to an external poisoned
|
|
233
|
+
// file. The realpath containment in `resolveSkillPath` must reject the
|
|
234
|
+
// fetch before the external file is read.
|
|
235
|
+
const externalDir = join(tmpdir(), uniq('external-poison'));
|
|
236
|
+
await fs.mkdir(externalDir, { recursive: true });
|
|
237
|
+
const poisonFile = join(externalDir, 'SKILL.md');
|
|
238
|
+
await fs.writeFile(poisonFile, '# POISONED — should never be read');
|
|
239
|
+
try {
|
|
240
|
+
// `skill-swap` inside root is a symlink to the external poisoned dir.
|
|
241
|
+
await fs.symlink(externalDir, join(testDir, 'skill-swap'));
|
|
242
|
+
await expect(adapter.fetchSkillContent({ path: 'skill-swap/SKILL.md' })).rejects.toThrow(/realpath containment|Path traversal|Symlink outside root/);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
await fs.rm(externalDir, { recursive: true, force: true }).catch(() => undefined);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
it('rejects getRepository on an escaping symlink', async () => {
|
|
249
|
+
if (platform() === 'win32')
|
|
250
|
+
return;
|
|
251
|
+
const externalDir = join(tmpdir(), uniq('external-getrepo'));
|
|
252
|
+
await fs.mkdir(externalDir, { recursive: true });
|
|
253
|
+
await fs.writeFile(join(externalDir, 'SKILL.md'), '# external');
|
|
254
|
+
try {
|
|
255
|
+
await fs.symlink(externalDir, join(testDir, 'escape-repo'));
|
|
256
|
+
await expect(adapter.getRepository({ path: 'escape-repo/SKILL.md' })).rejects.toThrow(/realpath containment|Path traversal|Symlink outside root/);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
await fs.rm(externalDir, { recursive: true, force: true }).catch(() => undefined);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
it('rejects skillExists on an escaping symlink', async () => {
|
|
263
|
+
if (platform() === 'win32')
|
|
264
|
+
return;
|
|
265
|
+
const externalDir = join(tmpdir(), uniq('external-exists'));
|
|
266
|
+
await fs.mkdir(externalDir, { recursive: true });
|
|
267
|
+
await fs.writeFile(join(externalDir, 'SKILL.md'), '# external');
|
|
268
|
+
try {
|
|
269
|
+
await fs.symlink(externalDir, join(testDir, 'escape-exists'));
|
|
270
|
+
// skillExists must throw (SMI-720 contract on containment failure)
|
|
271
|
+
// rather than silently returning true for an out-of-root realpath.
|
|
272
|
+
await expect(adapter.skillExists({ path: 'escape-exists/SKILL.md' })).rejects.toThrow(/realpath containment|Path traversal|Symlink outside root/);
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
await fs.rm(externalDir, { recursive: true, force: true }).catch(() => undefined);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
it('accepts fetchSkillContent via a symlink that stays inside root', async () => {
|
|
279
|
+
if (platform() === 'win32')
|
|
280
|
+
return;
|
|
281
|
+
// `skill-alias` inside root points to a real skill inside root.
|
|
282
|
+
// Containment check passes; content is read from the real target.
|
|
283
|
+
await fs.symlink(join(testDir, 'skill-one'), join(testDir, 'skill-alias'));
|
|
284
|
+
const content = await adapter.fetchSkillContent({
|
|
285
|
+
path: 'skill-alias/SKILL.md',
|
|
286
|
+
});
|
|
287
|
+
expect(content.rawContent).toContain('# Skill One');
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
describe('LocalFilesystemAdapter SMI-4287 opt-in regression (SMI-4320)', () => {
|
|
291
|
+
let testDir;
|
|
292
|
+
let externalDir;
|
|
293
|
+
beforeEach(async () => {
|
|
294
|
+
testDir = join(tmpdir(), uniq('skillsmith-4287-regr'));
|
|
295
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
296
|
+
externalDir = join(tmpdir(), uniq('external-opt-in'));
|
|
297
|
+
await fs.mkdir(externalDir, { recursive: true });
|
|
298
|
+
await fs.writeFile(join(externalDir, 'SKILL.md'), '# External Permitted');
|
|
299
|
+
});
|
|
300
|
+
afterEach(async () => {
|
|
301
|
+
await fs.rm(testDir, { recursive: true, force: true }).catch(() => undefined);
|
|
302
|
+
await fs.rm(externalDir, { recursive: true, force: true }).catch(() => undefined);
|
|
303
|
+
});
|
|
304
|
+
it('allowSymlinksOutsideRoot: true permits fetchSkillContent via an escaping symlink', async () => {
|
|
305
|
+
if (platform() === 'win32')
|
|
306
|
+
return;
|
|
307
|
+
await fs.symlink(externalDir, join(testDir, 'ext-link'));
|
|
308
|
+
const adapter = new LocalFilesystemAdapter({
|
|
309
|
+
id: 'opt-in-allow',
|
|
310
|
+
name: 'Opt-In Allow',
|
|
311
|
+
type: 'local',
|
|
312
|
+
baseUrl: 'file://',
|
|
313
|
+
enabled: true,
|
|
314
|
+
rootDir: testDir,
|
|
315
|
+
followSymlinks: true,
|
|
316
|
+
allowSymlinksOutsideRoot: true,
|
|
317
|
+
rateLimit: { maxRequests: 100, windowMs: 60000, minDelayMs: 0 },
|
|
318
|
+
});
|
|
319
|
+
await adapter.initialize();
|
|
320
|
+
const content = await adapter.fetchSkillContent({
|
|
321
|
+
path: 'ext-link/SKILL.md',
|
|
322
|
+
});
|
|
323
|
+
expect(content.rawContent).toContain('# External Permitted');
|
|
324
|
+
});
|
|
325
|
+
it('allowSymlinksOutsideRoot: false rejects the same symlink', async () => {
|
|
326
|
+
if (platform() === 'win32')
|
|
327
|
+
return;
|
|
328
|
+
await fs.symlink(externalDir, join(testDir, 'ext-link'));
|
|
329
|
+
const adapter = new LocalFilesystemAdapter({
|
|
330
|
+
id: 'opt-in-reject',
|
|
331
|
+
name: 'Opt-In Reject',
|
|
332
|
+
type: 'local',
|
|
333
|
+
baseUrl: 'file://',
|
|
334
|
+
enabled: true,
|
|
335
|
+
rootDir: testDir,
|
|
336
|
+
followSymlinks: true,
|
|
337
|
+
allowSymlinksOutsideRoot: false,
|
|
338
|
+
rateLimit: { maxRequests: 100, windowMs: 60000, minDelayMs: 0 },
|
|
339
|
+
});
|
|
340
|
+
await adapter.initialize();
|
|
341
|
+
await expect(adapter.fetchSkillContent({ path: 'ext-link/SKILL.md' })).rejects.toThrow(/realpath containment|Path traversal|Symlink outside root/);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
//# sourceMappingURL=LocalFilesystemAdapter.security.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalFilesystemAdapter.security.test.js","sourceRoot":"","sources":["../../tests/LocalFilesystemAdapter.security.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalFilesystemAdapter test suite
|
|
3
|
+
*
|
|
4
|
+
* SMI-591: original adapter tests (extracted from ScraperAdapters.test.ts by
|
|
5
|
+
* SMI-4287 so the file stays under the governance 500-line ceiling).
|
|
6
|
+
*
|
|
7
|
+
* SMI-4287-specific symlink, permission, loop, and case-insensitive coverage
|
|
8
|
+
* lives in the sidecar `LocalFilesystemAdapter.coverage.test.ts` — keeping
|
|
9
|
+
* the two split prevents this file from crossing 500 lines again.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=LocalFilesystemAdapter.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalFilesystemAdapter.test.d.ts","sourceRoot":"","sources":["../../tests/LocalFilesystemAdapter.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|