Skip to content

Add security-severity to metadata in NodeJS rules

Craig Smith requested to merge craigmsmith-fix-nodejs-severity into main

What does this MR do?

The NodeJS scanner handles severity differently from our other scanners, specifically semgrep where the rules are moving to.

NodeJS Scan maps severity as:

func severityToLevel(severity string) report.SeverityLevel {
	switch severity {
	case "INFO":
		return report.SeverityLevelInfo
	case "WARNING":
		return report.SeverityLevelMedium
	case "ERROR":
		return report.SeverityLevelHigh
	}
	return report.SeverityLevelUnknown
}

https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan/-/blob/3ab6dcc0353d5cf59f0931f85f86275f33b5b8b5/convert.go#L125

This means a rule with the severity ERROR will be displayed in the vulnerability report as severity High.

This differs from semgrep that maps the severities as:

switch r.DefaultConfiguration.Level {
	case "error":
		return SeverityLevelCritical
	case "warning":
		return SeverityLevelMedium
	case "note":
		return SeverityLevelInfo
	case "none":
		return SeverityLevelInfo
	default:
		return SeverityLevelMedium
	}

https://gitlab.com/gitlab-org/security-products/analyzers/report/-/blob/bcd52452c93ddc4c2e980a7e9a8db2096f499ad5/sarif.go#L240

This difference means that a rule imported from NodeJS Scan to semgrep with the severity of ERROR will show as a severity Critical, rather than High, as it was when the same rule ran in NodeJS Scan.

This MR addresses this by adding the security-severity field in metadata, as described in Include security severity levels in semgrep rules (gitlab-org/gitlab#398574 - closed) • Unassigned • 17.0.

Adding the metadata.security-severity field overrides the severity attribute and returns that in the semgrep.sarif file generated by semgrep. This is then correctly converted in the gl-sast-report.json.

I've tested this by updating the NodeJS Scan ruleset with metadata.security-severity and releasing in craigmsmith-fix-nodejs-severity-bfe89ae6bc9a4a8ea96091f97ea0287702e588b2. When installing this in semgrep and running on the nodeJS scan default fixture the resulting gl-sast-report.json showed the severity as HIGH

Generated gl-sast-report.json
{
    "version": "15.0.7",
    "vulnerabilities": [
        {
            "id": "937ac7a208f995e16c103004da8aed75673cacd222dae6a9f04d6347e00b00e9",
            "category": "sast",
            "name": "Semgrep Finding: javascript-redirect-rule-express_open_redirect",
            "description": "Untrusted user input in redirect() can result in Open Redirect vulnerability.\n",
            "cve": "semgrep_id:javascript-redirect-rule-express_open_redirect:46:46",
            "severity": "High",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "subdir/index.js",
                "start_line": 46
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-redirect-rule-express_open_redirect",
                    "value": "javascript-redirect-rule-express_open_redirect"
                },
                {
                    "type": "cwe",
                    "name": "CWE-601",
                    "value": "601",
                    "url": "https://cwe.mitre.org/data/definitions/601.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "42077986ee522d5e936137b0b262efc5c2cc9c19a5d2aa8b0405304e1d9449bd",
            "category": "sast",
            "name": "Semgrep Finding: javascript-redirect-rule-express_open_redirect",
            "description": "Untrusted user input in redirect() can result in Open Redirect vulnerability.\n",
            "cve": "semgrep_id:javascript-redirect-rule-express_open_redirect:51:51",
            "severity": "High",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "index.js",
                "start_line": 51
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-redirect-rule-express_open_redirect",
                    "value": "javascript-redirect-rule-express_open_redirect"
                },
                {
                    "type": "cwe",
                    "name": "CWE-601",
                    "value": "601",
                    "url": "https://cwe.mitre.org/data/definitions/601.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "8347660f5b977807af8e3fa1a1d2ab92e08a5ebf721c7f57c6c8620e231c9f1f",
            "category": "sast",
            "name": "Semgrep Finding: javascript-xss-rule-express_xss",
            "description": "Untrusted User Input in Response will result in Reflected Cross Site Scripting Vulnerability.\n",
            "cve": "semgrep_id:javascript-xss-rule-express_xss:28:28",
            "severity": "High",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "index.js",
                "start_line": 28
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-xss-rule-express_xss",
                    "value": "javascript-xss-rule-express_xss"
                },
                {
                    "type": "cwe",
                    "name": "CWE-79",
                    "value": "79",
                    "url": "https://cwe.mitre.org/data/definitions/79.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "a196c01a47d7f4cce00f9123943139c7138f86dd8b69d126bf89cc60e47879d6",
            "category": "sast",
            "name": "Semgrep Finding: javascript-xss-rule-express_xss",
            "description": "Untrusted User Input in Response will result in Reflected Cross Site Scripting Vulnerability.\n",
            "cve": "semgrep_id:javascript-xss-rule-express_xss:28:28",
            "severity": "High",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "subdir/index.js",
                "start_line": 28
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-xss-rule-express_xss",
                    "value": "javascript-xss-rule-express_xss"
                },
                {
                    "type": "cwe",
                    "name": "CWE-79",
                    "value": "79",
                    "url": "https://cwe.mitre.org/data/definitions/79.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "44093d9680057daaea3eaf46582dd271a70f88f67689454a0504b06a79d9fb81",
            "category": "sast",
            "name": "Semgrep Finding: javascript-xss-rule-express_xss",
            "description": "Untrusted User Input in Response will result in Reflected Cross Site Scripting Vulnerability.\n",
            "cve": "semgrep_id:javascript-xss-rule-express_xss:32:32",
            "severity": "High",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "subdir/index.js",
                "start_line": 32
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-xss-rule-express_xss",
                    "value": "javascript-xss-rule-express_xss"
                },
                {
                    "type": "cwe",
                    "name": "CWE-79",
                    "value": "79",
                    "url": "https://cwe.mitre.org/data/definitions/79.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "aa3489ae9a0df69486eb46412d75851ce9e21252dbbcdb1c29b5ea6d3e31de38",
            "category": "sast",
            "name": "Semgrep Finding: javascript-xss-rule-express_xss",
            "description": "Untrusted User Input in Response will result in Reflected Cross Site Scripting Vulnerability.\n",
            "cve": "semgrep_id:javascript-xss-rule-express_xss:32:32",
            "severity": "High",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "index.js",
                "start_line": 32
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-xss-rule-express_xss",
                    "value": "javascript-xss-rule-express_xss"
                },
                {
                    "type": "cwe",
                    "name": "CWE-79",
                    "value": "79",
                    "url": "https://cwe.mitre.org/data/definitions/79.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "7a0a8d47c41cc4897025914fc08f3652eb256fe4c7e2dd6b0628f292f7ce78f4",
            "category": "sast",
            "name": "Semgrep Finding: javascript-crypto-rule-node_insecure_random_generator",
            "description": "crypto.pseudoRandomBytes()/Math.random() is a cryptographically weak random number generator.\n",
            "cve": "semgrep_id:javascript-crypto-rule-node_insecure_random_generator:42:42",
            "severity": "Medium",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "index.js",
                "start_line": 42
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-crypto-rule-node_insecure_random_generator",
                    "value": "javascript-crypto-rule-node_insecure_random_generator"
                },
                {
                    "type": "cwe",
                    "name": "CWE-327",
                    "value": "327",
                    "url": "https://cwe.mitre.org/data/definitions/327.html"
                },
                {
                    "type": "owasp",
                    "name": "A9:2017 - Using Components with Known Vulnerabilities",
                    "value": "A9:2017"
                }
            ]
        }
    ],
    "dependency_files": [],
    "scan": {
        "analyzer": {
            "id": "semgrep",
            "name": "Semgrep",
            "url": "https://gitlab.com/gitlab-org/security-products/analyzers/semgrep",
            "vendor": {
                "name": "GitLab"
            },
            "version": "4.11.0"
        },
        "scanner": {
            "id": "semgrep",
            "name": "Semgrep",
            "url": "https://github.com/returntocorp/semgrep",
            "vendor": {
                "name": "GitLab"
            },
            "version": "1.56.0"
        },
        "type": "sast",
        "start_time": "2024-02-05T03:26:34",
        "end_time": "2024-02-05T03:26:38",
        "status": "success"
    }
}

