Skip to content

Commit 22c50af

Browse files
authored
Merge branch 'main' into store-all-downloaded-packages
2 parents f4bad69 + f557411 commit 22c50af

32 files changed

+3340
-66
lines changed

CHANGELOG.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
Changelog
22
=========
33

4+
v35.1.0 (unreleased)
5+
--------------------
6+
7+
- Add a ``--fail-on-vulnerabilities`` option in ``check-compliance`` management command.
8+
When this option is enabled, the command will exit with a non-zero status if known
9+
vulnerabilities are detected in discovered packages and dependencies.
10+
Requires the ``find_vulnerabilities`` pipeline to be executed beforehand.
11+
https://github.com/aboutcode-org/scancode.io/pull/1702
12+
13+
- Enable ``--license-references`` scan option in the ``scan_single_package`` pipeline.
14+
The ``license_references`` and ``license_rule_references`` attributes will now be
15+
available in the scan results, including the details about detected licenses and
16+
license rules used during the scan.
17+
https://github.com/aboutcode-org/scancode.io/issues/1657
18+
419
v35.0.0 (2025-06-23)
520
--------------------
621

@@ -36,6 +51,11 @@ v35.0.0 (2025-06-23)
3651
- Add "Package Compliance Alert" chart in the Policies section.
3752
https://github.com/aboutcode-org/scancode.io/pull/1699
3853

54+
- Update univers to v31.0.0, catch ``NotImplementedError`` in
55+
``get_unique_unresolved_purls``, and properly log error in project.
56+
https://github.com/aboutcode-org/scancode.io/pull/1700
57+
https://github.com/aboutcode-org/scancode.io/pull/1701
58+
3959
v34.11.0 (2025-05-02)
4060
---------------------
4161

docs/command-line-interface.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ Optional arguments:
497497
- ``--fail-level {ERROR,WARNING,MISSING}`` Compliance alert level that will cause the
498498
command to exit with a non-zero status. Default is ERROR.
499499

500+
- ``--fail-on-vulnerabilities`` Exit with a non-zero status if known vulnerabilities
501+
are detected in discovered packages and dependencies.
502+
Requires the ``find_vulnerabilities`` pipeline to be executed beforehand.
503+
500504
`$ scanpipe archive-project --project PROJECT`
501505
----------------------------------------------
502506

scancodeio/static/add-inputs.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,41 @@
2121
// Visit https://github.com/aboutcode-org/scancode.io for support and download.
2222

2323
const fileInput = document.querySelector("#id_input_files");
24+
let selectedFiles = []; // Store selected files
2425
fileInput.onchange = updateFiles;
2526

