From a8f192386d0c5762cdcab5fb382d9f663517788b Mon Sep 17 00:00:00 2001 From: seqradev Date: Tue, 18 Nov 2025 17:52:13 +0300 Subject: [PATCH 1/5] [feat] Add Seqra report converter --- .../analyzers/seqra/__init__.py | 7 + .../analyzers/seqra/analyzer_result.py | 31 +++ .../UserProfileController.java.sarif | 208 ++++++++++++++++++ .../files/UserProfileController.java | 20 ++ .../tests/unit/analyzers/test_seqra_parser.py | 106 +++++++++ 5 files changed, 372 insertions(+) create mode 100644 tools/report-converter/codechecker_report_converter/analyzers/seqra/__init__.py create mode 100644 tools/report-converter/codechecker_report_converter/analyzers/seqra/analyzer_result.py create mode 100644 tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.java.sarif create mode 100644 tools/report-converter/tests/unit/analyzers/seqra_output_test_files/files/UserProfileController.java create mode 100644 tools/report-converter/tests/unit/analyzers/test_seqra_parser.py diff --git a/tools/report-converter/codechecker_report_converter/analyzers/seqra/__init__.py b/tools/report-converter/codechecker_report_converter/analyzers/seqra/__init__.py new file mode 100644 index 0000000000..4259749345 --- /dev/null +++ b/tools/report-converter/codechecker_report_converter/analyzers/seqra/__init__.py @@ -0,0 +1,7 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- diff --git a/tools/report-converter/codechecker_report_converter/analyzers/seqra/analyzer_result.py b/tools/report-converter/codechecker_report_converter/analyzers/seqra/analyzer_result.py new file mode 100644 index 0000000000..e02a379529 --- /dev/null +++ b/tools/report-converter/codechecker_report_converter/analyzers/seqra/analyzer_result.py @@ -0,0 +1,31 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- + +import logging +from typing import List + +from codechecker_report_converter.report import Report +from codechecker_report_converter.report.parser import sarif + +from ..analyzer_result import AnalyzerResultBase + + +LOG = logging.getLogger('report-converter') + + +class AnalyzerResult(AnalyzerResultBase): + """ Transform analyzer result of the Seqra.""" + + TOOL_NAME = 'seqra' + NAME = 'Seqra Security-Focused Static Analyzer' + URL = 'https://seqra.dev/' + + def get_reports(self, file_path: str) -> List[Report]: + """ Get reports from the given analyzer result file. """ + + return sarif.Parser().get_reports(file_path) diff --git a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.java.sarif b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.java.sarif new file mode 100644 index 0000000000..18c5af95b7 --- /dev/null +++ b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.java.sarif @@ -0,0 +1,208 @@ +{ + "version": "2.1.0", + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "name": "SAST", + "organization": "Seqra", + "version": "2025.11.13.98f5209", + "rules": [ + { + "id": "seqra.java.spring.xss", + "name": "seqra.java.spring.xss", + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Controller returns an untrusted unvalidated data" + }, + "shortDescription": { + "text": "Controller returns an untrusted unvalidated data" + }, + "properties": { + "tags": [ + "CWE-79" + ] + } + } + ] + } + }, + "results": [ + { + "level": "error", + "message": { + "text": "Controller returns an untrusted unvalidated data" + }, + "ruleId": "seqra.java.spring.xss", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "UserProfileController.java", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startLine": 18 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "org.example.UserProfileController#displayUserProfile", + "decoratedName": "(id:69)org.example.UserProfileController#displayUserProfile(java.lang.String):1:(return %0)" + } + ] + } + ], + "relatedLocations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "UserProfileController.java" + }, + "region": { + "startLine": 18 + } + }, + "logicalLocations": [ + { + "name": "org.example.UserProfileController#displayUserProfile", + "fullyQualifiedName": "GET /profile/display", + "kind": "function" + } + ], + "message": { + "text": "Related Spring controller" + } + } + ], + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "UserProfileController.java", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startLine": 17 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "org.example.UserProfileController#displayUserProfile", + "decoratedName": "(id:69)org.example.UserProfileController#displayUserProfile(java.lang.String):0:(goto JIRInstRef(index=2))" + } + ], + "message": { + "text": "Method entry marks \"message\" as $PARAM" + } + }, + "executionOrder": 0, + "index": 0, + "kinds": [ + "taint" + ] + }, + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "UserProfileController.java", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startLine": 18 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "org.example.UserProfileController#displayUserProfile", + "decoratedName": "(id:69)org.example.UserProfileController#displayUserProfile(java.lang.String):4:(%0 = %str)" + } + ], + "message": { + "text": "Takes $PARAM data at \"message\" and ends up with $PARAM data at a local variable" + } + }, + "executionOrder": 1, + "index": 1, + "kinds": [ + "unknown" + ] + }, + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "UserProfileController.java", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startLine": 18 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "org.example.UserProfileController#displayUserProfile", + "decoratedName": "(id:69)org.example.UserProfileController#displayUserProfile(java.lang.String):1:(return %0)" + } + ], + "message": { + "text": "The returning value is assigned a value with $PARAM data" + } + }, + "executionOrder": 2, + "index": 2, + "kinds": [ + "unknown" + ] + }, + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "UserProfileController.java", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startLine": 18 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "org.example.UserProfileController#displayUserProfile", + "decoratedName": "(id:69)org.example.UserProfileController#displayUserProfile(java.lang.String):1:(return %0)" + } + ], + "message": { + "text": "Controller returns an untrusted unvalidated data" + } + }, + "executionOrder": 3, + "index": 3, + "kinds": [ + "taint" + ] + } + ] + } + ] + } + ] + } + ], + "originalUriBaseIds": { + "%SRCROOT%": { + "uri": "./" + } + } + } + ] +} diff --git a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/files/UserProfileController.java b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/files/UserProfileController.java new file mode 100644 index 0000000000..9822a0b0c0 --- /dev/null +++ b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/files/UserProfileController.java @@ -0,0 +1,20 @@ +package org.example; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.util.HtmlUtils; + +@Controller +public class UserProfileController { + + // Display user profile with custom message + @GetMapping("/profile/display") + @ResponseBody + public String displayUserProfile( + @RequestParam(defaultValue = "Welcome") String message) { + // Direct output without escaping + return "

