x-fidelity 3.18.0 → 3.19.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/.xfi-config.json +12 -1
- package/CHANGELOG.md +44 -0
- package/dist/core/engine/engineRunner.js +20 -11
- package/dist/core/engine/engineSetup.js +36 -3
- package/dist/core/engine/engineSetup.test.js +54 -0
- package/dist/utils/logger.js +1 -5
- package/dist/utils/repoXFIConfigLoader.js +191 -78
- package/dist/utils/repoXFIConfigLoader.test.js +36 -112
- package/package.json +2 -1
- package/src/core/engine/engineRunner.ts +18 -8
- package/src/core/engine/engineSetup.test.ts +66 -0
- package/src/core/engine/engineSetup.ts +41 -3
- package/src/utils/logger.ts +1 -5
- package/src/utils/repoXFIConfigLoader.test.ts +52 -148
- package/src/utils/repoXFIConfigLoader.ts +163 -81
- package/website/docs/local-configuration.md +126 -5
package/.xfi-config.json
CHANGED
|
@@ -29,8 +29,19 @@
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "sensitiveLogging-iterative",
|
|
35
|
+
"path": "src/demoConfig/rules/sensitiveLogging-iterative-rule.json"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "functionComplexity-iterative",
|
|
39
|
+
"path": "src/plugins/xfiPluginAst/sampleRules/functionComplexity-iterative-rule.json"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "remote-rule",
|
|
43
|
+
"url": "https://example.com/rules/custom-rule.json"
|
|
32
44
|
}
|
|
33
|
-
|
|
34
45
|
],
|
|
35
46
|
"additionalFacts": ["customFact"],
|
|
36
47
|
"additionalOperators": ["customOperator"],
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
## [3.19.1](https://github.com/zotoio/x-fidelity/compare/v3.19.0...v3.19.1) (2025-03-25)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add missing mock implementation for loadRepoXFIConfig in tests ([f77ef79](https://github.com/zotoio/x-fidelity/commit/f77ef79651fcbca54e4a8825dad1f0dd7f2e6a81))
|
|
7
|
+
|
|
8
|
+
# [3.19.0](https://github.com/zotoio/x-fidelity/compare/v3.18.0...v3.19.0) (2025-03-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* add non-null assertion for additionalRules array access ([9e7ee91](https://github.com/zotoio/x-fidelity/commit/9e7ee9117bd336c016bdeb5c1c429d6c59388fa1))
|
|
14
|
+
* correct try-catch block structure in config loader ([dc6fd2c](https://github.com/zotoio/x-fidelity/commit/dc6fd2c7683c2694381020e9ac9ffd6032dbb2da))
|
|
15
|
+
* correct try/catch block structure in repoXFIConfigLoader ([82ddb04](https://github.com/zotoio/x-fidelity/commit/82ddb04f4f9efa915386a213fad61dd55c923553))
|
|
16
|
+
* correct TypeScript type errors in repoXFIConfigLoader tests ([4100e06](https://github.com/zotoio/x-fidelity/commit/4100e067e71b4154b14da299c4760950e7ebaa8b))
|
|
17
|
+
* ensure inline rules are properly processed and assigned ([7ea7661](https://github.com/zotoio/x-fidelity/commit/7ea7661a962af83423f20270c1f27bb859573ef4))
|
|
18
|
+
* handle undefined sensitiveFileFalsePositives in config loader ([4f940cf](https://github.com/zotoio/x-fidelity/commit/4f940cf7677a29b5e1c611531b0294f1970a15d3))
|
|
19
|
+
* improve error message for missing rule files ([c271700](https://github.com/zotoio/x-fidelity/commit/c271700fa35bd9c43a68f996a7c1f29790bbab53))
|
|
20
|
+
* improve file path handling in repoXFIConfigLoader tests ([93a0289](https://github.com/zotoio/x-fidelity/commit/93a02891528852192d0d7c1b86f9b441f1c66c75))
|
|
21
|
+
* improve file system mocking in config loader test ([f0cfc25](https://github.com/zotoio/x-fidelity/commit/f0cfc25d1a3cfc9207b65078c2bc26b8c95cf0a0))
|
|
22
|
+
* improve inline rule validation and logging in config loader ([625a4aa](https://github.com/zotoio/x-fidelity/commit/625a4aae86220f6ed856f8ea3679b17e48f75cfc))
|
|
23
|
+
* improve test mocks for fs.promises and jsonSchemas validation ([8b1def8](https://github.com/zotoio/x-fidelity/commit/8b1def8bf577a7ad1d4314fe4787b8e7b6570264))
|
|
24
|
+
* prevent duplicate rule registration in engine setup ([a9e2956](https://github.com/zotoio/x-fidelity/commit/a9e295666dc41079f31074164a6c60babae4cc96))
|
|
25
|
+
* remove duplicate code blocks and fix return statements in config loader ([414e500](https://github.com/zotoio/x-fidelity/commit/414e5001b2a0329ce88f519a2d5563f815772154))
|
|
26
|
+
* Replace continue with return in rule registration loop ([44ef9fd](https://github.com/zotoio/x-fidelity/commit/44ef9fd92be577043d1cf04585841e3ded5bf42f))
|
|
27
|
+
* restructure rule loading logic and improve error handling ([ff8b6e6](https://github.com/zotoio/x-fidelity/commit/ff8b6e698d89b7adb99b294eec048c340d4976ac))
|
|
28
|
+
* restructure try/catch blocks and improve error handling in config loader ([a0f76bb](https://github.com/zotoio/x-fidelity/commit/a0f76bbabfc29c172fecf7d1d0691358ff44226b))
|
|
29
|
+
* simplify pino file transport configuration ([c99bf26](https://github.com/zotoio/x-fidelity/commit/c99bf2671abd96bf5b77d09ce3941852609dfaac))
|
|
30
|
+
* **tests:** update docs and tests for .xfi-config.json ([cc6e11e](https://github.com/zotoio/x-fidelity/commit/cc6e11e24fbb94c8cca34147026c2072db918ba3))
|
|
31
|
+
* update fs mock to include promises property in test ([1a50355](https://github.com/zotoio/x-fidelity/commit/1a50355aec723fa4cfd0ee4403b07fccd218303b))
|
|
32
|
+
* update glob import to use dynamic import pattern ([2677260](https://github.com/zotoio/x-fidelity/commit/2677260d854728195559798d66ccd1a94ca8a476))
|
|
33
|
+
* update glob import to use named import syntax ([c290016](https://github.com/zotoio/x-fidelity/commit/c2900168c270edd38bea766baa440baab8a16959))
|
|
34
|
+
* update test mocks to handle file paths correctly ([4407c1e](https://github.com/zotoio/x-fidelity/commit/4407c1e39a433f8c9e7bc1b7cab1d3781447acc3))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
|
|
39
|
+
* Add CLI options import to repo config loader ([26b97bc](https://github.com/zotoio/x-fidelity/commit/26b97bcbe80438debd1a07f3b22076ff9398f933))
|
|
40
|
+
* add deduplication for rules and failure results ([169c035](https://github.com/zotoio/x-fidelity/commit/169c0351de2390bb9f22823c6a64110306caf32e))
|
|
41
|
+
* add reference to sensitiveLogging-iterative rule ([fea3d28](https://github.com/zotoio/x-fidelity/commit/fea3d2817018c0b7e328cec539a1ac00a1cd48b6))
|
|
42
|
+
* add support for loading additional rules from repo config ([7abac68](https://github.com/zotoio/x-fidelity/commit/7abac6891076f2eb0d47b3e2a08470241ac0b3fe))
|
|
43
|
+
* add wildcard support and flexible rule loading patterns ([a2d19c2](https://github.com/zotoio/x-fidelity/commit/a2d19c2bb5ba38026ba3dce6ba0be17039b9387f))
|
|
44
|
+
|
|
1
45
|
# [3.18.0](https://github.com/zotoio/x-fidelity/compare/v3.17.1...v3.18.0) (2025-03-25)
|
|
2
46
|
|
|
3
47
|
|
|
@@ -15,7 +15,7 @@ const configManager_1 = require("../configManager");
|
|
|
15
15
|
const errorActionExecutor_1 = require("./errorActionExecutor");
|
|
16
16
|
function runEngineOnFiles(params) {
|
|
17
17
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
18
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
19
19
|
const { engine, fileData, installedDependencyVersions, minimumDependencyVersions, standardStructure } = params;
|
|
20
20
|
const msg = `\n==========================\nRUNNING FILE CHECKS..\n==========================`;
|
|
21
21
|
logger_1.logger.info(msg);
|
|
@@ -42,20 +42,29 @@ function runEngineOnFiles(params) {
|
|
|
42
42
|
const fileFailures = [];
|
|
43
43
|
try {
|
|
44
44
|
const { results } = yield engine.run(facts);
|
|
45
|
+
const seenFailures = new Set();
|
|
45
46
|
for (const result of results) {
|
|
46
47
|
logger_1.logger.trace(JSON.stringify(result));
|
|
47
48
|
if (result.result) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
// Create unique key for deduplication
|
|
50
|
+
const failureKey = `${result.name}:${(_a = result.event) === null || _a === void 0 ? void 0 : _a.type}:${(_c = (_b = result.event) === null || _b === void 0 ? void 0 : _b.params) === null || _c === void 0 ? void 0 : _c.message}`;
|
|
51
|
+
if (!seenFailures.has(failureKey)) {
|
|
52
|
+
fileFailures.push({
|
|
53
|
+
ruleFailure: result.name,
|
|
54
|
+
level: (_d = result.event) === null || _d === void 0 ? void 0 : _d.type,
|
|
55
|
+
details: Object.assign({ message: ((_f = (_e = result.event) === null || _e === void 0 ? void 0 : _e.params) === null || _f === void 0 ? void 0 : _f.message) || 'Rule failure detected' }, (_g = result.event) === null || _g === void 0 ? void 0 : _g.params)
|
|
56
|
+
});
|
|
57
|
+
seenFailures.add(failureKey);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
logger_1.logger.debug(`Skipping duplicate failure: ${failureKey}`);
|
|
61
|
+
}
|
|
53
62
|
}
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
catch (e) {
|
|
57
66
|
const error = e;
|
|
58
|
-
const failedRuleName = (
|
|
67
|
+
const failedRuleName = (_h = error === null || error === void 0 ? void 0 : error.rule) === null || _h === void 0 ? void 0 : _h.name;
|
|
59
68
|
const rule = failedRuleName ? engine.rules.find((r) => r.name === failedRuleName) : null;
|
|
60
69
|
// Determine error source and level
|
|
61
70
|
let errorSource = 'unknown';
|
|
@@ -80,7 +89,7 @@ function runEngineOnFiles(params) {
|
|
|
80
89
|
}
|
|
81
90
|
else if (failedRuleName) {
|
|
82
91
|
errorSource = 'rule';
|
|
83
|
-
errorLevel = (rule === null || rule === void 0 ? void 0 : rule.errorBehavior) === 'fatal' || ((
|
|
92
|
+
errorLevel = (rule === null || rule === void 0 ? void 0 : rule.errorBehavior) === 'fatal' || ((_j = rule === null || rule === void 0 ? void 0 : rule.event) === null || _j === void 0 ? void 0 : _j.type) === 'fatality' ? 'fatality' : 'error';
|
|
84
93
|
}
|
|
85
94
|
logger_1.logger.error({
|
|
86
95
|
index: i,
|
|
@@ -90,10 +99,10 @@ function runEngineOnFiles(params) {
|
|
|
90
99
|
source: errorSource,
|
|
91
100
|
type: errorLevel,
|
|
92
101
|
stack: (handledError || error).stack,
|
|
93
|
-
details: ((
|
|
102
|
+
details: ((_k = error === null || error === void 0 ? void 0 : error.pluginError) === null || _k === void 0 ? void 0 : _k.details) || error.message
|
|
94
103
|
}, `Execution error occurred at file ${file.filePath} (${i + 1} of ${fileCount})`);
|
|
95
104
|
// Execute error action if specified
|
|
96
|
-
if ((
|
|
105
|
+
if ((_l = rule === null || rule === void 0 ? void 0 : rule.onError) === null || _l === void 0 ? void 0 : _l.action) {
|
|
97
106
|
try {
|
|
98
107
|
const actionResult = yield (0, errorActionExecutor_1.executeErrorAction)(rule.onError.action, {
|
|
99
108
|
error: error,
|
|
@@ -124,7 +133,7 @@ function runEngineOnFiles(params) {
|
|
|
124
133
|
message: `${errorSource} execution failed: ${(handledError || error).message}`,
|
|
125
134
|
source: errorSource,
|
|
126
135
|
stack: (handledError || error).stack,
|
|
127
|
-
details: (
|
|
136
|
+
details: (_m = error === null || error === void 0 ? void 0 : error.pluginError) === null || _m === void 0 ? void 0 : _m.details
|
|
128
137
|
}
|
|
129
138
|
});
|
|
130
139
|
}
|
|
@@ -17,9 +17,11 @@ const facts_1 = require("../../facts");
|
|
|
17
17
|
const telemetry_1 = require("../../utils/telemetry");
|
|
18
18
|
const exemptionUtils_1 = require("../../utils/exemptionUtils");
|
|
19
19
|
const configManager_1 = require("../configManager");
|
|
20
|
+
const repoXFIConfigLoader_1 = require("../../utils/repoXFIConfigLoader");
|
|
20
21
|
function setupEngine(params) {
|
|
21
22
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
-
|
|
23
|
+
var _a;
|
|
24
|
+
const { archetypeConfig, archetype, executionLogPrefix, repoUrl, localConfigPath } = params;
|
|
23
25
|
const engine = new json_rules_engine_1.Engine([], { replaceFactsInEventParams: true, allowUndefinedFacts: true });
|
|
24
26
|
// Add operators to engine
|
|
25
27
|
logger_1.logger.info(`=== loading custom operators..`);
|
|
@@ -34,12 +36,21 @@ function setupEngine(params) {
|
|
|
34
36
|
// Add rules to engine
|
|
35
37
|
logger_1.logger.info(`=== loading json rules..`);
|
|
36
38
|
const config = yield configManager_1.ConfigManager.getConfig({ archetype, logPrefix: executionLogPrefix });
|
|
39
|
+
// Load repo config to get additional rules
|
|
40
|
+
const repoConfig = yield (0, repoXFIConfigLoader_1.loadRepoXFIConfig)(localConfigPath);
|
|
37
41
|
logger_1.logger.debug(`rules loaded: ${config.rules}`);
|
|
38
42
|
const addedRules = new Set();
|
|
43
|
+
// First add archetype rules
|
|
39
44
|
config.rules.forEach((rule) => {
|
|
40
45
|
try {
|
|
41
46
|
if (rule && rule.name) {
|
|
42
|
-
|
|
47
|
+
// Check if rule is already registered
|
|
48
|
+
if (addedRules.has(rule.name)) {
|
|
49
|
+
logger_1.logger.warn(`Skipping duplicate rule: ${rule.name}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
logger_1.logger.info(`adding archetype rule: ${rule.name}`);
|
|
53
|
+
// Check for exemption
|
|
43
54
|
if ((0, exemptionUtils_1.isExempt)({ exemptions: config.exemptions, repoUrl, ruleName: rule.name, logPrefix: executionLogPrefix })) {
|
|
44
55
|
// clone the rule to avoid modifying the original rule
|
|
45
56
|
const exemptRule = JSON.parse(JSON.stringify(rule));
|
|
@@ -50,6 +61,7 @@ function setupEngine(params) {
|
|
|
50
61
|
else {
|
|
51
62
|
engine.addRule(rule);
|
|
52
63
|
}
|
|
64
|
+
// Track added rule
|
|
53
65
|
addedRules.add(rule.name);
|
|
54
66
|
}
|
|
55
67
|
else {
|
|
@@ -57,10 +69,31 @@ function setupEngine(params) {
|
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
catch (e) {
|
|
60
|
-
logger_1.logger.error(`Error loading rule: ${(rule === null || rule === void 0 ? void 0 : rule.name) || 'unknown'}`);
|
|
72
|
+
logger_1.logger.error(`Error loading archetype rule: ${(rule === null || rule === void 0 ? void 0 : rule.name) || 'unknown'}`);
|
|
61
73
|
logger_1.logger.error(e.message);
|
|
62
74
|
}
|
|
63
75
|
});
|
|
76
|
+
// Then add additional rules from repo config
|
|
77
|
+
if ((_a = repoConfig.additionalRules) === null || _a === void 0 ? void 0 : _a.length) {
|
|
78
|
+
repoConfig.additionalRules.forEach((rule) => {
|
|
79
|
+
try {
|
|
80
|
+
if (rule && rule.name) {
|
|
81
|
+
// Check if rule is already registered
|
|
82
|
+
if (addedRules.has(rule.name)) {
|
|
83
|
+
logger_1.logger.warn(`Skipping duplicate additional rule: ${rule.name}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
logger_1.logger.info(`adding additional rule: ${rule.name}`);
|
|
87
|
+
engine.addRule(rule);
|
|
88
|
+
addedRules.add(rule.name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
logger_1.logger.error(`Error loading additional rule: ${(rule === null || rule === void 0 ? void 0 : rule.name) || 'unknown'}`);
|
|
93
|
+
logger_1.logger.error(e.message);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
64
97
|
engine.on('success', (_a) => __awaiter(this, [_a], void 0, function* ({ type, params }) {
|
|
65
98
|
if (type === 'warning') {
|
|
66
99
|
logger_1.logger.warn(`warning detected: ${JSON.stringify(params)}`);
|
|
@@ -16,12 +16,14 @@ const facts_1 = require("../../facts");
|
|
|
16
16
|
const telemetry_1 = require("../../utils/telemetry");
|
|
17
17
|
const configManager_1 = require("../configManager");
|
|
18
18
|
const logger_1 = require("../../utils/logger");
|
|
19
|
+
const repoXFIConfigLoader_1 = require("../../utils/repoXFIConfigLoader");
|
|
19
20
|
jest.mock('json-rules-engine');
|
|
20
21
|
jest.mock('../../operators');
|
|
21
22
|
jest.mock('../../facts');
|
|
22
23
|
jest.mock('../../utils/telemetry');
|
|
23
24
|
jest.mock('../configManager');
|
|
24
25
|
jest.mock('../../utils/logger');
|
|
26
|
+
jest.mock('../../utils/repoXFIConfigLoader');
|
|
25
27
|
describe('setupEngine', () => {
|
|
26
28
|
let engine;
|
|
27
29
|
afterEach(() => {
|
|
@@ -61,6 +63,13 @@ describe('setupEngine', () => {
|
|
|
61
63
|
],
|
|
62
64
|
cliOptions: {}
|
|
63
65
|
});
|
|
66
|
+
repoXFIConfigLoader_1.loadRepoXFIConfig.mockResolvedValue({
|
|
67
|
+
additionalRules: [],
|
|
68
|
+
additionalFacts: [],
|
|
69
|
+
additionalOperators: [],
|
|
70
|
+
additionalPlugins: [],
|
|
71
|
+
sensitiveFileFalsePositives: []
|
|
72
|
+
});
|
|
64
73
|
operators_1.loadOperators.mockResolvedValue([
|
|
65
74
|
{ name: 'operator1', fn: jest.fn() },
|
|
66
75
|
{ name: 'operator2', fn: jest.fn() }
|
|
@@ -159,4 +168,49 @@ describe('setupEngine', () => {
|
|
|
159
168
|
expect(mockAddFact).toHaveBeenCalledWith('fact1', expect.any(Function), { priority: 1 });
|
|
160
169
|
expect(mockAddFact).toHaveBeenCalledWith('openaiAnalysis', expect.any(Function), { priority: 1 });
|
|
161
170
|
}));
|
|
171
|
+
describe('rule loading order', () => {
|
|
172
|
+
it('should load archetype rules before additional rules', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
173
|
+
const mockArchetypeRule = { name: 'rule1', conditions: {}, event: {} };
|
|
174
|
+
const mockAdditionalRule = { name: 'rule2', conditions: {}, event: {} };
|
|
175
|
+
configManager_1.ConfigManager.getConfig.mockResolvedValue({
|
|
176
|
+
rules: [mockArchetypeRule],
|
|
177
|
+
exemptions: []
|
|
178
|
+
});
|
|
179
|
+
repoXFIConfigLoader_1.loadRepoXFIConfig.mockResolvedValue({
|
|
180
|
+
additionalRules: [mockAdditionalRule]
|
|
181
|
+
});
|
|
182
|
+
const mockAddRule = jest.fn();
|
|
183
|
+
json_rules_engine_1.Engine.mockImplementation(() => ({
|
|
184
|
+
addOperator: jest.fn(),
|
|
185
|
+
addRule: mockAddRule,
|
|
186
|
+
addFact: jest.fn(),
|
|
187
|
+
on: jest.fn()
|
|
188
|
+
}));
|
|
189
|
+
yield (0, engineSetup_1.setupEngine)(mockParams);
|
|
190
|
+
expect(mockAddRule).toHaveBeenNthCalledWith(1, mockArchetypeRule);
|
|
191
|
+
expect(mockAddRule).toHaveBeenNthCalledWith(2, mockAdditionalRule);
|
|
192
|
+
}));
|
|
193
|
+
it('should skip duplicate rules and keep first occurrence', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
194
|
+
const duplicateRule = { name: 'duplicate', conditions: {}, event: {} };
|
|
195
|
+
const uniqueRule = { name: 'unique', conditions: {}, event: {} };
|
|
196
|
+
configManager_1.ConfigManager.getConfig.mockResolvedValue({
|
|
197
|
+
rules: [duplicateRule],
|
|
198
|
+
exemptions: []
|
|
199
|
+
});
|
|
200
|
+
repoXFIConfigLoader_1.loadRepoXFIConfig.mockResolvedValue({
|
|
201
|
+
additionalRules: [duplicateRule, uniqueRule]
|
|
202
|
+
});
|
|
203
|
+
const mockAddRule = jest.fn();
|
|
204
|
+
json_rules_engine_1.Engine.mockImplementation(() => ({
|
|
205
|
+
addOperator: jest.fn(),
|
|
206
|
+
addRule: mockAddRule,
|
|
207
|
+
addFact: jest.fn(),
|
|
208
|
+
on: jest.fn()
|
|
209
|
+
}));
|
|
210
|
+
yield (0, engineSetup_1.setupEngine)(mockParams);
|
|
211
|
+
expect(mockAddRule).toHaveBeenCalledTimes(2);
|
|
212
|
+
expect(mockAddRule).toHaveBeenNthCalledWith(1, duplicateRule);
|
|
213
|
+
expect(mockAddRule).toHaveBeenNthCalledWith(2, uniqueRule);
|
|
214
|
+
}));
|
|
215
|
+
});
|
|
162
216
|
});
|
package/dist/utils/logger.js
CHANGED
|
@@ -41,11 +41,7 @@ function getLogger(force) {
|
|
|
41
41
|
// Determine if color should be enabled based on environment variable only
|
|
42
42
|
const useColor = process.env.XFI_LOG_COLOR !== 'false';
|
|
43
43
|
if (!loggerInstance || force) {
|
|
44
|
-
const fileTransport = pino_1.default.destination(
|
|
45
|
-
dest: 'x-fidelity.log',
|
|
46
|
-
sync: false,
|
|
47
|
-
mkdir: true
|
|
48
|
-
});
|
|
44
|
+
const fileTransport = pino_1.default.destination('x-fidelity.log');
|
|
49
45
|
const prettyTransport = pino_1.default.transport({
|
|
50
46
|
target: 'pino-pretty',
|
|
51
47
|
options: {
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
36
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
37
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -19,6 +52,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
19
52
|
const pathUtils_1 = require("./pathUtils");
|
|
20
53
|
const logger_1 = require("./logger");
|
|
21
54
|
const jsonSchemas_1 = require("./jsonSchemas");
|
|
55
|
+
const cli_1 = require("../core/cli");
|
|
22
56
|
exports.defaultRepoXFIConfig = {
|
|
23
57
|
sensitiveFileFalsePositives: [],
|
|
24
58
|
additionalRules: [],
|
|
@@ -28,103 +62,182 @@ exports.defaultRepoXFIConfig = {
|
|
|
28
62
|
};
|
|
29
63
|
function loadRepoXFIConfig(repoPath) {
|
|
30
64
|
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
var _a;
|
|
31
66
|
try {
|
|
32
67
|
const baseRepo = path_1.default.resolve(repoPath);
|
|
33
68
|
const configPath = path_1.default.resolve(baseRepo, '.xfi-config.json');
|
|
69
|
+
// Early return if no config file
|
|
34
70
|
if (!fs_1.default.existsSync(configPath)) {
|
|
35
|
-
|
|
71
|
+
logger_1.logger.warn(`No .xfi-config.json file found, returning default config`);
|
|
72
|
+
return exports.defaultRepoXFIConfig;
|
|
36
73
|
}
|
|
37
74
|
if (!(0, pathUtils_1.isPathInside)(configPath, baseRepo)) {
|
|
38
75
|
throw new Error('Resolved config path is outside allowed directory');
|
|
39
76
|
}
|
|
77
|
+
// Load and parse config
|
|
78
|
+
const parsedConfig = yield loadAndValidateConfig(configPath);
|
|
79
|
+
if (!parsedConfig)
|
|
80
|
+
return exports.defaultRepoXFIConfig;
|
|
81
|
+
// Process paths if they exist
|
|
82
|
+
if (parsedConfig.sensitiveFileFalsePositives) {
|
|
83
|
+
parsedConfig.sensitiveFileFalsePositives = processFilePaths(parsedConfig.sensitiveFileFalsePositives, repoPath);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
parsedConfig.sensitiveFileFalsePositives = [];
|
|
87
|
+
}
|
|
88
|
+
// Initialize arrays
|
|
89
|
+
initializeArrays(parsedConfig);
|
|
90
|
+
// Process additional rules
|
|
91
|
+
if ((_a = parsedConfig.additionalRules) === null || _a === void 0 ? void 0 : _a.length) {
|
|
92
|
+
parsedConfig.additionalRules = yield processAdditionalRules(parsedConfig.additionalRules, baseRepo);
|
|
93
|
+
}
|
|
94
|
+
return parsedConfig;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger_1.logger.error(`Error loading repo config: ${error}`);
|
|
98
|
+
return exports.defaultRepoXFIConfig;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function loadAndValidateConfig(configPath) {
|
|
103
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
try {
|
|
40
105
|
const configContent = yield fs_1.default.promises.readFile(configPath, 'utf8');
|
|
41
106
|
const parsedConfig = JSON.parse(configContent);
|
|
42
|
-
if ((0, jsonSchemas_1.validateXFIConfig)(parsedConfig)) {
|
|
43
|
-
logger_1.logger.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
107
|
+
if (!(0, jsonSchemas_1.validateXFIConfig)(parsedConfig)) {
|
|
108
|
+
logger_1.logger.warn('Invalid .xfi-config.json schema');
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return parsedConfig;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
logger_1.logger.error(`Error reading/parsing config: ${error}`);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function processFilePaths(paths = [], repoPath) {
|
|
120
|
+
return paths.map(filePath => path_1.default.join(repoPath, filePath));
|
|
121
|
+
}
|
|
122
|
+
function initializeArrays(config) {
|
|
123
|
+
config.additionalRules = config.additionalRules || [];
|
|
124
|
+
config.additionalFacts = config.additionalFacts || [];
|
|
125
|
+
config.additionalOperators = config.additionalOperators || [];
|
|
126
|
+
config.additionalPlugins = config.additionalPlugins || [];
|
|
127
|
+
}
|
|
128
|
+
function processAdditionalRules(rules, baseRepo) {
|
|
129
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
130
|
+
const validatedRules = [];
|
|
131
|
+
for (const rule of rules) {
|
|
132
|
+
try {
|
|
133
|
+
// Handle inline rules first
|
|
134
|
+
if (!('path' in rule) && !('url' in rule)) {
|
|
135
|
+
const ruleName = (rule === null || rule === void 0 ? void 0 : rule.name) || 'unnamed';
|
|
136
|
+
logger_1.logger.debug(`Validating inline rule ${ruleName}`);
|
|
137
|
+
if ((0, jsonSchemas_1.validateRule)(rule)) {
|
|
138
|
+
logger_1.logger.info(`Adding valid inline rule ${ruleName}`);
|
|
139
|
+
validatedRules.push(rule);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
logger_1.logger.warn(`Invalid inline rule ${ruleName} in .xfi-config.json, skipping`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// Handle remote URL
|
|
146
|
+
if ('url' in rule && rule.url) {
|
|
147
|
+
yield processRemoteRule(rule, validatedRules);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
// Handle local path with potential wildcards
|
|
151
|
+
if ('path' in rule && rule.path) {
|
|
152
|
+
yield processLocalRule(rule, baseRepo, validatedRules);
|
|
50
153
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
logger_1.logger.error(`Error processing rule: ${error}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return validatedRules;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function processRemoteRule(rule, validatedRules) {
|
|
163
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
164
|
+
try {
|
|
165
|
+
const response = yield fetch(rule.url);
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
168
|
+
}
|
|
169
|
+
const ruleContent = yield response.text();
|
|
170
|
+
const remoteRule = JSON.parse(ruleContent);
|
|
171
|
+
if ((0, jsonSchemas_1.validateRule)(remoteRule)) {
|
|
172
|
+
validatedRules.push(remoteRule);
|
|
173
|
+
logger_1.logger.info(`Loaded rule from URL ${rule.url}`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
logger_1.logger.warn(`Invalid rule from URL ${rule.url}, skipping`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
logger_1.logger.warn(`Error loading rule from URL ${rule.url}: ${error}`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function processLocalRule(rule, baseRepo, validatedRules) {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
const pathPattern = rule.path;
|
|
187
|
+
let foundValidRule = false;
|
|
188
|
+
// Try multiple base directories in order of precedence
|
|
189
|
+
const searchPaths = [
|
|
190
|
+
cli_1.options.localConfigPath, // Local config dir first
|
|
191
|
+
process.cwd(), // Current working dir second
|
|
192
|
+
baseRepo // Repository root last
|
|
193
|
+
].filter(Boolean); // Remove undefined/null paths
|
|
194
|
+
for (const basePath of searchPaths) {
|
|
195
|
+
if (foundValidRule)
|
|
196
|
+
break;
|
|
197
|
+
const fullPattern = path_1.default.resolve(basePath, pathPattern);
|
|
198
|
+
const baseDir = path_1.default.dirname(fullPattern);
|
|
199
|
+
// Check if path is inside allowed directories
|
|
200
|
+
if (!searchPaths.some(allowedPath => (0, pathUtils_1.isPathInside)(baseDir, allowedPath))) {
|
|
201
|
+
logger_1.logger.warn(`Rule path ${pathPattern} resolves outside allowed directories, skipping`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
let rulePaths = [];
|
|
206
|
+
if (pathPattern.includes('*')) {
|
|
207
|
+
// Handle wildcards using glob pattern
|
|
208
|
+
const { glob } = yield Promise.resolve().then(() => __importStar(require('glob')));
|
|
209
|
+
rulePaths = yield glob(fullPattern);
|
|
210
|
+
}
|
|
211
|
+
else if (fs_1.default.existsSync(fullPattern)) {
|
|
212
|
+
// Single file path
|
|
213
|
+
rulePaths = [fullPattern];
|
|
214
|
+
}
|
|
215
|
+
// Load and validate each rule file
|
|
216
|
+
for (const rulePath of rulePaths) {
|
|
217
|
+
try {
|
|
218
|
+
const content = yield fs_1.default.promises.readFile(rulePath, 'utf8');
|
|
219
|
+
const rule = JSON.parse(content);
|
|
220
|
+
if ((0, jsonSchemas_1.validateRule)(rule)) {
|
|
221
|
+
validatedRules.push(rule);
|
|
222
|
+
logger_1.logger.info(`Loaded rule from ${rulePath}`);
|
|
223
|
+
foundValidRule = true;
|
|
224
|
+
break; // Take first valid rule found
|
|
104
225
|
}
|
|
105
226
|
else {
|
|
106
|
-
|
|
107
|
-
if ((0, jsonSchemas_1.validateRule)(ruleConfig)) {
|
|
108
|
-
validatedRules.push(ruleConfig);
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
const ruleName = (ruleConfig === null || ruleConfig === void 0 ? void 0 : ruleConfig.name) || 'unnamed';
|
|
112
|
-
logger_1.logger.warn(`Invalid inline rule ${ruleName} in .xfi-config.json, skipping`);
|
|
113
|
-
}
|
|
227
|
+
logger_1.logger.warn(`Invalid rule in ${rulePath}, skipping`);
|
|
114
228
|
}
|
|
115
229
|
}
|
|
116
|
-
|
|
230
|
+
catch (error) {
|
|
231
|
+
logger_1.logger.warn(`Error loading rule from ${rulePath}: ${error}`);
|
|
232
|
+
}
|
|
117
233
|
}
|
|
118
|
-
return parsedConfig;
|
|
119
234
|
}
|
|
120
|
-
|
|
121
|
-
logger_1.logger.
|
|
122
|
-
return exports.defaultRepoXFIConfig;
|
|
235
|
+
catch (error) {
|
|
236
|
+
logger_1.logger.debug(`Error resolving pattern ${pathPattern} in ${basePath}: ${error}`);
|
|
123
237
|
}
|
|
124
238
|
}
|
|
125
|
-
|
|
126
|
-
logger_1.logger.warn(`No
|
|
127
|
-
return exports.defaultRepoXFIConfig;
|
|
239
|
+
if (!foundValidRule) {
|
|
240
|
+
logger_1.logger.warn(`No valid rules found for pattern: ${pathPattern}`);
|
|
128
241
|
}
|
|
129
242
|
});
|
|
130
243
|
}
|