From b6b87031eb88d33ec41d330a482a8d6ae3b9ce03 Mon Sep 17 00:00:00 2001 From: william brady Date: Sun, 1 Mar 2026 18:06:43 -0500 Subject: [PATCH 1/6] feat: report tool execution failures in scan results When a scanning tool binary is missing or fails to execute, the scanner now records a ToolError instead of silently producing zero findings. Tool errors are collected by the aggregator, displayed in CLI output, and rendered in markdown and HTML reports so users can distinguish between a clean scan and tools that didn't run. --- .gitignore | 4 +- build.md | 1 - src/formatters/html_formatter.py | 32 ++++ src/formatters/markdown_formatter.py | 16 ++ src/main.py | 12 ++ src/report_aggregator.py | 14 +- src/scanner_base.py | 22 +++ src/scanners/cloudformation_scanner.py | 26 ++- src/scanners/container_scanner.py | 17 +- src/scanners/npm_scanner.py | 13 +- src/scanners/python_scanner.py | 19 ++- src/scanners/secrets_scanner.py | 9 +- src/scanners/terraform_scanner.py | 47 +++++- tests/test_tool_errors.py | 224 +++++++++++++++++++++++++ 14 files changed, 431 insertions(+), 25 deletions(-) create mode 100644 tests/test_tool_errors.py diff --git a/.gitignore b/.gitignore index 7d8842a..f00196f 100644 --- a/.gitignore +++ b/.gitignore @@ -182,9 +182,9 @@ cython_debug/ .abstra/ # Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, +# and can be added to the global gitignore or merged into this file. However, if you prefer, # you could uncomment the following to ignore the entire vscode folder # .vscode/ diff --git a/build.md b/build.md index fc7c6fc..4e05870 100644 --- a/build.md +++ b/build.md @@ -517,4 +517,3 @@ tar xzf gitleaks_X.X.X_linux_x64.tar.gz && mv gitleaks /usr/local/bin/ - [x] Environment variables properly passed and used - [x] Error handling works for all failure scenarios - [x] Performance acceptable for typical repositories - diff --git a/src/formatters/html_formatter.py b/src/formatters/html_formatter.py index 10009e6..9c46b77 100644 --- a/src/formatters/html_formatter.py +++ b/src/formatters/html_formatter.py @@ -173,6 +173,38 @@ def _generate_html(self, report_data: Dict[str, Any]) -> str:
+""" + + # Tool Failures section + tool_errors = report_data.get("tool_errors", []) + if tool_errors: + html += """ +
+

Tool Failures

+

The following tools failed to execute. + Their results are not included in the findings.

+ + + + + + +""" + for error in tool_errors: + html += f""" + + + + + +""" + html += """ +
ToolScannerError
{escape(error.get('tool', 'Unknown'))}{escape(error.get('scanner', 'Unknown'))}{escape(error.get('error_message', 'Unknown error'))}
+
+""" + + html += """

Findings Details