Profile Message: " + message + "

"; + } +} diff --git a/tools/report-converter/tests/unit/analyzers/test_seqra_parser.py b/tools/report-converter/tests/unit/analyzers/test_seqra_parser.py new file mode 100644 index 0000000000..a4215c77d6 --- /dev/null +++ b/tools/report-converter/tests/unit/analyzers/test_seqra_parser.py @@ -0,0 +1,106 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- + +""" +This module tests the correctness of the SeqraAnalyzerResult, which +used in sequence transform seqra output to a plist file. +""" + + +import os +import plistlib +import shutil +import unittest + +from codechecker_report_converter.analyzers.seqra import analyzer_result +from codechecker_report_converter.report.parser import plist + +from libtest import env + + +OLD_PWD = None + + +class SeqraAnalyzerResultTestCase(unittest.TestCase): + """ Test the output of the SeqraAnalyzerResult. """ + + def setup_class(self): + """ Initialize test files. """ + + global TEST_WORKSPACE + TEST_WORKSPACE = env.get_workspace('seqra_parser') + os.environ['TEST_WORKSPACE'] = TEST_WORKSPACE + self.test_workspace = os.environ['TEST_WORKSPACE'] + self.cc_result_dir = self.test_workspace + + self.analyzer_result = analyzer_result.AnalyzerResult() + + self.test_files = os.path.join(os.path.dirname(__file__), + 'seqra_output_test_files') + global OLD_PWD + OLD_PWD = os.getcwd() + os.chdir(os.path.join(os.path.dirname(__file__), + 'seqra_output_test_files')) + + def teardown_class(self): + """Clean up after the test.""" + + global OLD_PWD + os.chdir(OLD_PWD) + + global TEST_WORKSPACE + + print("Removing: " + TEST_WORKSPACE) + shutil.rmtree(TEST_WORKSPACE, ignore_errors=True) + + def test_no_plist_file(self): + """ Test transforming single plist file. """ + analyzer_output_file = os.path.join(self.test_files, 'files', + 'UserProfileController.java') + + ret = self.analyzer_result.transform( + [analyzer_output_file], self.cc_result_dir, plist.EXTENSION, + file_name="{source_file}_{analyzer}") + self.assertFalse(ret) + + def test_no_plist_dir(self): + """ Test transforming single plist file. """ + analyzer_output_file = os.path.join(self.test_files, 'non_existing') + + ret = self.analyzer_result.transform( + [analyzer_output_file], self.cc_result_dir, plist.EXTENSION, + file_name="{source_file}_{analyzer}") + self.assertFalse(ret) + + def test_seqra_transform_single_file(self): + """ Test transforming single plist file. """ + analyzer_output_file = os.path.join( + self.test_files, 'UserProfileController.java.sarif') + self.analyzer_result.transform( + [analyzer_output_file], self.cc_result_dir, plist.EXTENSION, + file_name="{source_file}_{analyzer}") + + plist_file = os.path.join(self.cc_result_dir, + 'UserProfileController.java_seqra.plist') + with open(plist_file, mode='rb') as pfile: + res = plistlib.load(pfile) + + # Use relative path for this test. + res['files'][0] = 'files/UserProfileController.java' + + self.assertTrue(res['metadata']['generated_by']['version']) + res['metadata']['generated_by']['version'] = "x.y.z" + print( + res["diagnostics"][0]["issue_hash_content_of_line_in_context"]) + + plist_file = os.path.join(self.test_files, + 'UserProfileController.expected.plist') + with open(plist_file, mode='rb') as pfile: + exp = plistlib.load(pfile) + + self.assertEqual(res, exp) From 9628c04c7f58c25bc4ec92a0adacf4710e85d4c0 Mon Sep 17 00:00:00 2001 From: seqradev Date: Tue, 18 Nov 2025 19:07:05 +0300 Subject: [PATCH 2/5] Update --- .../UserProfileController.expected.plist | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist diff --git a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist new file mode 100644 index 0000000000..f7406a6159 --- /dev/null +++ b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist @@ -0,0 +1,251 @@ + + + + + diagnostics + + + category + unknown + check_name + seqra.java.spring.xss + description + Controller returns an untrusted unvalidated data + issue_hash_content_of_line_in_context + 05ee0e160863303796471643b8c352c6 + location + + col + 1 + file + 0 + line + 18 + + path + + + edges + + + end + + + col + 1 + file + 0 + line + 18 + + + col + 1 + file + 0 + line + 18 + + + start + + + col + 1 + file + 0 + line + 17 + + + col + 1 + file + 0 + line + 17 + + + + + kind + control + + + depth + 0 + kind + event + location + + col + 1 + file + 0 + line + 17 + + message + Method entry marks "message" as $PARAM + ranges + + + + col + 1 + file + 0 + line + 17 + + + col + 1 + file + 0 + line + 17 + + + + + + depth + 0 + kind + event + location + + col + 1 + file + 0 + line + 18 + + message + Takes $PARAM data at "message" and ends up with $PARAM data at a local variable + ranges + + + + col + 1 + file + 0 + line + 18 + + + col + 1 + file + 0 + line + 18 + + + + + + depth + 0 + kind + event + location + + col + 1 + file + 0 + line + 18 + + message + The returning value is assigned a value with $PARAM data + ranges + + + + col + 1 + file + 0 + line + 18 + + + col + 1 + file + 0 + line + 18 + + + + + + depth + 0 + kind + event + location + + col + 1 + file + 0 + line + 18 + + message + Controller returns an untrusted unvalidated data + ranges + + + + col + 1 + file + 0 + line + 18 + + + col + 1 + file + 0 + line + 18 + + + + + + type + seqra + + + files + + files/UserProfileController.java + + metadata + + analyzer + + name + seqra + + generated_by + + name + report-converter + version + 0.1.0 + + + + From f3f5724c46dfee49ceec19677769159982aeb6f7 Mon Sep 17 00:00:00 2001 From: seqradev Date: Tue, 18 Nov 2025 23:40:10 +0300 Subject: [PATCH 3/5] Fix --- .../UserProfileController.expected.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist index f7406a6159..59747af31d 100644 --- a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist +++ b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist @@ -12,7 +12,7 @@ description Controller returns an untrusted unvalidated data issue_hash_content_of_line_in_context - 05ee0e160863303796471643b8c352c6 + 9912049596cf713fc0bdaee7280274d8 location col From 727ef61f2b71ca86b8db3c3b579565e3aa1b9d2e Mon Sep 17 00:00:00 2001 From: seqradev Date: Thu, 20 Nov 2025 00:39:33 +0300 Subject: [PATCH 4/5] Fix --- .../UserProfileController.expected.plist | 2 +- .../seqra_output_test_files/UserProfileController.java.sarif | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist index 59747af31d..ebbdf490ec 100644 --- a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist +++ b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist @@ -244,7 +244,7 @@ name report-converter version - 0.1.0 + 1.4.1 diff --git a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.java.sarif b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.java.sarif index 18c5af95b7..6c7fe14524 100644 --- a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.java.sarif +++ b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.java.sarif @@ -7,7 +7,7 @@ "driver": { "name": "SAST", "organization": "Seqra", - "version": "2025.11.13.98f5209", + "version": "1.4.1", "rules": [ { "id": "seqra.java.spring.xss", From 685a2857e262c93afb3ef00492e2c353d67375eb Mon Sep 17 00:00:00 2001 From: seqradev Date: Thu, 20 Nov 2025 00:48:09 +0300 Subject: [PATCH 5/5] Update UserProfileController.expected.plist --- .../UserProfileController.expected.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist index ebbdf490ec..18aa89919d 100644 --- a/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist +++ b/tools/report-converter/tests/unit/analyzers/seqra_output_test_files/UserProfileController.expected.plist @@ -244,7 +244,7 @@ name report-converter version - 1.4.1 + x.y.z