x-fidelity 3.8.0 → 3.9.0

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
@@ -1,5 +1,38 @@
1
1
  {
2
2
  "sensitiveFileFalsePositives": [
3
3
  "/src/facts/repoFilesystemFacts.ts"
4
- ]
4
+ ],
5
+ "additionalRules": [
6
+ {
7
+ "name": "custom-rule",
8
+ "conditions": {
9
+ "all": [
10
+ {
11
+ "fact": "fileData",
12
+ "path": "$.fileName",
13
+ "operator": "equal",
14
+ "value": "REPO_GLOBAL_CHECK"
15
+ },
16
+ {
17
+ "fact": "customFact",
18
+ "operator": "customOperator",
19
+ "value": "custom fact data"
20
+ }
21
+ ]
22
+ },
23
+ "event": {
24
+ "type": "warning",
25
+ "params": {
26
+ "message": "Custom rule detected matching data",
27
+ "details": {
28
+ "fact": "customFact"
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ ],
35
+ "additionalFacts": ["customFact"],
36
+ "additionalOperators": ["customOperator"],
37
+ "additionalPlugins": ["xfiPluginSimpleExample"]
5
38
  }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ # [3.9.0](https://github.com/zotoio/x-fidelity/compare/v3.8.1...v3.9.0) (2025-03-02)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add optional chaining for rule name access in analyzer ([d0c4b02](https://github.com/zotoio/x-fidelity/commit/d0c4b02c83bf77cade8e7310fe14ad0b10dd00c1))
7
+ * add type assertion to resolve schema type compatibility issue ([76f7618](https://github.com/zotoio/x-fidelity/commit/76f761869a85d09eb18a047010e6409475c766fc))
8
+ * **demo:** minor logic and test fix ([3b03804](https://github.com/zotoio/x-fidelity/commit/3b0380465dcc75355941f5e1818187f9b448ef6c))
9
+ * register plugin facts and operators before loading custom rules ([5ab7667](https://github.com/zotoio/x-fidelity/commit/5ab76675c7387a78bb43cb8bd959362a89e16ae9))
10
+ * resolve TypeScript error by casting rule to any type ([eba8fd1](https://github.com/zotoio/x-fidelity/commit/eba8fd1b968d64ec867ebeca76a92a2764321a08))
11
+ * resolve TypeScript errors in schema validation and rule handling ([8f3dd6f](https://github.com/zotoio/x-fidelity/commit/8f3dd6ffca0956a30a4b17794e1b2e0cdee178c0))
12
+ * update custom rule filename and enhance fact almanac handling ([8d11d55](https://github.com/zotoio/x-fidelity/commit/8d11d55c930e4389185905449dc6561958386317))
13
+
14
+
15
+ ### Features
16
+
17
+ * Add custom rule configuration with example plugin and operators ([7e54510](https://github.com/zotoio/x-fidelity/commit/7e54510d9ef25249d70f3e4691f9aae203bf2489))
18
+ * add support for custom rules and plugins in .xfi-config.json ([6206802](https://github.com/zotoio/x-fidelity/commit/62068028636b2da1a7f25df503be6bbb7c8dfdb3))
19
+
20
+ ## [3.8.1](https://github.com/zotoio/x-fidelity/compare/v3.8.0...v3.8.1) (2025-03-02)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **commitzen:** fix glob compatibilty issue ([73a7058](https://github.com/zotoio/x-fidelity/commit/73a7058e82d5853ceff3d3e5e3e48a03180f077d))
26
+
1
27
  # [3.8.0](https://github.com/zotoio/x-fidelity/compare/v3.7.1...v3.8.0) (2025-03-02)
2
28
 
3
29
 
package/README.md CHANGED
@@ -802,9 +802,13 @@ The `.xfi-config.json` file allows you to configure x-fidelity behavior specific
802
802
 
803
803
  ### Configuration Options
804
804
 
805
- Currently, the `.xfi-config.json` file supports the following options:
805
+ The `.xfi-config.json` file supports the following options:
806
806
 
807
807
  1. `sensitiveFileFalsePositives`: An array of file paths that should be excluded from sensitive data checks.
808
+ 2. `additionalPlugins`: An array of plugin module names to load for this repository.
809
+ 3. `additionalFacts`: An array of fact names to add to the archetype's facts.
810
+ 4. `additionalOperators`: An array of operator names to add to the archetype's operators.
811
+ 5. `additionalRules`: An array of custom rule definitions to add to the archetype's rules.
808
812
 
809
813
  Example `.xfi-config.json`:
810
814
 
@@ -813,6 +817,44 @@ Example `.xfi-config.json`:
813
817
  "sensitiveFileFalsePositives": [
814
818
  "path/to/exclude/file1.js",
815
819
  "path/to/exclude/file2.ts"
820
+ ],
821
+ "additionalPlugins": [
822
+ "xfiPluginSimpleExample"
823
+ ],
824
+ "additionalFacts": [
825
+ "customFact"
826
+ ],
827
+ "additionalOperators": [
828
+ "customOperator"
829
+ ],
830
+ "additionalRules": [
831
+ {
832
+ "name": "custom-rule",
833
+ "conditions": {
834
+ "all": [
835
+ {
836
+ "fact": "fileData",
837
+ "path": "$.fileName",
838
+ "operator": "equal",
839
+ "value": "REPO_GLOBAL_CHECK"
840
+ },
841
+ {
842
+ "fact": "customFact",
843
+ "operator": "customOperator",
844
+ "value": "custom fact data"
845
+ }
846
+ ]
847
+ },
848
+ "event": {
849
+ "type": "warning",
850
+ "params": {
851
+ "message": "Custom rule detected matching data",
852
+ "details": {
853
+ "fact": "customFact"
854
+ }
855
+ }
856
+ }
857
+ }
816
858
  ]
817
859
  }
818
860
  ```
@@ -822,16 +864,19 @@ Example `.xfi-config.json`:
822
864
  - When x-fidelity runs, it looks for the `.xfi-config.json` file in your project's root directory.
823
865
  - If found, it applies the configurations specified in this file.
824
866
  - For `sensitiveFileFalsePositives`, the specified files will be excluded from checks that look for sensitive data, such as API keys or passwords.
867
+ - For `additionalPlugins`, the specified plugins will be loaded, making their facts and operators available.
868
+ - For `additionalFacts` and `additionalOperators`, the specified facts and operators will be added to the archetype's facts and operators.
869
+ - For `additionalRules`, the specified rules will be added to the archetype's rules.
825
870
 
826
871
  ### How to use it
827
872
 
828
873
  1. **Version Control**: Include `.xfi-config.json` in your version control system to ensure consistency across your team.
829
- 2. **Documentation**: Add comments in the listed file explaining why it is a false positive.
830
- 3. **Regular Review**: Periodically review your `.xfi-config.json` to ensure the exclusions are still necessary and valid.
831
- 4. **Minimal Use**: Use exclusions sparingly. It's better to fix issues than to exclude them from checks.
832
- 5. **Feedback**: If the rules being applied are resulting in too many false-postives, speak with the team that manages your central rule config.
874
+ 2. **Documentation**: Add comments in the listed file explaining why it is a false positive or why custom rules are needed.
875
+ 3. **Regular Review**: Periodically review your `.xfi-config.json` to ensure the configurations are still necessary and valid.
876
+ 4. **Minimal Use**: Use exclusions and custom rules sparingly. It's better to fix issues than to exclude them from checks.
877
+ 5. **Feedback**: If the rules being applied are resulting in too many false-positives, speak with the team that manages your central rule config.
833
878
 
834
- Remember, while `.xfi-config.json` allows you to adjust x-fidelity's behavior in limited ways, it should be used judiciously to maintain the integrity of your code quality checks.
879
+ Remember, while `.xfi-config.json` allows you to adjust x-fidelity's behavior, it should be used judiciously to maintain the integrity of your code quality checks.
835
880
 
836
881
  ### Using Extensions
837
882
 
package/SECURITY.md ADDED
@@ -0,0 +1,18 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Use this section to tell people about which versions of your project are
6
+ currently being supported with security updates.
7
+
8
+ | Version | Supported |
9
+ | ------- | ------------------ |
10
+ | 3.8.x | :white_check_mark: |
11
+ | 2.17.x | :white_check_mark: |
12
+ | < 2.17 | :x: |
13
+
14
+ ## Reporting a Vulnerability
15
+
16
+ If you discover a security vulnerability within x-fi, please submit a report via the GitHub's Private Vulnerability Reporting feature.
17
+
18
+ All security vulnerabilities will be promptly addressed.
@@ -22,6 +22,8 @@ const telemetryCollector_1 = require("./telemetryCollector");
22
22
  const engineSetup_1 = require("./engineSetup");
23
23
  const engineRunner_1 = require("./engineRunner");
24
24
  const utils_1 = require("../../utils/utils");
25
+ const jsonSchemas_1 = require("../../utils/jsonSchemas");
26
+ const pluginRegistry_1 = require("../pluginRegistry");
25
27
  const cli_1 = require("../cli");
26
28
  function analyzeCodebase(params) {
27
29
  return __awaiter(this, void 0, void 0, function* () {
@@ -45,6 +47,25 @@ function analyzeCodebase(params) {
45
47
  const installedDependencyVersions = yield (0, repoDependencyFacts_1.getDependencyVersionFacts)(archetypeConfig);
46
48
  const fileData = yield (0, repoFilesystemFacts_1.collectRepoFileData)(repoPath, archetypeConfig);
47
49
  const repoXFIConfig = yield (0, repoXFIConfigLoader_1.loadRepoXFIConfig)(repoPath);
50
+ // Load additional plugins from repo config
51
+ if (repoXFIConfig.additionalPlugins && repoXFIConfig.additionalPlugins.length > 0) {
52
+ logger_1.logger.info(`Loading additional plugins from repo config: ${repoXFIConfig.additionalPlugins.join(', ')}`);
53
+ try {
54
+ yield configManager_1.ConfigManager.loadPlugins(repoXFIConfig.additionalPlugins);
55
+ }
56
+ catch (error) {
57
+ logger_1.logger.warn(`Error loading additional plugins from repo config: ${error}`);
58
+ }
59
+ }
60
+ // Merge additional components into archetype config
61
+ if (repoXFIConfig.additionalFacts) {
62
+ archetypeConfig.facts = [...new Set([...archetypeConfig.facts, ...repoXFIConfig.additionalFacts])];
63
+ logger_1.logger.info(`Added additional facts from repo config: ${repoXFIConfig.additionalFacts.join(', ')}`);
64
+ }
65
+ if (repoXFIConfig.additionalOperators) {
66
+ archetypeConfig.operators = [...new Set([...archetypeConfig.operators, ...repoXFIConfig.additionalOperators])];
67
+ logger_1.logger.info(`Added additional operators from repo config: ${repoXFIConfig.additionalOperators.join(', ')}`);
68
+ }
48
69
  // add REPO_GLOBAL_CHECK to fileData, which is the trigger for global checks
49
70
  fileData.push({
50
71
  fileName: configManager_1.REPO_GLOBAL_CHECK,
@@ -63,6 +84,44 @@ function analyzeCodebase(params) {
63
84
  localConfigPath,
64
85
  repoUrl
65
86
  });
87
+ // Add plugin facts and operators directly to the engine
88
+ if (repoXFIConfig.additionalFacts) {
89
+ const pluginFacts = pluginRegistry_1.pluginRegistry.getPluginFacts();
90
+ for (const factName of repoXFIConfig.additionalFacts) {
91
+ const fact = pluginFacts.find(f => f.name === factName);
92
+ if (fact) {
93
+ logger_1.logger.info(`Adding custom fact to engine: ${fact.name}`);
94
+ engine.addFact(fact.name, fact.fn, { priority: fact.priority || 1 });
95
+ }
96
+ }
97
+ }
98
+ if (repoXFIConfig.additionalOperators) {
99
+ const pluginOperators = pluginRegistry_1.pluginRegistry.getPluginOperators();
100
+ for (const operatorName of repoXFIConfig.additionalOperators) {
101
+ const operator = pluginOperators.find(o => o.name === operatorName);
102
+ if (operator) {
103
+ logger_1.logger.info(`Adding custom operator to engine: ${operator.name}`);
104
+ engine.addOperator(operator.name, operator.fn);
105
+ }
106
+ }
107
+ }
108
+ // Load additional rules from repo config
109
+ if (repoXFIConfig.additionalRules && repoXFIConfig.additionalRules.length > 0) {
110
+ logger_1.logger.info(`Loading additional rules from repo config: ${repoXFIConfig.additionalRules.length} rules`);
111
+ for (const rule of repoXFIConfig.additionalRules) {
112
+ if ((0, jsonSchemas_1.validateRule)(rule)) {
113
+ logger_1.logger.info(`Adding custom rule from repo config: ${rule.name}`);
114
+ // Convert RuleConfig to RuleProperties for Engine
115
+ const ruleProperties = Object.assign(Object.assign({}, rule), { conditions: rule.conditions });
116
+ engine.addRule(ruleProperties);
117
+ }
118
+ else {
119
+ // Cast rule to any to safely access name property
120
+ const ruleName = (rule === null || rule === void 0 ? void 0 : rule.name) || 'unnamed rule';
121
+ logger_1.logger.warn(`Invalid custom rule in repo config: ${ruleName}`);
122
+ }
123
+ }
124
+ }
66
125
  if ((0, openaiUtils_1.isOpenAIEnabled)() && archetypeConfig.facts.includes('openaiAnalysisFacts')) {
67
126
  logger_1.logger.info(`adding additional openai facts to engine..`);
68
127
  engine.addFact('openaiAnalysis', openaiAnalysisFacts_1.openaiAnalysis);
@@ -17,10 +17,15 @@ const logger_1 = require("../../../utils/logger");
17
17
  */
18
18
  exports.customFact = {
19
19
  name: 'customFact',
20
- fn: (params) => __awaiter(void 0, void 0, void 0, function* () {
20
+ fn: (params, almanac) => __awaiter(void 0, void 0, void 0, function* () {
21
21
  try {
22
22
  logger_1.logger.debug('Executing customFact');
23
- return { result: 'custom fact data' };
23
+ const result = 'custom fact data';
24
+ // Add the result to the almanac if resultFact is provided
25
+ if (params && params.resultFact) {
26
+ almanac.addRuntimeFact(params.resultFact, result);
27
+ }
28
+ return result;
24
29
  }
25
30
  catch (error) {
26
31
  logger_1.logger.error(`Error in customFact: ${error}`);
@@ -16,6 +16,6 @@ describe('customFact', () => {
16
16
  factValue: jest.fn()
17
17
  };
18
18
  const result = yield customFact_1.customFact.fn({}, mockAlmanac);
19
- expect(result).toEqual({ result: 'custom fact data' });
19
+ expect(result).toEqual('custom fact data');
20
20
  }));
21
21
  });
@@ -242,6 +242,10 @@ export interface ValidationResult {
242
242
  }
243
243
  export interface RepoXFIConfig {
244
244
  sensitiveFileFalsePositives?: string[];
245
+ additionalRules?: RuleConfig[];
246
+ additionalFacts?: string[];
247
+ additionalOperators?: string[];
248
+ additionalPlugins?: string[];
245
249
  [key: string]: any;
246
250
  }
247
251
  export type RepoXFIConfigSchema = JSONSchemaType<RepoXFIConfig>;
@@ -12,6 +12,7 @@ ajv.addFormat("semverPattern", {
12
12
  type: "string",
13
13
  validate: (x) => semver_1.default.valid(x) !== null && semver_1.default.validRange(x) !== null,
14
14
  });
15
+ // Using a simpler schema definition to avoid TypeScript errors with complex Ajv types
15
16
  const repoXFIConfigSchema = {
16
17
  type: "object",
17
18
  properties: {
@@ -21,6 +22,30 @@ const repoXFIConfigSchema = {
21
22
  minItems: 0,
22
23
  nullable: true,
23
24
  },
25
+ additionalRules: {
26
+ type: "array",
27
+ items: { type: "object" },
28
+ minItems: 0,
29
+ nullable: true,
30
+ },
31
+ additionalFacts: {
32
+ type: "array",
33
+ items: { type: "string" },
34
+ minItems: 0,
35
+ nullable: true,
36
+ },
37
+ additionalOperators: {
38
+ type: "array",
39
+ items: { type: "string" },
40
+ minItems: 0,
41
+ nullable: true,
42
+ },
43
+ additionalPlugins: {
44
+ type: "array",
45
+ items: { type: "string" },
46
+ minItems: 0,
47
+ nullable: true,
48
+ },
24
49
  },
25
50
  required: [],
26
51
  additionalProperties: true,
@@ -40,6 +40,16 @@ function loadRepoXFIConfig(repoPath) {
40
40
  return filePath;
41
41
  });
42
42
  }
43
+ // Validate additional rules if present
44
+ if (parsedConfig.additionalRules && Array.isArray(parsedConfig.additionalRules)) {
45
+ for (let i = 0; i < parsedConfig.additionalRules.length; i++) {
46
+ if (!(0, jsonSchemas_1.validateRule)(parsedConfig.additionalRules[i])) {
47
+ logger_1.logger.warn(`Invalid rule at index ${i} in .xfi-config.json, removing it`);
48
+ parsedConfig.additionalRules.splice(i, 1);
49
+ i--;
50
+ }
51
+ }
52
+ }
43
53
  return parsedConfig;
44
54
  }
45
55
  else {
@@ -1,3 +1,4 @@
1
1
  import { LoadRulesParams, RuleConfig } from '../types/typeDefs';
2
2
  declare function loadRules(params: LoadRulesParams): Promise<RuleConfig[]>;
3
- export { loadRules };
3
+ declare function validateCustomRules(rules: any[]): RuleConfig[];
4
+ export { loadRules, validateCustomRules };
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.loadRules = loadRules;
16
+ exports.validateCustomRules = validateCustomRules;
16
17
  const logger_1 = require("./logger");
17
18
  const fs_1 = __importDefault(require("fs"));
18
19
  const path_1 = __importDefault(require("path"));
@@ -92,3 +93,16 @@ function loadLocalConfigRule(params) {
92
93
  return result;
93
94
  });
94
95
  }
96
+ // Function to validate custom rules
97
+ function validateCustomRules(rules) {
98
+ const validRules = [];
99
+ for (const rule of rules) {
100
+ if ((0, jsonSchemas_1.validateRule)(rule)) {
101
+ validRules.push(rule);
102
+ }
103
+ else {
104
+ logger_1.logger.error(`Invalid custom rule: ${rule.name || 'unnamed'}`);
105
+ }
106
+ }
107
+ return validRules;
108
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x-fidelity",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "cli for opinionated framework adherence checks",
5
5
  "main": "dist/index",
6
6
  "types": "dist/index.d.ts",
@@ -103,9 +103,6 @@
103
103
  "peerDependencies": {
104
104
  "artillery": "^2.0.22"
105
105
  },
106
- "resolutions": {
107
- "glob": "^10.0.0"
108
- },
109
106
  "config": {
110
107
  "commitizen": {
111
108
  "path": "./node_modules/cz-conventional-changelog"
@@ -11,6 +11,8 @@ import { collectTelemetryData } from './telemetryCollector';
11
11
  import { setupEngine } from './engineSetup';
12
12
  import { runEngineOnFiles } from './engineRunner';
13
13
  import { countRuleFailures, safeStringify } from '../../utils/utils';
14
+ import { validateRule } from '../../utils/jsonSchemas';
15
+ import { pluginRegistry } from '../pluginRegistry';
14
16
 
15
17
  import { AnalyzeCodebaseParams } from '../../types/typeDefs';
16
18
  import { options } from '../cli';
@@ -41,6 +43,27 @@ export async function analyzeCodebase(params: AnalyzeCodebaseParams): Promise<Re
41
43
  const installedDependencyVersions = await getDependencyVersionFacts(archetypeConfig);
42
44
  const fileData = await collectRepoFileData(repoPath, archetypeConfig);
43
45
  const repoXFIConfig = await loadRepoXFIConfig(repoPath);
46
+
47
+ // Load additional plugins from repo config
48
+ if (repoXFIConfig.additionalPlugins && repoXFIConfig.additionalPlugins.length > 0) {
49
+ logger.info(`Loading additional plugins from repo config: ${repoXFIConfig.additionalPlugins.join(', ')}`);
50
+ try {
51
+ await ConfigManager.loadPlugins(repoXFIConfig.additionalPlugins);
52
+ } catch (error) {
53
+ logger.warn(`Error loading additional plugins from repo config: ${error}`);
54
+ }
55
+ }
56
+
57
+ // Merge additional components into archetype config
58
+ if (repoXFIConfig.additionalFacts) {
59
+ archetypeConfig.facts = [...new Set([...archetypeConfig.facts, ...repoXFIConfig.additionalFacts])];
60
+ logger.info(`Added additional facts from repo config: ${repoXFIConfig.additionalFacts.join(', ')}`);
61
+ }
62
+
63
+ if (repoXFIConfig.additionalOperators) {
64
+ archetypeConfig.operators = [...new Set([...archetypeConfig.operators, ...repoXFIConfig.additionalOperators])];
65
+ logger.info(`Added additional operators from repo config: ${repoXFIConfig.additionalOperators.join(', ')}`);
66
+ }
44
67
 
45
68
  // add REPO_GLOBAL_CHECK to fileData, which is the trigger for global checks
46
69
  fileData.push({
@@ -64,6 +87,49 @@ export async function analyzeCodebase(params: AnalyzeCodebaseParams): Promise<Re
64
87
  repoUrl
65
88
  });
66
89
 
90
+ // Add plugin facts and operators directly to the engine
91
+ if (repoXFIConfig.additionalFacts) {
92
+ const pluginFacts = pluginRegistry.getPluginFacts();
93
+ for (const factName of repoXFIConfig.additionalFacts) {
94
+ const fact = pluginFacts.find(f => f.name === factName);
95
+ if (fact) {
96
+ logger.info(`Adding custom fact to engine: ${fact.name}`);
97
+ engine.addFact(fact.name, fact.fn, { priority: fact.priority || 1 });
98
+ }
99
+ }
100
+ }
101
+
102
+ if (repoXFIConfig.additionalOperators) {
103
+ const pluginOperators = pluginRegistry.getPluginOperators();
104
+ for (const operatorName of repoXFIConfig.additionalOperators) {
105
+ const operator = pluginOperators.find(o => o.name === operatorName);
106
+ if (operator) {
107
+ logger.info(`Adding custom operator to engine: ${operator.name}`);
108
+ engine.addOperator(operator.name, operator.fn);
109
+ }
110
+ }
111
+ }
112
+
113
+ // Load additional rules from repo config
114
+ if (repoXFIConfig.additionalRules && repoXFIConfig.additionalRules.length > 0) {
115
+ logger.info(`Loading additional rules from repo config: ${repoXFIConfig.additionalRules.length} rules`);
116
+ for (const rule of repoXFIConfig.additionalRules) {
117
+ if (validateRule(rule)) {
118
+ logger.info(`Adding custom rule from repo config: ${rule.name}`);
119
+ // Convert RuleConfig to RuleProperties for Engine
120
+ const ruleProperties = {
121
+ ...rule,
122
+ conditions: rule.conditions as any
123
+ };
124
+ engine.addRule(ruleProperties);
125
+ } else {
126
+ // Cast rule to any to safely access name property
127
+ const ruleName = (rule as any)?.name || 'unnamed rule';
128
+ logger.warn(`Invalid custom rule in repo config: ${ruleName}`);
129
+ }
130
+ }
131
+ }
132
+
67
133
  if (isOpenAIEnabled() && archetypeConfig.facts.includes('openaiAnalysisFacts')) {
68
134
  logger.info(`adding additional openai facts to engine..`);
69
135
  engine.addFact('openaiAnalysis', openaiAnalysis);
@@ -6,6 +6,6 @@ describe('customFact', () => {
6
6
  factValue: jest.fn()
7
7
  };
8
8
  const result = await customFact.fn({}, mockAlmanac);
9
- expect(result).toEqual({ result: 'custom fact data' });
9
+ expect(result).toEqual('custom fact data');
10
10
  });
11
11
  });
@@ -7,10 +7,17 @@ import { logger } from '../../../utils/logger';
7
7
  */
8
8
  export const customFact: FactDefn = {
9
9
  name: 'customFact',
10
- fn: async (params) => {
10
+ fn: async (params, almanac) => {
11
11
  try {
12
12
  logger.debug('Executing customFact');
13
- return { result: 'custom fact data' };
13
+ const result = 'custom fact data';
14
+
15
+ // Add the result to the almanac if resultFact is provided
16
+ if (params && params.resultFact) {
17
+ almanac.addRuntimeFact(params.resultFact, result);
18
+ }
19
+
20
+ return result;
14
21
  } catch (error) {
15
22
  logger.error(`Error in customFact: ${error}`);
16
23
  throw error;
@@ -275,6 +275,10 @@ export interface ValidationResult {
275
275
 
276
276
  export interface RepoXFIConfig {
277
277
  sensitiveFileFalsePositives?: string[];
278
+ additionalRules?: RuleConfig[];
279
+ additionalFacts?: string[];
280
+ additionalOperators?: string[];
281
+ additionalPlugins?: string[];
278
282
  [key: string]: any; // Allow for additional properties
279
283
  }
280
284
 
@@ -15,7 +15,8 @@ ajv.addFormat("semverPattern", {
15
15
  validate: (x) => semver.valid(x) !== null && semver.validRange(x) !== null,
16
16
  });
17
17
 
18
- const repoXFIConfigSchema: RepoXFIConfigSchema = {
18
+ // Using a simpler schema definition to avoid TypeScript errors with complex Ajv types
19
+ const repoXFIConfigSchema = {
19
20
  type: "object",
20
21
  properties: {
21
22
  sensitiveFileFalsePositives: {
@@ -24,10 +25,34 @@ const repoXFIConfigSchema: RepoXFIConfigSchema = {
24
25
  minItems: 0,
25
26
  nullable: true,
26
27
  },
28
+ additionalRules: {
29
+ type: "array",
30
+ items: { type: "object" },
31
+ minItems: 0,
32
+ nullable: true,
33
+ },
34
+ additionalFacts: {
35
+ type: "array",
36
+ items: { type: "string" },
37
+ minItems: 0,
38
+ nullable: true,
39
+ },
40
+ additionalOperators: {
41
+ type: "array",
42
+ items: { type: "string" },
43
+ minItems: 0,
44
+ nullable: true,
45
+ },
46
+ additionalPlugins: {
47
+ type: "array",
48
+ items: { type: "string" },
49
+ minItems: 0,
50
+ nullable: true,
51
+ },
27
52
  },
28
53
  required: [],
29
54
  additionalProperties: true,
30
- };
55
+ } as unknown as RepoXFIConfigSchema;
31
56
 
32
57
  const archetypeSchema: ArchetypeConfigSchema = {
33
58
  type: "object",
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { isPathInside } from './pathUtils';
4
4
  import { logger } from './logger';
5
5
  import { RepoXFIConfig } from '../types/typeDefs';
6
- import { validateXFIConfig } from './jsonSchemas';
6
+ import { validateXFIConfig, validateRule } from './jsonSchemas';
7
7
 
8
8
  const defaultXFIConfig: RepoXFIConfig = {
9
9
  sensitiveFileFalsePositives: []};
@@ -29,6 +29,17 @@ export async function loadRepoXFIConfig(repoPath: string): Promise<RepoXFIConfig
29
29
  });
30
30
  }
31
31
 
32
+ // Validate additional rules if present
33
+ if (parsedConfig.additionalRules && Array.isArray(parsedConfig.additionalRules)) {
34
+ for (let i = 0; i < parsedConfig.additionalRules.length; i++) {
35
+ if (!validateRule(parsedConfig.additionalRules[i])) {
36
+ logger.warn(`Invalid rule at index ${i} in .xfi-config.json, removing it`);
37
+ parsedConfig.additionalRules.splice(i, 1);
38
+ i--;
39
+ }
40
+ }
41
+ }
42
+
32
43
  return parsedConfig;
33
44
  } else {
34
45
  logger.warn(`Ignoring invalid .xfi-config.json file, returing default config: ${JSON.stringify(defaultXFIConfig)}`);
@@ -78,4 +78,19 @@ async function loadLocalConfigRule(params: LoadLocalConfigRuleParams): Promise<R
78
78
  return result;
79
79
  }
80
80
 
81
- export { loadRules };
81
+ // Function to validate custom rules
82
+ function validateCustomRules(rules: any[]): RuleConfig[] {
83
+ const validRules: RuleConfig[] = [];
84
+
85
+ for (const rule of rules) {
86
+ if (validateRule(rule)) {
87
+ validRules.push(rule);
88
+ } else {
89
+ logger.error(`Invalid custom rule: ${rule.name || 'unnamed'}`);
90
+ }
91
+ }
92
+
93
+ return validRules;
94
+ }
95
+
96
+ export { loadRules, validateCustomRules };
@@ -11,6 +11,7 @@ The `.xfi-config.json` file allows you to configure x-fidelity behavior specific
11
11
  The `.xfi-config.json` file provides repository-specific configurations that override or supplement global settings. This is particularly useful for:
12
12
  - Excluding false positives
13
13
  - Customizing behavior for specific repositories
14
+ - Adding custom rules, facts, operators, and plugins
14
15
  - Managing repository-specific exceptions
15
16
 
16
17
  ## Configuration File
@@ -22,7 +23,11 @@ Place `.xfi-config.json` in your repository's root directory:
22
23
  "sensitiveFileFalsePositives": [
23
24
  "path/to/exclude/file1.js",
24
25
  "path/to/exclude/file2.ts"
25
- ]
26
+ ],
27
+ "additionalRules": [],
28
+ "additionalFacts": [],
29
+ "additionalOperators": [],
30
+ "additionalPlugins": []
26
31
  }
27
32
  ```
28
33
 
@@ -45,26 +50,254 @@ An array of file paths that should be excluded from sensitive data checks:
45
50
  - Supports glob patterns
46
51
  - Case-sensitive matching
47
52
 
48
- ## Usage Examples
53
+ ### additionalPlugins
49
54
 
50
- ### Excluding Test Files
55
+ An array of plugin module names to load for this repository:
51
56
 
52
57
  ```json
53
58
  {
54
- "sensitiveFileFalsePositives": [
55
- "test/**/*.mock.ts",
56
- "test/fixtures/*.json"
59
+ "additionalPlugins": [
60
+ "xfiPluginSimpleExample",
61
+ "xfiPluginRequiredFiles",
62
+ "xfiPluginRemoteStringValidator"
57
63
  ]
58
64
  }
59
65
  ```
60
66
 
61
- ### Excluding Configuration Files
67
+ - Plugin modules must be installed and accessible to x-fidelity
68
+ - Plugins provide custom facts and operators that can be used in rules
69
+ - Repository-specific plugins are loaded after archetype-specified plugins
70
+
71
+ ### additionalFacts
72
+
73
+ An array of fact names to add to the archetype's facts:
74
+
75
+ ```json
76
+ {
77
+ "additionalFacts": [
78
+ "customFact",
79
+ "missingRequiredFiles",
80
+ "remoteSubstringValidation"
81
+ ]
82
+ }
83
+ ```
84
+
85
+ - Facts must be provided by loaded plugins
86
+ - Repository-specific facts are merged with archetype facts
87
+ - Duplicate fact names are only loaded once
88
+
89
+ ### additionalOperators
90
+
91
+ An array of operator names to add to the archetype's operators:
92
+
93
+ ```json
94
+ {
95
+ "additionalOperators": [
96
+ "customOperator",
97
+ "missingRequiredFiles",
98
+ "invalidRemoteValidation"
99
+ ]
100
+ }
101
+ ```
102
+
103
+ - Operators must be provided by loaded plugins
104
+ - Repository-specific operators are merged with archetype operators
105
+ - Duplicate operator names are only loaded once
106
+
107
+ ### additionalRules
108
+
109
+ An array of custom rule definitions to add to the archetype's rules:
110
+
111
+ ```json
112
+ {
113
+ "additionalRules": [
114
+ {
115
+ "name": "custom-rule",
116
+ "conditions": {
117
+ "all": [
118
+ {
119
+ "fact": "fileData",
120
+ "path": "$.fileName",
121
+ "operator": "equal",
122
+ "value": "REPO_GLOBAL_CHECK"
123
+ },
124
+ {
125
+ "fact": "customFact",
126
+ "operator": "customOperator",
127
+ "value": "custom fact data"
128
+ }
129
+ ]
130
+ },
131
+ "event": {
132
+ "type": "warning",
133
+ "params": {
134
+ "message": "Custom rule detected matching data",
135
+ "details": {
136
+ "fact": "customFact"
137
+ }
138
+ }
139
+ }
140
+ }
141
+ ]
142
+ }
143
+ ```
144
+
145
+ - Rules must follow the standard rule schema
146
+ - Rules can use any facts and operators available (including custom ones)
147
+ - Repository-specific rules are added to the archetype rules
148
+ - Rules are validated before being added
149
+
150
+ ## Complete Example
151
+
152
+ Here's a complete example of a `.xfi-config.json` file that uses all available options:
62
153
 
63
154
  ```json
64
155
  {
65
156
  "sensitiveFileFalsePositives": [
66
- "config/*.example.js",
67
- "src/defaults.ts"
157
+ "src/config/defaults.ts",
158
+ "test/fixtures/mockData.js"
159
+ ],
160
+ "additionalPlugins": [
161
+ "xfiPluginSimpleExample"
162
+ ],
163
+ "additionalFacts": [
164
+ "customFact"
165
+ ],
166
+ "additionalOperators": [
167
+ "customOperator"
168
+ ],
169
+ "additionalRules": [
170
+ {
171
+ "name": "custom-rule",
172
+ "conditions": {
173
+ "all": [
174
+ {
175
+ "fact": "fileData",
176
+ "path": "$.fileName",
177
+ "operator": "equal",
178
+ "value": "REPO_GLOBAL_CHECK"
179
+ },
180
+ {
181
+ "fact": "customFact",
182
+ "operator": "customOperator",
183
+ "value": "custom fact data"
184
+ }
185
+ ]
186
+ },
187
+ "event": {
188
+ "type": "warning",
189
+ "params": {
190
+ "message": "Custom rule detected matching data",
191
+ "details": {
192
+ "fact": "customFact"
193
+ }
194
+ }
195
+ }
196
+ }
197
+ ]
198
+ }
199
+ ```
200
+
201
+ ## Usage Examples
202
+
203
+ ### Adding Custom Validation with Remote API
204
+
205
+ ```json
206
+ {
207
+ "additionalPlugins": ["xfiPluginRemoteStringValidator"],
208
+ "additionalFacts": ["remoteSubstringValidation"],
209
+ "additionalOperators": ["invalidRemoteValidation"],
210
+ "additionalRules": [
211
+ {
212
+ "name": "invalid-system-id",
213
+ "conditions": {
214
+ "all": [
215
+ {
216
+ "fact": "fileData",
217
+ "path": "$.fileName",
218
+ "operator": "equal",
219
+ "value": "config.json"
220
+ },
221
+ {
222
+ "fact": "remoteSubstringValidation",
223
+ "params": {
224
+ "pattern": "\"systemId\":[\\s]*\"([a-z]*)\"",
225
+ "flags": "gi",
226
+ "validationParams": {
227
+ "url": "http://validation-api/validate",
228
+ "method": "POST",
229
+ "headers": {
230
+ "Content-Type": "application/json"
231
+ },
232
+ "body": {
233
+ "systemId": "#MATCH#"
234
+ },
235
+ "checkJsonPath": "$.validSystems[?(@.id == '#MATCH#')]"
236
+ },
237
+ "resultFact": "systemIdValidationResult"
238
+ },
239
+ "operator": "invalidRemoteValidation",
240
+ "value": true
241
+ }
242
+ ]
243
+ },
244
+ "event": {
245
+ "type": "fatality",
246
+ "params": {
247
+ "message": "Invalid system ID detected in configuration",
248
+ "details": {
249
+ "fact": "systemIdValidationResult"
250
+ }
251
+ }
252
+ }
253
+ }
254
+ ]
255
+ }
256
+ ```
257
+
258
+ ### Enforcing Required Files
259
+
260
+ ```json
261
+ {
262
+ "additionalPlugins": ["xfiPluginRequiredFiles"],
263
+ "additionalFacts": ["missingRequiredFiles"],
264
+ "additionalOperators": ["missingRequiredFiles"],
265
+ "additionalRules": [
266
+ {
267
+ "name": "required-files-check",
268
+ "conditions": {
269
+ "all": [
270
+ {
271
+ "fact": "fileData",
272
+ "path": "$.fileName",
273
+ "operator": "equal",
274
+ "value": "REPO_GLOBAL_CHECK"
275
+ },
276
+ {
277
+ "fact": "missingRequiredFiles",
278
+ "params": {
279
+ "requiredFiles": [
280
+ "/README.md",
281
+ "/CONTRIBUTING.md",
282
+ "/LICENSE"
283
+ ],
284
+ "resultFact": "missingRequiredFilesResult"
285
+ },
286
+ "operator": "missingRequiredFiles",
287
+ "value": true
288
+ }
289
+ ]
290
+ },
291
+ "event": {
292
+ "type": "warning",
293
+ "params": {
294
+ "message": "Required files are missing from the repository",
295
+ "details": {
296
+ "fact": "missingRequiredFilesResult"
297
+ }
298
+ }
299
+ }
300
+ }
68
301
  ]
69
302
  }
70
303
  ```
@@ -72,48 +305,33 @@ An array of file paths that should be excluded from sensitive data checks:
72
305
  ## Best Practices
73
306
 
74
307
  1. **Version Control**:
75
- - Include `.xfi-config.json` in version control
76
- - Document changes in commit messages
77
- - Review changes during code review
308
+ - Include `.xfi-config.json` in your version control system to ensure consistency across your team.
309
+ - Document changes in commit messages.
310
+ - Review changes during code review.
78
311
 
79
312
  2. **Documentation**:
80
- - Comment excluded files
81
- - Explain exclusion reasons
82
- - Keep documentation up-to-date
313
+ - Comment custom rules and their purpose.
314
+ - Explain why specific plugins are needed.
315
+ - Keep documentation up-to-date.
83
316
 
84
317
  3. **Regular Review**:
85
- - Periodically review exclusions
86
- - Remove unnecessary exclusions
87
- - Update as codebase changes
318
+ - Periodically review your `.xfi-config.json` to ensure the configurations are still necessary and valid.
319
+ - Remove unnecessary rules or plugins.
320
+ - Update as codebase changes.
88
321
 
89
322
  4. **Minimal Use**:
90
- - Use sparingly
91
- - Fix issues when possible
92
- - Don't use to bypass security checks
323
+ - Use custom rules sparingly.
324
+ - Consider adding important rules to the archetype configuration instead.
325
+ - Don't use to bypass security checks.
93
326
 
94
327
  5. **Team Communication**:
95
- - Discuss exclusions with team
96
- - Document decisions
97
- - Consider alternatives
328
+ - Discuss custom rules with team.
329
+ - Document decisions.
330
+ - Consider alternatives.
98
331
 
99
332
  ## Validation
100
333
 
101
- x-fidelity validates `.xfi-config.json` using JSON Schema:
102
-
103
- ```json
104
- {
105
- "type": "object",
106
- "properties": {
107
- "sensitiveFileFalsePositives": {
108
- "type": "array",
109
- "items": {
110
- "type": "string"
111
- }
112
- }
113
- },
114
- "additionalProperties": false
115
- }
116
- ```
334
+ x-fidelity validates `.xfi-config.json` using JSON Schema. Custom rules are also validated against the rule schema before being added to the engine.
117
335
 
118
336
  ## Error Handling
119
337
 
@@ -122,8 +340,14 @@ If `.xfi-config.json` is invalid:
122
340
  2. Default configuration used
123
341
  3. Analysis continues
124
342
 
343
+ If individual custom rules are invalid:
344
+ 1. Warning message displayed
345
+ 2. Invalid rules are skipped
346
+ 3. Valid rules are still applied
347
+
125
348
  ## Next Steps
126
349
 
127
350
  - Configure [Local Rules](local-config)
128
351
  - Set up [Remote Configuration](remote-config)
129
352
  - Learn about [Exemptions](exemptions)
353
+ - Explore [Creating Plugins](plugins/creating-plugins)