Alright team, I know many of us are using Checkmarx for SAST and have to wrangle those findings into reports for different audiences. While the UI is great for deep dives, I often need to batch export specific findings—like all "High" severity from the last sprint—into a clean CSV for project managers or to feed into our internal dashboards.
The API is robust, but doing this manually is a pain. I've built a Python script that automates this. It handles authentication, lets you filter by project, severity, and state, and outputs a tidy CSV with the key columns I find most useful for triage meetings.
Here's the core workflow it automates:
* Fetches a list of projects or targets a specific one by name/ID.
* Pulls scans and filters findings based on your criteria (e.g., `severity='High'`, `state='Not Exploitable'`).
* Flattens the nested JSON response into a structured table.
* Exports to CSV with configurable columns (e.g., `CweId`, `Name`, `Severity`, `State`, `FileName`, `Line`).
You'll need the `requests` and `pandas` libraries. The key is setting up your Checkmarx API credentials securely, of course. I keep mine in environment variables. The script is especially handy for creating regular snapshots to track remediation rates over time.
I've shared the full script and a config example over in the [Code Repository] section of the community. Would anyone find it useful if I added functionality to compare exports over time, highlighting newly introduced or fixed findings? Let me know what your reporting pain points are!
Oh this is so timely. I'm literally drowning in findings from our last few sprints and the manual CSV exports are killing me. I've been trying to decide between writing something like this myself or finding an existing tool, and you just saved me a week of tinkering.
I'm curious, how do you handle pagination on the API calls? I think our instance returns things in chunks of 50. Also, do you do any post-processing like grouping by CWE before the CSV export, or do you keep it as one row per finding?
Pagination? Right. I handle it, but I'm not happy about it. Checkmarx's API is inconsistent - some endpoints use limit/offset, others use a "next" link in the headers, and a few just dump everything. My script has three different pagination handlers and I pick one based on the endpoint path. For findings, it's usually the limit/offset model, so I just loop until I get an empty array.
On grouping by CWE - I keep it as one row per finding. I learned the hard way that collapsing them loses the context managers need, like specific file paths and line numbers. If they want a summary, they can pivot the CSV in Excel. My output does include a column for CWE ID though, so you can sort or filter on that.
I'd be curious if your instance has the same quirk with the 'state' field on historical findings - sometimes it returns an integer, sometimes a string. Broke my first version.
Expect the unexpected
Flattening the nested JSON is the right move, but the column selection always trips me up later. Management asks for one set of fields, security wants another, and then you're maintaining three different forks of the script. Been there.
I've moved to dumping the full flattened JSON to a file first, then using a separate config file to define which columns get pulled into the CSV for which audience. Saves me from re-hitting the API every time someone wants a new view.
And pandas is fine, but it's a heavy lift for a simple CSV. I've rewritten similar scripts using just the csv module. One less dependency for the team to fight over when deploying.
The config file approach is a lifesaver. I do something similar, but with YAML files that map to different "view" presets - management gets the 5-column summary, security gets the full 20. Saves so many "can you add just one more column?" cycles.
And yeah, pandas is overkill for this. I'll use it if I'm already doing transformations, but for straight JSON-to-CSV? The stdlib csv.DictWriter is cleaner. One less thing to explain in the README.
Though I'm curious, how do you handle when the API adds a new nested field that breaks your flattening logic? I've had my script choke on that a few times.
It's not marketing, it's logic.
> how do you handle when the API adds a new nested field that breaks your flattening logic?
I set a default flattener to `lambda x: str(x)` for any unexpected nested dicts. It's ugly, but it means the script doesn't crash at 2 AM, it just dumps a JSON string into the column. Lets me know the schema changed without halting the whole export.
Also, validating the flattened column list against a known schema file before the run catches most of those surprises early.
Script's fine. But using pandas for this is like using a crane to move a paperclip.
Just use csv.DictWriter. You'll save everyone from dependency hell and your script will run in places where pandas isn't installed (which is most production servers, because no one needs pandas there).
Also, filtering by state after the fetch? You're pulling everything then filtering locally. That's wasteful. Use the API's query parameters if they exist. If they don't, complain to Checkmarx. Their API is bad enough without us adding extra load.
-- old school