Skip to content

Commit c9ceede

Browse files
Merge branch 'main' into exclusion-framework-ruby
Signed-off-by: Ayan Sinha Mahapatra <ayansmahapatra@gmail.com>
2 parents aeba014 + 820d7a7 commit c9ceede

File tree

14 files changed

+519
-55
lines changed

14 files changed

+519
-55
lines changed

CHANGELOG.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
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+
413
v35.0.0 (2025-06-23)
514
--------------------
615

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

48+
- Update univers to v31.0.0, catch ``NotImplementedError`` in
49+
``get_unique_unresolved_purls``, and properly log error in project.
50+
https://github.com/aboutcode-org/scancode.io/pull/1700
51+
https://github.com/aboutcode-org/scancode.io/pull/1701
52+
3953
v34.11.0 (2025-05-02)
4054
---------------------
4155

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/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
@@ -3150,6 +3150,13 @@ class VulnerabilityQuerySetMixin:
31503150
def vulnerable(self):
31513151
return self.filter(~Q(affected_by_vulnerabilities__in=EMPTY_VALUES))
31523152

3153+
def vulnerable_ordered(self):
3154+
return (
3155+
self.vulnerable()
3156+
.only_package_url_fields(extra=["affected_by_vulnerabilities"])
3157+
.order_by_package_url()
3158+
)
3159+
31533160

31543161
class DiscoveredPackageQuerySet(
31553162
VulnerabilityQuerySetMixin,

scanpipe/pipes/d2d.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
from scanpipe.models import CodebaseRelation
5555
from scanpipe.models import CodebaseResource
5656
from scanpipe.models import convert_glob_to_django_regex
57-
from scanpipe.pipes import d2d_config
5857
from scanpipe.pipes import flag
5958
from scanpipe.pipes import get_resource_diff_ratio
6059
from scanpipe.pipes import js
@@ -70,7 +69,6 @@
7069
TO = "to/"
7170

7271

73-
7472
def get_inputs(project):
7573
"""
7674
Locate the ``from`` and ``to`` input files in project inputs/ directory.

scanpipe/pipes/d2d_config.py

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

2323

24-
from dataclasses import dataclass, field
25-
24+
from dataclasses import dataclass
25+
from dataclasses import field
2626

2727

2828
@dataclass
@@ -127,10 +127,11 @@ def load_ecosystem_config(pipeline, options):
127127
as `options` to the `pipeline`. These configurations are used for:
128128
- which resource/package extensions to match to purldb
129129
- which source files to get source symbols from
130-
- which unmapped paths to ignore in deployed binaries
130+
- which unmapped paths to ignore in deployed binaries
131131
"""
132132
configs_by_ecosystem = {
133-
ecosystem.ecosystem_option: ecosystem for ecosystem in ECOSYSTEM_CONFIGS.values()
133+
ecosystem.ecosystem_option: ecosystem
134+
for ecosystem in ECOSYSTEM_CONFIGS.values()
134135
}
135136

136137
# Add default configurations which are common accross ecosystems
@@ -148,11 +149,13 @@ def load_ecosystem_config(pipeline, options):
148149
)
149150

150151

151-
def add_ecosystem_config(pipeline_ecosystem_config, configs_by_ecosystem, selected_option):
152+
def add_ecosystem_config(
153+
pipeline_ecosystem_config, configs_by_ecosystem, selected_option
154+
):
152155
"""
153-
Set the `pipeline_ecosystem_config` values from all the individual ecosystem based configurations
154-
defined in `configs_by_ecosystem`, based on pipeline `selected_option` which selects an
155-
ecosytem.
156+
Set the `pipeline_ecosystem_config` values from all the individual ecosystem
157+
based configurations defined in `configs_by_ecosystem`, based on pipeline
158+
`selected_option` which selects an ecosytem.
156159
"""
157160
d2d_pipeline_configs = [
158161
"purldb_package_extensions",
@@ -172,4 +175,3 @@ def add_ecosystem_config(pipeline_ecosystem_config, configs_by_ecosystem, select
172175
new_config_value = pipeline_config_value.extend(config_value)
173176

174177
setattr(pipeline_ecosystem_config, config_name, new_config_value)
175-

0 commit comments

Comments
 (0)