diff --git a/README.md b/README.md index 85c7766e8..4b79426ce 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,7 @@ GuardDog's behavior can be customized using environment variables: |---------------------|-------------|---------------| | `GUARDDOG_PARALLELISM` | Number of threads to use for parallel processing | Number of CPUs available | | `GUARDDOG_VERIFY_EXHAUSTIVE_DEPENDENCIES` | Analyze all possible versions of dependencies (`true`/`false`) | `false` | +| `GUARDDOG_NPM_INCLUDE_DEV_DEPENDENCIES` | Include `devDependencies` when scanning npm `package.json` files (`true`/`false`) | `false` | | `GUARDDOG_TOP_PACKAGES_CACHE_LOCATION` | Location of the top packages cache directory | `guarddog/analyzer/metadata/resources` | | `GUARDDOG_YARA_EXT_EXCLUDE` | Comma-separated list of file extensions to exclude from YARA scanning | `ini,md,rst,txt,lock,json,yaml,yml,toml,xml,html,csv,sql,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,changelog,readme,makefile,dockerfile,pkg-info,d.ts` | diff --git a/guarddog/scanners/npm_project_scanner.py b/guarddog/scanners/npm_project_scanner.py index 7cd88d0dd..725c32ccc 100644 --- a/guarddog/scanners/npm_project_scanner.py +++ b/guarddog/scanners/npm_project_scanner.py @@ -9,7 +9,10 @@ from guarddog.scanners.npm_package_scanner import NPMPackageScanner from guarddog.scanners.scanner import Dependency, DependencyVersion, ProjectScanner -from guarddog.utils.config import VERIFY_EXHAUSTIVE_DEPENDENCIES +from guarddog.utils.config import ( + NPM_INCLUDE_DEV_DEPENDENCIES, + VERIFY_EXHAUSTIVE_DEPENDENCIES, +) log = logging.getLogger("guarddog") @@ -86,12 +89,15 @@ def find_all_versions(package_name: str) -> set[str]: return versions merged = {} # type: dict[str, set[str]] - for package, selector in list(dependencies_attr.items()) + list( - dev_dependencies_attr.items() - ): - if package not in merged: - merged[package] = set() - merged[package].add(selector) + dependency_groups = [dependencies_attr.items()] + if NPM_INCLUDE_DEV_DEPENDENCIES: + dependency_groups.append(dev_dependencies_attr.items()) + + for dependency_group in dependency_groups: + for package, selector in dependency_group: + if package not in merged: + merged[package] = set() + merged[package].add(selector) dependencies: List[Dependency] = [] for package, all_selectors in merged.items(): diff --git a/guarddog/utils/config.py b/guarddog/utils/config.py index 1824cf11b..02a20a7e9 100644 --- a/guarddog/utils/config.py +++ b/guarddog/utils/config.py @@ -18,6 +18,16 @@ os.environ.get("GUARDDOG_VERIFY_EXHAUSTIVE_DEPENDENCIES", "false").lower() == "true" ) +""" +This flag specifies if npm devDependencies should be included in project scans +- True: dependencies and devDependencies are analyzed +- False [default]: Only dependencies are analyzed +""" +NPM_INCLUDE_DEV_DEPENDENCIES: bool = ( + os.environ.get("GUARDDOG_NPM_INCLUDE_DEV_DEPENDENCIES", "false").lower() + == "true" +) + """ This parameter specifies the location of the top packages cache - Default: guarddog/analyzer/metadata/resources diff --git a/tests/core/test_npm_requirements_scanner.py b/tests/core/test_npm_requirements_scanner.py index f722baa18..a5a0b087e 100644 --- a/tests/core/test_npm_requirements_scanner.py +++ b/tests/core/test_npm_requirements_scanner.py @@ -1,6 +1,7 @@ import os import pathlib +import guarddog.scanners.npm_project_scanner as npm_project_scanner from guarddog.scanners.npm_project_scanner import NPMRequirementsScanner @@ -55,3 +56,45 @@ def test_npm_requirements_scanner_github(): ) assert lookup is not None assert "https://github.com/expressjs/cors.git" in lookup.versions + + +def test_npm_requirements_scanner_excludes_dev_dependencies_by_default(monkeypatch): + monkeypatch.setattr( + npm_project_scanner, "NPM_INCLUDE_DEV_DEPENDENCIES", False + ) + scanner = NPMRequirementsScanner() + result = scanner.parse_requirements(""" + { + "dependencies": { + "express": "4.x" + }, + "devDependencies": { + "joi": "17.6" + } + } + """) + + dependency_names = {dependency.name for dependency in result} + assert "express" in dependency_names + assert "joi" not in dependency_names + + +def test_npm_requirements_scanner_includes_dev_dependencies_when_enabled(monkeypatch): + monkeypatch.setattr( + npm_project_scanner, "NPM_INCLUDE_DEV_DEPENDENCIES", True + ) + scanner = NPMRequirementsScanner() + result = scanner.parse_requirements(""" + { + "dependencies": { + "express": "4.x" + }, + "devDependencies": { + "joi": "17.6" + } + } + """) + + dependency_names = {dependency.name for dependency in result} + assert "express" in dependency_names + assert "joi" in dependency_names