ferret-scan 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +15 -11
  3. package/bin/ferret.js +104 -8
  4. package/dist/__tests__/AgentMonitor.test.d.ts +6 -0
  5. package/dist/__tests__/AgentMonitor.test.js +235 -0
  6. package/dist/__tests__/AtlasNavigatorReporter.test.d.ts +6 -0
  7. package/dist/__tests__/AtlasNavigatorReporter.test.js +193 -0
  8. package/dist/__tests__/CorrelationAnalyzer.test.d.ts +6 -0
  9. package/dist/__tests__/CorrelationAnalyzer.test.js +211 -0
  10. package/dist/__tests__/IndicatorMatcher.test.d.ts +6 -0
  11. package/dist/__tests__/IndicatorMatcher.test.js +245 -0
  12. package/dist/__tests__/MarketplaceScanner.test.d.ts +5 -0
  13. package/dist/__tests__/MarketplaceScanner.test.js +212 -0
  14. package/dist/__tests__/RuleGenerator.test.d.ts +6 -0
  15. package/dist/__tests__/RuleGenerator.test.js +207 -0
  16. package/dist/__tests__/ThreatFeed.test.d.ts +6 -0
  17. package/dist/__tests__/ThreatFeed.test.js +359 -0
  18. package/dist/__tests__/WatchMode.test.d.ts +6 -0
  19. package/dist/__tests__/WatchMode.test.js +104 -0
  20. package/dist/__tests__/astAnalyzerExtra.test.d.ts +6 -0
  21. package/dist/__tests__/astAnalyzerExtra.test.js +67 -0
  22. package/dist/__tests__/astAnalyzerFull.test.d.ts +6 -0
  23. package/dist/__tests__/astAnalyzerFull.test.js +138 -0
  24. package/dist/__tests__/astAnalyzerPatterns.test.d.ts +6 -0
  25. package/dist/__tests__/astAnalyzerPatterns.test.js +143 -0
  26. package/dist/__tests__/atlas.test.d.ts +6 -0
  27. package/dist/__tests__/atlas.test.js +319 -0
  28. package/dist/__tests__/atlasCatalog.test.d.ts +6 -0
  29. package/dist/__tests__/atlasCatalog.test.js +200 -0
  30. package/dist/__tests__/atlasCatalogExtra.test.d.ts +6 -0
  31. package/dist/__tests__/atlasCatalogExtra.test.js +215 -0
  32. package/dist/__tests__/baseline.test.d.ts +6 -0
  33. package/dist/__tests__/baseline.test.js +321 -0
  34. package/dist/__tests__/baselineExtra.test.d.ts +6 -0
  35. package/dist/__tests__/baselineExtra.test.js +317 -0
  36. package/dist/__tests__/capabilityMapping.test.d.ts +5 -0
  37. package/dist/__tests__/capabilityMapping.test.js +49 -0
  38. package/dist/__tests__/capabilityMappingExtra.test.d.ts +5 -0
  39. package/dist/__tests__/capabilityMappingExtra.test.js +200 -0
  40. package/dist/__tests__/complianceExtra.test.d.ts +6 -0
  41. package/dist/__tests__/complianceExtra.test.js +121 -0
  42. package/dist/__tests__/config.test.js +1 -1
  43. package/dist/__tests__/configLoader.test.d.ts +6 -0
  44. package/dist/__tests__/configLoader.test.js +225 -0
  45. package/dist/__tests__/configLoaderExtra.test.d.ts +6 -0
  46. package/dist/__tests__/configLoaderExtra.test.js +186 -0
  47. package/dist/__tests__/correlationAnalyzerExtra.test.d.ts +5 -0
  48. package/dist/__tests__/correlationAnalyzerExtra.test.js +98 -0
  49. package/dist/__tests__/correlationAnalyzerFull.test.d.ts +6 -0
  50. package/dist/__tests__/correlationAnalyzerFull.test.js +154 -0
  51. package/dist/__tests__/customRules.extra.test.d.ts +6 -0
  52. package/dist/__tests__/customRules.extra.test.js +245 -0
  53. package/dist/__tests__/customRules.test.d.ts +7 -0
  54. package/dist/__tests__/customRules.test.js +347 -0
  55. package/dist/__tests__/dependencyRisk.test.d.ts +5 -0
  56. package/dist/__tests__/dependencyRisk.test.js +248 -0
  57. package/dist/__tests__/dependencyRiskExtra.test.d.ts +6 -0
  58. package/dist/__tests__/dependencyRiskExtra.test.js +177 -0
  59. package/dist/__tests__/featureExitCodes.test.d.ts +7 -0
  60. package/dist/__tests__/featureExitCodes.test.js +332 -0
  61. package/dist/__tests__/fileDiscoveryConfigOnly.test.d.ts +6 -0
  62. package/dist/__tests__/fileDiscoveryConfigOnly.test.js +195 -0
  63. package/dist/__tests__/fileDiscoveryExtra.test.d.ts +6 -0
  64. package/dist/__tests__/fileDiscoveryExtra.test.js +149 -0
  65. package/dist/__tests__/fixer.extra.test.d.ts +6 -0
  66. package/dist/__tests__/fixer.extra.test.js +135 -0
  67. package/dist/__tests__/fixerApply.test.d.ts +6 -0
  68. package/dist/__tests__/fixerApply.test.js +132 -0
  69. package/dist/__tests__/gitHooks.test.d.ts +7 -0
  70. package/dist/__tests__/gitHooks.test.js +188 -0
  71. package/dist/__tests__/htmlReporter.extra.test.d.ts +5 -0
  72. package/dist/__tests__/htmlReporter.extra.test.js +126 -0
  73. package/dist/__tests__/interactiveTui.test.d.ts +6 -0
  74. package/dist/__tests__/interactiveTui.test.js +180 -0
  75. package/dist/__tests__/interactiveTuiCommands.test.d.ts +6 -0
  76. package/dist/__tests__/interactiveTuiCommands.test.js +187 -0
  77. package/dist/__tests__/interactiveTuiMore.test.d.ts +6 -0
  78. package/dist/__tests__/interactiveTuiMore.test.js +194 -0
  79. package/dist/__tests__/interactiveTuiSession.test.d.ts +6 -0
  80. package/dist/__tests__/interactiveTuiSession.test.js +173 -0
  81. package/dist/__tests__/llmAnalysis.test.d.ts +6 -0
  82. package/dist/__tests__/llmAnalysis.test.js +229 -0
  83. package/dist/__tests__/llmAnalysisBuildExcerpt.test.d.ts +6 -0
  84. package/dist/__tests__/llmAnalysisBuildExcerpt.test.js +132 -0
  85. package/dist/__tests__/llmAnalysisExtra.test.d.ts +6 -0
  86. package/dist/__tests__/llmAnalysisExtra.test.js +214 -0
  87. package/dist/__tests__/llmAnalysisFilters.test.d.ts +6 -0
  88. package/dist/__tests__/llmAnalysisFilters.test.js +181 -0
  89. package/dist/__tests__/llmAnalysisMitre.test.d.ts +6 -0
  90. package/dist/__tests__/llmAnalysisMitre.test.js +192 -0
  91. package/dist/__tests__/llmGroqTPM.test.d.ts +6 -0
  92. package/dist/__tests__/llmGroqTPM.test.js +89 -0
  93. package/dist/__tests__/llmProviderRetry.test.d.ts +6 -0
  94. package/dist/__tests__/llmProviderRetry.test.js +172 -0
  95. package/dist/__tests__/mcpValidator.extra.test.d.ts +5 -0
  96. package/dist/__tests__/mcpValidator.extra.test.js +270 -0
  97. package/dist/__tests__/patternMatcherExtra.test.d.ts +7 -0
  98. package/dist/__tests__/patternMatcherExtra.test.js +198 -0
  99. package/dist/__tests__/patternsCommon.test.d.ts +6 -0
  100. package/dist/__tests__/patternsCommon.test.js +107 -0
  101. package/dist/__tests__/policyEnforcement.test.d.ts +5 -0
  102. package/dist/__tests__/policyEnforcement.test.js +510 -0
  103. package/dist/__tests__/quarantineExtra.test.d.ts +5 -0
  104. package/dist/__tests__/quarantineExtra.test.js +214 -0
  105. package/dist/__tests__/redactionExtra.test.d.ts +6 -0
  106. package/dist/__tests__/redactionExtra.test.js +228 -0
  107. package/dist/__tests__/scanDiff.test.d.ts +7 -0
  108. package/dist/__tests__/scanDiff.test.js +266 -0
  109. package/dist/__tests__/scanFull.test.d.ts +6 -0
  110. package/dist/__tests__/scanFull.test.js +158 -0
  111. package/dist/__tests__/scannerDampening.test.d.ts +6 -0
  112. package/dist/__tests__/scannerDampening.test.js +160 -0
  113. package/dist/__tests__/scannerExtra.test.d.ts +6 -0
  114. package/dist/__tests__/scannerExtra.test.js +194 -0
  115. package/dist/__tests__/scannerMitre.test.d.ts +5 -0
  116. package/dist/__tests__/scannerMitre.test.js +141 -0
  117. package/dist/__tests__/scannerSSRF.test.d.ts +5 -0
  118. package/dist/__tests__/scannerSSRF.test.js +149 -0
  119. package/dist/__tests__/schemas.test.d.ts +6 -0
  120. package/dist/__tests__/schemas.test.js +125 -0
  121. package/dist/__tests__/webhooks.extra.test.d.ts +6 -0
  122. package/dist/__tests__/webhooks.extra.test.js +144 -0
  123. package/dist/__tests__/webhooks.test.d.ts +6 -0
  124. package/dist/__tests__/webhooks.test.js +154 -0
  125. package/dist/features/customRules.js +22 -29
  126. package/dist/features/mcpTrustScore.d.ts +17 -0
  127. package/dist/features/mcpTrustScore.js +74 -0
  128. package/dist/features/mcpValidator.d.ts +2 -0
  129. package/dist/features/mcpValidator.js +13 -0
  130. package/dist/features/policyEnforcement.d.ts +22 -22
  131. package/dist/intelligence/ThreatFeed.js +207 -62
  132. package/dist/remediation/Quarantine.js +24 -6
  133. package/dist/reporters/ConsoleReporter.js +10 -0
  134. package/dist/reporters/HtmlReporter.js +5 -0
  135. package/dist/reporters/SarifReporter.d.ts +1 -0
  136. package/dist/reporters/SarifReporter.js +1 -0
  137. package/dist/scanner/IAnalyzer.d.ts +19 -0
  138. package/dist/scanner/IAnalyzer.js +5 -0
  139. package/dist/scanner/Scanner.js +64 -125
  140. package/dist/scanner/analyzers/CapabilityAnalyzer.d.ts +8 -0
  141. package/dist/scanner/analyzers/CapabilityAnalyzer.js +19 -0
  142. package/dist/scanner/analyzers/DependencyAnalyzer.d.ts +8 -0
  143. package/dist/scanner/analyzers/DependencyAnalyzer.js +18 -0
  144. package/dist/scanner/analyzers/EntropyAnalyzer.d.ts +8 -0
  145. package/dist/scanner/analyzers/EntropyAnalyzer.js +12 -0
  146. package/dist/scanner/analyzers/LlmAnalyzer.d.ts +17 -0
  147. package/dist/scanner/analyzers/LlmAnalyzer.js +36 -0
  148. package/dist/scanner/analyzers/McpAnalyzer.d.ts +8 -0
  149. package/dist/scanner/analyzers/McpAnalyzer.js +19 -0
  150. package/dist/scanner/analyzers/SemanticAnalyzer.d.ts +8 -0
  151. package/dist/scanner/analyzers/SemanticAnalyzer.js +21 -0
  152. package/dist/scanner/analyzers/ThreatIntelAnalyzer.d.ts +8 -0
  153. package/dist/scanner/analyzers/ThreatIntelAnalyzer.js +21 -0
  154. package/dist/types.d.ts +17 -0
  155. package/dist/types.js +1 -1
  156. package/dist/utils/safeRegex.d.ts +12 -51
  157. package/dist/utils/safeRegex.js +45 -62
  158. package/dist/utils/schemas.d.ts +64 -64
  159. package/package.json +25 -19
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Dependency Risk Analysis Tests
3
+ */
4
+ import { parsePackageJson, dependencyAssessmentsToFindings } from '../features/dependencyRisk.js';
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ import * as os from 'node:os';
8
+ describe('parsePackageJson', () => {
9
+ let tmpDir;
10
+ let pkgPath;
11
+ beforeEach(() => {
12
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ferret-dep-test-'));
13
+ pkgPath = path.join(tmpDir, 'package.json');
14
+ });
15
+ afterEach(() => {
16
+ fs.rmSync(tmpDir, { recursive: true, force: true });
17
+ });
18
+ it('returns error for non-existent file', () => {
19
+ const { packages, errors } = parsePackageJson('/nonexistent/package.json');
20
+ expect(packages).toHaveLength(0);
21
+ expect(errors.length).toBeGreaterThan(0);
22
+ expect(errors[0]).toContain('not found');
23
+ });
24
+ it('parses a simple package.json with dependencies', () => {
25
+ fs.writeFileSync(pkgPath, JSON.stringify({
26
+ name: 'test-app',
27
+ dependencies: {
28
+ 'express': '^4.18.0',
29
+ 'lodash': '4.17.21',
30
+ },
31
+ }));
32
+ const { packages, errors } = parsePackageJson(pkgPath);
33
+ expect(errors).toHaveLength(0);
34
+ expect(packages).toHaveLength(2);
35
+ expect(packages.map(p => p.name)).toContain('express');
36
+ expect(packages.map(p => p.name)).toContain('lodash');
37
+ expect(packages.every(p => p.type === 'dependency')).toBe(true);
38
+ });
39
+ it('parses devDependencies', () => {
40
+ fs.writeFileSync(pkgPath, JSON.stringify({
41
+ name: 'test-app',
42
+ devDependencies: {
43
+ 'jest': '^29.0.0',
44
+ },
45
+ }));
46
+ const { packages } = parsePackageJson(pkgPath);
47
+ expect(packages[0]?.type).toBe('devDependency');
48
+ expect(packages[0]?.name).toBe('jest');
49
+ });
50
+ it('parses peerDependencies', () => {
51
+ fs.writeFileSync(pkgPath, JSON.stringify({
52
+ name: 'test-lib',
53
+ peerDependencies: {
54
+ 'react': '>=17.0.0',
55
+ },
56
+ }));
57
+ const { packages } = parsePackageJson(pkgPath);
58
+ expect(packages[0]?.type).toBe('peerDependency');
59
+ });
60
+ it('parses optionalDependencies', () => {
61
+ fs.writeFileSync(pkgPath, JSON.stringify({
62
+ name: 'test-app',
63
+ optionalDependencies: {
64
+ 'fsevents': '^2.3.0',
65
+ },
66
+ }));
67
+ const { packages } = parsePackageJson(pkgPath);
68
+ expect(packages[0]?.type).toBe('optionalDependency');
69
+ });
70
+ it('detects local file dependencies', () => {
71
+ fs.writeFileSync(pkgPath, JSON.stringify({
72
+ name: 'test-app',
73
+ dependencies: {
74
+ 'my-local-lib': 'file:../local-lib',
75
+ },
76
+ }));
77
+ const { packages } = parsePackageJson(pkgPath);
78
+ expect(packages[0]?.isLocal).toBe(true);
79
+ });
80
+ it('detects git dependencies', () => {
81
+ fs.writeFileSync(pkgPath, JSON.stringify({
82
+ name: 'test-app',
83
+ dependencies: {
84
+ 'my-git-dep': 'github:user/repo#main',
85
+ },
86
+ }));
87
+ const { packages } = parsePackageJson(pkgPath);
88
+ expect(packages[0]?.isGit).toBe(true);
89
+ });
90
+ it('detects URL dependencies', () => {
91
+ fs.writeFileSync(pkgPath, JSON.stringify({
92
+ name: 'test-app',
93
+ dependencies: {
94
+ 'url-dep': 'https://example.com/package.tgz',
95
+ },
96
+ }));
97
+ const { packages } = parsePackageJson(pkgPath);
98
+ expect(packages[0]?.isUrl).toBe(true);
99
+ });
100
+ it('returns error for invalid JSON', () => {
101
+ fs.writeFileSync(pkgPath, 'not valid json {{{');
102
+ const { packages, errors } = parsePackageJson(pkgPath);
103
+ expect(packages).toHaveLength(0);
104
+ expect(errors.length).toBeGreaterThan(0);
105
+ });
106
+ it('handles empty dependencies section', () => {
107
+ fs.writeFileSync(pkgPath, JSON.stringify({
108
+ name: 'empty-app',
109
+ dependencies: {},
110
+ }));
111
+ const { packages, errors } = parsePackageJson(pkgPath);
112
+ expect(errors).toHaveLength(0);
113
+ expect(packages).toHaveLength(0);
114
+ });
115
+ it('handles package.json with no dependencies', () => {
116
+ fs.writeFileSync(pkgPath, JSON.stringify({
117
+ name: 'no-deps',
118
+ version: '1.0.0',
119
+ }));
120
+ const { packages, errors } = parsePackageJson(pkgPath);
121
+ expect(errors).toHaveLength(0);
122
+ expect(packages).toHaveLength(0);
123
+ });
124
+ });
125
+ describe('dependencyAssessmentsToFindings', () => {
126
+ function makeResult(overrides = {}) {
127
+ return {
128
+ packageJsonPath: '/project/package.json',
129
+ totalPackages: 0,
130
+ assessments: [],
131
+ summary: { critical: 0, high: 0, medium: 0, low: 0, vulnerable: 0 },
132
+ ...overrides,
133
+ };
134
+ }
135
+ it('returns empty array for no assessments', () => {
136
+ const findings = dependencyAssessmentsToFindings(makeResult());
137
+ expect(findings).toEqual([]);
138
+ });
139
+ it('converts issues to findings', () => {
140
+ const result = makeResult({
141
+ assessments: [
142
+ {
143
+ package: { name: 'event-stream', version: '3.3.6', type: 'dependency' },
144
+ riskLevel: 'critical',
145
+ issues: [
146
+ {
147
+ type: 'known-malicious',
148
+ severity: 'CRITICAL',
149
+ description: 'Known malicious package',
150
+ remediation: 'Remove immediately',
151
+ },
152
+ ],
153
+ vulnerabilities: [],
154
+ },
155
+ ],
156
+ });
157
+ const findings = dependencyAssessmentsToFindings(result);
158
+ expect(findings).toHaveLength(1);
159
+ expect(findings[0]?.severity).toBe('CRITICAL');
160
+ expect(findings[0]?.match).toBe('event-stream@3.3.6');
161
+ expect(findings[0]?.ruleId).toMatch(/^DEP-/);
162
+ });
163
+ it('converts vulnerabilities to findings', () => {
164
+ const result = makeResult({
165
+ assessments: [
166
+ {
167
+ package: { name: 'lodash', version: '4.17.0', type: 'dependency' },
168
+ riskLevel: 'high',
169
+ issues: [],
170
+ vulnerabilities: [
171
+ {
172
+ id: 'CVE-2021-1234',
173
+ severity: 'high',
174
+ title: 'Prototype Pollution',
175
+ url: 'https://nvd.nist.gov/CVE-2021-1234',
176
+ fixAvailable: true,
177
+ affectedVersions: '<4.17.21',
178
+ },
179
+ ],
180
+ },
181
+ ],
182
+ });
183
+ const findings = dependencyAssessmentsToFindings(result);
184
+ expect(findings).toHaveLength(1);
185
+ expect(findings[0]?.severity).toBe('HIGH');
186
+ expect(findings[0]?.ruleId).toBe('DEP-VULN-CVE-2021-1234');
187
+ expect(findings[0]?.remediation).toContain('Update lodash');
188
+ });
189
+ it('maps vulnerability severity correctly', () => {
190
+ const result = makeResult({
191
+ assessments: [
192
+ {
193
+ package: { name: 'pkg', version: '1.0.0', type: 'dependency' },
194
+ riskLevel: 'medium',
195
+ issues: [],
196
+ vulnerabilities: [
197
+ { id: 'CVE-001', severity: 'critical', title: 'Critical Vuln', fixAvailable: false },
198
+ { id: 'CVE-002', severity: 'moderate', title: 'Moderate Vuln', fixAvailable: false },
199
+ { id: 'CVE-003', severity: 'low', title: 'Low Vuln', fixAvailable: false },
200
+ ],
201
+ },
202
+ ],
203
+ });
204
+ const findings = dependencyAssessmentsToFindings(result);
205
+ expect(findings[0]?.severity).toBe('CRITICAL');
206
+ expect(findings[1]?.severity).toBe('MEDIUM');
207
+ expect(findings[2]?.severity).toBe('LOW');
208
+ });
209
+ it('includes no-fix message when fixAvailable is false', () => {
210
+ const result = makeResult({
211
+ assessments: [
212
+ {
213
+ package: { name: 'vulnerable-pkg', version: '1.0.0', type: 'dependency' },
214
+ riskLevel: 'high',
215
+ issues: [],
216
+ vulnerabilities: [
217
+ { id: 'CVE-001', severity: 'high', title: 'Bad Vuln', fixAvailable: false },
218
+ ],
219
+ },
220
+ ],
221
+ });
222
+ const findings = dependencyAssessmentsToFindings(result);
223
+ expect(findings[0]?.remediation).toContain('No fix available');
224
+ });
225
+ it('assigns correct risk scores', () => {
226
+ const result = makeResult({
227
+ assessments: [
228
+ {
229
+ package: { name: 'pkg', version: '1.0.0', type: 'dependency' },
230
+ riskLevel: 'critical',
231
+ issues: [
232
+ { type: 'known-malicious', severity: 'CRITICAL', description: 'X', remediation: 'Y' },
233
+ { type: 'git-dep', severity: 'HIGH', description: 'X', remediation: 'Y' },
234
+ { type: 'unpinned', severity: 'MEDIUM', description: 'X', remediation: 'Y' },
235
+ { type: 'local-dep', severity: 'LOW', description: 'X', remediation: 'Y' },
236
+ ],
237
+ vulnerabilities: [],
238
+ },
239
+ ],
240
+ });
241
+ const findings = dependencyAssessmentsToFindings(result);
242
+ expect(findings[0]?.riskScore).toBe(95); // CRITICAL
243
+ expect(findings[1]?.riskScore).toBe(80); // HIGH
244
+ expect(findings[2]?.riskScore).toBe(60); // MEDIUM
245
+ expect(findings[3]?.riskScore).toBe(40); // LOW
246
+ });
247
+ });
248
+ //# sourceMappingURL=dependencyRisk.test.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Additional Dependency Risk Tests
3
+ * Covers analyzePackage, analyzeDependencies, findAndAnalyzeDependencies
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=dependencyRiskExtra.test.d.ts.map
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Additional Dependency Risk Tests
3
+ * Covers analyzePackage, analyzeDependencies, findAndAnalyzeDependencies
4
+ */
5
+ import { analyzeDependencies, } from '../features/dependencyRisk.js';
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import * as os from 'node:os';
9
+ // Mock execSync to prevent real npm audit calls
10
+ jest.mock('node:child_process', () => ({
11
+ execSync: jest.fn().mockReturnValue(JSON.stringify({
12
+ vulnerabilities: {},
13
+ })),
14
+ }));
15
+ describe('analyzeDependencies', () => {
16
+ let tmpDir;
17
+ beforeEach(() => {
18
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ferret-deps-analyze-'));
19
+ });
20
+ afterEach(() => {
21
+ fs.rmSync(tmpDir, { recursive: true, force: true });
22
+ });
23
+ it('handles non-existent package.json', () => {
24
+ const result = analyzeDependencies('/nonexistent/package.json');
25
+ expect(result.totalPackages).toBe(0);
26
+ expect(result.assessments).toHaveLength(0);
27
+ });
28
+ it('analyzes package with no risky deps', () => {
29
+ const pkgPath = path.join(tmpDir, 'package.json');
30
+ fs.writeFileSync(pkgPath, JSON.stringify({
31
+ name: 'safe-app',
32
+ dependencies: {
33
+ 'express': '^4.18.0',
34
+ 'lodash': '4.17.21',
35
+ },
36
+ }));
37
+ const result = analyzeDependencies(pkgPath, false);
38
+ expect(result.totalPackages).toBe(2);
39
+ expect(result.summary.critical).toBe(0);
40
+ });
41
+ it('detects known high-risk package (event-stream)', () => {
42
+ const pkgPath = path.join(tmpDir, 'package.json');
43
+ fs.writeFileSync(pkgPath, JSON.stringify({
44
+ name: 'risky-app',
45
+ dependencies: {
46
+ 'event-stream': '3.3.6',
47
+ },
48
+ }));
49
+ const result = analyzeDependencies(pkgPath, false);
50
+ expect(result.summary.critical).toBeGreaterThan(0);
51
+ const criticalAssessments = result.assessments.filter(a => a.riskLevel === 'critical');
52
+ expect(criticalAssessments.length).toBeGreaterThan(0);
53
+ expect(criticalAssessments[0]?.issues.some(i => i.type === 'known-malicious')).toBe(true);
54
+ });
55
+ it('detects flatmap-stream as high-risk', () => {
56
+ const pkgPath = path.join(tmpDir, 'package.json');
57
+ fs.writeFileSync(pkgPath, JSON.stringify({
58
+ dependencies: { 'flatmap-stream': '0.1.1' },
59
+ }));
60
+ const result = analyzeDependencies(pkgPath, false);
61
+ const assessment = result.assessments.find(a => a.package.name === 'flatmap-stream');
62
+ expect(assessment?.riskLevel).toBe('critical');
63
+ });
64
+ it('detects security concern packages (node-serialize)', () => {
65
+ const pkgPath = path.join(tmpDir, 'package.json');
66
+ fs.writeFileSync(pkgPath, JSON.stringify({
67
+ dependencies: { 'node-serialize': '0.0.4' },
68
+ }));
69
+ const result = analyzeDependencies(pkgPath, false);
70
+ const assessment = result.assessments.find(a => a.package.name === 'node-serialize');
71
+ expect(assessment?.issues.some(i => i.type === 'security-concern')).toBe(true);
72
+ });
73
+ it('detects vm2 as medium security concern', () => {
74
+ const pkgPath = path.join(tmpDir, 'package.json');
75
+ fs.writeFileSync(pkgPath, JSON.stringify({
76
+ dependencies: { 'vm2': '3.9.19' },
77
+ }));
78
+ const result = analyzeDependencies(pkgPath, false);
79
+ const assessment = result.assessments.find(a => a.package.name === 'vm2');
80
+ expect(assessment?.issues.some(i => i.type === 'security-concern')).toBe(true);
81
+ });
82
+ it('detects git dependency', () => {
83
+ const pkgPath = path.join(tmpDir, 'package.json');
84
+ fs.writeFileSync(pkgPath, JSON.stringify({
85
+ dependencies: { 'my-lib': 'github:user/repo#main' },
86
+ }));
87
+ const result = analyzeDependencies(pkgPath, false);
88
+ const assessment = result.assessments.find(a => a.package.name === 'my-lib');
89
+ expect(assessment?.issues.some(i => i.type === 'git-dependency')).toBe(true);
90
+ });
91
+ it('detects URL dependency', () => {
92
+ const pkgPath = path.join(tmpDir, 'package.json');
93
+ fs.writeFileSync(pkgPath, JSON.stringify({
94
+ dependencies: { 'url-dep': 'https://example.com/pkg.tgz' },
95
+ }));
96
+ const result = analyzeDependencies(pkgPath, false);
97
+ const assessment = result.assessments.find(a => a.package.name === 'url-dep');
98
+ expect(assessment?.issues.some(i => i.type === 'url-dependency')).toBe(true);
99
+ });
100
+ it('detects insecure HTTP URL', () => {
101
+ const pkgPath = path.join(tmpDir, 'package.json');
102
+ fs.writeFileSync(pkgPath, JSON.stringify({
103
+ dependencies: { 'http-dep': 'http://example.com/pkg.tgz' },
104
+ }));
105
+ const result = analyzeDependencies(pkgPath, false);
106
+ const assessment = result.assessments.find(a => a.package.name === 'http-dep');
107
+ expect(assessment?.issues.some(i => i.type === 'insecure-url')).toBe(true);
108
+ });
109
+ it('detects wildcard version', () => {
110
+ const pkgPath = path.join(tmpDir, 'package.json');
111
+ fs.writeFileSync(pkgPath, JSON.stringify({
112
+ dependencies: { 'wild-dep': '*' },
113
+ }));
114
+ const result = analyzeDependencies(pkgPath, false);
115
+ const assessment = result.assessments.find(a => a.package.name === 'wild-dep');
116
+ expect(assessment?.issues.some(i => i.type === 'unpinned-version')).toBe(true);
117
+ });
118
+ it('detects "latest" as unpinned version', () => {
119
+ const pkgPath = path.join(tmpDir, 'package.json');
120
+ fs.writeFileSync(pkgPath, JSON.stringify({
121
+ dependencies: { 'latest-dep': 'latest' },
122
+ }));
123
+ const result = analyzeDependencies(pkgPath, false);
124
+ const assessment = result.assessments.find(a => a.package.name === 'latest-dep');
125
+ expect(assessment?.issues.some(i => i.type === 'unpinned-version')).toBe(true);
126
+ });
127
+ it('detects very early version (0.0.x) as possibly-abandoned', () => {
128
+ const pkgPath = path.join(tmpDir, 'package.json');
129
+ fs.writeFileSync(pkgPath, JSON.stringify({
130
+ dependencies: { 'old-dep': '0.0.1' },
131
+ }));
132
+ const result = analyzeDependencies(pkgPath, false);
133
+ const assessment = result.assessments.find(a => a.package.name === 'old-dep');
134
+ expect(assessment?.issues.some(i => i.type === 'possibly-abandoned')).toBe(true);
135
+ });
136
+ it('calculates correct summary counts', () => {
137
+ const pkgPath = path.join(tmpDir, 'package.json');
138
+ fs.writeFileSync(pkgPath, JSON.stringify({
139
+ dependencies: {
140
+ 'event-stream': '3.3.6', // critical
141
+ 'express': '^4.18.0', // none
142
+ 'node-serialize': '0.0.4', // high
143
+ },
144
+ }));
145
+ const result = analyzeDependencies(pkgPath, false);
146
+ expect(result.summary.critical).toBeGreaterThan(0);
147
+ expect(result.totalPackages).toBe(3);
148
+ });
149
+ it('runs with audit by default (mocked)', () => {
150
+ const pkgPath = path.join(tmpDir, 'package.json');
151
+ fs.writeFileSync(pkgPath, JSON.stringify({
152
+ dependencies: { 'lodash': '4.17.21' },
153
+ }));
154
+ // Should not throw even with audit=true
155
+ expect(() => analyzeDependencies(pkgPath, true)).not.toThrow();
156
+ });
157
+ });
158
+ describe('local and file dependencies', () => {
159
+ let tmpDir;
160
+ beforeEach(() => {
161
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ferret-local-deps-'));
162
+ });
163
+ afterEach(() => {
164
+ fs.rmSync(tmpDir, { recursive: true, force: true });
165
+ });
166
+ it('detects local file dependency', () => {
167
+ const pkgPath = path.join(tmpDir, 'package.json');
168
+ fs.writeFileSync(pkgPath, JSON.stringify({
169
+ dependencies: { 'local-lib': 'file:../local-lib' },
170
+ }));
171
+ const result = analyzeDependencies(pkgPath, false);
172
+ const assessment = result.assessments.find(a => a.package.name === 'local-lib');
173
+ expect(assessment?.issues.some(i => i.type === 'local-dependency')).toBe(true);
174
+ expect(assessment?.riskLevel).toBe('low');
175
+ });
176
+ });
177
+ //# sourceMappingURL=dependencyRiskExtra.test.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Feature ExitCodes Tests
3
+ * Tests for features/exitCodes.ts: determineExitCode, generateExitCodeSummary,
4
+ * formatExitCodeForCI, parseExitCodesFromEnv, validateExitCodes.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=featureExitCodes.test.d.ts.map