Skip to content

Commit

Permalink
Search by purl. Also fixes #185 (#186)
Browse files Browse the repository at this point in the history
Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>
  • Loading branch information
prabhu committed Dec 15, 2023
1 parent a741750 commit 311b7f9
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 29 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,26 @@ Use the `/scan` endpoint to perform scans.
> [!NOTE]
> The `type` parameter is mandatory in server mode.
* Scanning a local directory.
- Scanning a local directory.

```bash
curl --json '{"path": "/tmp/vulnerable-aws-koa-app", "type": "js"}' http://0.0.0.0:7070/scan
```

* Scanning a SBOM file (present locally).
- Scanning a SBOM file (present locally).

```bash
curl --json '{"path": "/tmp/vulnerable-aws-koa-app/sbom_file.json", "type": "js"}' http://0.0.0.0:7070/scan
```

* Scanning a GitHub repo.
- Scanning a GitHub repo.

```bash
curl --json '{"url": "https://github.com/HooliCorp/vulnerable-aws-koa-app", "type": "js"}' http://0.0.0.0:7070/scan -o app.vdr.json
```

* Uploading a SBOM file and generating results based on it.
- Uploading a SBOM file and generating results based on it.

```bash
curl -X POST -H 'Content-Type: multipart/form-data' -F 'file=@/tmp/app/sbom_file.json' http://0.0.0.0:7070/scan?type=js
```
Expand Down Expand Up @@ -203,6 +207,7 @@ options:
--explain Makes depscan to explain the various analysis. Useful for creating detailed reports.
--reachables-slices-file REACHABLES_SLICES_FILE
Path for the reachables slices file created by atom.
--purl SEARCH_PURL Scan a single package url.
-v, --version Display the version
```
Expand Down
57 changes: 42 additions & 15 deletions depscan/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ def build_args():
dest="reachables_slices_file",
help="Path for the reachables slices file created by atom.",
)
parser.add_argument(
"--purl",
dest="search_purl",
help="Scan a single package url.",
)
parser.add_argument(
"-v",
"--version",
Expand Down Expand Up @@ -448,7 +453,7 @@ def summarise(
reached_purls=reached_purls,
)
pkg_vulnerabilities, pkg_group_rows = prepare_vdr(options)
vdr_file = bom_file.replace(".json", ".vdr.json")
vdr_file = bom_file.replace(".json", ".vdr.json") if bom_file else None
if pkg_vulnerabilities and bom_file:
try:
with open(bom_file, encoding="utf-8") as fp:
Expand Down Expand Up @@ -508,9 +513,15 @@ def download_rafs_based_image():
target=vdb_rafs_database_url, outdir=rafs_data_dir.name
)

if paths_list and os.path.exists(
os.path.join(rafs_data_dir.name, "data.rafs")
) and os.path.exists(os.path.join(rafs_data_dir.name, "meta.rafs")):
if (
paths_list
and os.path.exists(
os.path.join(rafs_data_dir.name, "data.rafs")
)
and os.path.exists(
os.path.join(rafs_data_dir.name, "meta.rafs")
)
):
nydus_download_command = [
f"{nydus_image_command}",
"unpack",
Expand Down Expand Up @@ -540,7 +551,8 @@ def download_rafs_based_image():

except Exception:
LOG.info(
"Unable to pull the vulnerability database (rafs image) from %s. Trying to pull the non-rafs-based VDB image.", vdb_rafs_database_url
"Unable to pull the vulnerability database (rafs image) from %s. Trying to pull the non-rafs-based VDB image.",
vdb_rafs_database_url,
)
rafs_image_downloaded = False

Expand Down Expand Up @@ -819,6 +831,12 @@ def main():
# Detect the project types and perform the right type of scan
if args.project_type:
project_types_list = args.project_type.split(",")
elif args.search_purl:
purl_obj = parse_purl(args.search_purl)
purl_obj["purl"] = args.search_purl
purl_obj["vendor"] = purl_obj.get("namespace")
project_types_list = [purl_obj.get("type")]
pkg_list = [purl_obj]
elif args.bom and not args.project_type:
project_types_list = ["bom"]
elif not args.non_universal_scan:
Expand Down Expand Up @@ -857,7 +875,12 @@ def main():
risk_report_file = areport_file.replace(
".json", f"-risk.{project_type}.json"
)
if args.bom and os.path.exists(args.bom):
# Are we scanning a single purl
if args.search_purl:
bom_file = None
creation_status = True
# Are we scanning a bom file
elif args.bom and os.path.exists(args.bom):
bom_file = args.bom
creation_status = True
else:
Expand All @@ -876,14 +899,15 @@ def main():
if not creation_status:
LOG.debug("Bom file %s was not created successfully", bom_file)
continue
LOG.debug("Scanning using the bom file %s", bom_file)
if not args.bom:
LOG.info(
"To improve performance, cache the bom file and invoke "
"depscan with --bom %s instead of -i",
bom_file,
)
pkg_list = get_pkg_list(bom_file)
if bom_file:
LOG.debug("Scanning using the bom file %s", bom_file)
if not args.bom:
LOG.info(
"To improve performance, cache the bom file and invoke "
"depscan with --bom %s instead of -i",
bom_file,
)
pkg_list = get_pkg_list(bom_file)
if not pkg_list:
LOG.debug("No packages found in the project!")
continue
Expand Down Expand Up @@ -1097,7 +1121,10 @@ def main():
result_file=os.path.join(reports_dir, args.report_name),
)
elif args.report_template:
LOG.warning("Template file %s doesn't exist, custom report not created.", args.report_template)
LOG.warning(
"Template file %s doesn't exist, custom report not created.",
args.report_template,
)
# Submit vdr/vex files to threatdb server
if args.threatdb_server and (args.threatdb_username or args.threatdb_token):
submit_bom(
Expand Down
12 changes: 9 additions & 3 deletions depscan/lib/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from rich.table import Table
from rich.tree import Tree
from vdb.lib import CPE_FULL_REGEX
from vdb.lib.config import placeholder_fix_version
from vdb.lib.config import placeholder_exclude_version, placeholder_fix_version
from vdb.lib.utils import parse_cpe, parse_purl

from depscan.lib import config
Expand Down Expand Up @@ -84,7 +84,7 @@ def retrieve_bom_dependency_tree(bom_file):
:return: Dependency tree as a list
"""
if not bom_file:
return []
return [], None
try:
with open(bom_file, encoding="utf-8") as bfp:
bom_data = json.load(bfp)
Expand All @@ -97,6 +97,8 @@ def retrieve_bom_dependency_tree(bom_file):

def retrieve_oci_properties(bom_data):
props = {}
if not bom_data:
return props
for p in bom_data.get("metadata", {}).get("properties", []):
if p.get("name", "").startswith("oci:image:"):
props[p.get("name")] = p.get("value")
Expand Down Expand Up @@ -1174,7 +1176,11 @@ def suggest_version(results, pkg_aliases=None, purl_aliases=None):
else:
full_pkg = pkg_aliases.get(full_pkg, full_pkg)
version_upgrades = pkg_fix_map.get(full_pkg, set())
version_upgrades.add(fixed_location)
if (
fixed_location != placeholder_fix_version
and fixed_location != placeholder_exclude_version
):
version_upgrades.add(fixed_location)
pkg_fix_map[full_pkg] = version_upgrades
for k, v in pkg_fix_map.items():
# Don't go near certain packages
Expand Down
4 changes: 3 additions & 1 deletion depscan/lib/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
from depscan.lib.pkg_query import npm_metadata, pypi_metadata

# Dict mapping project type to the audit source
type_audit_map = {"nodejs": NpmSource(), "js": NpmSource()}
type_audit_map = {"nodejs": NpmSource(), "js": NpmSource(), "npm": NpmSource()}

# Dict mapping project type to risk audit
risk_audit_map = {
"npm": npm_metadata,
"nodejs": npm_metadata,
"js": npm_metadata,
"python": pypi_metadata,
"py": pypi_metadata,
"pypi": pypi_metadata,
}


Expand Down
1 change: 1 addition & 0 deletions depscan/lib/csaf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1577,6 +1577,7 @@ def parse_toml(metadata):
'current_release_date'.
"""
tracking = parse_revision_history(metadata.get("tracking"))
# FIXME: Could this be simplified as list comprehension without append
refs = []
[refs.append(v) for v in metadata.get("reference")]
notes = []
Expand Down
8 changes: 4 additions & 4 deletions depscan/lib/normalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ def create_pkg_variations(pkg_dict):
if purl_obj:
pkg_type = purl_obj.get("type")
qualifiers = purl_obj.get("qualifiers", {})
if qualifiers.get("distro_name"):
if qualifiers and qualifiers.get("distro_name"):
os_distro_name = qualifiers.get("distro_name")
name_aliases.add(f"""{os_distro_name}/{name}""")
if qualifiers.get("distro"):
if qualifiers and qualifiers.get("distro"):
os_distro = qualifiers.get("distro")
name_aliases.add(f"""{os_distro}/{name}""")
# almalinux-9.2 becomes almalinux-9
Expand All @@ -70,6 +70,7 @@ def create_pkg_variations(pkg_dict):
if vendor:
vendor_aliases.add(vendor)
vendor_aliases.add(vendor.lower())
vendor_aliases.add(vendor.lstrip("@"))
if (
vendor.startswith("org.")
or vendor.startswith("io.")
Expand All @@ -94,7 +95,6 @@ def create_pkg_variations(pkg_dict):
vendor_aliases.add("golang")
if pkg_type not in config.OS_PKG_TYPES:
name_aliases.add("package_" + name)
vendor_aliases.add(pkg_type)
if purl.startswith("pkg:composer"):
vendor_aliases.add("get" + name)
vendor_aliases.add(name + "_project")
Expand Down Expand Up @@ -168,7 +168,7 @@ def create_pkg_variations(pkg_dict):
name_aliases.add(name + "-bin")
else:
# Filter vendor aliases that are also name aliases
vendor_aliases = [x for x in vendor_aliases if x not in name_aliases]
vendor_aliases = [x for x in vendor_aliases if x not in name_aliases or x == vendor]
if len(vendor_aliases) > 0:
for vvar in list(vendor_aliases):
for nvar in list(name_aliases):
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "owasp-depscan"
version = "5.0.2"
version = "5.0.3"
description = "Fully open-source security audit for project dependencies based on known vulnerabilities and advisories."
authors = [
{name = "Team AppThreat", email = "cloud@appthreat.com"},
]
dependencies = [
"appthreat-vulnerability-db>=5.5.4",
"appthreat-vulnerability-db>=5.5.5",
"defusedxml",
"oras",
"PyYAML",
Expand Down

0 comments on commit 311b7f9

Please sign in to comment.