Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ee690b5
Remove presetManagerAPI and update .gitignore
cx-happy-yang Jun 4, 2026
6bda300
Rename SastPresetManagerAPI to PresetManagerAPI
cx-happy-yang Jun 8, 2026
ba4d7dd
Rename sast_ prefix to scanner_ in PresetManagerAPI
cx-happy-yang Jun 8, 2026
d4b5b27
Rename test_sast_preset_manager_api to test_preset_manager_api
cx-happy-yang Jun 8, 2026
bfb0eca
Add missing API parameters and fix redirect handling
cx-happy-yang Jun 10, 2026
200f97e
Add CxOne example scripts
cx-happy-yang Jun 10, 2026
e282273
Add DTOs for SAST results predicates and refactor
cx-happy-yang Jun 10, 2026
b40b100
Add triage workflow script and ignore HAR files
cx-happy-yang Jun 10, 2026
e8d226c
Add get_all_scan_results helper with dedup for SAST pagination bug
cx-happy-yang Jun 15, 2026
305e82e
gitignore
cx-happy-yang Jun 15, 2026
c7cf775
Fix get_all_scan_results to use exact remaining count on last page
cx-happy-yang Jun 15, 2026
39b7b93
Fix get_all_scan_results: use page-based offset for /cxrestapi/sast/r…
cx-happy-yang Jun 15, 2026
44ffa66
Add dump_all_path_ids diagnostic script using get_all_scan_results
cx-happy-yang Jun 15, 2026
22a6076
Move dump_all_path_ids.py from scripts/ to examples/CxSAST/
cx-happy-yang Jun 15, 2026
80d1949
Bump version to 1.8.8 and update changelog
cx-happy-yang Jun 15, 2026
bfb7a66
Add get_source_by_scan_id to Portal SOAP API (deprecated)
cx-happy-yang Jun 17, 2026
c8cb7f6
Add get_sources_by_scan_id and get_file_names_for_path (Portal SOAP)
cx-happy-yang Jun 17, 2026
1debae0
Update changelog for 1.8.8 Portal SOAP additions
cx-happy-yang Jun 17, 2026
5a8f22c
Add 64 missing Portal and Audit SOAP API methods with tests
cx-happy-yang Jun 18, 2026
6924ab3
Update CxOne examples: modify export_sast_state_by_query, remove expo…
cx-happy-yang Jun 18, 2026
b20de25
Reformat API documentation to table format matching CxOne_REST_API_Li…
cx-happy-yang Jun 18, 2026
4563254
Fix get_branch_project_status to use status names instead of magic nu…
cx-happy-yang Jun 22, 2026
356e848
Update changelog for get_branch_project_status fix and CxCliPy.py
cx-happy-yang Jun 22, 2026
41fa6ec
Fix api_client Content-Type header for file uploads and zip cleanup o…
cx-happy-yang Jun 22, 2026
d7c5bf9
Remove examples/CxSAST/CxCliPy.py (moved to dedicated repo)
cx-happy-yang Jun 22, 2026
14d6513
Fix SSL context creation for self-signed CA certificates without Basi…
cx-happy-yang Jun 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,5 @@ build

.claude
*.txt
*.json
*.json
*.har
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# Changelog
All notable changes to this project will be documented in this file.

1.8.8 - 2026-06-15
* [Fix] CxRestAPISDK get_all_scan_results — the /cxrestapi/sast/results endpoint interprets offset as a page number (records skipped = offset * limit), not a record count. Fixed pagination loop to increment offset by 1 per page instead of by the page size, which was causing results to be silently skipped.
* [Add] get_all_scan_results method in CxRestAPISDK ScansAPI — safe pagination helper that correctly walks all pages and deduplicates by path_id
* [Add] examples/CxSAST/dump_all_path_ids.py — example script dumping all path_ids from a scan using get_all_scan_results
* [Add] tests/CxSAST/CxRestAPI/test_sast_results_pagination.py — diagnostic tests for SAST results pagination
* [Add] CxPortalWebService.get_sources_by_scan_id — retrieve per-file source code for specific files in a scan (Portal SOAP GetSourcesByScanID)
* [Add] CxPortalWebService.get_source_by_scan_id — deprecated singular variant; CxSAST 9.x returns "no longer supported"
* [Add] CxPortalWebService.get_file_names_for_path — get file names associated with a result path (Portal SOAP GetFileNamesForPath)
* [Add] tests for get_sources_by_scan_id, get_file_names_for_path, and get_source_by_scan_id
* [Fix] get_branch_project_status — replaced magic number `item["status"]["id"] == 2` with status name extraction via `status.get("value")`; now returns string ("Started"/"InProgress"/"Completed"/"Failed") instead of bool; added docstring with known status values; handles both dict and plain-string status formats
* [Update] test_get_branch_project_status — now creates a real branch from an existing project, polls and displays all intermediate statuses, asserts on status name string
* [Fix] api_client.call_api — strip explicit Content-Type header when files are present so httpx can auto-set multipart/form-data boundary; fixes 400/500 errors on file upload endpoints

