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 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
- fileFailures.push({
49
- ruleFailure: result.name,
50
- level: (_a = result.event) === null || _a === void 0 ? void 0 : _a.type,
51
- details: Object.assign({ message: ((_c = (_b = result.event) === null || _b === void 0 ? void 0 : _b.params) === null || _c === void 0 ? void 0 : _c.message) || 'Rule failure detected' }, (_d = result.event) === null || _d === void 0 ? void 0 : _d.params)
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 = (_e = error === null || error === void 0 ? void 0 : error.rule) === null || _e === void 0 ? void 0 : _e.name;
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' || ((_f = rule === null || rule === void 0 ? void 0 : rule.event) === null || _f === void 0 ? void 0 : _f.type) === 'fatality' ? 'fatality' : 'error';
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: ((_g = error === null || error === void 0 ? void 0 : error.pluginError) === null || _g === void 0 ? void 0 : _g.details) || error.message
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 ((_h = rule === null || rule === void 0 ? void 0 : rule.onError) === null || _h === void 0 ? void 0 : _h.action) {
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: (_j = error === null || error === void 0 ? void 0 : error.pluginError) === null || _j === void 0 ? void 0 : _j.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
- const { archetypeConfig, archetype, executionLogPrefix, repoUrl } = params;
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
- logger_1.logger.info(`adding rule: ${rule.name}`);
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
  });
@@ -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
- throw new Error('No .xfi-config.json file found');
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.info('Loaded .xfi-config.json file from repo..');
44
- // Add the repoPath prefix to the relative paths
45
- if (parsedConfig.sensitiveFileFalsePositives) {
46
- parsedConfig.sensitiveFileFalsePositives = parsedConfig.sensitiveFileFalsePositives.map((filePath) => {
47
- filePath = path_1.default.join(repoPath, filePath);
48
- return filePath;
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
- // Validate and load additional rules if present
52
- if (parsedConfig.additionalRules && Array.isArray(parsedConfig.additionalRules)) {
53
- const validatedRules = [];
54
- for (const ruleConfig of parsedConfig.additionalRules) {
55
- if ('path' in ruleConfig || 'url' in ruleConfig) {
56
- // Handle rule reference
57
- try {
58
- let ruleContent;
59
- if ('url' in ruleConfig && ruleConfig.url) {
60
- // Handle remote URL
61
- const response = yield fetch(ruleConfig.url);
62
- if (!response.ok) {
63
- throw new Error(`HTTP error! status: ${response.status}`);
64
- }
65
- ruleContent = yield response.text();
66
- }
67
- else if ('path' in ruleConfig && ruleConfig.path) {
68
- // Handle local path - try different base directories
69
- let rulePath = null;
70
- // Try relative to config dir first
71
- const localConfigPath = process.env.LOCAL_CONFIG_PATH;
72
- if (localConfigPath) {
73
- const configDirPath = path_1.default.resolve(localConfigPath, ruleConfig.path);
74
- if (fs_1.default.existsSync(configDirPath)) {
75
- rulePath = configDirPath;
76
- }
77
- }
78
- // Then try relative to repo dir
79
- if (!rulePath) {
80
- const repoDirPath = path_1.default.resolve(baseRepo, ruleConfig.path);
81
- if ((0, pathUtils_1.isPathInside)(repoDirPath, baseRepo) && fs_1.default.existsSync(repoDirPath)) {
82
- rulePath = repoDirPath;
83
- }
84
- }
85
- if (!rulePath) {
86
- throw new Error(`Could not resolve rule path: ${ruleConfig.path}`);
87
- }
88
- ruleContent = yield fs_1.default.promises.readFile(rulePath, 'utf8');
89
- }
90
- else {
91
- throw new Error('Rule reference must have either url or path');
92
- }
93
- const rule = JSON.parse(ruleContent);
94
- if ((0, jsonSchemas_1.validateRule)(rule)) {
95
- validatedRules.push(rule);
96
- }
97
- else {
98
- logger_1.logger.warn(`Invalid rule in referenced file ${ruleConfig.path || ruleConfig.url}, skipping`);
99
- }
100
- }
101
- catch (error) {
102
- logger_1.logger.warn(`Error loading rule from ${ruleConfig.path || ruleConfig.url}: ${error}`);
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
- // Handle inline rule
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
- parsedConfig.additionalRules = validatedRules;
230
+ catch (error) {
231
+ logger_1.logger.warn(`Error loading rule from ${rulePath}: ${error}`);
232
+ }
117
233
  }
118
- return parsedConfig;
119
234
  }
120
- else {
121
- logger_1.logger.warn(`Ignoring invalid .xfi-config.json file, returing default config: ${JSON.stringify(exports.defaultRepoXFIConfig)}`);
122
- return exports.defaultRepoXFIConfig;
235
+ catch (error) {
236
+ logger_1.logger.debug(`Error resolving pattern ${pathPattern} in ${basePath}: ${error}`);
123
237
  }
124
238
  }
125
- catch (error) {
126
- logger_1.logger.warn(`No .xfi-config.json file found, returing default config: ${JSON.stringify(exports.defaultRepoXFIConfig)}`);
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
  }