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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x-fidelity",
3
- "version": "3.19.0",
3
+ "version": "3.19.1",
4
4
  "description": "cli for opinionated framework adherence checks",
5
5
  "main": "dist/index",
6
6
  "types": "dist/index.d.ts",
@@ -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 three flexible ways:
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 relative to your repository or config paths:
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
- - Absolute paths (relative to repo root)
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