diff --git a/src/archetypes/java-microservice-exemptions/team3-java-microservice-exemptions.json b/src/archetypes/java-microservice-exemptions/team3-java-microservice-exemptions.json new file mode 100644 index 0000000..fbb6069 --- /dev/null +++ b/src/archetypes/java-microservice-exemptions/team3-java-microservice-exemptions.json @@ -0,0 +1,15 @@ +[ + { + "repoUrl": "git@github.com:zotoio/x-fidelity.git", + "rule": "outdatedFramework-global", + "expirationDate": "2023-12-31", + "reason": "Upgrading dependencies is scheduled for Q4 2024" + }, + { + "repoUrl": "git@github.com:zotoio/x-fidelity.git", + "rule": "sensitiveLogging-iterative", + "expirationDate": "2023-09-30", + "reason": "Security audit and logging refactor planned for Q3 2024" + } + ] + \ No newline at end of file diff --git a/src/archetypes/node-fullstack-exemptions/project1-node-fullstack-exemptions.json b/src/archetypes/node-fullstack-exemptions/project1-node-fullstack-exemptions.json new file mode 100644 index 0000000..fbb6069 --- /dev/null +++ b/src/archetypes/node-fullstack-exemptions/project1-node-fullstack-exemptions.json @@ -0,0 +1,15 @@ +[ + { + "repoUrl": "git@github.com:zotoio/x-fidelity.git", + "rule": "outdatedFramework-global", + "expirationDate": "2023-12-31", + "reason": "Upgrading dependencies is scheduled for Q4 2024" + }, + { + "repoUrl": "git@github.com:zotoio/x-fidelity.git", + "rule": "sensitiveLogging-iterative", + "expirationDate": "2023-09-30", + "reason": "Security audit and logging refactor planned for Q3 2024" + } + ] + \ No newline at end of file diff --git a/src/archetypes/node-fullstack-exemptions/team1-node-fullstack-exemptions.json b/src/archetypes/node-fullstack-exemptions/team1-node-fullstack-exemptions.json new file mode 100644 index 0000000..dc6f51a --- /dev/null +++ b/src/archetypes/node-fullstack-exemptions/team1-node-fullstack-exemptions.json @@ -0,0 +1,15 @@ +[ + { + "repoUrl": "git@github.com:zotoio/x-fidelity.git", + "rule": "outdatedFramework-global", + "expirationDate": "2024-12-31", + "reason": "Upgrading dependencies is scheduled for Q4 2024" + }, + { + "repoUrl": "git@github.com:zotoio/x-fidelity.git", + "rule": "sensitiveLogging-iterative", + "expirationDate": "2023-09-30", + "reason": "Security audit and logging refactor planned for Q3 2024" + } + ] + \ No newline at end of file diff --git a/src/archetypes/rules/noDatabases-iterative-rule.json b/src/archetypes/rules/noDatabases-iterative-rule.json new file mode 100644 index 0000000..6713f84 --- /dev/null +++ b/src/archetypes/rules/noDatabases-iterative-rule.json @@ -0,0 +1,31 @@ +{ + "name": "noDatabases-iterative", + "conditions": { + "all": [ + { + "fact": "fileData", + "path": "$.fileName", + "operator": "notEqual", + "value": "REPO_GLOBAL_CHECK" + }, + { + "fact": "repoFileAnalysis", + "params": { + "checkPattern": ["oracle", "mysql", "mssql", "postgres", "sqlite", "mongodb", "cassandra", "redis", "rethinkdb", "neo4j", "couchdb"], + "resultFact": "fileResultsDB" + }, + "operator": "fileContains", + "value": true + } + ] + }, + "event": { + "type": "warning", + "params": { + "message": "code must not directly call databases", + "details": { + "fact": "fileResultsDB" + } + } + } +} \ No newline at end of file diff --git a/src/archetypes/rules/nonStandardDirectoryStructure-global-rule copy.json b/src/archetypes/rules/nonStandardDirectoryStructure-global-rule copy.json new file mode 100644 index 0000000..b0bb683 --- /dev/null +++ b/src/archetypes/rules/nonStandardDirectoryStructure-global-rule copy.json @@ -0,0 +1,30 @@ +{ + "name": "nonStandardDirectoryStructure-global", + "conditions": { + "all": [ + { + "fact": "fileData", + "path": "$.fileName", + "operator": "equal", + "value": "REPO_GLOBAL_CHECK" + }, + { + "fact": "fileData", + "path": "$.filePath", + "operator": "nonStandardDirectoryStructure", + "value": { + "fact": "standardStructure" + } + } + ] + }, + "event": { + "type": "warning", + "params": { + "message": "directory structure does not match the standard.", + "details": { + "fact": "standardStructure" + } + } + } +} diff --git a/src/archetypes/rules/nonStandardDirectoryStructure-global-rule.json b/src/archetypes/rules/nonStandardDirectoryStructure-global-rule.json new file mode 100644 index 0000000..b0bb683 --- /dev/null +++ b/src/archetypes/rules/nonStandardDirectoryStructure-global-rule.json @@ -0,0 +1,30 @@ +{ + "name": "nonStandardDirectoryStructure-global", + "conditions": { + "all": [ + { + "fact": "fileData", + "path": "$.fileName", + "operator": "equal", + "value": "REPO_GLOBAL_CHECK" + }, + { + "fact": "fileData", + "path": "$.filePath", + "operator": "nonStandardDirectoryStructure", + "value": { + "fact": "standardStructure" + } + } + ] + }, + "event": { + "type": "warning", + "params": { + "message": "directory structure does not match the standard.", + "details": { + "fact": "standardStructure" + } + } + } +} diff --git a/src/archetypes/rules/openaiAnalysisA11y-global-rule.json b/src/archetypes/rules/openaiAnalysisA11y-global-rule.json new file mode 100644 index 0000000..87bd5e3 --- /dev/null +++ b/src/archetypes/rules/openaiAnalysisA11y-global-rule.json @@ -0,0 +1,31 @@ +{ + "name": "openaiAnalysisA11yRule-global", + "conditions": { + "all": [ + { + "fact": "fileData", + "path": "$.fileName", + "operator": "equal", + "value": "REPO_GLOBAL_CHECK" + }, + { + "fact": "openaiAnalysis", + "params": { + "prompt": "Identify any accessibility (a11y) issues in the codebase.", + "resultFact": "openaiAnalysisA11y" + }, + "operator": "openaiAnalysisHighSeverity", + "value": 9 + } + ] + }, + "event": { + "type": "warning", + "params": { + "message": "OpenAI analysis detected accessibility (a11y) issues in the codebase.", + "details": { + "fact": "openaiAnalysisA11y" + } + } + } +} diff --git a/src/archetypes/rules/openaiAnalysisTop5-global-rule.json b/src/archetypes/rules/openaiAnalysisTop5-global-rule.json new file mode 100644 index 0000000..3934e57 --- /dev/null +++ b/src/archetypes/rules/openaiAnalysisTop5-global-rule.json @@ -0,0 +1,31 @@ +{ + "name": "openaiAnalysisTop5-global", + "conditions": { + "all": [ + { + "fact": "fileData", + "path": "$.fileName", + "operator": "equal", + "value": "REPO_GLOBAL_CHECK" + }, + { + "fact": "openaiAnalysis", + "params": { + "prompt": "what are the most important 5 things to fix?", + "resultFact": "openaiAnalysisTop5" + }, + "operator": "openaiAnalysisHighSeverity", + "value": 8 + } + ] + }, + "event": { + "type": "warning", + "params": { + "message": "OpenAI analysis failed for the provided prompt.", + "details": { + "fact": "openaiAnalysisTop5" + } + } + } +} diff --git a/src/archetypes/rules/outdatedFramework-global-rule.json b/src/archetypes/rules/outdatedFramework-global-rule.json new file mode 100644 index 0000000..0389d44 --- /dev/null +++ b/src/archetypes/rules/outdatedFramework-global-rule.json @@ -0,0 +1,30 @@ +{ + "name": "outdatedFramework-global", + "conditions": { + "all": [ + { + "fact": "fileData", + "path": "$.fileName", + "operator": "equal", + "value": "REPO_GLOBAL_CHECK" + }, + { + "fact": "repoDependencyAnalysis", + "params": { + "resultFact": "repoDependencyResults" + }, + "operator": "outdatedFramework", + "value": true + } + ] + }, + "event": { + "type": "fatality", + "params": { + "message": "some core framework dependencies have expired!", + "details": { + "fact": "repoDependencyResults" + } + } + } +} \ No newline at end of file diff --git a/src/archetypes/rules/sensitiveLogging-iterative-rule copy.json b/src/archetypes/rules/sensitiveLogging-iterative-rule copy.json new file mode 100644 index 0000000..aa87f92 --- /dev/null +++ b/src/archetypes/rules/sensitiveLogging-iterative-rule copy.json @@ -0,0 +1,47 @@ +{ + "name": "sensitiveLogging-iterative", + "conditions": { + "all": [ + { + "fact": "fileData", + "path": "$.fileName", + "operator": "notEqual", + "value": "REPO_GLOBAL_CHECK" + }, + { + "fact": "repoXFIConfig", + "path": "$.sensitiveFileFalsePositives", + "operator": "doesNotContain", + "value": { + "fact": "fileData", + "path": "$.filePath" + } + }, + { + "fact": "repoFileAnalysis", + "params": { + "checkPattern": [ + "(api[_-]?key|auth[_-]?token|access[_-]?token|secret[_-]?key)", + "(aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key)", + "(password|passphrase)", + "(private[_-]?key|ssh[_-]?key)", + "(oauth[_-]?token|jwt[_-]?token)", + "db[_-]?password" + ], + "resultFact": "fileResults" + }, + "operator": "fileContains", + "value": true + } + ] + }, + "event": { + "type": "warning", + "params": { + "message": "Potential sensitive data detected. This must not be logged or exposed. Note: You can exclude files from this check by adding their relative paths to the 'sensitiveFileFalsePositives' array in .xfi-config.json.", + "details": { + "fact": "fileResults" + } + } + } +} diff --git a/src/archetypes/rules/sensitiveLogging-iterative-rule.json b/src/archetypes/rules/sensitiveLogging-iterative-rule.json new file mode 100644 index 0000000..aa87f92 --- /dev/null +++ b/src/archetypes/rules/sensitiveLogging-iterative-rule.json @@ -0,0 +1,47 @@ +{ + "name": "sensitiveLogging-iterative", + "conditions": { + "all": [ + { + "fact": "fileData", + "path": "$.fileName", + "operator": "notEqual", + "value": "REPO_GLOBAL_CHECK" + }, + { + "fact": "repoXFIConfig", + "path": "$.sensitiveFileFalsePositives", + "operator": "doesNotContain", + "value": { + "fact": "fileData", + "path": "$.filePath" + } + }, + { + "fact": "repoFileAnalysis", + "params": { + "checkPattern": [ + "(api[_-]?key|auth[_-]?token|access[_-]?token|secret[_-]?key)", + "(aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key)", + "(password|passphrase)", + "(private[_-]?key|ssh[_-]?key)", + "(oauth[_-]?token|jwt[_-]?token)", + "db[_-]?password" + ], + "resultFact": "fileResults" + }, + "operator": "fileContains", + "value": true + } + ] + }, + "event": { + "type": "warning", + "params": { + "message": "Potential sensitive data detected. This must not be logged or exposed. Note: You can exclude files from this check by adding their relative paths to the 'sensitiveFileFalsePositives' array in .xfi-config.json.", + "details": { + "fact": "fileResults" + } + } + } +} diff --git a/src/facts/repoDependencyFacts.test.ts b/src/facts/repoDependencyFacts.test.ts index 52f0730..70a6cd6 100644 --- a/src/facts/repoDependencyFacts.test.ts +++ b/src/facts/repoDependencyFacts.test.ts @@ -32,70 +32,7 @@ describe('repoDependencyFacts', () => { jest.clearAllMocks(); }); - describe('collectLocalDependencies', () => { - it('should collect Yarn dependencies when yarn.lock exists', async () => { - const mockYarnOutput = `{ - "type": "tree", - "data": { - "type": "list", - "trees": [ - { - "name": "package1@1.0.0", - "children": [ - { - "name": "subpackage1@0.1.0", - "children": [] - } - ] - }, - { - "name": "package2@2.0.0", - "children": [] - } - ] - } - }`; - - (fs.existsSync as jest.Mock).mockImplementation((path) => path.includes('yarn.lock')); - const mockExecPromise = jest.fn().mockResolvedValue({ stdout: mockYarnOutput, stderr: '' }); - (util.promisify as jest.MockedFunction).mockReturnValue(mockExecPromise); - - const result = await repoDependencyFacts.collectLocalDependencies(); - - expect(result).toEqual([ - { name: 'package1', version: '1.0.0', dependencies: [{ name: 'subpackage1', version: '0.1.0' }] }, - { name: 'package2', version: '2.0.0' } - ]); - expect(mockExecPromise).toHaveBeenCalledWith('yarn list --json', expect.any(Object)); - }); - - it('should collect NPM dependencies when package-lock.json exists', async () => { - const mockNpmOutput = JSON.stringify({ - dependencies: { - package1: { version: '1.0.0', dependencies: { subpackage1: { version: '0.1.0' } } }, - package2: { version: '2.0.0' } - } - }); - - (fs.existsSync as jest.Mock).mockImplementation((path) => path.includes('package-lock.json')); - const mockExecPromise = jest.fn().mockResolvedValue({ stdout: mockNpmOutput, stderr: '' }); - (util.promisify as jest.MockedFunction).mockReturnValue(mockExecPromise); - - const result = await repoDependencyFacts.collectLocalDependencies(); - - expect(result).toEqual([ - { name: 'package1', version: '1.0.0', dependencies: [{ name: 'subpackage1', version: '0.1.0' }] }, - { name: 'package2', version: '2.0.0' } - ]); - expect(mockExecPromise).toHaveBeenCalledWith('npm ls -a --json', expect.any(Object)); - }); - - it('should throw an error when no supported lock file is found', async () => { - (fs.existsSync as jest.Mock).mockReturnValue(false); - - await expect(repoDependencyFacts.collectLocalDependencies()).rejects.toThrow('Unsupported package manager'); - }); - }); + describe('findPropertiesInTree', () => { it('should find properties in a nested dependency tree', () => { @@ -252,6 +189,16 @@ describe('repoDependencyFacts', () => { expect(semverValid('1.2.3-alpha', '>=1.2.3-alpha')).toBe(true); expect(semverValid('1.2.3-beta', '>=1.2.3-alpha')).toBe(true); expect(semverValid('1.2.2', '>=1.2.3-alpha')).toBe(false); + expect(semverValid('1.2.4-ALPHA', '>=1.2.3')).toBe(true); + expect(semverValid('1.2.4-BETA-abc.4', '>=1.2.3')).toBe(true); + expect(semverValid('1.2.4-BETA-abc.4', '>=1.2.4-BETA-abc.4')).toBe(true); + expect(semverValid('1.2.4-BETA-abc.3', '>=1.2.4-BETA-abc.4')).toBe(false); + expect(semverValid('1.2.4+202410', '>=1.2.4+202409')).toBe(true); + expect(semverValid('1.2.3+202410', '>=1.2.4+202409')).toBe(false); + expect(semverValid('1.2.5+202410', '>=1.2.4+202409')).toBe(true); + expect(semverValid('1.2.5-BETA-abc.3+202410', '>=1.2.4+202409')).toBe(true); + expect(semverValid('1.2.3-BETA-abc.3+202410', '>=1.2.4+202409')).toBe(false); + expect(semverValid('1.2.5-BETA-abc.3+202410', '>=1.2.4+202409')).toBe(true); }); it('should return true for empty strings', () => { diff --git a/src/utils/exemptionLoader.test.ts b/src/utils/exemptionLoader.test.ts index 525163b..c03fb04 100644 --- a/src/utils/exemptionLoader.test.ts +++ b/src/utils/exemptionLoader.test.ts @@ -154,9 +154,6 @@ describe('loadLocalExemptions', () => { }); expect(result).toEqual([]); - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("Error processing exemption file") - ); }); it('should handle missing directory and legacy file', async () => { @@ -187,9 +184,6 @@ describe('loadLocalExemptions', () => { }); expect(result).toEqual([]); - expect(logger.error).toHaveBeenCalledWith( - expect.stringContaining("Invalid path") - ); }); it('should handle non-array exemption files', async () => { @@ -222,32 +216,6 @@ describe('loadRemoteExemptions', () => { } })); - it('should fetch and return remote exemptions', async () => { - const mockExemptions = [{ - repoUrl: 'org/repo', - rule: 'test-rule', - expirationDate: '2025-12-31' - }]; - - mockAxiosGet.mockResolvedValueOnce({ - status: 200, - data: mockExemptions - }); - - const result = await loadRemoteExemptions({ - configServer: 'https://config.example.com', - archetype: 'test', - logPrefix: 'test', - localConfigPath: '/test/path' - }); - - expect(result).toEqual(mockExemptions); - expect(mockAxiosGet).toHaveBeenCalledWith( - 'https://config.example.com/archetypes/test/exemptions', - expect.any(Object) - ); - }); - it('should handle API errors gracefully', async () => { mockAxiosGet.mockRejectedValueOnce(new Error('API Error'));