2627
// Update the list of files to be uploaded in the UI
2728
function updateFiles() {
2829
if (fileInput.files.length > 0) {
2930
const fileName = document.querySelector("#inputs_file_name");
3031
fileName.innerHTML = "";
31-
for (let file of fileInput.files) {
32-
fileName.innerHTML += `<span class="is-block">${file.name}</span>`;
32+
33+
// Update the selectedFiles array
34+
const newFiles = Array.from(fileInput.files);
35+
// Create a Set to track unique file names
36+
const uniqueFileNames = new Set(selectedFiles.map(file => file.name));
37+
// Filter out files with the same name
38+
const filteredNewFiles = newFiles.filter(file => !uniqueFileNames.has(file.name));
39+
// Concatenate the unique files to the existing selectedFiles array
40+
selectedFiles = selectedFiles.concat(filteredNewFiles);
41+
42+
for (let file of selectedFiles) {
43+
const fileNameWithoutSpaces = file.name.replace(/\s/g, '');
44+
fileName.innerHTML += `
45+
<span class="is-flex is-justify-content-space-between is-block" id="file-name-${fileNameWithoutSpaces}">
46+
<span class="is-block">${file.name}</span>
47+
<a href="#" onclick="removeFile('${fileNameWithoutSpaces}')" class="model-button" id="file-delete-btn-${fileNameWithoutSpaces}">
48+
<i class="fa-solid fa-trash-can"></i>
49+
</a>
50+
</span>
51+
`;
52+
document.getElementById("file-delete-btn-"+ fileNameWithoutSpaces).addEventListener("click", function(event){
53+
disableEvent(event);
54+
removeFile(fileNameWithoutSpaces);
55+
if(selectedFiles.length == 0){
56+
fileName.innerHTML ="<i>No files selected</i>"
57+
}
58+
});
3359
}
3460
}
3561
}
@@ -40,15 +66,37 @@ function disableEvent(event) {
4066
event.preventDefault();
4167
}
4268

69+
function removeFile(fileName) {
70+
selectedFiles = selectedFiles.filter(file => {
71+
const fileNameWithoutSpaces = file.name.replace(/\s/g, '');
72+
return fileNameWithoutSpaces !== fileName;
73+
});
74+
75+
const fileNameElement = document.getElementById(`file-name-${fileName}`);
76+
if (fileNameElement) {
77+
fileNameElement.remove();
78+
}
79+
80+
const dataTransfer = new DataTransfer();
81+
for (let file of selectedFiles) {
82+
dataTransfer.items.add(file);
83+
}
84+
85+
fileInput.files = dataTransfer.files;
86+
}
87+
4388
function dropHandler(event) {
4489
disableEvent(event);
4590
const droppedFiles = event.dataTransfer.files;
46-
const updatedFiles = Array.from(fileInput.files);
91+
const updatedFilesSet = new Set(Array.from(fileInput.files));
4792

4893
for (let file of droppedFiles) {
49-
updatedFiles.push(file);
94+
updatedFilesSet.add(file);
5095
}
5196

97+
// Convert the Set back to an array if needed
98+
const updatedFiles = Array.from(updatedFilesSet);
99+
52100
const dataTransfer = new DataTransfer();
53101
for (let file of updatedFiles) {
54102
dataTransfer.items.add(file);

scanpipe/filters.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ def filter(self, qs, value):
483483
("about_file", "about file"),
484484
("java_to_class", "java to class"),
485485
("jar_to_source", "jar to source"),
486+
("javascript_strings", "js strings"),
486487
("javascript_symbols", "js symbols"),
487488
("js_compiled", "js compiled"),
488489
("js_colocation", "js colocation"),

scanpipe/management/commands/check-compliance.py

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,64 @@ def add_arguments(self, parser):
4545
"non-zero status. Default is ERROR."
4646
),
4747
)
48+
parser.add_argument(
49+
"--fail-on-vulnerabilities",
50+
action="store_true",
51+
help=(
52+
"Exit with a non-zero status if known vulnerabilities are detected in "
53+
"discovered packages and dependencies. "
54+
"Requires the `find_vulnerabilities` pipeline to be executed "
55+
"beforehand."
56+
),
57+
)
4858

4959
def handle(self, *args, **options):
5060
super().handle(*args, **options)
51-
fail_level = options["fail_level"]
52-
compliance_alerts = get_project_compliance_alerts(self.project, fail_level)
61+
exit_code = 0
62+
63+
if self.check_compliance(options["fail_level"]):
64+
exit_code = 1
65+
66+
if options["fail_on_vulnerabilities"] and self.check_vulnerabilities():
67+
exit_code = 1
5368

54-
compliance_alerts_count = sum(
55-
len(issues_by_severity)
56-
for model_alerts in compliance_alerts.values()
57-
for issues_by_severity in model_alerts.values()
69+
sys.exit(exit_code)
70+
71+
def check_compliance(self, fail_level):
72+
alerts = get_project_compliance_alerts(self.project, fail_level)
73+
count = sum(
74+
len(issues) for model in alerts.values() for issues in model.values()
5875
)
59-
if not compliance_alerts_count:
60-
sys.exit(0)
6176

62-
if self.verbosity > 0:
63-
msg = [
64-
f"{compliance_alerts_count} compliance issues detected on this project."
65-
]
66-
for label, issues in compliance_alerts.items():
67-
msg.append(f"[{label}]")
68-
for severity, entries in issues.items():
69-
msg.append(f" > {severity.upper()}: {len(entries)}")
77+
if count and self.verbosity > 0:
78+
self.stderr.write(f"{count} compliance issues detected.")
79+
for label, model in alerts.items():
80+
self.stderr.write(f"[{label}]")
81+
for severity, entries in model.items():
82+
self.stderr.write(f" > {severity.upper()}: {len(entries)}")
7083
if self.verbosity > 1:
71-
msg.append(" " + "\n ".join(entries))
84+
self.stderr.write(" " + "\n ".join(entries))
85+
86+
return count > 0
7287

73-
self.stderr.write("\n".join(msg))
88+
def check_vulnerabilities(self):
89+
packages = self.project.discoveredpackages.vulnerable_ordered()
90+
dependencies = self.project.discovereddependencies.vulnerable_ordered()
91+
92+
vulnerable_records = list(packages) + list(dependencies)
93+
count = len(vulnerable_records)
94+
95+
if self.verbosity > 0:
96+
if count:
97+
self.stderr.write(f"{count} vulnerable records found:")
98+
for entry in vulnerable_records:
99+
self.stderr.write(str(entry))
100+
vulnerability_ids = [
101+
vulnerability.get("vulnerability_id")
102+
for vulnerability in entry.affected_by_vulnerabilities
103+
]
104+
self.stderr.write(" > " + ", ".join(vulnerability_ids))
105+
else:
106+
self.stdout.write("No vulnerabilities found")
74107

75-
sys.exit(1)
108+
return count > 0

scanpipe/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3155,6 +3155,13 @@ class VulnerabilityQuerySetMixin:
31553155
def vulnerable(self):
31563156
return self.filter(~Q(affected_by_vulnerabilities__in=EMPTY_VALUES))
31573157

3158+
def vulnerable_ordered(self):
3159+
return (
3160+
self.vulnerable()
3161+
.only_package_url_fields(extra=["affected_by_vulnerabilities"])
3162+
.order_by_package_url()
3163+
)
3164+
31583165

31593166
class DiscoveredPackageQuerySet(
31603167
VulnerabilityQuerySetMixin,

scanpipe/pipelines/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ def flag_ignored_resources(self):
7878
ignored_patterns = ignored_patterns.splitlines()
7979
ignored_patterns.extend(flag.DEFAULT_IGNORED_PATTERNS)
8080

81-
flag.flag_ignored_patterns(self.project, patterns=ignored_patterns)
81+
flag.flag_ignored_patterns(
82+
codebaseresources=self.project.codebaseresources.no_status(),
83+
patterns=ignored_patterns,
84+
)
8285

8386
def extract_archive(self, location, target):
8487
"""Extract archive at `location` to `target`. Save errors as messages."""
@@ -179,6 +182,8 @@ def __init__(self, run_instance):
179182
self.selected_groups = run_instance.selected_groups
180183
self.selected_steps = run_instance.selected_steps
181184

185+
self.ecosystem_config = None
186+
182187
@classmethod
183188
def get_initial_steps(cls):
184189
"""Add the ``download_inputs`` step as an initial step if enabled."""

scanpipe/pipelines/deploy_to_develop.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@
2121
# Visit https://github.com/aboutcode-org/scancode.io for support and download.
2222

2323
import logging
24-
<<<<<<< HEAD
25-
=======
26-
from pathlib import Path
27-
>>>>>>> ca3a1ac0c0147a6f3f59999a67bf586eab9b8a36
2824
from aboutcode.pipeline import optional_step
2925
from scanpipe import pipes
3026
from scanpipe.pipelines import Pipeline
3127
from scanpipe.pipes import d2d
28+
from scanpipe.pipes import d2d_config
3229
from scanpipe.pipes import flag
3330
from scanpipe.pipes import input
3431
from scanpipe.pipes import matchcode
@@ -72,6 +69,8 @@ def steps(cls):
7269
cls.flag_empty_files,
7370
cls.flag_whitespace_files,
7471
cls.flag_ignored_resources,
72+
cls.load_ecosystem_config,
73+
cls.map_ruby,
7574
cls.map_about_files,
7675
cls.map_checksum,
7776
cls.match_archives_to_purldb,
@@ -80,6 +79,7 @@ def steps(cls):
8079
cls.map_jar_to_source,
8180
cls.map_javascript,
8281
cls.map_javascript_symbols,
82+
cls.map_javascript_strings,
8383
cls.map_elf,
8484
cls.map_macho,
8585
cls.map_winpe,
@@ -102,6 +102,7 @@ def steps(cls):
102102
cls.create_local_files_packages,
103103
)
104104

105+
105106
purldb_package_extensions = [".jar", ".war", ".zip"]
106107
purldb_resource_extensions = [
107108
".map",
@@ -197,6 +198,15 @@ def flag_whitespace_files(self):
197198
"""Flag whitespace files with size less than or equal to 100 byte as ignored."""
198199
d2d.flag_whitespace_files(project=self.project)
199200

201+
def load_ecosystem_config(self):
202+
"""Load ecosystem specific configurations for d2d steps for selected options."""
203+
d2d_config.load_ecosystem_config(pipeline=self, options=self.selected_groups)
204+
205+
@optional_step("Ruby")
206+
def map_ruby(self):
207+
"""Load Ruby specific configurations for d2d steps."""
208+
pass
209+
200210
def map_about_files(self):
201211
"""Map ``from/`` .ABOUT files to their related ``to/`` resources."""
202212
d2d.map_about_files(project=self.project, logger=self.log)
@@ -213,7 +223,7 @@ def match_archives_to_purldb(self):
213223

214224
d2d.match_purldb_resources(
215225
project=self.project,
216-
extensions=self.purldb_package_extensions,
226+
extensions=self.matchable_package_extensions,
217227
matcher_func=d2d.match_purldb_package,
218228
logger=self.log,
219229
)
@@ -246,6 +256,11 @@ def map_javascript_symbols(self):
246256
"""Map deployed JavaScript, TypeScript to its sources using symbols."""
247257
d2d.map_javascript_symbols(project=self.project, logger=self.log)
248258

259+
@optional_step("JavaScript")
260+
def map_javascript_strings(self):
261+
"""Map deployed JavaScript, TypeScript to its sources using string literals."""
262+
d2d.map_javascript_strings(project=self.project, logger=self.log)
263+
249264
@optional_step("Elf")
250265
def map_elf(self):
251266
"""Map ELF binaries to their sources using dwarf paths and symbols."""
@@ -291,7 +306,7 @@ def match_resources_to_purldb(self):
291306

292307
d2d.match_purldb_resources(
293308
project=self.project,
294-
extensions=self.purldb_resource_extensions,
309+
extensions=self.matchable_resource_extensions,
295310
matcher_func=d2d.match_purldb_resource,
296311
logger=self.log,
297312
)
@@ -329,6 +344,7 @@ def flag_mapped_resources_archives_and_ignored_directories(self):
329344
def perform_house_keeping_tasks(self):
330345
"""
331346
On deployed side
347+
- Ignore specific files based on ecosystem based configurations.
332348
- PurlDB match files with ``no-java-source`` and empty status,
333349
if no match is found update status to ``requires-review``.
334350
- Update status for uninteresting files.
@@ -339,9 +355,14 @@ def perform_house_keeping_tasks(self):
339355
"""
340356
d2d.match_resources_with_no_java_source(project=self.project, logger=self.log)
341357
d2d.handle_dangling_deployed_legal_files(project=self.project, logger=self.log)
358+
d2d.ignore_unmapped_resources_from_config(
359+
project=self.project,
360+
patterns_to_ignore=self.ecosystem_config.deployed_resource_path_exclusions,
361+
logger=self.log,
362+
)
342363
d2d.match_unmapped_resources(
343364
project=self.project,
344-
matched_extensions=self.purldb_resource_extensions,
365+
matched_extensions=self.ecosystem_config.matchable_resource_extensions,
345366
logger=self.log,
346367
)
347368
d2d.flag_undeployed_resources(project=self.project)
@@ -377,5 +398,5 @@ def flag_deployed_from_resources_with_missing_license(self):
377398
"""Update the status for deployed from files with missing license."""
378399
d2d.flag_deployed_from_resources_with_missing_license(
379400
self.project,
380-
doc_extensions=self.doc_extensions,
401+
doc_extensions=self.ecosystem_config.doc_extensions,
381402
)

scanpipe/pipelines/scan_single_package.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def steps(cls):
6666
"info": True,
6767
"license": True,
6868
"license_text": True,
69+
"license_references": True,
6970
"package": True,
7071
"url": True,
7172
"classify": True,

0 commit comments

Comments
 (0)