When the same test runs but with only a ruleset that includes only original severity field, the report shows the severity as CRITICAL.

Generated gl-sast-report.json
{
    "version": "15.0.7",
    "vulnerabilities": [
        {
            "id": "1b438ced3d7a3e165767f49f8de9c8235b6b686299879ae880d7c8c52af7fc92",
            "category": "sast",
            "name": "Semgrep Finding: javascript-redirect-rule-express_open_redirect",
            "description": "Untrusted user input in redirect() can result in Open Redirect vulnerability.\n",
            "cve": "semgrep_id:javascript-redirect-rule-express_open_redirect:46:46",
            "severity": "Critical",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "subdir/index.js",
                "start_line": 46
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-redirect-rule-express_open_redirect",
                    "value": "javascript-redirect-rule-express_open_redirect"
                },
                {
                    "type": "cwe",
                    "name": "CWE-601",
                    "value": "601",
                    "url": "https://cwe.mitre.org/data/definitions/601.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "296ed9ef674fdcf56fe6992422bcb70063595dc299c08e2ae83fe2f817984310",
            "category": "sast",
            "name": "Semgrep Finding: javascript-redirect-rule-express_open_redirect",
            "description": "Untrusted user input in redirect() can result in Open Redirect vulnerability.\n",
            "cve": "semgrep_id:javascript-redirect-rule-express_open_redirect:51:51",
            "severity": "Critical",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "index.js",
                "start_line": 51
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-redirect-rule-express_open_redirect",
                    "value": "javascript-redirect-rule-express_open_redirect"
                },
                {
                    "type": "cwe",
                    "name": "CWE-601",
                    "value": "601",
                    "url": "https://cwe.mitre.org/data/definitions/601.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "53de400182e61bdddb81e6f2cb4658fcefb4989ba5391d95056fc98b1ae3e4e2",
            "category": "sast",
            "name": "Semgrep Finding: javascript-xss-rule-express_xss",
            "description": "Untrusted User Input in Response will result in Reflected Cross Site Scripting Vulnerability.\n",
            "cve": "semgrep_id:javascript-xss-rule-express_xss:28:28",
            "severity": "Critical",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "index.js",
                "start_line": 28
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-xss-rule-express_xss",
                    "value": "javascript-xss-rule-express_xss"
                },
                {
                    "type": "cwe",
                    "name": "CWE-79",
                    "value": "79",
                    "url": "https://cwe.mitre.org/data/definitions/79.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "2df15ebf1f829b7bfab1d9370a516588f835f1afe86a48b7fcdeaf1c9f18fbbe",
            "category": "sast",
            "name": "Semgrep Finding: javascript-xss-rule-express_xss",
            "description": "Untrusted User Input in Response will result in Reflected Cross Site Scripting Vulnerability.\n",
            "cve": "semgrep_id:javascript-xss-rule-express_xss:28:28",
            "severity": "Critical",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "subdir/index.js",
                "start_line": 28
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-xss-rule-express_xss",
                    "value": "javascript-xss-rule-express_xss"
                },
                {
                    "type": "cwe",
                    "name": "CWE-79",
                    "value": "79",
                    "url": "https://cwe.mitre.org/data/definitions/79.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "ff0a7be9241facf81e4673a008513d5530d7ad92dbdea7dc986862260e8eb63e",
            "category": "sast",
            "name": "Semgrep Finding: javascript-xss-rule-express_xss",
            "description": "Untrusted User Input in Response will result in Reflected Cross Site Scripting Vulnerability.\n",
            "cve": "semgrep_id:javascript-xss-rule-express_xss:32:32",
            "severity": "Critical",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "subdir/index.js",
                "start_line": 32
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-xss-rule-express_xss",
                    "value": "javascript-xss-rule-express_xss"
                },
                {
                    "type": "cwe",
                    "name": "CWE-79",
                    "value": "79",
                    "url": "https://cwe.mitre.org/data/definitions/79.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "7a21cbe7f4aa6ea61c3aa24d6326057b0df29551d7db54e8f929c2f47df4eba6",
            "category": "sast",
            "name": "Semgrep Finding: javascript-xss-rule-express_xss",
            "description": "Untrusted User Input in Response will result in Reflected Cross Site Scripting Vulnerability.\n",
            "cve": "semgrep_id:javascript-xss-rule-express_xss:32:32",
            "severity": "Critical",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "index.js",
                "start_line": 32
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-xss-rule-express_xss",
                    "value": "javascript-xss-rule-express_xss"
                },
                {
                    "type": "cwe",
                    "name": "CWE-79",
                    "value": "79",
                    "url": "https://cwe.mitre.org/data/definitions/79.html"
                },
                {
                    "type": "owasp",
                    "name": "A1:2017 - Injection",
                    "value": "A1:2017"
                }
            ]
        },
        {
            "id": "7a0a8d47c41cc4897025914fc08f3652eb256fe4c7e2dd6b0628f292f7ce78f4",
            "category": "sast",
            "name": "Semgrep Finding: javascript-crypto-rule-node_insecure_random_generator",
            "description": "crypto.pseudoRandomBytes()/Math.random() is a cryptographically weak random number generator.\n",
            "cve": "semgrep_id:javascript-crypto-rule-node_insecure_random_generator:42:42",
            "severity": "Medium",
            "scanner": {
                "id": "semgrep",
                "name": "Semgrep"
            },
            "location": {
                "file": "index.js",
                "start_line": 42
            },
            "identifiers": [
                {
                    "type": "semgrep_id",
                    "name": "javascript-crypto-rule-node_insecure_random_generator",
                    "value": "javascript-crypto-rule-node_insecure_random_generator"
                },
                {
                    "type": "cwe",
                    "name": "CWE-327",
                    "value": "327",
                    "url": "https://cwe.mitre.org/data/definitions/327.html"
                },
                {
                    "type": "owasp",
                    "name": "A9:2017 - Using Components with Known Vulnerabilities",
                    "value": "A9:2017"
                }
            ]
        }
    ],
    "dependency_files": [],
    "scan": {
        "analyzer": {
            "id": "semgrep",
            "name": "Semgrep",
            "url": "https://gitlab.com/gitlab-org/security-products/analyzers/semgrep",
            "vendor": {
                "name": "GitLab"
            },
            "version": "4.11.0"
        },
        "scanner": {
            "id": "semgrep",
            "name": "Semgrep",
            "url": "https://github.com/returntocorp/semgrep",
            "vendor": {
                "name": "GitLab"
            },
            "version": "1.56.0"
        },
        "type": "sast",
        "start_time": "2024-02-05T03:38:52",
        "end_time": "2024-02-05T03:38:57",
        "status": "success"
    }
}

Why is the original severity field still included in the report?

Removing the severity field causes the semgrep validity check to fail.

What are the relevant issue numbers?

Does this MR meet the acceptance criteria?

Edited by Adam Cohen

Merge request reports