1.8.7 - 2026-06-02
* [Add] AiAssetsAPI — AI supply chain asset management (findings, asset types, assets, applications, global inventory results, scan results, risks)
* [Add] AnalyticsAPI — KPI query endpoint
Expand Down
12 changes: 12 additions & 0 deletions CheckmarxPythonSDK/CxOne/dto/CommentJSON.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,15 @@ class CommentJSON:
user: str = None
content: str = None
is_deleted: bool = None

@classmethod
def from_dict(cls, item: dict) -> "CommentJSON":
if not item:
return None
return cls(
id=item.get("id"),
date=item.get("date"),
user=item.get("user"),
content=item.get("content"),
is_deleted=item.get("isDeleted", False),
)
26 changes: 23 additions & 3 deletions CheckmarxPythonSDK/CxOne/dto/PredicateHistory.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import List

from .PredicateInitialValues import PredicateInitialValues
from .PredicateWithCommentJSON import PredicateWithCommentJSON


@dataclass
class PredicateHistory:

"""Predicates for one similarity ID within one project."""
similarity_id: str = None
project_id: str = None
predicates: List[PredicateWithCommentJSON] = None
predicates: List[PredicateWithCommentJSON] = field(default_factory=list)
total_count: int = None
initial_predicate_values: PredicateInitialValues = None

@classmethod
def from_dict(cls, item: dict) -> "PredicateHistory":
if not item:
return None
return cls(
similarity_id=item.get("similarityId"),
project_id=item.get("projectId"),
predicates=[
PredicateWithCommentJSON.from_dict(p)
for p in (item.get("predicates") or [])
],
total_count=item.get("totalCount"),
initial_predicate_values=PredicateInitialValues.from_dict(
item.get("initialPredicateValues")
),
)
25 changes: 25 additions & 0 deletions CheckmarxPythonSDK/CxOne/dto/PredicateHistoryResponse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from dataclasses import dataclass, field
from typing import List

from .PredicateHistory import PredicateHistory


@dataclass
class PredicateHistoryResponse:
"""Full response from GET /sast-results-predicates/{similarityId}
and GET /sast-results-predicates/{similarityId}/latest."""

predicate_history_per_project: List[PredicateHistory] = field(default_factory=list)
total_count: int = 0

@classmethod
def from_dict(cls, item: dict) -> "PredicateHistoryResponse":
if not item:
return None
return cls(
predicate_history_per_project=[
PredicateHistory.from_dict(p)
for p in (item.get("predicateHistoryPerProject") or [])
],
total_count=item.get("totalCount", 0),
)
14 changes: 14 additions & 0 deletions CheckmarxPythonSDK/CxOne/dto/PredicateInitialValues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from dataclasses import dataclass


@dataclass
class PredicateInitialValues:
"""Initial state/severity before triage."""
state: str = None
severity: str = None

@classmethod
def from_dict(cls, item: dict) -> "PredicateInitialValues":
if not item:
return None
return cls(state=item.get("state"), severity=item.get("severity"))
18 changes: 18 additions & 0 deletions CheckmarxPythonSDK/CxOne/dto/PredicateWithCommentJSON.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,21 @@ class PredicateWithCommentJSON:
created_at: str = None
change_origin_type: int = None
change_origin_name: str = None

@classmethod
def from_dict(cls, item: dict) -> "PredicateWithCommentJSON":
if not item:
return None
return cls(
id=item.get("ID"),
similarity_id=item.get("similarityId"),
project_id=item.get("projectId"),
severity=item.get("severity"),
state=item.get("state"),
comment=item.get("comment"),
comment_json=CommentJSON.from_dict(item.get("commentJSON")),
created_by=item.get("createdBy"),
created_at=item.get("createdAt"),
change_origin_type=item.get("changeOriginType"),
change_origin_name=item.get("changeOriginName"),
)
2 changes: 2 additions & 0 deletions CheckmarxPythonSDK/CxOne/dto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@
from .PlatformSummary import PlatformSummary
from .Predicate import Predicate
from .PredicateHistory import PredicateHistory
from .PredicateInitialValues import PredicateInitialValues
from .PredicateHistoryResponse import PredicateHistoryResponse
from .PredicateWithCommentJSON import PredicateWithCommentJSON
from .PredicateWithCommentsJSON import PredicateWithCommentsJSON
from .Preset import Preset
Expand Down
15 changes: 12 additions & 3 deletions CheckmarxPythonSDK/CxOne/sastQueriesAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,22 @@ def get_sast_queries_presets(self) -> List[Preset]:
]

