From a63afa53bf7249429e568c50408618c041566b82 Mon Sep 17 00:00:00 2001 From: ipeleg Date: Wed, 13 Sep 2023 17:01:15 +0300 Subject: [PATCH] add javascript_alias_mapping_strategy --- checkov/common/sca/reachability/__init__.py | 0 .../reachability/alias_mapping_strategy.py | 8 ++ .../sca/reachability/javascript/__init__.py | 0 .../javascript_alias_mapping_strategy.py | 117 ++++++++++++++++++ tests/common/sca/reachability/__init__.py | 0 .../test_javascript_alias_mapping_strategy.py | 107 ++++++++++++++++ 6 files changed, 232 insertions(+) create mode 100644 checkov/common/sca/reachability/__init__.py create mode 100644 checkov/common/sca/reachability/alias_mapping_strategy.py create mode 100644 checkov/common/sca/reachability/javascript/__init__.py create mode 100644 checkov/common/sca/reachability/javascript/javascript_alias_mapping_strategy.py create mode 100644 tests/common/sca/reachability/__init__.py create mode 100644 tests/common/sca/reachability/test_javascript_alias_mapping_strategy.py diff --git a/checkov/common/sca/reachability/__init__.py b/checkov/common/sca/reachability/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/checkov/common/sca/reachability/alias_mapping_strategy.py b/checkov/common/sca/reachability/alias_mapping_strategy.py new file mode 100644 index 00000000000..378939695c3 --- /dev/null +++ b/checkov/common/sca/reachability/alias_mapping_strategy.py @@ -0,0 +1,8 @@ +from abc import ABC, abstractmethod +from typing import List, Dict + + +class AliasMappingStrategy(ABC): + @abstractmethod + def create_alias_mapping(self, root_dir: str, relevant_packages: List[str]) -> Dict[str, List[str]]: + pass diff --git a/checkov/common/sca/reachability/javascript/__init__.py b/checkov/common/sca/reachability/javascript/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/checkov/common/sca/reachability/javascript/javascript_alias_mapping_strategy.py b/checkov/common/sca/reachability/javascript/javascript_alias_mapping_strategy.py new file mode 100644 index 00000000000..3b5e5b3ece9 --- /dev/null +++ b/checkov/common/sca/reachability/javascript/javascript_alias_mapping_strategy.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import os.path +from abc import ABC +from typing import List, Dict, Set, Any +import re +import json +import os + +MODULE_EXPORTS_PATTERN = r'module\.exports\s*=\s*({.*?});' +EXPORT_DEFAULT_PATTERN = r'export\s*default\s*({.*?});' + + +class JavascriptAliasMappingStrategy(ABC): + + def _parse_export(self, file_content: str, pattern: str) -> Dict[str, Any] | None: + module_export_match = re.search(pattern, file_content, re.DOTALL) + + if module_export_match: + module_exports_str = module_export_match.group(1) + module_exports_str = re.sub(r'([{\s,])(\w+):', r'\1"\2":', module_exports_str).replace("'", "\"") + print(module_exports_str) + module_exports: Dict[str, Any]= json.loads(module_exports_str) + return module_exports + return None + + def parse_webpack_file(self, alias_mapping: Dict[str, List[str]], file_content: str, relevant_packages: Set[str])\ + -> None: + module_exports_json = self._parse_export(file_content, MODULE_EXPORTS_PATTERN) + + if module_exports_json: + aliases = module_exports_json.get("resolve", {}).get("alias", {}) + for imported_name in aliases: + package_name = aliases[imported_name] + if package_name in relevant_packages: + alias_mapping.setdefault(package_name, []).append(imported_name) + + def parse_tsconfig_file(self, alias_mapping: Dict[str, List[str]], file_content: str, relevant_packages: Set[str])\ + -> None: + tsconfig_json = json.loads(file_content) + paths = tsconfig_json.get("compilerOptions", {}).get("paths", {}) + for imported_name in paths: + for package_relative_path in paths[imported_name]: + package_name = os.path.basename(package_relative_path) + if package_name in relevant_packages: + alias_mapping.setdefault(package_name, []).append(imported_name) + + + def parse_babel_file(self, alias_mapping: Dict[str, List[str]], file_content: str, relevant_packages: Set[str])\ + -> None: + babelrc_json = json.loads(file_content) + plugins = babelrc_json.get("plugins", {}) + for plugin in plugins: + if len(plugin) > 1: + plugin_object = plugin[1] + aliases = plugin_object.get("alias", {}) + for imported_name in aliases: + package_name = aliases[imported_name] + if package_name in relevant_packages: + alias_mapping.setdefault(package_name, []).append(imported_name) + + def parse_rollup_file(self, alias_mapping: Dict[str, List[str]], file_content: str, relevant_packages: Set[str])\ + -> None: + export_default_match = re.search(EXPORT_DEFAULT_PATTERN, file_content, re.DOTALL) + + if export_default_match: + export_default_str = export_default_match.group(1) + export_default_str = re.sub(r'([{\s,])(\w+):', r'\1"\2":', export_default_str).replace("'", "\"") + export_default_str = re.sub(r'\s+', '', export_default_str) + + # Define a regular expression pattern to match the elements within the "plugins" list + pattern = r'alias\(\{[^)]*\}\)' + + # Use the findall() function to find all matches of the pattern in the input string + matches = re.findall(pattern, export_default_str) + for alias_object_str in matches: + alias_object = json.loads(alias_object_str[6:-1]) # removing 'alias(' and ')' + print(alias_object) + for entry in alias_object.get("entries", []): + alias_mapping.setdefault(entry["replacement"], []).append(entry["find"]) + + + def parse_package_json_file(self, alias_mapping: Dict[str, List[str]], file_content: str, relevant_packages: Set[str])\ + -> None: + package_json = json.loads(file_content) + aliases: Dict[str, str] = dict() + if "alias" in package_json: + aliases.update(package_json["alias"]) + if package_json.get("aliasify", {}).get("aliases"): + aliases.update(package_json["aliasify"]["aliases"]) + for imported_name in aliases: + alias_mapping.setdefault(aliases[imported_name], []).append(imported_name) + + def parse_snowpack_file(self, alias_mapping: Dict[str, List[str]], file_content: str, relevant_packages: Set[str])\ + -> None: + module_exports_json = self._parse_export(file_content, MODULE_EXPORTS_PATTERN) + + if module_exports_json: + aliases = module_exports_json.get("alias", {}) + for imported_name in aliases: + package_name = aliases[imported_name] + if package_name in relevant_packages: + alias_mapping.setdefault(package_name, []).append(imported_name) + + def parse_vite_file(self, alias_mapping: Dict[str, List[str]], file_content: str, relevant_packages: Set[str])\ + -> None: + export_default_match = self._parse_export(file_content, EXPORT_DEFAULT_PATTERN) + + if export_default_match: + aliases = export_default_match.get("resolve", {}).get("alias", {}) + for imported_name in aliases: + package_name = aliases[imported_name] + if package_name in relevant_packages: + alias_mapping.setdefault(package_name, []).append(imported_name) + + def create_alias_mapping(self, root_dir: str, relevant_packages: List[str]) -> Dict[str, List[str]]: + return dict() diff --git a/tests/common/sca/reachability/__init__.py b/tests/common/sca/reachability/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/common/sca/reachability/test_javascript_alias_mapping_strategy.py b/tests/common/sca/reachability/test_javascript_alias_mapping_strategy.py new file mode 100644 index 00000000000..82dc47c604c --- /dev/null +++ b/tests/common/sca/reachability/test_javascript_alias_mapping_strategy.py @@ -0,0 +1,107 @@ +from checkov.common.sca.reachability.javascript.javascript_alias_mapping_strategy import JavascriptAliasMappingStrategy + + +def test_parse_webpack_file(): + strategy_object = JavascriptAliasMappingStrategy() + alias_mapping: dict[str, list[str]] = dict() + file_content = \ + "module.exports = {" \ + " resolve: {" \ + " alias: {" \ + " ax: 'axios'" \ + " }" \ + " }" \ + "};" + strategy_object.parse_webpack_file(alias_mapping, file_content, {'axios'}) + assert alias_mapping == {"axios": ["ax"]} + + +def test_parse_babel_file(): + strategy_object = JavascriptAliasMappingStrategy() + alias_mapping: dict[str, list[str]] = dict() + file_content = \ + '{' \ + ' "plugins": [' \ + ' ["module-resolver", {' \ + ' "alias": {' \ + ' "ax": "axios"' \ + ' }' \ + ' }]' \ + ' ]' \ + '}' + strategy_object.parse_babel_file(alias_mapping, file_content, {'axios'}) + assert alias_mapping == {"axios": ["ax"]} + + +def test_parse_rollup_file(): + strategy_object = JavascriptAliasMappingStrategy() + alias_mapping: dict[str, list[str]] = dict() + file_content = \ + "import alias from '@rollup/plugin-alias';" \ + "export default {" \ + " plugins: [" \ + " alias({" \ + " entries: [" \ + " { find: 'ax', replacement: 'axios' }" \ + " ]" \ + " })" \ + " ]" \ + "};" + strategy_object.parse_rollup_file(alias_mapping, file_content, {'axios'}) + assert alias_mapping == {"axios": ["ax"]} + + +def test_parse_package_json_alias(): + strategy_object = JavascriptAliasMappingStrategy() + alias_mapping: dict[str, list[str]] = dict() + file_content = \ + '{' \ + ' "alias": {' \ + ' "ax": "axios"' \ + ' }' \ + '}' + strategy_object.parse_package_json_file(alias_mapping, file_content, {'axios'}) + assert alias_mapping == {"axios": ["ax"]} + + +def test_parse_package_json_aliasify(): + strategy_object = JavascriptAliasMappingStrategy() + alias_mapping: dict[str, list[str]] = dict() + file_content = \ + '{' \ + ' "aliasify": {' \ + ' "aliases": {' \ + ' "ax": "axios"' \ + ' }' \ + ' }' \ + '}' + strategy_object.parse_package_json_file(alias_mapping, file_content, {'axios'}) + assert alias_mapping == {"axios": ["ax"]} + + +def test_parse_snowpack(): + strategy_object = JavascriptAliasMappingStrategy() + alias_mapping: dict[str, list[str]] = dict() + file_content = \ + 'module.exports = {' \ + ' alias: {' \ + ' "ax": "axios"' \ + ' }' \ + '};' + strategy_object.parse_snowpack_file(alias_mapping, file_content, {'axios'}) + assert alias_mapping == {"axios": ["ax"]} + + +def test_parse_vite(): + strategy_object = JavascriptAliasMappingStrategy() + alias_mapping: dict[str, list[str]] = dict() + file_content = \ + 'export default {' \ + ' resolve: {' \ + ' alias: {' \ + ' "ax": "axios"' \ + ' }' \ + ' }' \ + '};' + strategy_object.parse_vite_file(alias_mapping, file_content, {'axios'}) + assert alias_mapping == {"axios": ["ax"]}