Skip to content

Commit

Permalink
feat: Add support for Javascript package scanning (Fixes intel#1453)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonyharrison committed Feb 7, 2022
1 parent f281311 commit 48391e3
Show file tree
Hide file tree
Showing 7 changed files with 1,283 additions and 29 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@ The scanner examines the `pom.xml` file within a Java package archive to identif

JAR, WAR and EAR archives are supported.

### Javascript

The scanner examines the `package-lock.json` file within a javascript application
to identify components. The package names and versions are used to search the database for vulnerabilities.


### Python

The scanner examines the `PKG-INFO` and `METADATA` files for an installed Python package to extract the component name and version which
Expand Down
76 changes: 47 additions & 29 deletions cve_bin_tool/version_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import defusedxml.ElementTree as ET
import pkg_resources

from cve_bin_tool.cvedb import CVEDB
from cve_bin_tool.egg_updater import IS_DEVELOP, update_egg
from cve_bin_tool.error_handler import ErrorMode
Expand Down Expand Up @@ -167,13 +166,11 @@ def scan_file(self, filename):

# Check for Java package
if output and "pom.xml" in output:
java_lines = "\n".join(lines.splitlines())
yield from self.run_java_checker(filename, java_lines)
yield from self.run_java_checker(filename)

# Javascript checker
if output and "package-lock.json" in output:
javascript_lines = "\n".join(lines.splitlines())
yield from self.run_js_checker(filename, javascript_lines)
yield from self.run_js_checker(filename)

# If python package then strip the lines to avoid detecting other product strings
if output and ("PKG-INFO: " in output or "METADATA: " in output):
Expand Down Expand Up @@ -202,7 +199,7 @@ def find_java_vendor(self, product, version):
return ProductInfo(vendor, product, version), file_path
return None, None

def run_java_checker(self, filename, lines):
def run_java_checker(self, filename):
"""Process maven pom.xml file and extract product and dependency details"""
tree = ET.parse(filename)
# Find root element
Expand Down Expand Up @@ -252,40 +249,61 @@ def run_java_checker(self, filename, lines):

def find_js_vendor(self, product, version):
"""Find vendor for Javascript product"""
if version == "*":
return None
vendor_package_pair = self.cve_db.get_vendor_product_pairs(product)
vendorlist = []
if vendor_package_pair != []:
vendor = vendor_package_pair[0]["vendor"]
file_path = "".join(self.file_stack)
if version[0] == "^":
version = version[1:]
self.logger.debug(f"{file_path} {product} {version} by {vendor}")
return ProductInfo(vendor, product, version), file_path
return None, None

def run_js_checker(self, filename, lines):
# To handle multiple vendors, return all combinations of product/vendor mappings
for v in vendor_package_pair:
vendor = v["vendor"]
file_path = "".join(self.file_stack)
# Tidy up version string
if "^" in version:
version = version[1:]
self.logger.debug(f"{file_path} {product} {version} by {vendor}")
vendorlist.append([ProductInfo(vendor, product, version), file_path])
return vendorlist if len(vendorlist) > 0 else None
return None

def run_js_checker(self, filename):
"""Process package-lock.json file and extract product and dependency details"""
fh = open(filename)
# returns JSON object as a dictionary
data = json.load(fh)
product = data["name"]
version = data["version"]
product_info, file_path = self.find_js_vendor(product, version)
if file_path is not None:
yield product_info, file_path
vendor = self.find_js_vendor(product, version)
if vendor is not None:
for v in vendor:
yield v[0], v[1] # product_info, file_path
# Now process dependencies
for i in data["dependencies"]:
product_info, file_path = self.find_js_vendor(
i, data["dependencies"][i]["version"]
)
if file_path is not None:
yield product_info, file_path
# To handle @actions/<product>: lines, extract product name from line
product = i.split("/")[1] if "/" in i else i
# Handle different formats. Either <product> : <version> or
# <product>: {
# ...
# "version" : <version>
# ...
# }
try:
version = data["dependencies"][i]["version"]
except Exception:
# Cater for case when version field not present
version = data["dependencies"][i]
vendor = self.find_js_vendor(product, version)
if vendor is not None:
for v in vendor:
yield v[0], v[1] # product_info, file_path
if "requires" in data["dependencies"][i]:
for r in data["dependencies"][i]["requires"]:
product_info, file_path = self.find_js_vendor(
r, data["dependencies"][i]["requires"][r]
)
if file_path is not None:
yield product_info, file_path
# To handle @actions/<product>: lines, extract product name from line
product = r.split("/")[1] if "/" in r else r
version = data["dependencies"][i]["requires"][r]
vendor = self.find_js_vendor(product, version)
if vendor is not None:
for v in vendor:
yield v[0], v[1] # product_info, file_path
self.logger.debug(f"Done scanning file: {filename}")

def run_python_package_checkers(self, filename, lines):
Expand Down
114 changes: 114 additions & 0 deletions test/language_data/package-lock1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{
"name": "setup-python",
"version": "2.2.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@actions/cache": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-1.0.8.tgz",
"integrity": "sha512-GWNNB67w93HGJRQXlsV56YqrdAuDoP3esK/mo5mzU8WoDCVjtQgJGsTdkYUX7brswtT7xnI30bWNo1WLKQ8FZQ==",
"requires": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0",
"@actions/http-client": "^1.0.9",
"@actions/io": "^1.0.1",
"@azure/ms-rest-js": "^2.0.7",
"@azure/storage-blob": "^12.1.2",
"semver": "^6.1.0",
"uuid": "^3.3.3"
},
"dependencies": {
"@actions/glob": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.1.2.tgz",
"integrity": "sha512-SclLR7Ia5sEqjkJTPs7Sd86maMDw43p769YxBOxvPvEWuPEhpAnBsQfENOpXjFYMmhCqd127bmf+YdvJqVqR4A==",
"requires": {
"@actions/core": "^1.2.6",
"minimatch": "^3.0.4"
}
},
"@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"requires": {
"tunnel": "0.0.6"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
"@actions/core": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
},
"jest-snapshot": {
"version": "27.2.5",
"resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.2.5.tgz",
"integrity": "sha512-2/Jkn+VN6Abwz0llBltZaiJMnL8b1j5Bp/gRIxe9YR3FCEh9qp0TXVV0dcpTGZ8AcJV1SZGQkczewkI9LP5yGw==",
"dev": true,
"requires": {
"@babel/core": "^7.7.2",
"@babel/generator": "^7.7.2",
"@babel/parser": "^7.7.2",
"@babel/plugin-syntax-typescript": "^7.7.2",
"@babel/traverse": "^7.7.2",
"@babel/types": "^7.0.0",
"@jest/transform": "^27.2.5",
"@jest/types": "^27.2.5",
"@types/babel__traverse": "^7.0.4",
"@types/prettier": "^2.1.5",
"babel-preset-current-node-syntax": "^1.0.0",
"chalk": "^4.0.0",
"expect": "^27.2.5",
"graceful-fs": "^4.2.4",
"jest-diff": "^27.2.5",
"jest-get-type": "^27.0.6",
"jest-haste-map": "^27.2.5",
"jest-matcher-utils": "^27.2.5",
"jest-message-util": "^27.2.5",
"jest-resolve": "^27.2.5",
"jest-util": "^27.2.5",
"natural-compare": "^1.4.0",
"pretty-format": "^27.2.5",
"semver": "^7.3.2"
},
"dependencies": {
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"node-releases": {
"version": "1.1.77",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz",
"integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==",
"dev": true
},
"typescript": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
"dev": true
},
"yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true
}
}
}
29 changes: 29 additions & 0 deletions test/language_data/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "node-js-sample",
"version": "0.2.0",
"description": "A sample Node.js app using Express 4",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.13.3"
},
"engines": {
"node": "4.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/heroku/node-js-sample"
},
"keywords": [
"node",
"heroku",
"express"
],
"author": "Mark Pundsack",
"contributors": [
"Zeke Sikelianos <zeke@sikelianos.com> (http://zeke.sikelianos.com)"
],
"license": "MIT"
}

0 comments on commit 48391e3

Please sign in to comment.