def get_sast_query_description(
self, ids: List[str]
self,
ids: List[str],
scan_id: str = None,
tenant_id: str = None,
) -> List[QueryDescription]:
"""
Args:
ids (List[str]): list of query ids.
scan_id (str): optional scan ID for context.
tenant_id (str): optional tenant ID.

Returns:
List[QueryDescription]
"""
url = f"{self.base_url}/descriptions"
params = {"ids": ids}
params = {"ids": ids, "scan-id": scan_id, "tenant-id": tenant_id}
response = self.api_client.call_api(
method="GET", url=url, params=params
)
Expand Down Expand Up @@ -121,8 +126,12 @@ def get_sast_queries_presets() -> List[Preset]:

def get_sast_query_description(
ids: List[str],
scan_id: str = None,
tenant_id: str = None,
) -> List[QueryDescription]:
return SastQueriesAPI().get_sast_query_description(ids=ids)
return SastQueriesAPI().get_sast_query_description(
ids=ids, scan_id=scan_id, tenant_id=tenant_id,
)


def get_mapping_between_ast_and_sast_query_ids() -> List[dict]:
Expand Down
30 changes: 21 additions & 9 deletions CheckmarxPythonSDK/CxOne/sastResultsPredicatesAPI.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import List

from CheckmarxPythonSDK.api_client import ApiClient
from CheckmarxPythonSDK.CxOne.config import construct_configuration
from CheckmarxPythonSDK.utilities.compat import NO_CONTENT, CREATED, OK
from typing import List
from .dto import PredicateHistoryResponse


class SastResultsPredicatesAPI(object):
Expand All @@ -22,49 +24,55 @@ def get_all_predicates_for_similarity_id(
project_ids: List[str] = None,
include_comment_json: bool = None,
scan_id: str = None,
) -> dict:
offset: int = 0,
limit: int = 100,
) -> PredicateHistoryResponse:
"""
Args:
similarity_id (str):
project_ids (list of str):
include_comment_json (bool):
scan_id (str):
offset (int):
limit (int):

Returns:
dict
PredicateHistoryResponse
"""
url = f"{self.base_url}/{similarity_id}"
params = {
"project-ids": project_ids,
"include-comment-json": include_comment_json,
"scan-id": scan_id,
"offset": offset,
"limit": limit,
}
response = self.api_client.call_api(
method="GET", url=url, params=params
)
return response.json()
return PredicateHistoryResponse.from_dict(response.json())

def get_latest_predicates_for_similarity_id(
self,
similarity_id: str,
project_ids: List[str] = None,
scan_id: str = None,
) -> dict:
) -> PredicateHistoryResponse:
"""
Args:
similarity_id (str):
project_ids (list of str):
scan_id (str):

Returns:
dict
PredicateHistoryResponse
"""
url = f"{self.base_url}/{similarity_id}/latest"
params = {"project-ids": project_ids, "scan-id": scan_id}
response = self.api_client.call_api(
method="GET", url=url, params=params
)
return response.json()
return PredicateHistoryResponse.from_dict(response.json())

def predicate_severity_and_state_by_similarity_id_and_project_id(
self, data: List[dict]
Expand Down Expand Up @@ -245,20 +253,24 @@ def get_all_predicates_for_similarity_id(
project_ids: List[str] = None,
include_comment_json: bool = None,
scan_id: str = None,
) -> dict:
offset: int = 0,
limit: int = 100,
) -> PredicateHistoryResponse:
return SastResultsPredicatesAPI().get_all_predicates_for_similarity_id(
similarity_id=similarity_id,
project_ids=project_ids,
include_comment_json=include_comment_json,
scan_id=scan_id,
offset=offset,
limit=limit,
)


def get_latest_predicates_for_similarity_id(
similarity_id: str,
project_ids: List[str] = None,
scan_id: str = None,
) -> dict:
) -> PredicateHistoryResponse:
return SastResultsPredicatesAPI().get_latest_predicates_for_similarity_id(
similarity_id=similarity_id,
project_ids=project_ids,
Expand Down
Loading