<aside> 📜 TABLE OF CONTENTS
Our app uses JEST for automated testing
The following python script was used to check if any of our app uses any on the compromised packages by recursively exploring packages in package.json and in package-lock.json to see if they match any of the compromised packages
import argparse
import json
import os
import sys
from typing import Dict, List, Optional, Tuple, Set
def load_json_file(path: str) -> Optional[dict]:
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return None
except json.JSONDecodeError as e:
print(f"[!] Failed to parse JSON file {path}: {e}", file=sys.stderr)
return None
def is_compromised(pkg: str, version: Optional[str], compromised_map: Dict[str, List[str]]) -> bool:
if pkg not in compromised_map:
return False
bad_versions = compromised_map[pkg]
if not bad_versions:
return True
return version in bad_versions
def check_package_json(package_json: dict, compromised_map: Dict[str, List[str]]) -> List[Dict]:
found = []
if not package_json:
return found
fields = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]
for field in fields:
bucket = package_json.get(field, {}) or {}
for name, version_spec in bucket.items():
if is_compromised(name, version_spec, compromised_map):
found.append({
"package": name,
"version_spec": version_spec,
"field": field
})
elif name in compromised_map and not compromised_map[name]:
found.append({
"package": name,
"version_spec": version_spec,
"field": field
})
return found
def search_lock_dependencies(lock: dict, compromised_map: Dict[str, List[str]]) -> List[Dict]:
results = []
visited = set()
def dfs(dep_name, dep_info, path):
if isinstance(dep_info, str):
version = dep_info
subdeps = {}
elif isinstance(dep_info, dict):
version = dep_info.get("version", None)
subdeps = dep_info.get("dependencies", {})
else:
version = None
subdeps = {}
if version and dep_name in compromised and version in compromised[dep_name]:
matches.append({
"package": dep_name,
"version": version,
"path": " -> ".join(path + [f"{dep_name}@{version}"])
})
if isinstance(subdeps, dict):
for sub_name, sub_info in subdeps.items():
dfs(sub_name, sub_info, path + [f"{dep_name}@{version}" if version else dep_name])
dfs(lock, [])
return results
def print_summary(direct_pkgjson, lock_matches):
any_found = bool(direct_pkgjson or lock_matches)
print("\\n=== Compromised package detection summary ===")
if not any_found:
print("No compromised packages found.")
return
if direct_pkgjson:
print("\\nDirect references in root package.json:")
for m in direct_pkgjson:
print(f" - {m['package']} (version spec: {m['version_spec']}) in {m['field']}")
if lock_matches:
print("\\nTransitive references found in package-lock.json (root -> ... -> compromised):")
for m in lock_matches:
chain = " -> ".join(f"{p['name']}@{p.get('version') or '?'}" for p in m["path_chain"])
print(f" - {m['package']}@{m.get('version')} (chain: {chain})")
def main(argv=None):
p = argparse.ArgumentParser(description="Detect compromised npm packages with version checks.")
p.add_argument("--compromised-json", required=True,
help="JSON file mapping {package: [versions]} (empty list = all versions).")
p.add_argument("--package-json", default="package.json",
help="Path to package.json (default: ./package.json)")
p.add_argument("--package-lock", default="package-lock.json",
help="Path to package-lock.json (default: ./package-lock.json)")
p.add_argument("--output", default="report.json",
help="Write JSON report to this file (default: report.json).")
args = p.parse_args(argv)
compromised_map = load_json_file(args.compromised_json)
if not compromised_map:
print("[!] Compromised JSON file missing or invalid.", file=sys.stderr)
return 2
root_pj = load_json_file(args.package_json)
direct_matches = check_package_json(root_pj, compromised_map) if root_pj else []
lock = load_json_file(args.package_lock)
lock_matches = []
if lock and "dependencies" in lock:
lock_root = {"dependencies": lock.get("dependencies", {})}
lock_matches = search_lock_dependencies(lock_root, compromised_map)
elif lock and "packages" in lock:
synthetic_root = {"dependencies": {}}
for path_key, info in lock.get("packages", {}).items():
if path_key == "":
continue
name = info.get("name") or os.path.basename(path_key)
synthetic_root["dependencies"].setdefault(name, info)
lock_matches = search_lock_dependencies(synthetic_root, compromised_map)
report = {
"compromised_input": compromised_map,
"direct_package_json_matches": direct_matches,
"package_lock_matches": lock_matches,
}
with open(args.output, "w", encoding="utf-8") as outf:
json.dump(report, outf, indent=2)
print(f"[+] Report written to {args.output}")
print_summary(direct_matches, lock_matches)
return 1 if direct_matches or lock_matches else 0
if __name__ == "__main__":
sys.exit(main())