""" diff --git a/src/formatters/markdown_formatter.py b/src/formatters/markdown_formatter.py index ae09e4a..89f369f 100644 --- a/src/formatters/markdown_formatter.py +++ b/src/formatters/markdown_formatter.py @@ -62,6 +62,22 @@ def _generate_markdown(self, report_data: Dict[str, Any]) -> str: by_tool = summary.get("by_tool", {}) for tool, count in sorted(by_tool.items(), key=lambda x: x[1], reverse=True): md.append(f"- **{tool}:** {count}") + md.append("") + + # Tool Failures + tool_errors = report_data.get("tool_errors", []) + if tool_errors: + md.append("### Tool Failures\n") + md.append("The following tools failed to execute during the scan:\n") + md.append("| Tool | Scanner | Error |") + md.append("|------|---------|-------|") + for error in tool_errors: + tool = error.get("tool", "Unknown") + scanner = error.get("scanner", "Unknown") + error_msg = error.get("error_message", "Unknown error").replace("|", "\\|").replace("\n", " ") + md.append(f"| {tool} | {scanner} | {error_msg} |") + md.append("") + md.append("\n---\n") # Findings Details diff --git a/src/main.py b/src/main.py index b710180..a0495d5 100644 --- a/src/main.py +++ b/src/main.py @@ -226,7 +226,11 @@ def scan_local( click.echo(f"Running {scanner.__class__.__name__}...") findings = scanner.run(repo_path) aggregator.add_findings(findings) + tool_errors = scanner.get_tool_errors() + aggregator.add_tool_errors(tool_errors) click.echo(f" Found {len(findings)} findings") + for error in tool_errors: + click.echo(f" WARNING: {error.tool} failed to execute: {error.error_message}", err=True) click.echo("") @@ -257,6 +261,14 @@ def scan_local( click.echo(f" {sev}: {count}") click.echo("") + # Display tool failures + tool_errors = report_data.get("tool_errors", []) + if tool_errors: + click.echo("Tool Failures:") + for error in tool_errors: + click.echo(f" {error['tool']} ({error['scanner']}): {error['error_message']}") + click.echo("") + # Generate reports click.echo("Generating reports...") formatters = { diff --git a/src/report_aggregator.py b/src/report_aggregator.py index 28f7a38..e3c9b3a 100644 --- a/src/report_aggregator.py +++ b/src/report_aggregator.py @@ -8,7 +8,7 @@ from typing import List, Dict, Any from collections import defaultdict from datetime import datetime -from src.scanner_base import Finding, Severity +from src.scanner_base import Finding, Severity, ToolError class ReportAggregator: @@ -25,6 +25,7 @@ def __init__(self, config: Dict[str, Any]): self.logger = logging.getLogger(__name__) self.findings: List[Finding] = [] self.deduplicated_findings: List[Finding] = [] + self.tool_errors: List[ToolError] = [] self.stats: Dict[str, Any] = {} def add_findings(self, findings: List[Finding]): @@ -36,6 +37,15 @@ def add_findings(self, findings: List[Finding]): """ self.findings.extend(findings) + def add_tool_errors(self, errors: List[ToolError]): + """ + Add tool errors from a scanner + + Args: + errors: List of tool errors to add + """ + self.tool_errors.extend(errors) + def aggregate(self, repository_path: str = None, branch: str = None) -> Dict[str, Any]: """ Aggregate all findings and generate statistics @@ -70,9 +80,11 @@ def aggregate(self, repository_path: str = None, branch: str = None) -> Dict[str "unique_files": self.stats["unique_files"], }, "findings": [f.to_dict() for f in self.deduplicated_findings], + "tool_errors": [e.to_dict() for e in self.tool_errors], "metadata": { "scanners_run": list(self.stats["by_tool"].keys()), "total_raw_findings": len(self.findings), + "tools_failed": len(self.tool_errors), "deduplication_enabled": self.config.get("aggregation", {}).get("deduplicate", True), }, } diff --git a/src/scanner_base.py b/src/scanner_base.py index 33e38b4..87e390e 100644 --- a/src/scanner_base.py +++ b/src/scanner_base.py @@ -52,6 +52,23 @@ def to_dict(self) -> Dict[str, Any]: } +@dataclass +class ToolError: + """Represents a tool execution failure""" + + tool: str + error_message: str + scanner: str + + def to_dict(self) -> Dict[str, Any]: + """Convert tool error to dictionary""" + return { + "tool": self.tool, + "error_message": self.error_message, + "scanner": self.scanner, + } + + class ScannerBase(ABC): """Abstract base class for all scanners""" @@ -66,6 +83,7 @@ def __init__(self, config: Dict[str, Any], logger: Optional[logging.Logger] = No self.config = config self.logger = logger or logging.getLogger(self.__class__.__name__) self.findings: List[Finding] = [] + self.tool_errors: List[ToolError] = [] @abstractmethod def is_applicable(self, path: str) -> bool: @@ -184,6 +202,10 @@ def get_findings(self) -> List[Finding]: """Return all findings from this scanner""" return self.findings + def get_tool_errors(self) -> List[ToolError]: + """Return all tool errors from this scanner""" + return self.tool_errors + def severity_from_string(self, severity_str: str) -> Severity: """Convert string to Severity enum""" severity_map = { diff --git a/src/scanners/cloudformation_scanner.py b/src/scanners/cloudformation_scanner.py index 1525d1a..a027e7e 100644 --- a/src/scanners/cloudformation_scanner.py +++ b/src/scanners/cloudformation_scanner.py @@ -6,7 +6,7 @@ import json from pathlib import Path from typing import List -from src.scanner_base import ScannerBase, Finding, Severity +from src.scanner_base import ScannerBase, Finding, Severity, ToolError class CloudFormationScanner(ScannerBase): @@ -99,6 +99,7 @@ def is_applicable(self, path: str) -> bool: def run(self, path: str) -> List[Finding]: """Run all enabled CloudFormation scanners""" self.findings = [] + self.tool_errors = [] if not self.is_applicable(path): self.logger.info("No CloudFormation templates found, skipping CFN scanners") @@ -141,7 +142,11 @@ def _run_cfn_lint(self, path: str) -> List[Finding]: ) # Run cfn-lint on all templates - _, stdout, stderr = self.execute_command(["cfn-lint"] + cfn_templates + ["--format", "json"], cwd=path) + returncode, stdout, stderr = self.execute_command(["cfn-lint"] + cfn_templates + ["--format", "json"], cwd=path) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="cfn-lint", error_message=stderr, scanner="CloudFormationScanner")) + return findings try: if stdout: @@ -199,12 +204,21 @@ def _run_cfn_nag(self, path: str) -> List[Finding]: # We'll scan each template individually to ensure proper validation all_results = [] stderr = "" # Initialize for error logging + cfn_nag_failed = False for template in cfn_templates: - _, stdout, stderr = self.execute_command( + returncode, stdout, stderr = self.execute_command( ["cfn_nag_scan", "--input-path", template, "--output-format", "json"], cwd=path, ) + if returncode == -1: + if not cfn_nag_failed: + self.tool_errors.append( + ToolError(tool="cfn-nag", error_message=stderr, scanner="CloudFormationScanner") + ) + cfn_nag_failed = True + continue + if stdout: try: result = json.loads(stdout) @@ -305,7 +319,11 @@ def _run_checkov(self, path: str) -> List[Finding]: cmd.extend(["--skip-path", exclude_path]) # Checkov scans directory but we'll filter results to only validated templates - _, stdout, _ = self.execute_command(cmd, cwd=path) + returncode, stdout, stderr = self.execute_command(cmd, cwd=path) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="checkov", error_message=stderr, scanner="CloudFormationScanner")) + return findings excluded_count = 0 try: diff --git a/src/scanners/container_scanner.py b/src/scanners/container_scanner.py index 5643b31..f567b33 100644 --- a/src/scanners/container_scanner.py +++ b/src/scanners/container_scanner.py @@ -8,7 +8,7 @@ import logging from typing import List, Dict, Any, Optional -from src.scanner_base import ScannerBase, Finding, Severity +from src.scanner_base import ScannerBase, Finding, Severity, ToolError class ContainerScanner(ScannerBase): @@ -58,13 +58,19 @@ def run(self, path: str) -> List[Finding]: """Run container image vulnerability scanning""" path = os.path.abspath(path) self.findings = [] + self.tool_errors = [] if not self.is_enabled(): self.logger.info("Container scanning is disabled") return self.findings # Check for trivy availability - trivy_check, _, _ = self.execute_command(["trivy", "--version"]) + trivy_check, _, trivy_stderr = self.execute_command(["trivy", "--version"]) + if trivy_check == -1: + self.tool_errors.append( + ToolError(tool="trivy-container", error_message=trivy_stderr, scanner="ContainerScanner") + ) + return self.findings if trivy_check != 0: self.logger.warning("Trivy not found, skipping container scanning") return self.findings @@ -89,7 +95,12 @@ def run(self, path: str) -> List[Finding]: if build_images: # Check for docker availability when building images - docker_check, _, _ = self.execute_command(["docker", "--version"]) + docker_check, _, docker_stderr = self.execute_command(["docker", "--version"]) + if docker_check == -1: + self.tool_errors.append( + ToolError(tool="docker", error_message=docker_stderr, scanner="ContainerScanner") + ) + return self.findings if docker_check != 0: self.logger.warning("Docker not found, skipping container image building") return self.findings diff --git a/src/scanners/npm_scanner.py b/src/scanners/npm_scanner.py index 4a647d9..5b04e0b 100644 --- a/src/scanners/npm_scanner.py +++ b/src/scanners/npm_scanner.py @@ -9,7 +9,7 @@ import tempfile from pathlib import Path from typing import List -from src.scanner_base import ScannerBase, Finding, Severity +from src.scanner_base import ScannerBase, Finding, Severity, ToolError class NPMScanner(ScannerBase): @@ -36,6 +36,7 @@ def is_applicable(self, path: str) -> bool: def run(self, path: str) -> List[Finding]: """Run npm and Snyk scanners""" self.findings = [] + self.tool_errors = [] if not self.is_applicable(path): self.logger.info("No npm projects or IaC files found, skipping npm scanners") @@ -78,7 +79,11 @@ def _run_npm_audit(self, path: str) -> List[Finding]: continue # Run npm audit with JSON output - _, stdout, _ = self.execute_command(["npm", "audit", "--json"], cwd=str(package_dir)) + returncode, stdout, stderr = self.execute_command(["npm", "audit", "--json"], cwd=str(package_dir)) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="npm-audit", error_message=stderr, scanner="NPMScanner")) + continue try: if stdout: @@ -167,6 +172,10 @@ def _run_snyk_iac(self, path: str) -> List[Finding]: # Run Snyk IaC test - set cwd to path so Snyk scans from that directory returncode, stdout, stderr = self.execute_command(cmd, cwd=path, timeout=600, env=env) + if returncode == -1: + self.tool_errors.append(ToolError(tool="snyk-iac", error_message=stderr, scanner="NPMScanner")) + return findings + # Log raw output for debugging self.logger.debug("Snyk return code: %s", returncode) self.logger.debug("Snyk stdout length: %s", len(stdout)) diff --git a/src/scanners/python_scanner.py b/src/scanners/python_scanner.py index 39f1151..19f792d 100644 --- a/src/scanners/python_scanner.py +++ b/src/scanners/python_scanner.py @@ -6,7 +6,7 @@ import json from pathlib import Path from typing import List -from src.scanner_base import ScannerBase, Finding, Severity +from src.scanner_base import ScannerBase, Finding, Severity, ToolError class PythonScanner(ScannerBase): @@ -22,6 +22,7 @@ def is_applicable(self, path: str) -> bool: def run(self, path: str) -> List[Finding]: """Run Python security scanners""" self.findings = [] + self.tool_errors = [] self.logger.info("Running Python security scanners...") @@ -59,7 +60,11 @@ def _run_bandit(self, path: str) -> List[Finding]: cmd.extend(["--exclude", ",".join(excluded_paths)]) # Run bandit with JSON output - _, stdout, _ = self.execute_command(cmd, cwd=path) + returncode, stdout, stderr = self.execute_command(cmd, cwd=path) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="bandit", error_message=stderr, scanner="PythonScanner")) + return findings excluded_count = 0 try: @@ -141,6 +146,10 @@ def _run_safety(self, path: str) -> List[Finding]: cwd=path, ) + if returncode == -1: + self.tool_errors.append(ToolError(tool="safety", error_message=stderr, scanner="PythonScanner")) + return findings + # Check if Safety requires authentication if returncode != 0 and ( "authentication" in stderr.lower() or "api" in stderr.lower() or "EOF when reading" in stderr @@ -263,7 +272,11 @@ def _run_pylint(self, path: str) -> List[Finding]: # Use --exit-zero to prevent pylint from returning non-zero exit codes cmd = ["pylint", "--output-format=json", "--exit-zero"] + filtered_files - _, stdout, _ = self.execute_command(cmd, cwd=path, timeout=300) + returncode, stdout, stderr = self.execute_command(cmd, cwd=path, timeout=300) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="pylint", error_message=stderr, scanner="PythonScanner")) + return findings excluded_count = 0 try: diff --git a/src/scanners/secrets_scanner.py b/src/scanners/secrets_scanner.py index 346eef6..683d4f0 100644 --- a/src/scanners/secrets_scanner.py +++ b/src/scanners/secrets_scanner.py @@ -6,7 +6,7 @@ import json import tempfile from typing import List -from src.scanner_base import ScannerBase, Finding, Severity +from src.scanner_base import ScannerBase, Finding, Severity, ToolError class SecretsScanner(ScannerBase): @@ -19,6 +19,7 @@ def is_applicable(self, path: str) -> bool: def run(self, path: str) -> List[Finding]: """Run secrets detection tools""" self.findings = [] + self.tool_errors = [] self.logger.info("Running secrets scanners...") @@ -44,7 +45,7 @@ def _run_gitleaks(self, path: str) -> List[Finding]: report_path = temp_report.name try: - _, _, _ = self.execute_command( + returncode, _, stderr = self.execute_command( [ "gitleaks", "detect", @@ -59,6 +60,10 @@ def _run_gitleaks(self, path: str) -> List[Finding]: cwd=path, ) + if returncode == -1: + self.tool_errors.append(ToolError(tool="gitleaks", error_message=stderr, scanner="SecretsScanner")) + return findings + # Gitleaks writes to file, read it excluded_count = 0 try: diff --git a/src/scanners/terraform_scanner.py b/src/scanners/terraform_scanner.py index 4e5fe7a..3089be9 100644 --- a/src/scanners/terraform_scanner.py +++ b/src/scanners/terraform_scanner.py @@ -6,7 +6,7 @@ import json from pathlib import Path from typing import List -from src.scanner_base import ScannerBase, Finding, Severity +from src.scanner_base import ScannerBase, Finding, Severity, ToolError class TerraformScanner(ScannerBase): @@ -20,6 +20,7 @@ def is_applicable(self, path: str) -> bool: def run(self, path: str) -> List[Finding]: """Run all enabled Terraform scanners""" self.findings = [] + self.tool_errors = [] if not self.is_applicable(path): self.logger.info("No Terraform files found, skipping Terraform scanners") @@ -59,7 +60,11 @@ def _run_terraform_fmt(self, path: str) -> List[Finding]: findings = [] self.logger.info("Running terraform fmt...") - returncode, stdout, _ = self.execute_command(["terraform", "fmt", "-check", "-recursive"], cwd=path) + returncode, stdout, stderr = self.execute_command(["terraform", "fmt", "-check", "-recursive"], cwd=path) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="terraform-fmt", error_message=stderr, scanner="TerraformScanner")) + return findings # terraform fmt returns non-zero if files need formatting if returncode != 0 and stdout: @@ -92,10 +97,22 @@ def _run_terraform_validate(self, path: str) -> List[Finding]: self.logger.info("Running terraform validate...") # First initialize (quietly) - init_code, _, _ = self.execute_command(["terraform", "init", "-backend=false"], cwd=path) + init_code, _, init_stderr = self.execute_command(["terraform", "init", "-backend=false"], cwd=path) + + if init_code == -1: + self.tool_errors.append( + ToolError(tool="terraform-validate", error_message=init_stderr, scanner="TerraformScanner") + ) + return findings if init_code == 0: - returncode, stdout, _ = self.execute_command(["terraform", "validate", "-json"], cwd=path) + returncode, stdout, stderr = self.execute_command(["terraform", "validate", "-json"], cwd=path) + + if returncode == -1: + self.tool_errors.append( + ToolError(tool="terraform-validate", error_message=stderr, scanner="TerraformScanner") + ) + return findings if returncode != 0: try: @@ -126,6 +143,10 @@ def _run_tflint(self, path: str) -> List[Finding]: returncode, stdout, stderr = self.execute_command(["tflint", "--format=json", "--recursive"], cwd=path) + if returncode == -1: + self.tool_errors.append(ToolError(tool="tflint", error_message=stderr, scanner="TerraformScanner")) + return findings + findings = self.parse_output(stdout, stderr, returncode) return self.filter_excluded_findings(findings, "tflint", "terraform") @@ -142,7 +163,11 @@ def _run_tfsec(self, path: str) -> List[Finding]: for exclude_path in excluded_paths: cmd.extend(["--exclude-path", exclude_path]) - _, stdout, _ = self.execute_command(cmd, cwd=path) + returncode, stdout, stderr = self.execute_command(cmd, cwd=path) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="tfsec", error_message=stderr, scanner="TerraformScanner")) + return findings try: if stdout: @@ -194,7 +219,11 @@ def _run_checkov(self, path: str) -> List[Finding]: for exclude_path in excluded_paths: cmd.extend(["--skip-path", exclude_path]) - _, stdout, _ = self.execute_command(cmd, cwd=path) + returncode, stdout, stderr = self.execute_command(cmd, cwd=path) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="checkov", error_message=stderr, scanner="TerraformScanner")) + return findings excluded_count = 0 try: @@ -322,7 +351,11 @@ def _run_trivy(self, path: str) -> List[Finding]: cmd.append(path) - _, stdout, _ = self.execute_command(cmd, cwd=path) + returncode, stdout, stderr = self.execute_command(cmd, cwd=path) + + if returncode == -1: + self.tool_errors.append(ToolError(tool="trivy", error_message=stderr, scanner="TerraformScanner")) + return findings try: if stdout: diff --git a/tests/test_tool_errors.py b/tests/test_tool_errors.py new file mode 100644 index 0000000..441e3bb --- /dev/null +++ b/tests/test_tool_errors.py @@ -0,0 +1,224 @@ +""" +Tests for tool error handling + +Verifies that tool execution failures are properly tracked and reported +rather than silently producing zero findings. +""" + +from unittest.mock import patch + +from src.scanner_base import ToolError +from src.report_aggregator import ReportAggregator +from src.formatters.markdown_formatter import MarkdownFormatter +from src.scanners.terraform_scanner import TerraformScanner +from src.scanners.python_scanner import PythonScanner +from src.scanners.secrets_scanner import SecretsScanner + + +class TestToolError: + """Tests for the ToolError dataclass""" + + def test_to_dict(self): + """Verify ToolError.to_dict() returns correct dictionary""" + error = ToolError(tool="trivy", error_message="command not found", scanner="TerraformScanner") + result = error.to_dict() + + assert result == { + "tool": "trivy", + "error_message": "command not found", + "scanner": "TerraformScanner", + } + + def test_fields(self): + """Verify ToolError fields are set correctly""" + error = ToolError(tool="tfsec", error_message="timeout", scanner="TerraformScanner") + assert error.tool == "tfsec" + assert error.error_message == "timeout" + assert error.scanner == "TerraformScanner" + + +class TestScannerToolErrorRecording: + """Tests that scanners record tool errors on execute_command failure""" + + def test_terraform_scanner_records_error_on_fmt_failure(self, tmp_path): + """Scanner records ToolError when terraform fmt returns -1""" + tf_file = tmp_path / "main.tf" + tf_file.write_text('resource "aws_s3_bucket" "test" {}') + + config = { + "tools": { + "terraform": { + "terraform_fmt": True, + "terraform_validate": False, + "tflint": False, + "tfsec": False, + "checkov": False, + "trivy": False, + } + } + } + scanner = TerraformScanner(config) + + with patch.object(scanner, "execute_command", return_value=(-1, "", "command not found")): + findings = scanner.run(str(tmp_path)) + + assert len(findings) == 0 + assert len(scanner.get_tool_errors()) == 1 + assert scanner.get_tool_errors()[0].tool == "terraform-fmt" + assert scanner.get_tool_errors()[0].error_message == "command not found" + assert scanner.get_tool_errors()[0].scanner == "TerraformScanner" + + def test_scanner_no_error_on_normal_nonzero_exit(self, tmp_path): + """Scanner does NOT record ToolError for normal non-zero exit codes""" + tf_file = tmp_path / "main.tf" + tf_file.write_text('resource "aws_s3_bucket" "test" {}') + + config = { + "tools": { + "terraform": { + "terraform_fmt": True, + "terraform_validate": False, + "tflint": False, + "tfsec": False, + "checkov": False, + "trivy": False, + } + } + } + scanner = TerraformScanner(config) + + # Return code 1 means files need formatting, not a tool failure + with patch.object(scanner, "execute_command", return_value=(1, "main.tf\n", "")): + findings = scanner.run(str(tmp_path)) + + assert len(scanner.get_tool_errors()) == 0 + assert len(findings) == 1 + + def test_tool_errors_reset_between_runs(self, tmp_path): + """Tool errors list is reset at the start of each run""" + tf_file = tmp_path / "main.tf" + tf_file.write_text('resource "aws_s3_bucket" "test" {}') + + config = { + "tools": { + "terraform": { + "terraform_fmt": True, + "terraform_validate": False, + "tflint": False, + "tfsec": False, + "checkov": False, + "trivy": False, + } + } + } + scanner = TerraformScanner(config) + + # First run: failure + with patch.object(scanner, "execute_command", return_value=(-1, "", "not found")): + scanner.run(str(tmp_path)) + assert len(scanner.get_tool_errors()) == 1 + + # Second run: success + with patch.object(scanner, "execute_command", return_value=(0, "", "")): + scanner.run(str(tmp_path)) + assert len(scanner.get_tool_errors()) == 0 + + def test_python_scanner_records_bandit_error(self, tmp_path): + """PythonScanner records ToolError when bandit fails""" + py_file = tmp_path / "app.py" + py_file.write_text("print('hello')") + + config = {"tools": {"python": {"bandit": True, "safety": False, "pylint": False}}} + scanner = PythonScanner(config) + + with patch.object(scanner, "execute_command", return_value=(-1, "", "Command timed out")): + findings = scanner.run(str(tmp_path)) + + assert len(findings) == 0 + assert len(scanner.get_tool_errors()) == 1 + assert scanner.get_tool_errors()[0].tool == "bandit" + + def test_secrets_scanner_records_gitleaks_error(self, tmp_path): + """SecretsScanner records ToolError when gitleaks fails""" + config = {"tools": {"secrets": {"gitleaks": True}}} + scanner = SecretsScanner(config) + + with patch.object(scanner, "execute_command", return_value=(-1, "", "gitleaks: not found")): + findings = scanner.run(str(tmp_path)) + + assert len(findings) == 0 + assert len(scanner.get_tool_errors()) == 1 + assert scanner.get_tool_errors()[0].tool == "gitleaks" + + +class TestReportAggregatorToolErrors: + """Tests for ReportAggregator tool error collection""" + + def test_aggregate_includes_tool_errors(self): + """ReportAggregator includes tool_errors in aggregate output""" + config = {} + aggregator = ReportAggregator(config) + + errors = [ + ToolError(tool="trivy", error_message="not found", scanner="TerraformScanner"), + ToolError(tool="tfsec", error_message="timeout", scanner="TerraformScanner"), + ] + aggregator.add_tool_errors(errors) + + report = aggregator.aggregate() + + assert "tool_errors" in report + assert len(report["tool_errors"]) == 2 + assert report["tool_errors"][0]["tool"] == "trivy" + assert report["metadata"]["tools_failed"] == 2 + + def test_aggregate_empty_tool_errors(self): + """ReportAggregator includes empty tool_errors when no failures""" + config = {} + aggregator = ReportAggregator(config) + report = aggregator.aggregate() + + assert "tool_errors" in report + assert len(report["tool_errors"]) == 0 + assert report["metadata"]["tools_failed"] == 0 + + +class TestMarkdownFormatterToolErrors: + """Tests for markdown formatter tool error rendering""" + + def test_renders_tool_errors_section(self): + """Markdown formatter renders tool errors table when present""" + formatter = MarkdownFormatter() + report_data = { + "repository": "test-repo", + "scan_timestamp": "2026-03-01T00:00:00", + "summary": {"total_findings": 0, "unique_files": 0, "by_severity": {}, "by_tool": {}}, + "findings": [], + "tool_errors": [ + {"tool": "trivy", "error_message": "command not found", "scanner": "TerraformScanner"}, + ], + "metadata": {"scanners_run": [], "tools_failed": 1}, + } + + md = formatter._generate_markdown(report_data) + + assert "Tool Failures" in md + assert "trivy" in md + assert "command not found" in md + assert "TerraformScanner" in md + + def test_omits_tool_errors_section_when_empty(self): + """Markdown formatter omits tool errors section when no errors""" + formatter = MarkdownFormatter() + report_data = { + "repository": "test-repo", + "scan_timestamp": "2026-03-01T00:00:00", + "summary": {"total_findings": 0, "unique_files": 0, "by_severity": {}, "by_tool": {}}, + "findings": [], + "tool_errors": [], + "metadata": {"scanners_run": [], "tools_failed": 0}, + } + + md = formatter._generate_markdown(report_data) + + assert "Tool Failures" not in md From 842c9f27e19c57c8a4da4fe05737deeab4fd79e9 Mon Sep 17 00:00:00 2001 From: william brady Date: Sun, 8 Mar 2026 15:36:45 -0400 Subject: [PATCH 2/6] fix: update Trivy to v0.69.3 and apply branding alignment - Update Trivy from v0.48.3 to v0.69.3 (old release deleted from GitHub) - Align all branding references to portfolio-code-scanner - Remove unused pytest import to fix flake8 F401 --- .env.example | 2 +- .github/workflows/ci.yml | 8 ++-- .github/workflows/example-usage.yml | 16 ++++---- .gitignore | 2 +- COMMIT_MESSAGE.md | 2 +- Dockerfile | 6 +-- LICENSE | 6 +-- README.md | 58 ++++++++++++++-------------- action.yml | 4 +- build.md | 4 +- config/config.yaml | 6 +-- entrypoint.sh | 16 ++++---- scripts/run-local-scan.sh | 6 +-- src/__init__.py | 4 +- src/formatters/html_formatter.py | 4 +- src/formatters/markdown_formatter.py | 2 +- src/formatters/sarif_formatter.py | 4 +- src/main.py | 6 +-- tests/__init__.py | 2 +- tests/test_basic.py | 3 +- 20 files changed, 80 insertions(+), 81 deletions(-) diff --git a/.env.example b/.env.example index 5c71d09..3e96d4a 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -# SDLC Code Scanner - Environment Variables Template +# Portfolio Code Scanner - Environment Variables Template # Copy this file to .env and fill in your values # ======================================== diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04bbca0..298b3a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# CI workflow for testing the SDLC Code Scanner action itself +# CI workflow for testing the Portfolio Code Scanner action itself name: CI on: @@ -73,7 +73,7 @@ jobs: with: context: . push: false - tags: sdlc-code-scanner:test + tags: portfolio-code-scanner:test cache-from: type=gha cache-to: type=gha,mode=max @@ -85,7 +85,7 @@ jobs: - uses: actions/checkout@v4 - name: Build Docker image - run: docker build -t sdlc-code-scanner:test . + run: docker build -t portfolio-code-scanner:test . - name: Create output directory run: mkdir -p test-reports && chmod 777 test-reports @@ -95,7 +95,7 @@ jobs: docker run --rm \ -v ${{ github.workspace }}/tests/fixtures:/repo:ro \ -v ${{ github.workspace }}/test-reports:/app/reports \ - sdlc-code-scanner:test \ + portfolio-code-scanner:test \ scan-local --repo-path /repo --output-dir /app/reports --format json || true - name: Verify report generated diff --git a/.github/workflows/example-usage.yml b/.github/workflows/example-usage.yml index dc55534..5a0006e 100644 --- a/.github/workflows/example-usage.yml +++ b/.github/workflows/example-usage.yml @@ -1,8 +1,8 @@ -# Example workflow showing how to use SDLC Code Scanner in your repository +# Example workflow showing how to use Portfolio Code Scanner in your repository # Copy this file to your repository's .github/workflows/ directory # # NOTE: This workflow is disabled by default in this repository since -# it references the published action (crofton-cloud/sdlc-code-scanner@v1). +# it references the published action (williambrady/portfolio-code-scanner@v1). # The actual CI tests are in ci.yml which uses the local action. name: Security Scan @@ -23,16 +23,16 @@ permissions: jobs: security-scan: - name: SDLC Code Scanner Security Scan + name: Portfolio Code Scanner Security Scan runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Run SDLC Code Scanner + - name: Run Portfolio Code Scanner id: security-scan - uses: crofton-cloud/sdlc-code-scanner@v1 + uses: williambrady/portfolio-code-scanner@v1 with: # Scan the entire repository (default) scan-path: '.' @@ -54,14 +54,14 @@ jobs: if: always() && steps.security-scan.outputs.sarif-path != '' with: sarif_file: ${{ steps.security-scan.outputs.sarif-path }} - category: 'sdlc-code-scanner' + category: 'portfolio-code-scanner' - name: Upload scan reports as artifact uses: actions/upload-artifact@v4 if: always() with: name: security-scan-reports - path: .sdlc-code-scanner-reports/ + path: .portfolio-code-scanner-reports/ retention-days: 30 - name: Post scan summary comment on PR @@ -78,7 +78,7 @@ jobs: const statusEmoji = status === 'passed' ? ':white_check_mark:' : ':x:'; - const body = `## SDLC Code Scanner Security Scan ${statusEmoji} + const body = `## Portfolio Code Scanner Security Scan ${statusEmoji} | Severity | Count | |----------|-------| diff --git a/.gitignore b/.gitignore index f00196f..6209158 100644 --- a/.gitignore +++ b/.gitignore @@ -207,7 +207,7 @@ marimo/_lsp/ __marimo__/ # ======================================== -# SDLC Code Scanner - Scan Artifacts +# Portfolio Code Scanner - Scan Artifacts # ======================================== # Scan reports directory reports/ diff --git a/COMMIT_MESSAGE.md b/COMMIT_MESSAGE.md index c48a856..7bfaf08 100644 --- a/COMMIT_MESSAGE.md +++ b/COMMIT_MESSAGE.md @@ -87,7 +87,7 @@ - Proper resource cleanup (temp files) ### Testing -- Self-scan of sdlc-code-scanner codebase: **0 findings** (100% clean) +- Self-scan of portfolio-code-scanner codebase: **0 findings** (100% clean) - All 10 initial findings remediated or properly excluded - Test directories excluded from production scans diff --git a/Dockerfile b/Dockerfile index d746afb..9783061 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -# SDLC Code Scanner - Multi-stage Dockerfile +# Portfolio Code Scanner - Multi-stage Dockerfile # Installs all IaC security scanning tools in a single container # Compatible with GitHub Actions FROM python:3.11-slim as base # GitHub Actions labels -LABEL org.opencontainers.image.source="https://github.com/crofton-cloud/sdlc-code-scanner" +LABEL org.opencontainers.image.source="https://github.com/williambrady/portfolio-code-scanner" LABEL org.opencontainers.image.description="Security scanner for AWS Infrastructure-as-Code" LABEL org.opencontainers.image.licenses="PolyForm-Noncommercial-1.0.0" @@ -68,7 +68,7 @@ RUN wget -q https://github.com/aquasecurity/tfsec/releases/download/v${TFSEC_VER # ======================================== # Install Trivy # ======================================== -ARG TRIVY_VERSION=0.48.3 +ARG TRIVY_VERSION=0.69.3 RUN wget -q https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz && \ tar zxvf trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz && \ mv trivy /usr/local/bin/ && \ diff --git a/LICENSE b/LICENSE index 1500044..b2406e5 100644 --- a/LICENSE +++ b/LICENSE @@ -34,7 +34,7 @@ URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example: -> Required Notice: Copyright Crofton Cloud (https://crofton.cloud) +> Required Notice: Copyright William Brady (https://github.com/williambrady) ## Changes and New Works License @@ -131,6 +131,6 @@ of your licenses. --- -Required Notice: Copyright (c) 2026 Crofton Cloud (https://crofton.cloud) +Required Notice: Copyright (c) 2026 William Brady (https://github.com/williambrady) -For licensing inquiries, contact: licensing@crofton.cloud +For licensing inquiries, visit: https://github.com/williambrady diff --git a/README.md b/README.md index c7d53c1..4cd8674 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# SDLC Code Scanner +# Portfolio Code Scanner > Comprehensive security assessment tool for AWS Infrastructure-as-Code -[![GitHub Action](https://img.shields.io/badge/GitHub%20Action-available-2088FF?logo=github-actions&logoColor=white)](https://github.com/marketplace/actions/sdlc-code-scanner) +[![GitHub Action](https://img.shields.io/badge/GitHub%20Action-available-2088FF?logo=github-actions&logoColor=white)](https://github.com/marketplace/actions/portfolio-code-scanner) [![License](https://img.shields.io/badge/License-PolyForm%20Noncommercial-blue.svg)](LICENSE) -SDLC Code Scanner is a Docker-based security scanning platform that orchestrates multiple industry-leading security tools to provide comprehensive analysis of your AWS infrastructure code. It implements a multi-layered security scanning approach covering linting, security policies, dependency vulnerabilities, and secrets detection. +Portfolio Code Scanner is a Docker-based security scanning platform that orchestrates multiple industry-leading security tools to provide comprehensive analysis of your AWS infrastructure code. It implements a multi-layered security scanning approach covering linting, security policies, dependency vulnerabilities, and secrets detection. **Available as a GitHub Action for seamless CI/CD integration!** @@ -58,7 +58,7 @@ SDLC Code Scanner is a Docker-based security scanning platform that orchestrates ### GitHub Action (Recommended) -The easiest way to use SDLC Code Scanner is as a GitHub Action: +The easiest way to use Portfolio Code Scanner is as a GitHub Action: ```yaml name: Security Scan @@ -71,8 +71,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Run SDLC Code Scanner - uses: crofton-cloud/sdlc-code-scanner@v1 + - name: Run Portfolio Code Scanner + uses: williambrady/portfolio-code-scanner@v1 with: scan-path: '.' fail-on-severity: 'HIGH' @@ -87,13 +87,13 @@ See [GitHub Action Usage](#github-action) for full documentation. ### Build the Docker Image ```bash -docker build -t sdlc-code-scanner:latest . +docker build -t portfolio-code-scanner:latest . ``` Or use the pre-built image (if available): ```bash -docker pull croftoncloud/sdlc-code-scanner:latest +docker pull williambrady/portfolio-code-scanner:latest ``` ### Basic Usage @@ -104,7 +104,7 @@ Scan a local repository: docker run --rm \ -v /path/to/your/repo:/repo:ro \ -v $(pwd)/reports:/app/reports \ - sdlc-code-scanner:latest \ + portfolio-code-scanner:latest \ scan-local --repo-path /repo ``` @@ -124,7 +124,7 @@ Scan your IaC code for security issues: docker run --rm \ -v /path/to/repo:/repo:ro \ -v $(pwd)/reports:/app/reports \ - sdlc-code-scanner:latest \ + portfolio-code-scanner:latest \ scan-local \ --repo-path /repo \ --output-dir /app/reports \ @@ -141,7 +141,7 @@ docker run --rm \ ### List Available Tools ```bash -docker run --rm sdlc-code-scanner:latest list-tools +docker run --rm portfolio-code-scanner:latest list-tools ``` ### Validate Configuration (Planned) @@ -149,7 +149,7 @@ docker run --rm sdlc-code-scanner:latest list-tools ```bash docker run --rm \ -v $(pwd)/config:/app/config:ro \ - sdlc-code-scanner:latest \ + portfolio-code-scanner:latest \ validate-config ``` @@ -321,7 +321,7 @@ Documentation-ready format with: ## GitHub Action -SDLC Code Scanner is available as a GitHub Action for seamless CI/CD integration with GitHub Code Scanning support. +Portfolio Code Scanner is available as a GitHub Action for seamless CI/CD integration with GitHub Code Scanning support. ### Basic Usage @@ -340,8 +340,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Run SDLC Code Scanner - uses: crofton-cloud/sdlc-code-scanner@v1 + - name: Run Portfolio Code Scanner + uses: williambrady/portfolio-code-scanner@v1 with: scan-path: '.' fail-on-severity: 'HIGH' @@ -394,9 +394,9 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Run SDLC Code Scanner + - name: Run Portfolio Code Scanner id: scan - uses: crofton-cloud/sdlc-code-scanner@v1 + uses: williambrady/portfolio-code-scanner@v1 with: scan-path: '.' output-formats: 'json,html,sarif' @@ -414,7 +414,7 @@ jobs: if: always() with: name: security-reports - path: .sdlc-code-scanner-reports/ + path: .portfolio-code-scanner-reports/ retention-days: 30 - name: Check scan results @@ -428,7 +428,7 @@ jobs: ```yaml - name: Scan only infrastructure directory - uses: crofton-cloud/sdlc-code-scanner@v1 + uses: williambrady/portfolio-code-scanner@v1 with: scan-path: 'infrastructure/terraform' fail-on-severity: 'MEDIUM' @@ -438,16 +438,16 @@ jobs: ```yaml - name: Scan with custom config - uses: crofton-cloud/sdlc-code-scanner@v1 + uses: williambrady/portfolio-code-scanner@v1 with: - config-path: '.github/sdlc-code-scanner-config.yaml' + config-path: '.github/portfolio-code-scanner-config.yaml' ``` ### Don't Fail on Findings ```yaml - name: Scan without failing - uses: crofton-cloud/sdlc-code-scanner@v1 + uses: williambrady/portfolio-code-scanner@v1 with: fail-on-severity: 'NONE' ``` @@ -458,7 +458,7 @@ Build and scan Docker images from Dockerfiles found in the repository: ```yaml - name: Scan with container image scanning - uses: crofton-cloud/sdlc-code-scanner@v1 + uses: williambrady/portfolio-code-scanner@v1 with: build-container-images: 'true' fail-on-severity: 'HIGH' @@ -479,7 +479,7 @@ security-scan: - docker run --rm -v $CI_PROJECT_DIR:/repo:ro -v $CI_PROJECT_DIR/reports:/app/reports - sdlc-code-scanner:latest + portfolio-code-scanner:latest scan-local --repo-path /repo --format json artifacts: paths: @@ -494,8 +494,8 @@ security-scan: repos: - repo: local hooks: - - id: sdlc-code-scanner - name: SDLC Code Scanner + - id: portfolio-code-scanner + name: Portfolio Code Scanner entry: ./scripts/run-local-scan.sh language: script pass_filenames: false @@ -543,7 +543,7 @@ repos: ``` ┌─────────────────────────────────────────────────────────────┐ -│ SDLC Code Scanner │ +│ Portfolio Code Scanner │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌─────────────────────────┐ │ @@ -588,7 +588,7 @@ repos: ## File Structure ``` -sdlc-code-scanner/ +portfolio-code-scanner/ ├── .github/ │ └── workflows/ │ ├── ci.yml # CI workflow for this repo @@ -715,7 +715,7 @@ This project is licensed under the terms specified in [LICENSE](LICENSE). ## Support -- **Issues**: Report bugs or request features via [GitHub Issues](https://github.com/croftoncloud/sdlc-code-scanner/issues) +- **Issues**: Report bugs or request features via [GitHub Issues](https://github.com/williambrady/portfolio-code-scanner/issues) - **Documentation**: See [docs/](docs/) for detailed guides - **CLAUDE.md**: For development with Claude Code diff --git a/action.yml b/action.yml index d9cb342..b8898e9 100644 --- a/action.yml +++ b/action.yml @@ -1,6 +1,6 @@ -name: 'SDLC Code Scanner' +name: 'Portfolio Code Scanner' description: 'Security scanner for AWS Infrastructure-as-Code (Terraform, CloudFormation, CDK) with multi-tool analysis' -author: 'Crofton Cloud' +author: 'William Brady' branding: icon: 'shield' diff --git a/build.md b/build.md index 4e05870..fb40a41 100644 --- a/build.md +++ b/build.md @@ -1,6 +1,6 @@ -# SDLC Code Scanner - Build Steps +# Portfolio Code Scanner - Build Steps -This document enumerates all steps required to build the SDLC Code Scanner system as defined in `plan.md`. +This document enumerates all steps required to build the Portfolio Code Scanner system as defined in `plan.md`. --- diff --git a/config/config.yaml b/config/config.yaml index b41c01f..56e1973 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,10 +1,10 @@ -# SDLC Code Scanner Configuration +# Portfolio Code Scanner Configuration # Docker Configuration docker: base_image: "python:3.11-slim" dockerfile_path: "./Dockerfile" - container_name: "sdlc-code-scanner" + container_name: "portfolio-code-scanner" working_dir: "/app" # Repository Scanning Configuration @@ -184,7 +184,7 @@ execution: # Logging Configuration logging: level: "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL - file: "/app/logs/sdlc-code-scanner.log" + file: "/app/logs/portfolio-code-scanner.log" console: true format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" diff --git a/entrypoint.sh b/entrypoint.sh index f35df7a..c56c1be 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,5 +1,5 @@ #!/bin/bash -# GitHub Action entrypoint script for SDLC Code Scanner +# GitHub Action entrypoint script for Portfolio Code Scanner set -e # ============================================================================= @@ -18,13 +18,13 @@ VERBOSE="${INPUT_VERBOSE:-false}" # GitHub-specific paths GITHUB_WORKSPACE="${GITHUB_WORKSPACE:-/github/workspace}" -REPORT_DIR="${GITHUB_WORKSPACE}/.sdlc-code-scanner-reports" +REPORT_DIR="${GITHUB_WORKSPACE}/.portfolio-code-scanner-reports" # Create report directory with permission handling # GitHub Actions workspace may have restrictive permissions create_fallback_report_dir() { echo "::warning::Using /tmp for reports due to workspace permission issues" - REPORT_DIR="/tmp/.sdlc-code-scanner-reports" + REPORT_DIR="/tmp/.portfolio-code-scanner-reports" mkdir -p "$REPORT_DIR" # Copy reports to workspace at the end if possible FALLBACK_REPORT_DIR="true" @@ -78,7 +78,7 @@ add_step_summary() { # ============================================================================= echo "==============================================" -echo "SDLC Code Scanner - GitHub Action" +echo "Portfolio Code Scanner - GitHub Action" echo "==============================================" echo "" @@ -229,7 +229,7 @@ log_end_group log_info "Generating Summary" -add_step_summary "# SDLC Code Scanner Security Scan Results" +add_step_summary "# Portfolio Code Scanner Security Scan Results" add_step_summary "" add_step_summary "## Summary" add_step_summary "" @@ -365,7 +365,7 @@ log_end_group add_step_summary "" add_step_summary "---" -add_step_summary "*Generated by [SDLC Code Scanner](https://github.com/crofton-cloud/sdlc-code-scanner)*" +add_step_summary "*Generated by [Portfolio Code Scanner](https://github.com/williambrady/portfolio-code-scanner)*" log_end_group @@ -390,7 +390,7 @@ fi # If we used fallback report directory, try to copy reports to workspace if [[ "${FALLBACK_REPORT_DIR:-}" == "true" ]]; then - WORKSPACE_REPORT_DIR="${GITHUB_WORKSPACE}/.sdlc-code-scanner-reports" + WORKSPACE_REPORT_DIR="${GITHUB_WORKSPACE}/.portfolio-code-scanner-reports" if mkdir -p "$WORKSPACE_REPORT_DIR" 2>/dev/null; then # Attempt to copy reports and verify success if cp -r "$REPORT_DIR"/* "$WORKSPACE_REPORT_DIR"/ 2>/dev/null; then @@ -399,7 +399,7 @@ if [[ "${FALLBACK_REPORT_DIR:-}" == "true" ]]; then if [[ "$COPIED_FILES" -gt 0 ]]; then echo "Reports copied to workspace: $WORKSPACE_REPORT_DIR ($COPIED_FILES files)" # Update output to point to workspace location (relative path) - set_output "report-path" ".sdlc-code-scanner-reports" + set_output "report-path" ".portfolio-code-scanner-reports" SARIF_IN_WORKSPACE=$(find "$WORKSPACE_REPORT_DIR" -name "*.sarif" -type f | head -1) if [[ -f "$SARIF_IN_WORKSPACE" ]]; then RELATIVE_SARIF="${SARIF_IN_WORKSPACE#$GITHUB_WORKSPACE/}" diff --git a/scripts/run-local-scan.sh b/scripts/run-local-scan.sh index f0695e0..379bd0f 100644 --- a/scripts/run-local-scan.sh +++ b/scripts/run-local-scan.sh @@ -1,5 +1,5 @@ #!/bin/bash -# SDLC Code Scanner - Local Repository Scan +# Portfolio Code Scanner - Local Repository Scan # Usage: ./scripts/run-local-scan.sh /path/to/repo set -e @@ -8,7 +8,7 @@ REPO_PATH="${1:-.}" OUTPUT_DIR="${2:-./reports}" CONFIG_FILE="${3:-./config/config.yaml}" -echo "SDLC Code Scanner - Local Repository Scan" +echo "Portfolio Code Scanner - Local Repository Scan" echo "========================================" echo "Repository: $REPO_PATH" echo "Output: $OUTPUT_DIR" @@ -39,7 +39,7 @@ docker run --rm \ -v "$(realpath "$REPO_PATH"):/repo:ro" \ -v "$(realpath "$OUTPUT_DIR"):/app/reports" \ -v "$(realpath "$CONFIG_FILE"):/app/config/config.yaml:ro" \ - sdlc-code-scanner:latest \ + portfolio-code-scanner:latest \ scan-local \ --repo-path /repo \ --output-dir /app/reports \ diff --git a/src/__init__.py b/src/__init__.py index 30018de..1c5446e 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,4 +1,4 @@ -"""SDLC Code Scanner - Security scanning tool for AWS IaC""" +"""Portfolio Code Scanner - Security scanning tool for AWS IaC""" __version__ = "0.1.0" -__author__ = "Crofton Cloud" +__author__ = "William Brady" diff --git a/src/formatters/html_formatter.py b/src/formatters/html_formatter.py index 9c46b77..dc1fe3c 100644 --- a/src/formatters/html_formatter.py +++ b/src/formatters/html_formatter.py @@ -39,7 +39,7 @@ def _generate_html(self, report_data: Dict[str, Any]) -> str: - SDLC Code Scanner - Scan Report + Portfolio Code Scanner - Scan Report