"""The severity a SARIF finding is reported under. In order of preference: 1. The finding's own ``security-severity`` score, banded like CVSS. Where a tool scored the finding itself -- Lynkeus does -- that is the most specific reading there is. 2. Its weakness class, from :data:`CWE_SEVERITY`. The tools do not agree on what their levels mean -- Trivy's ``error`` for a container running as root and Bandit's ``note`` for a hard-coded database password say more about the tool than about the risk -- so the CWE is preferred to them. 3. Its SARIF level, from :data:`SEVERITY_OF_LEVEL`, for a finding with neither. Each entry of the table is the severity a confirmed instance of that weakness typically carries, before any context about the application is known: roughly where the CVEs filed under it land on the CVSS bands. Critical is kept for classes that usually hand an attacker code execution or the whole data store; weaknesses that only matter together with another flaw, or that harden rather than defend, sit at Medium or Low. """ from __future__ import annotations import re CWE_SEVERITY = { # Injection that typically ends in code execution or full database access. "CWE-77": "Critical", # Command injection "CWE-78": "Critical", # OS command injection "CWE-89": "Critical", # SQL injection "CWE-94": "Critical", # Code injection "CWE-95": "Critical", # Eval injection "CWE-502": "Critical", # Deserialization of untrusted data # Direct compromise of confidentiality or access control, usually needing some foothold. "CWE-22": "High", # Path traversal "CWE-96": "High", # Static code injection "CWE-259": "High", # Hard-coded password "CWE-287": "High", # Improper authentication "CWE-306": "High", # Missing authentication for a critical function "CWE-321": "High", # Hard-coded cryptographic key "CWE-434": "High", # Unrestricted upload of dangerous file type "CWE-611": "High", # XML external entities "CWE-798": "High", # Hard-coded credentials "CWE-862": "High", # Missing authorization "CWE-863": "High", # Incorrect authorization "CWE-918": "High", # Server-side request forgery # Real weaknesses whose impact depends on context, or that need another flaw to be exploited. "CWE-15": "Medium", # External control of a configuration setting "CWE-20": "Medium", # Improper input validation "CWE-79": "Medium", # Cross-site scripting "CWE-200": "Medium", # Exposure of sensitive information "CWE-250": "Medium", # Execution with unnecessary privileges "CWE-269": "Medium", # Improper privilege management "CWE-295": "Medium", # Improper certificate validation "CWE-312": "Medium", # Cleartext storage of sensitive information "CWE-319": "Medium", # Cleartext transmission of sensitive information "CWE-326": "Medium", # Inadequate encryption strength "CWE-327": "Medium", # Broken or risky cryptographic algorithm "CWE-328": "Medium", # Weak hash "CWE-330": "Medium", # Insufficiently random values "CWE-338": "Medium", # Cryptographically weak PRNG "CWE-352": "Medium", # Cross-site request forgery "CWE-400": "Medium", # Uncontrolled resource consumption "CWE-522": "Medium", # Insufficiently protected credentials "CWE-532": "Medium", # Sensitive information in a log file "CWE-601": "Medium", # Open redirect "CWE-732": "Medium", # Incorrect permission assignment "CWE-770": "Medium", # Allocation of resources without limits "CWE-1333": "Medium", # Inefficient regular expression (ReDoS) # Hygiene: worth fixing, rarely exploitable on its own. "CWE-117": "Low", # Log injection "CWE-209": "Low", # Information in an error message "CWE-377": "Low", # Insecure temporary file "CWE-390": "Low", # Error detected without action "CWE-391": "Low", # Unchecked error condition "CWE-614": "Low", # Sensitive cookie without the Secure flag "CWE-703": "Low", # Improper handling of exceptional conditions "CWE-754": "Low", # Improper check for unusual conditions "CWE-755": "Low", # Improper handling of exceptional conditions "CWE-1004": "Low", # Sensitive cookie without the HttpOnly flag } # For a finding whose CWE is not in the table, or that has none. One band below the old one-to-one # reading of the level, because a tool's "error" is not a statement that the finding is critical. SEVERITY_OF_LEVEL = { "error": "High", "warning": "Medium", "note": "Low", "none": "Low", "info": "Low", } # The CVSS v3 qualitative bands, by their lower bounds. A score of 0 has no band of its own in the # report and reads as Low. SCORE_BANDS = ((9.0, "Critical"), (7.0, "High"), (4.0, "Medium")) _CWE = re.compile(r"CWE-(\d+)", re.IGNORECASE) def finding_severity(rule_id: str, level: str, security_severity: str = "") -> str: """The severity a SARIF finding is reported under. Args: rule_id: The finding's rule. The pipeline's tools name their rules by CWE. level: The SARIF level. security_severity: The finding's ``security-severity`` score, empty where it has none. Returns: One of Critical, High, Medium or Low. """ scored = _score_band(security_severity) if scored is not None: return scored found = _CWE.search(rule_id) known = CWE_SEVERITY.get(f"CWE-{found.group(1)}") if found else None return known or SEVERITY_OF_LEVEL.get(level, "Low") def _score_band(stated: str) -> str | None: """The band a ``security-severity`` score falls in, or nothing where it is not a score.""" try: score = float(stated) except ValueError: return None if not 0 <= score <= 10: return None return next((name for bound, name in SCORE_BANDS if score >= bound), "Low")