x-fidelity 2.12.1 → 2.13.1
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 +23 -0
- package/dist/archetypes/java-microservice.json +1 -1
- package/dist/archetypes/node-fullstack.json +6 -5
- package/dist/core/engine/engineSetup.js +1 -14
- package/dist/core/engine/engineSetup.test.js +0 -25
- package/dist/facts/repoDependencyFacts.js +15 -6
- package/dist/facts/repoDependencyFacts.test.js +57 -14
- package/dist/facts/repoFilesystemFacts.js +20 -8
- package/dist/facts/repoFilesystemFacts.test.js +12 -8
- package/dist/operators/outdatedFramework.js +1 -1
- package/dist/server/configServer.js +1 -1
- package/dist/utils/jsonSchemas.js +20 -9
- package/package.json +1 -1
- package/src/archetypes/java-microservice.json +1 -1
- package/src/archetypes/node-fullstack.json +6 -5
- package/src/core/engine/engineSetup.test.ts +0 -29
- package/src/core/engine/engineSetup.ts +1 -15
- package/src/facts/repoDependencyFacts.test.ts +69 -18
- package/src/facts/repoDependencyFacts.ts +18 -7
- package/src/facts/repoFilesystemFacts.test.ts +12 -8
- package/src/facts/repoFilesystemFacts.ts +25 -9
- package/src/operators/outdatedFramework.ts +1 -1
- package/src/server/configServer.ts +1 -1
- package/src/types/typeDefs.ts +17 -1
- package/src/utils/jsonSchemas.ts +24 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
## [2.13.1](https://github.com/zotoio/x-fidelity/compare/v2.13.0...v2.13.1) (2024-08-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **schema:** relax for fix ([ee5e123](https://github.com/zotoio/x-fidelity/commit/ee5e12350cef95dcdcbe3e4930b1ca6f5a9b4609))
|
|
7
|
+
|
|
8
|
+
# [2.13.0](https://github.com/zotoio/x-fidelity/compare/v2.12.1...v2.13.0) (2024-08-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* add readonly constraint to archetypeSchema ([c74d116](https://github.com/zotoio/x-fidelity/commit/c74d1168733795aac523ba8ceb33020f2f41911e))
|
|
14
|
+
* Improve path traversal prevention in repoFilesystemFacts.ts ([f65f595](https://github.com/zotoio/x-fidelity/commit/f65f59532683e7a8e392e8bcd259869b8fe2f006))
|
|
15
|
+
* **schema:** fix schema validation and incorrect default rule code in setupEngine ([0a3c65c](https://github.com/zotoio/x-fidelity/commit/0a3c65ce5d0873ec1b3e679ac343ae91e6c0e463))
|
|
16
|
+
* update import statement for RuleConfigSchema ([a658127](https://github.com/zotoio/x-fidelity/commit/a6581272708bcf77a37040c1fb0a6d0411d29a36))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* Add new types for IsBlacklistedParams and isWhitelistedParams ([2c43c82](https://github.com/zotoio/x-fidelity/commit/2c43c822f5dbeeb8f10fcf157886fe607e451fe3))
|
|
22
|
+
* Update archetype typedef and jsonschema to validate semver strings ([2bba2b1](https://github.com/zotoio/x-fidelity/commit/2bba2b15a66f0937609b04815b029ff181f86ccf))
|
|
23
|
+
|
|
1
24
|
## [2.12.1](https://github.com/zotoio/x-fidelity/compare/v2.12.0...v2.12.1) (2024-08-25)
|
|
2
25
|
|
|
3
26
|
|
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
],
|
|
22
22
|
"config": {
|
|
23
23
|
"minimumDependencyVersions": {
|
|
24
|
-
"@types/react": "
|
|
25
|
-
"react": "
|
|
26
|
-
"@yarnpkg/lockfile": "
|
|
27
|
-
"commander": "
|
|
28
|
-
"nodemon": "
|
|
24
|
+
"@types/react": ">=18.0.0",
|
|
25
|
+
"react": "~17.0.0",
|
|
26
|
+
"@yarnpkg/lockfile": "<1.2.0",
|
|
27
|
+
"commander": ">=2.0.0 <13.0.0",
|
|
28
|
+
"nodemon": ">4.9.0",
|
|
29
|
+
"@colors/colors": "1.7.0 || 1.6.0"
|
|
29
30
|
},
|
|
30
31
|
"standardStructure": {
|
|
31
32
|
"app": {
|
|
@@ -34,7 +34,7 @@ function setupEngine(params) {
|
|
|
34
34
|
// Add rules to engine
|
|
35
35
|
logger_1.logger.info(`=== loading json rules..`);
|
|
36
36
|
const config = yield configManager_1.ConfigManager.getConfig({ archetype, logPrefix: executionLogPrefix });
|
|
37
|
-
logger_1.logger.debug(config.rules);
|
|
37
|
+
logger_1.logger.debug(`rules loaded: ${config.rules}`);
|
|
38
38
|
const addedRules = new Set();
|
|
39
39
|
config.rules.forEach((rule) => {
|
|
40
40
|
try {
|
|
@@ -61,19 +61,6 @@ function setupEngine(params) {
|
|
|
61
61
|
logger_1.logger.error(e.message);
|
|
62
62
|
}
|
|
63
63
|
});
|
|
64
|
-
if (addedRules.size === 0) {
|
|
65
|
-
logger_1.logger.info('No valid rules were added. Adding default rules.');
|
|
66
|
-
engine.addRule({
|
|
67
|
-
name: 'default-rule-1',
|
|
68
|
-
conditions: { all: [] },
|
|
69
|
-
event: { type: 'warning', params: { message: 'Default rule 1 triggered' } }
|
|
70
|
-
});
|
|
71
|
-
engine.addRule({
|
|
72
|
-
name: 'default-rule-2',
|
|
73
|
-
conditions: { all: [] },
|
|
74
|
-
event: { type: 'warning', params: { message: 'Default rule 2 triggered' } }
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
64
|
engine.on('success', (_a) => __awaiter(this, [_a], void 0, function* ({ type, params }) {
|
|
78
65
|
if (type === 'warning') {
|
|
79
66
|
logger_1.logger.warn(`warning detected: ${JSON.stringify(params)}`);
|
|
@@ -83,31 +83,6 @@ describe('setupEngine', () => {
|
|
|
83
83
|
expect(mockOn).toHaveBeenCalledTimes(1);
|
|
84
84
|
expect(engine).toBeDefined();
|
|
85
85
|
}));
|
|
86
|
-
it('should handle errors when loading rules and add default rules', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
87
|
-
const mockAddRule = jest.fn();
|
|
88
|
-
json_rules_engine_1.Engine.mockImplementation(() => ({
|
|
89
|
-
addOperator: jest.fn(),
|
|
90
|
-
addRule: mockAddRule,
|
|
91
|
-
addFact: jest.fn(),
|
|
92
|
-
on: jest.fn()
|
|
93
|
-
}));
|
|
94
|
-
configManager_1.ConfigManager.getConfig.mockResolvedValue({
|
|
95
|
-
archetype: mockArchetypeConfig,
|
|
96
|
-
rules: [
|
|
97
|
-
undefined,
|
|
98
|
-
{ name: undefined },
|
|
99
|
-
{ name: 'invalidRule', conditions: null }
|
|
100
|
-
],
|
|
101
|
-
cliOptions: {}
|
|
102
|
-
});
|
|
103
|
-
yield (0, engineSetup_1.setupEngine)(mockParams);
|
|
104
|
-
expect(logger_1.logger.error).toHaveBeenCalledWith('Invalid rule configuration: rule or rule name is undefined');
|
|
105
|
-
expect(logger_1.logger.error).toHaveBeenCalledWith('Invalid rule configuration: rule or rule name is undefined');
|
|
106
|
-
expect(logger_1.logger.error).toHaveBeenCalledWith('Error loading rule: invalidRule');
|
|
107
|
-
expect(logger_1.logger.info).toHaveBeenCalledWith('No valid rules were added. Adding default rules.');
|
|
108
|
-
expect(mockAddRule).toHaveBeenCalledWith(expect.objectContaining({ name: 'default-rule-1' }));
|
|
109
|
-
expect(mockAddRule).toHaveBeenCalledWith(expect.objectContaining({ name: 'default-rule-2' }));
|
|
110
|
-
}));
|
|
111
86
|
it('should set up event listeners for success events', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
112
87
|
const mockOn = jest.fn();
|
|
113
88
|
json_rules_engine_1.Engine.mockImplementation(() => ({
|
|
@@ -214,28 +214,37 @@ function repoDependencyAnalysis(params, almanac) {
|
|
|
214
214
|
function semverValid(installed, required) {
|
|
215
215
|
// Remove potential @namespace from installed version
|
|
216
216
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
217
|
-
|
|
218
|
-
if (!
|
|
217
|
+
installed = installed.includes('@') ? installed.split('@').pop() : installed;
|
|
218
|
+
if (!installed || !required) {
|
|
219
219
|
return true;
|
|
220
220
|
}
|
|
221
|
+
// ensure that both inputs are now valid semver or range strings
|
|
222
|
+
if (!semver.valid(installed) && !semver.validRange(installed)) {
|
|
223
|
+
logger_1.logger.error(`semverValid: invalid installed version or range: ${installed}`);
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
if (!semver.valid(required) && !semver.validRange(required)) {
|
|
227
|
+
logger_1.logger.error(`semverValid: invalid required version or range: ${required}`);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
221
230
|
// If 'installed' is a single version and 'required' is a range
|
|
222
231
|
if (semver.valid(installed) && semver.validRange(required)) {
|
|
223
|
-
logger_1.logger.
|
|
232
|
+
logger_1.logger.info('range vs version');
|
|
224
233
|
return semver.satisfies(installed, required);
|
|
225
234
|
}
|
|
226
235
|
// If 'required' is a single version and 'installed' is a range
|
|
227
236
|
if (semver.valid(required) && semver.validRange(installed)) {
|
|
228
|
-
logger_1.logger.
|
|
237
|
+
logger_1.logger.info('version vs range');
|
|
229
238
|
return semver.satisfies(required, installed);
|
|
230
239
|
}
|
|
231
240
|
// If both are single versions, simply compare them
|
|
232
241
|
if (semver.valid(required) && semver.valid(installed)) {
|
|
233
|
-
logger_1.logger.
|
|
242
|
+
logger_1.logger.info('version vs version');
|
|
234
243
|
return semver.gt(installed, required);
|
|
235
244
|
}
|
|
236
245
|
// If both are ranges, check if they intersect
|
|
237
246
|
if (semver.validRange(required) && semver.validRange(installed)) {
|
|
238
|
-
logger_1.logger.
|
|
247
|
+
logger_1.logger.info('range vs range');
|
|
239
248
|
return semver.intersects(required, installed);
|
|
240
249
|
}
|
|
241
250
|
return false;
|
|
@@ -38,6 +38,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
38
38
|
const repoDependencyFacts = __importStar(require("./repoDependencyFacts"));
|
|
39
39
|
const child_process_1 = require("child_process");
|
|
40
40
|
const fs_1 = __importDefault(require("fs"));
|
|
41
|
+
const repoDependencyFacts_1 = require("./repoDependencyFacts");
|
|
41
42
|
jest.mock('child_process');
|
|
42
43
|
jest.mock('fs');
|
|
43
44
|
jest.mock('../utils/logger');
|
|
@@ -142,28 +143,70 @@ describe('repoDependencyFacts', () => {
|
|
|
142
143
|
});
|
|
143
144
|
describe('semverValid', () => {
|
|
144
145
|
it('should return true for valid version comparisons', () => {
|
|
145
|
-
expect(
|
|
146
|
-
expect(
|
|
147
|
-
expect(
|
|
148
|
-
expect(
|
|
146
|
+
expect((0, repoDependencyFacts_1.semverValid)('2.0.0', '^1.0.0')).toBe(false);
|
|
147
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.5.0', '1.0.0 - 2.0.0')).toBe(true);
|
|
148
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.0.0', '1.0.0')).toBe(true);
|
|
149
|
+
expect((0, repoDependencyFacts_1.semverValid)('2.0.0', '>=1.0.0')).toBe(true);
|
|
149
150
|
});
|
|
150
151
|
it('should return false for invalid version comparisons', () => {
|
|
151
|
-
expect(
|
|
152
|
-
expect(
|
|
153
|
-
expect(
|
|
152
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.0.0', '^2.0.0')).toBe(false);
|
|
153
|
+
expect((0, repoDependencyFacts_1.semverValid)('3.0.0', '1.0.0 - 2.0.0')).toBe(false);
|
|
154
|
+
expect((0, repoDependencyFacts_1.semverValid)('0.9.0', '>=1.0.0')).toBe(false);
|
|
154
155
|
});
|
|
155
156
|
it('should handle complex version ranges', () => {
|
|
156
|
-
expect(
|
|
157
|
-
expect(
|
|
158
|
-
expect(
|
|
159
|
-
expect(
|
|
157
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3')).toBe(true);
|
|
158
|
+
expect((0, repoDependencyFacts_1.semverValid)('2.5.0', '1.x || >=2.5.0 || 5.0.0 - 7.2.3')).toBe(true);
|
|
159
|
+
expect((0, repoDependencyFacts_1.semverValid)('5.5.5', '1.x || >=2.5.0 || 5.0.0 - 7.2.3')).toBe(true);
|
|
160
|
+
expect((0, repoDependencyFacts_1.semverValid)('8.0.0', '1.x || >=9.5.0 || 5.0.0 - 7.2.3')).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
it('should handle caret ranges', () => {
|
|
163
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '^1.2.3')).toBe(true);
|
|
164
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.3.0', '^1.2.3')).toBe(true);
|
|
165
|
+
expect((0, repoDependencyFacts_1.semverValid)('2.0.0', '^1.2.3')).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
it('should handle tilde ranges', () => {
|
|
168
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '~1.2.3')).toBe(true);
|
|
169
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.9', '~1.2.3')).toBe(true);
|
|
170
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.3.0', '~1.2.3')).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
it('should handle x-ranges', () => {
|
|
173
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '1.2.x')).toBe(true);
|
|
174
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.9', '1.2.x')).toBe(true);
|
|
175
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.3.0', '1.2.x')).toBe(false);
|
|
176
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '1.x')).toBe(true);
|
|
177
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.3.0', '1.x')).toBe(true);
|
|
178
|
+
expect((0, repoDependencyFacts_1.semverValid)('2.0.0', '1.x')).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
it('should handle star ranges', () => {
|
|
181
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '*')).toBe(true);
|
|
182
|
+
expect((0, repoDependencyFacts_1.semverValid)('2.0.0', '*')).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
it('should handle greater than and less than ranges', () => {
|
|
185
|
+
expect((0, repoDependencyFacts_1.semverValid)('2.0.0', '>1.2.3')).toBe(true);
|
|
186
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '>1.2.3')).toBe(false);
|
|
187
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.2', '<1.2.3')).toBe(true);
|
|
188
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '<1.2.3')).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
it('should handle AND ranges', () => {
|
|
191
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '>1.2.2 <1.2.4')).toBe(true);
|
|
192
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.4', '>1.2.2 <1.2.4')).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
it('should handle OR ranges', () => {
|
|
195
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3', '1.2.3 || 1.2.4')).toBe(true);
|
|
196
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.4', '1.2.3 || 1.2.4')).toBe(true);
|
|
197
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.5', '1.2.3 || 1.2.4')).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
it('should handle pre-release versions', () => {
|
|
200
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3-alpha', '>=1.2.3-alpha')).toBe(true);
|
|
201
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.3-beta', '>=1.2.3-alpha')).toBe(true);
|
|
202
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.2.2', '>=1.2.3-alpha')).toBe(false);
|
|
160
203
|
});
|
|
161
204
|
it('should return true for empty strings', () => {
|
|
162
|
-
expect(
|
|
205
|
+
expect((0, repoDependencyFacts_1.semverValid)('', '')).toBe(true);
|
|
163
206
|
});
|
|
164
207
|
it('should return false for invalid input', () => {
|
|
165
|
-
expect(
|
|
166
|
-
expect(
|
|
208
|
+
expect((0, repoDependencyFacts_1.semverValid)('not-a-version', '1.0.0')).toBe(false);
|
|
209
|
+
expect((0, repoDependencyFacts_1.semverValid)('1.0.0', 'not-a-range')).toBe(false);
|
|
167
210
|
});
|
|
168
211
|
});
|
|
169
212
|
});
|
|
@@ -39,13 +39,13 @@ function collectRepoFileData(repoPath, archetypeConfig) {
|
|
|
39
39
|
logger_1.logger.debug(`checking file: ${filePath}`);
|
|
40
40
|
const stats = yield fs_1.default.promises.lstat(filePath);
|
|
41
41
|
if (stats.isDirectory()) {
|
|
42
|
-
if (!isBlacklisted(filePath, archetypeConfig.config.blacklistPatterns)) {
|
|
42
|
+
if (!isBlacklisted({ filePath, repoPath, blacklistPatterns: archetypeConfig.config.blacklistPatterns })) {
|
|
43
43
|
const dirFilesData = yield collectRepoFileData(filePath, archetypeConfig);
|
|
44
44
|
filesData.push(...dirFilesData);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
48
|
-
if (!isBlacklisted(filePath, archetypeConfig.config.blacklistPatterns) && isWhitelisted(filePath, archetypeConfig.config.whitelistPatterns)) {
|
|
48
|
+
if (!isBlacklisted({ filePath, repoPath, blacklistPatterns: archetypeConfig.config.blacklistPatterns }) && isWhitelisted({ filePath, repoPath, whitelistPatterns: archetypeConfig.config.whitelistPatterns })) {
|
|
49
49
|
logger_1.logger.debug(`adding file: ${filePath}`);
|
|
50
50
|
const fileData = yield parseFile(filePath);
|
|
51
51
|
filesData.push(fileData);
|
|
@@ -55,21 +55,33 @@ function collectRepoFileData(repoPath, archetypeConfig) {
|
|
|
55
55
|
return filesData;
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
function isBlacklisted(filePath, blacklistPatterns) {
|
|
58
|
+
function isBlacklisted({ filePath, repoPath, blacklistPatterns }) {
|
|
59
59
|
logger_1.logger.debug(`checking blacklist for file: ${filePath}`);
|
|
60
|
+
const normalizedPath = path_1.default.normalize(filePath);
|
|
61
|
+
const normalizedRepoPath = path_1.default.normalize(repoPath);
|
|
62
|
+
if (!normalizedPath.startsWith(normalizedRepoPath)) {
|
|
63
|
+
logger_1.logger.warn(`Potential path traversal attempt detected: ${filePath}`);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
60
66
|
for (const pattern of blacklistPatterns) {
|
|
61
|
-
if (new RegExp(pattern).test(
|
|
62
|
-
logger_1.logger.debug(`skipping blacklisted file: ${
|
|
67
|
+
if (new RegExp(pattern).test(normalizedPath)) {
|
|
68
|
+
logger_1.logger.debug(`skipping blacklisted file: ${normalizedPath} with pattern: ${pattern}`);
|
|
63
69
|
return true;
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
return false;
|
|
67
73
|
}
|
|
68
|
-
function isWhitelisted(filePath, whitelistPatterns) {
|
|
74
|
+
function isWhitelisted({ filePath, repoPath, whitelistPatterns }) {
|
|
69
75
|
logger_1.logger.debug(`checking whitelist for file: ${filePath}`);
|
|
76
|
+
const normalizedPath = path_1.default.normalize(filePath);
|
|
77
|
+
const normalizedRepoPath = path_1.default.normalize(repoPath);
|
|
78
|
+
if (!normalizedPath.startsWith(normalizedRepoPath)) {
|
|
79
|
+
logger_1.logger.warn(`Potential path traversal attempt detected: ${filePath}`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
70
82
|
for (const pattern of whitelistPatterns) {
|
|
71
|
-
if (new RegExp(pattern).test(
|
|
72
|
-
logger_1.logger.debug(`allowing file: ${
|
|
83
|
+
if (new RegExp(pattern).test(normalizedPath)) {
|
|
84
|
+
logger_1.logger.debug(`allowing file: ${normalizedPath} with pattern: ${pattern}`);
|
|
73
85
|
return true;
|
|
74
86
|
}
|
|
75
87
|
}
|
|
@@ -60,22 +60,26 @@ describe('File operations', () => {
|
|
|
60
60
|
});
|
|
61
61
|
describe('isBlacklisted', () => {
|
|
62
62
|
it('should return true if file path matches any blacklist pattern', () => {
|
|
63
|
-
const filePath = '/node_modules/index.js';
|
|
64
|
-
|
|
63
|
+
const filePath = '/test/repo/node_modules/index.js';
|
|
64
|
+
const repoPath = '/test/repo';
|
|
65
|
+
expect((0, repoFilesystemFacts_1.isBlacklisted)({ filePath, repoPath, blacklistPatterns: mockArchetypeConfig.config.blacklistPatterns })).toBe(true);
|
|
65
66
|
});
|
|
66
67
|
it('should return false if file path does not match any blacklist pattern', () => {
|
|
67
|
-
const filePath = 'src/index.js';
|
|
68
|
-
|
|
68
|
+
const filePath = '/test/repo/src/index.js';
|
|
69
|
+
const repoPath = '/test/repo';
|
|
70
|
+
expect((0, repoFilesystemFacts_1.isBlacklisted)({ filePath, repoPath, blacklistPatterns: mockArchetypeConfig.config.blacklistPatterns })).toBe(false);
|
|
69
71
|
});
|
|
70
72
|
});
|
|
71
73
|
describe('isWhitelisted', () => {
|
|
72
74
|
it('should return true if file path matches any whitelist pattern', () => {
|
|
73
|
-
const filePath = '/src/index.js';
|
|
74
|
-
|
|
75
|
+
const filePath = '/test/repo/src/index.js';
|
|
76
|
+
const repoPath = '/test/repo';
|
|
77
|
+
expect((0, repoFilesystemFacts_1.isWhitelisted)({ filePath, repoPath, whitelistPatterns: mockArchetypeConfig.config.whitelistPatterns })).toBe(true);
|
|
75
78
|
});
|
|
76
79
|
it('should return false if file path does not match any whitelist pattern', () => {
|
|
77
|
-
const filePath = 'build/index.txt';
|
|
78
|
-
|
|
80
|
+
const filePath = '/test/repo/build/index.txt';
|
|
81
|
+
const repoPath = '/test/repo';
|
|
82
|
+
expect((0, repoFilesystemFacts_1.isWhitelisted)({ filePath, repoPath, whitelistPatterns: mockArchetypeConfig.config.whitelistPatterns })).toBe(false);
|
|
79
83
|
});
|
|
80
84
|
});
|
|
81
85
|
});
|
|
@@ -8,7 +8,7 @@ const outdatedFramework = {
|
|
|
8
8
|
var _a;
|
|
9
9
|
let result = false;
|
|
10
10
|
try {
|
|
11
|
-
logger_1.logger.debug(`outdatedFramework: processing ${repoDependencyAnalysis}`);
|
|
11
|
+
logger_1.logger.debug(`outdatedFramework: processing ${JSON.stringify(repoDependencyAnalysis)}`);
|
|
12
12
|
if (((_a = repoDependencyAnalysis === null || repoDependencyAnalysis === void 0 ? void 0 : repoDependencyAnalysis.result) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
@@ -27,7 +27,7 @@ const validateGithubWebhook_1 = require("./middleware/validateGithubWebhook");
|
|
|
27
27
|
const chokidar_1 = __importDefault(require("chokidar"));
|
|
28
28
|
const cacheManager_1 = require("./cacheManager");
|
|
29
29
|
const SHARED_SECRET = process.env.XFI_SHARED_SECRET;
|
|
30
|
-
const maskedSecret = SHARED_SECRET ? `${SHARED_SECRET.substring(0,
|
|
30
|
+
const maskedSecret = SHARED_SECRET ? `${SHARED_SECRET.substring(0, 1)}****${SHARED_SECRET.substring(SHARED_SECRET.length - 1)}` : 'not set';
|
|
31
31
|
logger_1.logger.info(`Shared secret is ${maskedSecret}`);
|
|
32
32
|
const app = (0, express_1.default)();
|
|
33
33
|
// Add security headers
|
|
@@ -3,10 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.validateRule = exports.validateArchetype = void 0;
|
|
6
|
+
exports.validateRule = exports.validateArchetype = exports.validateRuleSchema = exports.validateArchetypeSchema = void 0;
|
|
7
7
|
const ajv_1 = __importDefault(require("ajv"));
|
|
8
8
|
const logger_1 = require("./logger");
|
|
9
|
+
const semver_1 = __importDefault(require("semver"));
|
|
9
10
|
const ajv = new ajv_1.default();
|
|
11
|
+
ajv.addFormat("semverPattern", {
|
|
12
|
+
type: "string",
|
|
13
|
+
validate: (x) => semver_1.default.valid(x) !== null && semver_1.default.validRange(x) !== null
|
|
14
|
+
});
|
|
10
15
|
const archetypeSchema = {
|
|
11
16
|
type: 'object',
|
|
12
17
|
properties: {
|
|
@@ -19,13 +24,19 @@ const archetypeSchema = {
|
|
|
19
24
|
properties: {
|
|
20
25
|
minimumDependencyVersions: {
|
|
21
26
|
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
'^.*$': {
|
|
29
|
+
type: 'string',
|
|
30
|
+
format: 'semverPattern'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
22
33
|
minProperties: 1,
|
|
34
|
+
additionalProperties: true,
|
|
23
35
|
required: []
|
|
24
36
|
},
|
|
25
37
|
standardStructure: {
|
|
26
38
|
type: 'object',
|
|
27
39
|
minProperties: 1,
|
|
28
|
-
required: []
|
|
29
40
|
},
|
|
30
41
|
blacklistPatterns: {
|
|
31
42
|
type: 'array',
|
|
@@ -42,7 +53,7 @@ const archetypeSchema = {
|
|
|
42
53
|
additionalProperties: true
|
|
43
54
|
}
|
|
44
55
|
},
|
|
45
|
-
required: ['rules', 'operators', 'facts', 'config'],
|
|
56
|
+
required: ['name', 'rules', 'operators', 'facts', 'config'],
|
|
46
57
|
additionalProperties: false
|
|
47
58
|
};
|
|
48
59
|
const ruleSchema = {
|
|
@@ -71,8 +82,8 @@ const ruleSchema = {
|
|
|
71
82
|
},
|
|
72
83
|
required: ['name', 'conditions', 'event']
|
|
73
84
|
};
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
exports.validateArchetypeSchema = ajv.compile(archetypeSchema);
|
|
86
|
+
exports.validateRuleSchema = ajv.compile(ruleSchema);
|
|
76
87
|
// Helper function to log validation errors
|
|
77
88
|
const logValidationErrors = (errors) => {
|
|
78
89
|
if (errors) {
|
|
@@ -83,17 +94,17 @@ const logValidationErrors = (errors) => {
|
|
|
83
94
|
};
|
|
84
95
|
// Wrap the validate functions to log errors
|
|
85
96
|
const validateArchetype = (data) => {
|
|
86
|
-
const isValid = validateArchetypeSchema(data);
|
|
97
|
+
const isValid = (0, exports.validateArchetypeSchema)(data);
|
|
87
98
|
if (!isValid) {
|
|
88
|
-
logValidationErrors(validateArchetypeSchema.errors);
|
|
99
|
+
logValidationErrors(exports.validateArchetypeSchema.errors);
|
|
89
100
|
}
|
|
90
101
|
return isValid;
|
|
91
102
|
};
|
|
92
103
|
exports.validateArchetype = validateArchetype;
|
|
93
104
|
const validateRule = (data) => {
|
|
94
|
-
const isValid = validateRuleSchema(data);
|
|
105
|
+
const isValid = (0, exports.validateRuleSchema)(data);
|
|
95
106
|
if (!isValid) {
|
|
96
|
-
logValidationErrors(validateRuleSchema.errors);
|
|
107
|
+
logValidationErrors(exports.validateRuleSchema.errors);
|
|
97
108
|
}
|
|
98
109
|
return isValid;
|
|
99
110
|
};
|
package/package.json
CHANGED
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
],
|
|
22
22
|
"config": {
|
|
23
23
|
"minimumDependencyVersions": {
|
|
24
|
-
"@types/react": "
|
|
25
|
-
"react": "
|
|
26
|
-
"@yarnpkg/lockfile": "
|
|
27
|
-
"commander": "
|
|
28
|
-
"nodemon": "
|
|
24
|
+
"@types/react": ">=18.0.0",
|
|
25
|
+
"react": "~17.0.0",
|
|
26
|
+
"@yarnpkg/lockfile": "<1.2.0",
|
|
27
|
+
"commander": ">=2.0.0 <13.0.0",
|
|
28
|
+
"nodemon": ">4.9.0",
|
|
29
|
+
"@colors/colors": "1.7.0 || 1.6.0"
|
|
29
30
|
},
|
|
30
31
|
"standardStructure": {
|
|
31
32
|
"app": {
|
|
@@ -83,35 +83,6 @@ describe('setupEngine', () => {
|
|
|
83
83
|
expect(engine).toBeDefined();
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
it('should handle errors when loading rules and add default rules', async () => {
|
|
87
|
-
const mockAddRule = jest.fn();
|
|
88
|
-
(Engine as jest.Mock).mockImplementation(() => ({
|
|
89
|
-
addOperator: jest.fn(),
|
|
90
|
-
addRule: mockAddRule,
|
|
91
|
-
addFact: jest.fn(),
|
|
92
|
-
on: jest.fn()
|
|
93
|
-
}));
|
|
94
|
-
|
|
95
|
-
(ConfigManager.getConfig as jest.Mock).mockResolvedValue({
|
|
96
|
-
archetype: mockArchetypeConfig,
|
|
97
|
-
rules: [
|
|
98
|
-
undefined,
|
|
99
|
-
{ name: undefined },
|
|
100
|
-
{ name: 'invalidRule', conditions: null }
|
|
101
|
-
],
|
|
102
|
-
cliOptions: {}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
await setupEngine(mockParams);
|
|
106
|
-
|
|
107
|
-
expect(logger.error).toHaveBeenCalledWith('Invalid rule configuration: rule or rule name is undefined');
|
|
108
|
-
expect(logger.error).toHaveBeenCalledWith('Invalid rule configuration: rule or rule name is undefined');
|
|
109
|
-
expect(logger.error).toHaveBeenCalledWith('Error loading rule: invalidRule');
|
|
110
|
-
expect(logger.info).toHaveBeenCalledWith('No valid rules were added. Adding default rules.');
|
|
111
|
-
expect(mockAddRule).toHaveBeenCalledWith(expect.objectContaining({ name: 'default-rule-1' }));
|
|
112
|
-
expect(mockAddRule).toHaveBeenCalledWith(expect.objectContaining({ name: 'default-rule-2' }));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
86
|
it('should set up event listeners for success events', async () => {
|
|
116
87
|
const mockOn = jest.fn();
|
|
117
88
|
(Engine as jest.Mock).mockImplementation(() => ({
|
|
@@ -25,7 +25,7 @@ export async function setupEngine(params: SetupEngineParams): Promise<Engine> {
|
|
|
25
25
|
logger.info(`=== loading json rules..`);
|
|
26
26
|
const config = await ConfigManager.getConfig({ archetype, logPrefix: executionLogPrefix });
|
|
27
27
|
|
|
28
|
-
logger.debug(config.rules);
|
|
28
|
+
logger.debug(`rules loaded: ${config.rules}`);
|
|
29
29
|
|
|
30
30
|
const addedRules = new Set();
|
|
31
31
|
config.rules.forEach((rule) => {
|
|
@@ -51,20 +51,6 @@ export async function setupEngine(params: SetupEngineParams): Promise<Engine> {
|
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
if (addedRules.size === 0) {
|
|
55
|
-
logger.info('No valid rules were added. Adding default rules.');
|
|
56
|
-
engine.addRule({
|
|
57
|
-
name: 'default-rule-1',
|
|
58
|
-
conditions: { all: [] },
|
|
59
|
-
event: { type: 'warning', params: { message: 'Default rule 1 triggered' } }
|
|
60
|
-
});
|
|
61
|
-
engine.addRule({
|
|
62
|
-
name: 'default-rule-2',
|
|
63
|
-
conditions: { all: [] },
|
|
64
|
-
event: { type: 'warning', params: { message: 'Default rule 2 triggered' } }
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
54
|
engine.on('success', async ({ type, params }: Event) => {
|
|
69
55
|
if (type === 'warning') {
|
|
70
56
|
logger.warn(`warning detected: ${JSON.stringify(params)}`);
|
|
@@ -3,6 +3,7 @@ import { execSync } from 'child_process';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { Almanac } from 'json-rules-engine';
|
|
5
5
|
import { LocalDependencies, MinimumDepVersions } from '../types/typeDefs';
|
|
6
|
+
import { semverValid } from './repoDependencyFacts';
|
|
6
7
|
|
|
7
8
|
jest.mock('child_process');
|
|
8
9
|
jest.mock('fs');
|
|
@@ -131,32 +132,82 @@ describe('repoDependencyFacts', () => {
|
|
|
131
132
|
|
|
132
133
|
describe('semverValid', () => {
|
|
133
134
|
it('should return true for valid version comparisons', () => {
|
|
134
|
-
expect(
|
|
135
|
-
expect(
|
|
136
|
-
expect(
|
|
137
|
-
expect(
|
|
135
|
+
expect(semverValid('2.0.0', '^1.0.0')).toBe(false);
|
|
136
|
+
expect(semverValid('1.5.0', '1.0.0 - 2.0.0')).toBe(true);
|
|
137
|
+
expect(semverValid('1.0.0', '1.0.0')).toBe(true);
|
|
138
|
+
expect(semverValid('2.0.0', '>=1.0.0')).toBe(true);
|
|
138
139
|
});
|
|
139
|
-
|
|
140
|
+
|
|
140
141
|
it('should return false for invalid version comparisons', () => {
|
|
141
|
-
expect(
|
|
142
|
-
expect(
|
|
143
|
-
expect(
|
|
142
|
+
expect(semverValid('1.0.0', '^2.0.0')).toBe(false);
|
|
143
|
+
expect(semverValid('3.0.0', '1.0.0 - 2.0.0')).toBe(false);
|
|
144
|
+
expect(semverValid('0.9.0', '>=1.0.0')).toBe(false);
|
|
144
145
|
});
|
|
145
|
-
|
|
146
|
+
|
|
146
147
|
it('should handle complex version ranges', () => {
|
|
147
|
-
expect(
|
|
148
|
-
expect(
|
|
149
|
-
expect(
|
|
150
|
-
expect(
|
|
148
|
+
expect(semverValid('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3')).toBe(true);
|
|
149
|
+
expect(semverValid('2.5.0', '1.x || >=2.5.0 || 5.0.0 - 7.2.3')).toBe(true);
|
|
150
|
+
expect(semverValid('5.5.5', '1.x || >=2.5.0 || 5.0.0 - 7.2.3')).toBe(true);
|
|
151
|
+
expect(semverValid('8.0.0', '1.x || >=9.5.0 || 5.0.0 - 7.2.3')).toBe(false);
|
|
151
152
|
});
|
|
152
|
-
|
|
153
|
+
|
|
154
|
+
it('should handle caret ranges', () => {
|
|
155
|
+
expect(semverValid('1.2.3', '^1.2.3')).toBe(true);
|
|
156
|
+
expect(semverValid('1.3.0', '^1.2.3')).toBe(true);
|
|
157
|
+
expect(semverValid('2.0.0', '^1.2.3')).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should handle tilde ranges', () => {
|
|
161
|
+
expect(semverValid('1.2.3', '~1.2.3')).toBe(true);
|
|
162
|
+
expect(semverValid('1.2.9', '~1.2.3')).toBe(true);
|
|
163
|
+
expect(semverValid('1.3.0', '~1.2.3')).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle x-ranges', () => {
|
|
167
|
+
expect(semverValid('1.2.3', '1.2.x')).toBe(true);
|
|
168
|
+
expect(semverValid('1.2.9', '1.2.x')).toBe(true);
|
|
169
|
+
expect(semverValid('1.3.0', '1.2.x')).toBe(false);
|
|
170
|
+
expect(semverValid('1.2.3', '1.x')).toBe(true);
|
|
171
|
+
expect(semverValid('1.3.0', '1.x')).toBe(true);
|
|
172
|
+
expect(semverValid('2.0.0', '1.x')).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle star ranges', () => {
|
|
176
|
+
expect(semverValid('1.2.3', '*')).toBe(true);
|
|
177
|
+
expect(semverValid('2.0.0', '*')).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle greater than and less than ranges', () => {
|
|
181
|
+
expect(semverValid('2.0.0', '>1.2.3')).toBe(true);
|
|
182
|
+
expect(semverValid('1.2.3', '>1.2.3')).toBe(false);
|
|
183
|
+
expect(semverValid('1.2.2', '<1.2.3')).toBe(true);
|
|
184
|
+
expect(semverValid('1.2.3', '<1.2.3')).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should handle AND ranges', () => {
|
|
188
|
+
expect(semverValid('1.2.3', '>1.2.2 <1.2.4')).toBe(true);
|
|
189
|
+
expect(semverValid('1.2.4', '>1.2.2 <1.2.4')).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should handle OR ranges', () => {
|
|
193
|
+
expect(semverValid('1.2.3', '1.2.3 || 1.2.4')).toBe(true);
|
|
194
|
+
expect(semverValid('1.2.4', '1.2.3 || 1.2.4')).toBe(true);
|
|
195
|
+
expect(semverValid('1.2.5', '1.2.3 || 1.2.4')).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should handle pre-release versions', () => {
|
|
199
|
+
expect(semverValid('1.2.3-alpha', '>=1.2.3-alpha')).toBe(true);
|
|
200
|
+
expect(semverValid('1.2.3-beta', '>=1.2.3-alpha')).toBe(true);
|
|
201
|
+
expect(semverValid('1.2.2', '>=1.2.3-alpha')).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
153
204
|
it('should return true for empty strings', () => {
|
|
154
|
-
expect(
|
|
205
|
+
expect(semverValid('', '')).toBe(true);
|
|
155
206
|
});
|
|
156
|
-
|
|
207
|
+
|
|
157
208
|
it('should return false for invalid input', () => {
|
|
158
|
-
expect(
|
|
159
|
-
expect(
|
|
209
|
+
expect(semverValid('not-a-version', '1.0.0')).toBe(false);
|
|
210
|
+
expect(semverValid('1.0.0', 'not-a-range')).toBe(false);
|
|
160
211
|
});
|
|
161
212
|
});
|
|
162
213
|
});
|
|
@@ -186,35 +186,46 @@ export async function repoDependencyAnalysis(params: any, almanac: Almanac) {
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
export function semverValid(installed: string, required: string): boolean {
|
|
189
|
+
|
|
189
190
|
// Remove potential @namespace from installed version
|
|
190
191
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (!
|
|
192
|
+
installed = installed.includes('@') ? installed.split('@').pop()! : installed;
|
|
193
|
+
|
|
194
|
+
if (!installed || !required) {
|
|
194
195
|
return true;
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
// ensure that both inputs are now valid semver or range strings
|
|
199
|
+
if (!semver.valid(installed) && !semver.validRange(installed)) {
|
|
200
|
+
logger.error(`semverValid: invalid installed version or range: ${installed}`);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (!semver.valid(required) && !semver.validRange(required)) {
|
|
204
|
+
logger.error(`semverValid: invalid required version or range: ${required}`);
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
197
208
|
// If 'installed' is a single version and 'required' is a range
|
|
198
209
|
if (semver.valid(installed) && semver.validRange(required)) {
|
|
199
|
-
logger.
|
|
210
|
+
logger.info('range vs version');
|
|
200
211
|
return semver.satisfies(installed, required);
|
|
201
212
|
}
|
|
202
213
|
|
|
203
214
|
// If 'required' is a single version and 'installed' is a range
|
|
204
215
|
if (semver.valid(required) && semver.validRange(installed)) {
|
|
205
|
-
logger.
|
|
216
|
+
logger.info('version vs range');
|
|
206
217
|
return semver.satisfies(required, installed);
|
|
207
218
|
}
|
|
208
219
|
|
|
209
220
|
// If both are single versions, simply compare them
|
|
210
221
|
if (semver.valid(required) && semver.valid(installed)) {
|
|
211
|
-
logger.
|
|
222
|
+
logger.info('version vs version');
|
|
212
223
|
return semver.gt(installed, required);
|
|
213
224
|
}
|
|
214
225
|
|
|
215
226
|
// If both are ranges, check if they intersect
|
|
216
227
|
if (semver.validRange(required) && semver.validRange(installed)) {
|
|
217
|
-
logger.
|
|
228
|
+
logger.info('range vs range');
|
|
218
229
|
return semver.intersects(required, installed);
|
|
219
230
|
}
|
|
220
231
|
|
|
@@ -60,25 +60,29 @@ describe('File operations', () => {
|
|
|
60
60
|
|
|
61
61
|
describe('isBlacklisted', () => {
|
|
62
62
|
it('should return true if file path matches any blacklist pattern', () => {
|
|
63
|
-
const filePath = '/node_modules/index.js';
|
|
64
|
-
|
|
63
|
+
const filePath = '/test/repo/node_modules/index.js';
|
|
64
|
+
const repoPath = '/test/repo';
|
|
65
|
+
expect(isBlacklisted({ filePath, repoPath, blacklistPatterns: mockArchetypeConfig.config.blacklistPatterns })).toBe(true);
|
|
65
66
|
});
|
|
66
67
|
|
|
67
68
|
it('should return false if file path does not match any blacklist pattern', () => {
|
|
68
|
-
const filePath = 'src/index.js';
|
|
69
|
-
|
|
69
|
+
const filePath = '/test/repo/src/index.js';
|
|
70
|
+
const repoPath = '/test/repo';
|
|
71
|
+
expect(isBlacklisted({ filePath, repoPath, blacklistPatterns: mockArchetypeConfig.config.blacklistPatterns })).toBe(false);
|
|
70
72
|
});
|
|
71
73
|
});
|
|
72
74
|
|
|
73
75
|
describe('isWhitelisted', () => {
|
|
74
76
|
it('should return true if file path matches any whitelist pattern', () => {
|
|
75
|
-
const filePath = '/src/index.js';
|
|
76
|
-
|
|
77
|
+
const filePath = '/test/repo/src/index.js';
|
|
78
|
+
const repoPath = '/test/repo';
|
|
79
|
+
expect(isWhitelisted({ filePath, repoPath, whitelistPatterns: mockArchetypeConfig.config.whitelistPatterns })).toBe(true);
|
|
77
80
|
});
|
|
78
81
|
|
|
79
82
|
it('should return false if file path does not match any whitelist pattern', () => {
|
|
80
|
-
const filePath = 'build/index.txt';
|
|
81
|
-
|
|
83
|
+
const filePath = '/test/repo/build/index.txt';
|
|
84
|
+
const repoPath = '/test/repo';
|
|
85
|
+
expect(isWhitelisted({ filePath, repoPath, whitelistPatterns: mockArchetypeConfig.config.whitelistPatterns })).toBe(false);
|
|
82
86
|
});
|
|
83
87
|
});
|
|
84
88
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger } from '../utils/logger';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { ArchetypeConfig } from '../types/typeDefs';
|
|
4
|
+
import { ArchetypeConfig, IsBlacklistedParams, isWhitelistedParams } from '../types/typeDefs';
|
|
5
5
|
|
|
6
6
|
interface FileData {
|
|
7
7
|
fileName: string;
|
|
@@ -29,12 +29,12 @@ async function collectRepoFileData(repoPath: string, archetypeConfig: ArchetypeC
|
|
|
29
29
|
logger.debug(`checking file: ${filePath}`);
|
|
30
30
|
const stats = await fs.promises.lstat(filePath);
|
|
31
31
|
if (stats.isDirectory()) {
|
|
32
|
-
if (!isBlacklisted(filePath, archetypeConfig.config.blacklistPatterns)) {
|
|
32
|
+
if (!isBlacklisted({ filePath, repoPath, blacklistPatterns: archetypeConfig.config.blacklistPatterns })) {
|
|
33
33
|
const dirFilesData = await collectRepoFileData(filePath, archetypeConfig);
|
|
34
34
|
filesData.push(...dirFilesData);
|
|
35
35
|
}
|
|
36
36
|
} else {
|
|
37
|
-
if (!isBlacklisted(filePath, archetypeConfig.config.blacklistPatterns) && isWhitelisted(filePath, archetypeConfig.config.whitelistPatterns)) {
|
|
37
|
+
if (!isBlacklisted({ filePath, repoPath, blacklistPatterns: archetypeConfig.config.blacklistPatterns }) && isWhitelisted({ filePath, repoPath, whitelistPatterns: archetypeConfig.config.whitelistPatterns })) {
|
|
38
38
|
logger.debug(`adding file: ${filePath}`);
|
|
39
39
|
const fileData = await parseFile(filePath);
|
|
40
40
|
filesData.push(fileData);
|
|
@@ -45,22 +45,38 @@ async function collectRepoFileData(repoPath: string, archetypeConfig: ArchetypeC
|
|
|
45
45
|
return filesData;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function isBlacklisted(filePath
|
|
48
|
+
function isBlacklisted({ filePath, repoPath, blacklistPatterns }: IsBlacklistedParams): boolean {
|
|
49
49
|
logger.debug(`checking blacklist for file: ${filePath}`);
|
|
50
|
+
const normalizedPath = path.normalize(filePath);
|
|
51
|
+
const normalizedRepoPath = path.normalize(repoPath);
|
|
52
|
+
|
|
53
|
+
if (!normalizedPath.startsWith(normalizedRepoPath)) {
|
|
54
|
+
logger.warn(`Potential path traversal attempt detected: ${filePath}`);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
for (const pattern of blacklistPatterns) {
|
|
51
|
-
if (new RegExp(pattern).test(
|
|
52
|
-
logger.debug(`skipping blacklisted file: ${
|
|
59
|
+
if (new RegExp(pattern).test(normalizedPath)) {
|
|
60
|
+
logger.debug(`skipping blacklisted file: ${normalizedPath} with pattern: ${pattern}`);
|
|
53
61
|
return true;
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
return false;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
function isWhitelisted(filePath
|
|
67
|
+
function isWhitelisted({ filePath, repoPath, whitelistPatterns }: isWhitelistedParams): boolean {
|
|
60
68
|
logger.debug(`checking whitelist for file: ${filePath}`);
|
|
69
|
+
const normalizedPath = path.normalize(filePath);
|
|
70
|
+
const normalizedRepoPath = path.normalize(repoPath);
|
|
71
|
+
|
|
72
|
+
if (!normalizedPath.startsWith(normalizedRepoPath)) {
|
|
73
|
+
logger.warn(`Potential path traversal attempt detected: ${filePath}`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
for (const pattern of whitelistPatterns) {
|
|
62
|
-
if (new RegExp(pattern).test(
|
|
63
|
-
logger.debug(`allowing file: ${
|
|
78
|
+
if (new RegExp(pattern).test(normalizedPath)) {
|
|
79
|
+
logger.debug(`allowing file: ${normalizedPath} with pattern: ${pattern}`);
|
|
64
80
|
return true;
|
|
65
81
|
}
|
|
66
82
|
}
|
|
@@ -7,7 +7,7 @@ const outdatedFramework: OperatorDefn = {
|
|
|
7
7
|
let result = false;
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
|
-
logger.debug(`outdatedFramework: processing ${repoDependencyAnalysis}`);
|
|
10
|
+
logger.debug(`outdatedFramework: processing ${JSON.stringify(repoDependencyAnalysis)}`);
|
|
11
11
|
|
|
12
12
|
if (repoDependencyAnalysis?.result?.length > 0) {
|
|
13
13
|
return true;
|
|
@@ -23,7 +23,7 @@ import chokidar from 'chokidar';
|
|
|
23
23
|
import { handleConfigChange } from './cacheManager';
|
|
24
24
|
|
|
25
25
|
const SHARED_SECRET = process.env.XFI_SHARED_SECRET;
|
|
26
|
-
const maskedSecret = SHARED_SECRET ? `${SHARED_SECRET.substring(0,
|
|
26
|
+
const maskedSecret = SHARED_SECRET ? `${SHARED_SECRET.substring(0, 1)}****${SHARED_SECRET.substring(SHARED_SECRET.length - 1)}` : 'not set';
|
|
27
27
|
logger.info(`Shared secret is ${maskedSecret}`);
|
|
28
28
|
|
|
29
29
|
const app = express();
|
package/src/types/typeDefs.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Engine, OperatorEvaluator } from 'json-rules-engine';
|
|
2
2
|
import { FileData } from '../facts/repoFilesystemFacts';
|
|
3
|
+
import { JSONSchemaType } from 'ajv';
|
|
3
4
|
|
|
4
5
|
export type OperatorDefn = {
|
|
5
6
|
name: string,
|
|
@@ -89,13 +90,16 @@ export interface ArchetypeConfig {
|
|
|
89
90
|
operators: string[];
|
|
90
91
|
facts: string[];
|
|
91
92
|
config: {
|
|
92
|
-
minimumDependencyVersions: Record<string, string>;
|
|
93
|
+
minimumDependencyVersions: Record<string, string>; // This will be validated in the JSON schema
|
|
93
94
|
standardStructure: Record<string, any>;
|
|
94
95
|
blacklistPatterns: string[];
|
|
95
96
|
whitelistPatterns: string[];
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
// Add a new type for the JSON schema
|
|
101
|
+
export type ArchetypeConfigSchema = JSONSchemaType<ArchetypeConfig>;
|
|
102
|
+
|
|
99
103
|
export interface RuleConfig {
|
|
100
104
|
name: string;
|
|
101
105
|
conditions: {
|
|
@@ -107,6 +111,7 @@ export interface RuleConfig {
|
|
|
107
111
|
params: Record<string, any>;
|
|
108
112
|
};
|
|
109
113
|
}
|
|
114
|
+
export type RuleConfigSchema = JSONSchemaType<RuleConfig>;
|
|
110
115
|
|
|
111
116
|
export interface OpenAIAnalysisParams {
|
|
112
117
|
prompt: string;
|
|
@@ -217,3 +222,14 @@ export interface LoadExemptionsParams {
|
|
|
217
222
|
archetype: string;
|
|
218
223
|
}
|
|
219
224
|
|
|
225
|
+
export interface IsBlacklistedParams {
|
|
226
|
+
filePath: string;
|
|
227
|
+
repoPath: string;
|
|
228
|
+
blacklistPatterns: string[];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface isWhitelistedParams {
|
|
232
|
+
filePath: string;
|
|
233
|
+
repoPath: string;
|
|
234
|
+
whitelistPatterns: string[];
|
|
235
|
+
}
|
package/src/utils/jsonSchemas.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import Ajv
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
2
|
import { logger } from './logger';
|
|
3
|
-
import {
|
|
3
|
+
import { ArchetypeConfigSchema, RuleConfigSchema } from '../types/typeDefs';
|
|
4
|
+
import semver from 'semver';
|
|
4
5
|
|
|
5
6
|
const ajv = new Ajv();
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
ajv.addFormat("semverPattern", {
|
|
9
|
+
type: "string",
|
|
10
|
+
validate: (x) => semver.valid(x) !== null && semver.validRange(x) !== null
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const archetypeSchema: ArchetypeConfigSchema = {
|
|
8
14
|
type: 'object',
|
|
9
15
|
properties: {
|
|
10
16
|
name: { type: 'string' },
|
|
@@ -16,34 +22,40 @@ const archetypeSchema: JSONSchemaType<ArchetypeConfig> = {
|
|
|
16
22
|
properties: {
|
|
17
23
|
minimumDependencyVersions: {
|
|
18
24
|
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
'^.*$': {
|
|
27
|
+
type: 'string',
|
|
28
|
+
format: 'semverPattern'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
19
31
|
minProperties: 1,
|
|
32
|
+
additionalProperties: true,
|
|
20
33
|
required: []
|
|
21
34
|
},
|
|
22
35
|
standardStructure: {
|
|
23
36
|
type: 'object',
|
|
24
37
|
minProperties: 1,
|
|
25
|
-
required: []
|
|
26
38
|
},
|
|
27
39
|
blacklistPatterns: {
|
|
28
40
|
type: 'array',
|
|
29
|
-
items: { type: 'string'},
|
|
41
|
+
items: { type: 'string' },
|
|
30
42
|
minItems: 1
|
|
31
43
|
},
|
|
32
44
|
whitelistPatterns: {
|
|
33
45
|
type: 'array',
|
|
34
|
-
items: { type: 'string'},
|
|
46
|
+
items: { type: 'string' },
|
|
35
47
|
minItems: 1
|
|
36
48
|
}
|
|
37
49
|
},
|
|
38
|
-
required: ['minimumDependencyVersions', 'standardStructure', 'blacklistPatterns', 'whitelistPatterns'],
|
|
50
|
+
required: ['minimumDependencyVersions', 'standardStructure', 'blacklistPatterns', 'whitelistPatterns'] as const,
|
|
39
51
|
additionalProperties: true
|
|
40
52
|
}
|
|
41
53
|
},
|
|
42
|
-
required: ['rules', 'operators', 'facts', 'config'],
|
|
54
|
+
required: ['name', 'rules', 'operators', 'facts', 'config'] as const,
|
|
43
55
|
additionalProperties: false
|
|
44
|
-
};
|
|
56
|
+
} as const;
|
|
45
57
|
|
|
46
|
-
const ruleSchema:
|
|
58
|
+
const ruleSchema: RuleConfigSchema = {
|
|
47
59
|
type: 'object',
|
|
48
60
|
properties: {
|
|
49
61
|
name: { type: 'string' },
|
|
@@ -70,8 +82,8 @@ const ruleSchema: JSONSchemaType<RuleConfig> = {
|
|
|
70
82
|
required: ['name', 'conditions', 'event']
|
|
71
83
|
};
|
|
72
84
|
|
|
73
|
-
const validateArchetypeSchema = ajv.compile(archetypeSchema);
|
|
74
|
-
const validateRuleSchema = ajv.compile(ruleSchema);
|
|
85
|
+
export const validateArchetypeSchema = ajv.compile(archetypeSchema);
|
|
86
|
+
export const validateRuleSchema = ajv.compile(ruleSchema);
|
|
75
87
|
|
|
76
88
|
// Helper function to log validation errors
|
|
77
89
|
const logValidationErrors = (errors: any[] | null | undefined) => {
|