From e04170d030782e847c22ee4694040f70c67e2014 Mon Sep 17 00:00:00 2001 From: tsaekao-endor Date: Tue, 6 Jan 2026 21:34:56 -0500 Subject: [PATCH 1/4] Add script to export SAST findings with CWE column This script exports SAST findings with an added CWE column to a CSV file, using SAST-specific metadata. It serves as a temporary solution until the platform's export includes CWE. --- export_sast_with_cwe/export_sast_with_cwe.py | 201 +++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 export_sast_with_cwe/export_sast_with_cwe.py diff --git a/export_sast_with_cwe/export_sast_with_cwe.py b/export_sast_with_cwe/export_sast_with_cwe.py new file mode 100644 index 0000000..a89d433 --- /dev/null +++ b/export_sast_with_cwe/export_sast_with_cwe.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +""" +WHAT IT DOES: +- Adds CWE column to the SAST export via API, since the platform export doesn't include CWE yet. +- This is a TEMPORARY SOLUTION until the platform export includes the CWE column for SAST findings. +- The script uses the SAST-specific metadata that the UI export doesn't expose. +""" +import argparse +import csv +import json +import subprocess +import sys +from typing import Dict, List, Any + +def run_endorctl_api_list(args: List[str]) -> Dict[str, Any]: + """Call `endorctl api list` and return parsed JSON.""" + cmd = ["endorctl", "api", "list"] + args + proc = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + ) + return json.loads(proc.stdout) + +def get_projects_with_namespaces(root_namespace: str) -> List[Dict[str, str]]: + """ + List all projects in root namespace AND child namespaces. + Uses --traverse on Project. + """ + args = [ + "--namespace", + root_namespace, + "--resource", + "Project", + "--traverse", + "--list-all", + "--field-mask", + "uuid,meta.name,tenant_meta", + ] + data = run_endorctl_api_list(args) + projects = [] + for obj in data.get("list", {}).get("objects", []): + projects.append( + { + "uuid": obj["uuid"], + "name": obj["meta"]["name"], + "namespace": obj["tenant_meta"]["namespace"], + } + ) + return projects + +def get_sast_findings_for_project(project: Dict[str, str]) -> List[Dict[str, Any]]: + """Return all non-exception SAST findings for a single project UUID.""" + proj_uuid = project["uuid"] + filter_expr = ( + 'context.type == "CONTEXT_TYPE_MAIN" ' + f'and spec.project_uuid=="{proj_uuid}" ' + 'and spec.finding_categories contains ["FINDING_CATEGORY_SAST"] ' + 'and spec.finding_tags not contains ["FINDING_TAGS_EXCEPTION"]' + ) + + field_mask = ",".join( + [ + "uuid", + "meta.description", + "spec.level", + "spec.finding_tags", + "spec.finding_categories", + "spec.remediation", + "spec.finding_metadata.custom", + ] + ) + + args = [ + "--namespace", + project["namespace"], + "--resource", + "Finding", + "--filter", + filter_expr, + "--field-mask", + field_mask, + "--list-all", + ] + + data = run_endorctl_api_list(args) + return data.get("list", {}).get("objects", []) + +def build_csv_row(finding: Dict[str, Any], project_name: str) -> Dict[str, str]: + """ + Map a Finding object into UI-equivalent CSV columns: + UUID, Title, Severity Level, Attributes, Finding Categories, + Remediation, Fix Version, Risk Details, Explanation, CVE, CWE, Project Name + """ + uuid = finding.get("uuid", "") + meta = finding.get("meta", {}) or {} + spec = finding.get("spec", {}) or {} + + fm = spec.get("finding_metadata", {}) or {} + custom = fm.get("custom", {}) or {} + + title = meta.get("description") or custom.get("message") or "" + severity = spec.get("level", "") + attributes = ",".join(spec.get("finding_tags", []) or []) + categories = ",".join(spec.get("finding_categories", []) or []) + remediation = spec.get("remediation", "") or "" + + fix_version = "" # no fix version for SAST + cve = "" # no CVE for SAST + + risk_details = "A SAST finding was identified in this repository version." + explanation = custom.get("explanation", "") + + cwes = custom.get("cwes", []) or [] + cwe_str = ";".join(cwes) + + return { + "UUID": uuid, + "Title": title, + "Severity Level": severity, + "Attributes": attributes, + "Finding Categories": categories, + "Remediation": remediation, + "Fix Version": fix_version, + "Risk Details": risk_details, + "Explanation": explanation, + "CVE": cve, + "CWE": cwe_str, + "Project Name": project_name, + } + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Export all SAST findings (including child namespaces) for a root " + "namespace into a CSV, with CWE populated from SAST rule metadata." + ) + ) + parser.add_argument( + "-n", + "--namespace", + required=True, + help="Root namespace to traverse (e.g. rubrik.prod)", + ) + parser.add_argument( + "-o", + "--output", + required=True, + help="Output CSV file path (e.g. sast-findings-with-cwe.csv)", + ) + return parser.parse_args() + +def main(): + args = parse_args() + root_namespace = args.namespace + output_csv = args.output + + print(f"Enumerating projects under namespace '{root_namespace}' (including children)...") + projects = get_projects_with_namespaces(root_namespace) + print(f"Found {len(projects)} projects") + + fieldnames = [ + "UUID", + "Title", + "Severity Level", + "Attributes", + "Finding Categories", + "Remediation", + "Fix Version", + "Risk Details", + "Explanation", + "CVE", + "CWE", + "Project Name", + ] + + total_findings = 0 + with open(output_csv, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + + for proj in projects: + pname = proj["name"] + print(f"Fetching SAST findings for project '{pname}' in namespace '{proj['namespace']}'...") + findings = get_sast_findings_for_project(proj) + for finding in findings: + row = build_csv_row(finding, pname) + writer.writerow(row) + total_findings += 1 + + print(f"Done. Wrote {total_findings} SAST findings to {output_csv}") + +if __name__ == "__main__": + try: + main() + except subprocess.CalledProcessError as e: + print("endorctl command failed:", file=sys.stderr) + print(e.stderr, file=sys.stderr) + sys.exit(1) From a4a699283391f13cf3a6b847adebd419c409ab9d Mon Sep 17 00:00:00 2001 From: tsaekao-endor Date: Tue, 6 Jan 2026 21:43:36 -0500 Subject: [PATCH 2/4] Add README for SAST Findings CSV Export with CWE This README provides an overview of a Python script that exports SAST findings from Endor Labs into a CSV file, including CWE information. It details the script's functionality, prerequisites, usage instructions, and the output format. --- export_sast_with_cwe/README.md | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 export_sast_with_cwe/README.md diff --git a/export_sast_with_cwe/README.md b/export_sast_with_cwe/README.md new file mode 100644 index 0000000..cf04cda --- /dev/null +++ b/export_sast_with_cwe/README.md @@ -0,0 +1,58 @@ +# SAST Findings CSV Export with CWE + +This is a Python script that exports **SAST findings** from Endor Labs into a CSV file, with the **CWE column populated** from the SAST rule metadata. It's intended as a drop-in replacement for the standard SAST Findings export when you specifically need CWE values for SAST. + +--- + +## What the script does + +- Uses **endorctl** to query the Endor Labs **Finding** API. +- Traverses a **root namespace and all child namespaces**. +- Collects all **non-exception SAST findings** for every project in scope. +- Reads CWE information from `spec.finding_metadata.custom.cwes`. +- Writes a CSV similar to the UI Findings export, but with **CWE filled for SAST**. + +--- + +## Prerequisites + +- **Python 3.8+** +- **endorctl** installed and authenticated + +--- + +## Usage + +```bash +python export_sast_cwe.py -n -o +``` + +| Flag | Description | +|------|-------------| +| `-n, --namespace` | Root namespace to traverse | +| `-o, --output` | Output CSV file name | + +**Example:** + +```bash +python export_sast_cwe.py -n my-org.prod -o sast-findings-with-cwe.csv +``` + +--- + +## Output + +The CSV includes these columns: + +- UUID +- Title +- Severity Level +- Attributes +- Finding Categories +- Remediation +- Fix Version +- Risk Details +- Explanation +- CVE +- **CWE** ← populated from SAST rule metadata +- Project Name From 3fd8e7d2846c15f7bf173a91205c9bc6c63dcf1f Mon Sep 17 00:00:00 2001 From: tsaekao-endor Date: Tue, 6 Jan 2026 21:44:09 -0500 Subject: [PATCH 3/4] Update README to clarify CWE column inclusion Clarified the purpose of the script regarding CWE values. --- export_sast_with_cwe/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/export_sast_with_cwe/README.md b/export_sast_with_cwe/README.md index cf04cda..ea443e0 100644 --- a/export_sast_with_cwe/README.md +++ b/export_sast_with_cwe/README.md @@ -1,6 +1,6 @@ -# SAST Findings CSV Export with CWE +# SAST Findings CSV Export with CWE Field -This is a Python script that exports **SAST findings** from Endor Labs into a CSV file, with the **CWE column populated** from the SAST rule metadata. It's intended as a drop-in replacement for the standard SAST Findings export when you specifically need CWE values for SAST. +This is a Python script that exports **SAST findings** from Endor Labs into a CSV file, with the **CWE column populated** from the SAST rule metadata. It's intended as a drop-in replacement for the standard SAST Findings export when you specifically need CWE values for SAST since the platform export doesn't include the CWE column. --- From c493100c37c858560bec93d7a007794a053abaf7 Mon Sep 17 00:00:00 2001 From: tsaekao-endor Date: Tue, 6 Jan 2026 21:45:06 -0500 Subject: [PATCH 4/4] Update script name in README usage examples --- export_sast_with_cwe/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/export_sast_with_cwe/README.md b/export_sast_with_cwe/README.md index ea443e0..bc03352 100644 --- a/export_sast_with_cwe/README.md +++ b/export_sast_with_cwe/README.md @@ -24,7 +24,7 @@ This is a Python script that exports **SAST findings** from Endor Labs into a CS ## Usage ```bash -python export_sast_cwe.py -n -o +python export_sast_with_cwe.py -n -o ``` | Flag | Description | @@ -35,7 +35,7 @@ python export_sast_cwe.py -n -o **Example:** ```bash -python export_sast_cwe.py -n my-org.prod -o sast-findings-with-cwe.csv +python export_sast_with_cwe.py -n mynamespace -o sast-findings-with-cwe.csv ``` ---