x-fidelity 3.19.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
CHANGED
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
{
|
|
34
34
|
"name": "sensitiveLogging-iterative",
|
|
35
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"
|
|
36
44
|
}
|
|
37
45
|
],
|
|
38
46
|
"additionalFacts": ["customFact"],
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
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
|
+
|
|
1
8
|
# [3.19.0](https://github.com/zotoio/x-fidelity/compare/v3.18.0...v3.19.0) (2025-03-25)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -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/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import { loadFacts } from '../../facts';
|
|
|
5
5
|
import { sendTelemetry } from '../../utils/telemetry';
|
|
6
6
|
import { ConfigManager } from '../configManager';
|
|
7
7
|
import { logger } from '../../utils/logger';
|
|
8
|
+
import { loadRepoXFIConfig } from '../../utils/repoXFIConfigLoader';
|
|
8
9
|
|
|
9
10
|
jest.mock('json-rules-engine');
|
|
10
11
|
jest.mock('../../operators');
|
|
@@ -12,6 +13,7 @@ jest.mock('../../facts');
|
|
|
12
13
|
jest.mock('../../utils/telemetry');
|
|
13
14
|
jest.mock('../configManager');
|
|
14
15
|
jest.mock('../../utils/logger');
|
|
16
|
+
jest.mock('../../utils/repoXFIConfigLoader');
|
|
15
17
|
|
|
16
18
|
describe('setupEngine', () => {
|
|
17
19
|
let engine: Engine & { removeAllListeners?: () => void } | undefined;
|
|
@@ -56,6 +58,13 @@ describe('setupEngine', () => {
|
|
|
56
58
|
],
|
|
57
59
|
cliOptions: {}
|
|
58
60
|
});
|
|
61
|
+
(loadRepoXFIConfig as jest.Mock).mockResolvedValue({
|
|
62
|
+
additionalRules: [],
|
|
63
|
+
additionalFacts: [],
|
|
64
|
+
additionalOperators: [],
|
|
65
|
+
additionalPlugins: [],
|
|
66
|
+
sensitiveFileFalsePositives: []
|
|
67
|
+
});
|
|
59
68
|
(loadOperators as jest.Mock).mockResolvedValue([
|
|
60
69
|
{ name: 'operator1', fn: jest.fn() },
|
|
61
70
|
{ name: 'operator2', fn: jest.fn() }
|
|
@@ -179,4 +188,61 @@ describe('setupEngine', () => {
|
|
|
179
188
|
expect(mockAddFact).toHaveBeenCalledWith('fact1', expect.any(Function), { priority: 1 });
|
|
180
189
|
expect(mockAddFact).toHaveBeenCalledWith('openaiAnalysis', expect.any(Function), { priority: 1 });
|
|
181
190
|
});
|
|
191
|
+
|
|
192
|
+
describe('rule loading order', () => {
|
|
193
|
+
it('should load archetype rules before additional rules', async () => {
|
|
194
|
+
const mockArchetypeRule = { name: 'rule1', conditions: {}, event: {} };
|
|
195
|
+
const mockAdditionalRule = { name: 'rule2', conditions: {}, event: {} };
|
|
196
|
+
|
|
197
|
+
(ConfigManager.getConfig as jest.Mock).mockResolvedValue({
|
|
198
|
+
rules: [mockArchetypeRule],
|
|
199
|
+
exemptions: []
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
(loadRepoXFIConfig as jest.Mock).mockResolvedValue({
|
|
203
|
+
additionalRules: [mockAdditionalRule]
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const mockAddRule = jest.fn();
|
|
207
|
+
(Engine as jest.Mock).mockImplementation(() => ({
|
|
208
|
+
addOperator: jest.fn(),
|
|
209
|
+
addRule: mockAddRule,
|
|
210
|
+
addFact: jest.fn(),
|
|
211
|
+
on: jest.fn()
|
|
212
|
+
}));
|
|
213
|
+
|
|
214
|
+
await setupEngine(mockParams);
|
|
215
|
+
|
|
216
|
+
expect(mockAddRule).toHaveBeenNthCalledWith(1, mockArchetypeRule);
|
|
217
|
+
expect(mockAddRule).toHaveBeenNthCalledWith(2, mockAdditionalRule);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should skip duplicate rules and keep first occurrence', async () => {
|
|
221
|
+
const duplicateRule = { name: 'duplicate', conditions: {}, event: {} };
|
|
222
|
+
const uniqueRule = { name: 'unique', conditions: {}, event: {} };
|
|
223
|
+
|
|
224
|
+
(ConfigManager.getConfig as jest.Mock).mockResolvedValue({
|
|
225
|
+
rules: [duplicateRule],
|
|
226
|
+
exemptions: []
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
(loadRepoXFIConfig as jest.Mock).mockResolvedValue({
|
|
230
|
+
additionalRules: [duplicateRule, uniqueRule]
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const mockAddRule = jest.fn();
|
|
234
|
+
(Engine as jest.Mock).mockImplementation(() => ({
|
|
235
|
+
addOperator: jest.fn(),
|
|
236
|
+
addRule: mockAddRule,
|
|
237
|
+
addFact: jest.fn(),
|
|
238
|
+
on: jest.fn()
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
await setupEngine(mockParams);
|
|
242
|
+
|
|
243
|
+
expect(mockAddRule).toHaveBeenCalledTimes(2);
|
|
244
|
+
expect(mockAddRule).toHaveBeenNthCalledWith(1, duplicateRule);
|
|
245
|
+
expect(mockAddRule).toHaveBeenNthCalledWith(2, uniqueRule);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
182
248
|
});
|
|
@@ -106,7 +106,7 @@ The `.xfi-config.json` file in your repository root allows you to customize x-fi
|
|
|
106
106
|
An array of file paths relative to your repository root that should be excluded from sensitive data checks.
|
|
107
107
|
|
|
108
108
|
#### additionalRules
|
|
109
|
-
An array of custom rules to add to your configuration. Rules can be specified in
|
|
109
|
+
An array of custom rules to add to your configuration. Rules can be specified in multiple flexible ways:
|
|
110
110
|
|
|
111
111
|
1. **Inline Rules**: Define the complete rule configuration directly in the config file:
|
|
112
112
|
```json
|
|
@@ -130,21 +130,47 @@ An array of custom rules to add to your configuration. Rules can be specified in
|
|
|
130
130
|
}
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
2. **Local File References**: Reference rule files
|
|
133
|
+
2. **Local File References**: Reference rule files using paths that can be:
|
|
134
|
+
|
|
135
|
+
a) Relative to repository root:
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"name": "sensitiveLogging-iterative",
|
|
139
|
+
"path": "src/demoConfig/rules/sensitiveLogging-iterative-rule.json"
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
b) Relative to local config directory (if using --localConfigPath):
|
|
134
144
|
```json
|
|
135
145
|
{
|
|
136
146
|
"name": "local-rule",
|
|
137
147
|
"path": "rules/custom-rule.json"
|
|
138
148
|
}
|
|
139
149
|
```
|
|
150
|
+
|
|
151
|
+
c) Relative to plugin directory (for plugin-provided rules):
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"name": "functionComplexity-iterative",
|
|
155
|
+
"path": "src/plugins/xfiPluginAst/sampleRules/functionComplexity-iterative-rule.json"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
140
159
|
The `path` property supports:
|
|
141
|
-
-
|
|
142
|
-
- Relative paths (relative to config location)
|
|
143
|
-
- Glob patterns (e.g. `rules/*.json`)
|
|
160
|
+
- Glob patterns (e.g. `rules/*.json`, `**/*-rule.json`)
|
|
144
161
|
- Multiple base directories searched in order:
|
|
145
|
-
1. Local config directory
|
|
162
|
+
1. Local config directory (if --localConfigPath is specified)
|
|
146
163
|
2. Current working directory
|
|
147
164
|
3. Repository root
|
|
165
|
+
4. Plugin directories (for plugin rules)
|
|
166
|
+
|
|
167
|
+
Example using glob pattern:
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"name": "all-iterative-rules",
|
|
171
|
+
"path": "**/*-iterative-rule.json"
|
|
172
|
+
}
|
|
173
|
+
```
|
|
148
174
|
|
|
149
175
|
3. **Remote Rules**: Load rules from remote URLs:
|
|
150
176
|
```json
|
|
@@ -156,15 +182,39 @@ The `path` property supports:
|
|
|
156
182
|
Remote rules support:
|
|
157
183
|
- HTTPS URLs
|
|
158
184
|
- Authentication headers
|
|
159
|
-
- Automatic retries
|
|
160
|
-
- Error handling
|
|
185
|
+
- Automatic retries with exponential backoff
|
|
186
|
+
- Error handling with detailed logging
|
|
161
187
|
- Response validation
|
|
188
|
+
- Caching with TTL
|
|
189
|
+
|
|
190
|
+
4. **Reference Existing Rules**: Reference rules from archetypes:
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"name": "sensitiveLogging-iterative",
|
|
194
|
+
"path": "src/demoConfig/rules/sensitiveLogging-iterative-rule.json"
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
5. **Plugin Rules**: Load rules provided by plugins:
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"additionalPlugins": ["xfiPluginAst"],
|
|
202
|
+
"additionalRules": [
|
|
203
|
+
{
|
|
204
|
+
"name": "functionComplexity-iterative",
|
|
205
|
+
"path": "src/plugins/xfiPluginAst/sampleRules/functionComplexity-iterative-rule.json"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
```
|
|
162
210
|
|
|
163
211
|
Rules are loaded in the order specified. For each rule:
|
|
164
212
|
1. The rule is validated against the schema
|
|
165
213
|
2. For path/URL rules, the first valid rule found is used
|
|
166
214
|
3. Invalid rules are skipped with a warning
|
|
167
215
|
4. Rules can be exempted via exemptions config
|
|
216
|
+
5. Duplicate rules (same name) are skipped
|
|
217
|
+
6. Plugin rules are loaded after archetype rules
|
|
168
218
|
|
|
169
219
|
Each rule must follow the standard rule schema with:
|
|
170
220
|
- `name`: Unique identifier for the rule
|
|
@@ -173,6 +223,21 @@ Each rule must follow the standard rule schema with:
|
|
|
173
223
|
- `errorBehavior`: Optional "swallow" or "fatal"
|
|
174
224
|
- `onError`: Optional error handling configuration
|
|
175
225
|
|
|
226
|
+
Rule Loading Order:
|
|
227
|
+
1. Archetype rules (from config server or local config)
|
|
228
|
+
2. Plugin rules (from installed plugins)
|
|
229
|
+
3. Additional rules from .xfi-config.json:
|
|
230
|
+
- Inline rules
|
|
231
|
+
- Local file rules
|
|
232
|
+
- Remote URL rules
|
|
233
|
+
- Referenced rules
|
|
234
|
+
|
|
235
|
+
Duplicate Rule Handling:
|
|
236
|
+
- Rules with the same name are not loaded twice
|
|
237
|
+
- First rule loaded takes precedence
|
|
238
|
+
- Warning logged for duplicate rule names
|
|
239
|
+
- Archetype rules take precedence over additional rules
|
|
240
|
+
|
|
176
241
|
#### additionalFacts
|
|
177
242
|
An array of fact names to enable from installed plugins or custom implementations.
|
|
178
243
|
|