Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
1edc44d
Created "Scan Rust Package" pipeline #1767
chinyeungli Mar 13, 2026
d6f63d0
Add step to compare the license declared in Cargo.toml with the licen…
chinyeungli Mar 13, 2026
9ca39ea
Implemented the "evaluate_license_mismatch" function #1767
chinyeungli May 15, 2026
cb9a90c
feat!: replace plain-text DRF token with PBKDF2-hashed API token (#2087)
tdruez Mar 11, 2026
a092939
chore: set explicit workflow permissions and pin down actions (#2090)
tdruez Mar 11, 2026
731599b
chore: bump version to v37.0.0 for release (#2091)
tdruez Mar 11, 2026
a5911c7
fix: add the checkout step to pypi release workflow
tdruez Mar 11, 2026
1574a50
Reorder the command #1767
chinyeungli Jul 29, 2026
d7c58e7
Set a default User-Agent #1767
chinyeungli Jul 29, 2026
ba9326f
Evolve Rust pipeline (#1767)
chinyeungli Jul 30, 2026
e10bd2a
Implement the rust pipeline #1767
chinyeungli Aug 5, 2026
0d6780f
chore: set explicit workflow permissions and pin down actions (#2090)
tdruez Mar 11, 2026
21ab23d
fix: add the checkout step to pypi release workflow
tdruez Mar 11, 2026
a70388e
chore: refine gh workflows for security and consistency
tdruez Mar 11, 2026
4f2e785
feat: display scio and toolkit versions in place of django version (#…
tdruez Mar 16, 2026
15e399c
fix missing space in scan_max_file_size help text (#2097)
rishabh23rohil Mar 16, 2026
2535102
feat: add tests for chunked and get_purls utilities (#2100)
rishabh23rohil Mar 16, 2026
239df34
feat: display layers information (created_by, comment) in tree view (…
tdruez Mar 16, 2026
0e1b8b8
feat: enhance the global HTML structure (#2103)
tdruez Mar 17, 2026
b99006d
feat: remove the need for context processor and improve base template…
tdruez Mar 18, 2026
511d21e
chore: remove the PR quality workflow
tdruez Mar 25, 2026
b33efc1
chore: upgrade the scancode-action to latest version (pinned deps) (#…
tdruez Mar 27, 2026
e7820d6
fix: XSS and duplicate file detection in file upload UI (#2129)
tdruez Mar 30, 2026
19c6638
fix: incomplete URL sanitization for Slack webhook detection (#2130)
tdruez Mar 30, 2026
1388966
chore: correct typo in `pipes/clamav` (#2121)
OmAnand857 Mar 30, 2026
703ce14
feat: is_safe_url to mitigate SSRF in URL requests (#2131)
tdruez Mar 30, 2026
9bc2de6
feat: add search to resource tree (#2114)
aayushkdev Mar 30, 2026
893380f
feat: enhance tree search rendering and implementation (#2133)
tdruez Mar 30, 2026
8850f9e
feat: use uv and uv.lock for reproducible builds in Docker image (#2134)
tdruez Apr 1, 2026
a50d1bb
fix: add support for sqlite in the apitoken data migration (#2138)
tdruez Apr 1, 2026
182a056
feat!: convert Dockerfile to multi-stage build (#2139)
tdruez Apr 1, 2026
bc92bce
chore: bump version to v37.1.0 for release (#2140)
tdruez Apr 1, 2026
a6750df
chore: upgrade scancode-action to docker base v0.1 release (#2141)
tdruez Apr 2, 2026
804b29f
feat: add "base" Docker build stage (#2142)
tdruez Apr 3, 2026
1a31682
fix: fallback to license_declared when loading SPDX SBOM (#2144)
tdruez Apr 3, 2026
a6de1f2
feat!: Switch development workflow from local venv to Docker Compose …
tdruez Apr 10, 2026
f62c408
fix: relax validation for non-HTTP URLs (#2147)
tdruez Apr 13, 2026
62ab095
fix: load the .env in the Docker dev compose (#2149)
tdruez Apr 14, 2026
fd3f657
chore: upgrade Django and cryptography to latest version (#2150)
tdruez Apr 14, 2026
5be463c
fix: replace the hardcoded /var/www/html by named volume (#2160)
tdruez May 11, 2026
df71e0b
chore: bump minecode-pipelines version to 1.0.0 (#2170)
JonoYang May 19, 2026
f37a7eb
chore: update minecode-pipelines version to 1.0.1 (#2171)
JonoYang May 19, 2026
5ae5c71
Install Java and jadx in Dockerfile #1812 (#2174)
JonoYang Jun 12, 2026
12dc858
chore: upgrade dependencies (#2173)
tdruez Jun 12, 2026
da46584
chore: bump version to v37.2.0 for release (#2175)
tdruez Jun 12, 2026
ad5a000
fix: add setup-buildx step to enable GHA cache support (#2176)
tdruez Jun 15, 2026
73a0e3b
fix: add latest-full tag and OCI annotations to Docker publish workflow
tdruez Jun 15, 2026
8b22624
Migrate VulnerableCode integration to API v3 (#2148)
tdruez Jun 19, 2026
b46fee0
feat: denormalize project counts to improve performances (#2178)
tdruez Jun 22, 2026
b370bf9
chore: remove the db-check postgres migration service
tdruez Jun 23, 2026
0c61aa7
feat: UI refinements for project list and detail pages (#2180)
tdruez Jun 24, 2026
830c8fa
chore: upgrade service images to latest version (#2183)
tdruez Jun 25, 2026
fc1c81d
feat: auto-fill project PURL from single input URL (#2185)
tdruez Jun 26, 2026
358ba5b
chore: refine dev compose file
tdruez Jun 30, 2026
55ed020
feat: render the compliance alert value as a colored label (#2186)
tdruez Jun 30, 2026
c03bcfb
feat: add pipeline availability check with disabled state in UI (#2188)
tdruez Jul 1, 2026
fe8d54d
feat: replace django-taggit with a plain Project.labels JSONField (#2…
tdruez Jul 2, 2026
98ea188
feat: move scanpipe settings to local settings.py with lazy resolutio…
tdruez Jul 9, 2026
6f37958
fix: ClamAV scan crash on codebase root directory (#2190)
tdruez Jul 9, 2026
60ce19c
chore: upgrade vulnerable dependencies (#2191)
tdruez Jul 9, 2026
bf4106b
fix: harden SSRF mitigation against redirect and URL-parser bypasses …
tdruez Jul 10, 2026
a7d294e
Fix spelling typos in docs, models, and pipes (#2046)
mr-raj12 Jul 10, 2026
3ecf5d9
Fix get_data_from_manifests returning wrong type on early exit (#2182)
prajakta128 Jul 10, 2026
86a4690
docs: update Errors section to Messages in Web UI tutorial (#2066)
dikshaa2909 Jul 10, 2026
24da439
Fix flag_and_ignore_files_over_max_size inconsistent return value and…
prajakta128 Jul 10, 2026
58986ff
fix: silent overwrite when two input sources share the same filename …
tdruez Jul 10, 2026
4463f57
fix: xlsx_errors column overwriting last data field in XLSX export (#…
tdruez Jul 10, 2026
db8bd37
chore: bump version to v38.0.0 for release (#2195)
tdruez Jul 10, 2026
46ea1b8
chore: upgrade dependencies (#2198)
tdruez Jul 16, 2026
dc52963
feat: add pipeline specialized in Maven package scan (#1953)
chinyeungli Jul 29, 2026
3f9e57a
Implement Nix Pipeline #1938
chinyeungli Aug 12, 2026
3b2c128
Remove the "rust" references and move check_docker_command to utils.p…
chinyeungli Aug 18, 2026
4341cff
Include extra_data in package list query
chinyeungli Aug 18, 2026
7fe1177
Enhance efficiency for the pipeline #1938
chinyeungli Sep 2, 2026
e4e70a2
Better error handling and add back the missing "collect_and_create_co…
chinyeungli Sep 2, 2026
bd3e5d3
Add back the missing f prefix #1938
chinyeungli Sep 3, 2026
c5f0850
Refactor pipeline to use native scan_for_files instead of `run_scan` …
chinyeungli Sep 3, 2026
4069e08
chore: upgrade dependencies
tdruez Aug 31, 2026
cb07ae8
chore: upgrade Docker images and dependencies to latest version (#2221)
tdruez Aug 31, 2026
768fce0
Create pipeline for symbol reachability (#2151)
ziadhany Aug 31, 2026
7383622
feat: enhance d2d pipeline to identify generated code #1900
chinyeungli Sep 1, 2026
473a1c2
cravex2-reachability: Expose reachability in REST API (#2213)
ziadhany Sep 1, 2026
0191627
docs: add documentation for reachability analysis (#2211)
ziadhany Sep 1, 2026
3d2f059
feat: add ability to configure content of the JSON output (#2222)
tdruez Sep 1, 2026
716506c
fix: serve files as_attachment only when too large (#2223)
tdruez Sep 1, 2026
1e68a53
Merge branch 'main' into 1938_pipeline_for_nix
chinyeungli Sep 4, 2026
d3ea0f7
Fix code formatting #1938
chinyeungli Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/built-in-pipelines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ Scan Maven Package
:members:
:member-order: bysource

Scan Nix Package
-------------------
.. autoclass:: scanpipe.pipelines.scan_nix_package.ScanNixPackage()
:members:
:member-order: bysource

Fetch Scores (addon)
--------------------
.. warning::
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ resolve_dependencies = "scanpipe.pipelines.resolve_dependencies:ResolveDependenc
scan_codebase = "scanpipe.pipelines.scan_codebase:ScanCodebase"
scan_for_virus = "scanpipe.pipelines.scan_for_virus:ScanForVirus"
scan_maven_package = "scanpipe.pipelines.scan_maven_package:ScanMavenPackage"
scan_nix_package = "scanpipe.pipelines.scan_nix_package:ScanNixPackage"
scan_single_package = "scanpipe.pipelines.scan_single_package:ScanSinglePackage"

[tool.setuptools.packages.find]
Expand Down
230 changes: 230 additions & 0 deletions scanpipe/pipelines/scan_nix_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# SPDX-License-Identifier: Apache-2.0
#
# http://nexb.com and https://github.com/aboutcode-org/scancode.io
# The ScanCode.io software is licensed under the Apache License version 2.0.
# Data generated with ScanCode.io is provided as-is without warranties.
# ScanCode is a trademark of nexB Inc.
#
# You may not use this software except in compliance with the License.
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
# for any legal advice.
#
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
# Visit https://github.com/aboutcode-org/scancode.io for support and download.

import shutil
from pathlib import Path

from scanpipe.pipelines.deploy_to_develop import DeployToDevelop
from scanpipe.pipelines.scan_codebase import ScanCodebase
from scanpipe.pipelines.scan_single_package import ScanSinglePackage
from scanpipe.pipes import d2d
from scanpipe.pipes import flag
from scanpipe.pipes import nix
from scanpipe.pipes import utils
from scanpipe.pipes.nix import check_input_and_return_purl
from scanpipe.pipes.nix import cleanup_docker_volumes
from scanpipe.pipes.nix import fetch_inputs


class ScanNixPackage(ScanSinglePackage, DeployToDevelop, ScanCodebase):
"""
Download the nix source and binary, and run a deployment to development
scan between the binary and the source to detect any discrepancies.

Scan the sources and confirm that the detected license aligns with
the declared license that is detected from the nix package.
"""

download_inputs = False

@classmethod
def steps(cls):
return (
cls.check_input_and_return_purl,
cls.check_docker_command,
cls.fetch_inputs,
cls.collect_input_info,
cls.extract_input_to_codebase_directory,
cls.collect_and_create_codebase_resources,
cls.scan_for_application_packages,
cls.scan_for_files,
cls.collect_and_create_license_detections,
cls.add_from_to_tag,
cls.d2d_steps,
cls.validate_package_license_integrity,
cls.flag_mapped_status,
cls.cleanup_docker_volumes,
)

def check_input_and_return_purl(self):
"""Validate the input is a PURL string and return the PURL object."""
self.purl = check_input_and_return_purl(self.project)

def check_docker_command(self):
"""Check if the Docker command is available."""
if not utils.check_docker_command():
raise Exception("Docker is required and its daemon must be running.")
nix.ensure_multiarch_emulation()

def fetch_inputs(self):
"""Fetch the binary and source of the given PURL."""
from_file = ""
to_file = ""
output_format = ""
from_file, to_file, output_format = fetch_inputs(
self.purl, self.project.codebase_path
)
self.from_file = from_file
self.to_file = to_file
self.output_format = output_format

self.d2d_enable = bool(self.from_file and self.to_file)

def collect_input_info(self):
"""Collect information about the input."""
self.input_path = ""
if self.to_file:
self.input_path = self.to_file
self.collect_input_information()

def extract_input_to_codebase_directory(self):
"""Extract input to project codebase/ directory."""
if self.input_path:
extracted_path = nix.extract_nar_archive(
self.input_path, self.project.codebase_path, self.output_format
)

to_dir = Path(self.project.codebase_path) / "to"
# If the extraction failed (returned "") or we found it was empty
if not extracted_path or (to_dir.exists() and not list(to_dir.rglob("*"))):
if to_dir.exists():
shutil.rmtree(to_dir)
self.d2d_enable = False
self.project.add_error(
"Failed to extract NAR archive, D2D scan disabled."
)

self.env = self.project.get_env()

def add_from_to_tag(self):
"""Update 'from' and 'to' tag to resources based on their path."""
if self.d2d_enable:
d2d.update_from_to_tag(self.project)

def d2d_steps(self):
"""
Run the deployment to development scan if both the source and
binary are available.
"""
if self.d2d_enable:
self.flag_empty_files()
self.flag_whitespace_files()
self.flag_ignored_resources()
self.map_about_files()
self.map_checksum()
self.match_archives_to_purldb()
self.load_ecosystem_config()
self.d2d_java()
self.d2d_scala()
self.d2d_kotlin()
self.d2d_grammar()
self.d2d_groovy()
self.d2d_aspectj()
self.d2d_clojure()
self.d2d_xtend()
self.d2d_javascript()
self.d2d_process()

def d2d_java(self):
self.find_java_packages()
self.map_java_to_class()
self.map_jar_to_java_source()

def d2d_scala(self):
self.find_scala_packages()
self.map_scala_to_class()
self.map_jar_to_scala_source()

def d2d_kotlin(self):
self.find_kotlin_packages()
self.map_kotlin_to_class()
self.map_jar_to_kotlin_source()

def d2d_grammar(self):
self.find_grammar_packages()
self.map_grammar_to_class()
self.map_jar_to_grammar_source()

def d2d_groovy(self):
self.find_groovy_packages()
self.map_groovy_to_class()
self.map_jar_to_groovy_source()

def d2d_aspectj(self):
self.find_aspectj_packages()
self.map_aspectj_to_class()
self.map_jar_to_aspectj_source()

def d2d_clojure(self):
self.find_clojure_packages()
self.map_clojure_to_class()
self.map_jar_to_clojure_source()

def d2d_xtend(self):
self.find_xtend_packages()
self.map_xtend_to_class()

def d2d_javascript(self):
self.map_javascript()
self.map_javascript_symbols()
self.map_javascript_strings()

def d2d_process(self):
self.get_symbols_from_binaries()
self.map_elf()
self.map_macho()
self.map_winpe()
self.map_go()
self.map_rust()
self.map_python()
self.match_directories_to_purldb()
self.match_resources_to_purldb()
self.map_javascript_post_purldb_match()
self.map_javascript_path()
self.map_javascript_colocation()
self.map_thirdparty_npm_packages()
self.map_path()
self.flag_mapped_resources_archives_and_ignored_directories()
self.perform_house_keeping_tasks()
self.match_purldb_resources_post_process()
self.remove_packages_without_resources()
self.scan_ignored_to_files()
self.scan_unmapped_to_files()
self.scan_mapped_from_for_files()
self.flag_deployed_from_resources_with_missing_license()
self.create_local_files_packages()

def validate_package_license_integrity(self):
"""
Validate the correctness of the package license compare with the
detected license from the codebase.
"""
utils.validate_package_license_integrity(self.project)

def flag_mapped_status(self):
"""Flag the from codebase resources that were mapped."""
if self.d2d_enable:
flag.flag_mapped_resources(self.project)

def cleanup_docker_volumes(self):
"""Cleanup the Docker volumes used for Nix."""
cleanup_docker_volumes()
46 changes: 34 additions & 12 deletions scanpipe/pipes/d2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -1767,20 +1767,25 @@ def map_paths_resource(
relations_to_create[rel_key] = relation
if paths_not_mapped:
to_resource.status = flag.REQUIRES_REVIEW
logger(
f"WARNING: #{len(paths_not_mapped)} {map_type} paths NOT mapped for: "
f"{to_resource.path!r}"
)
if logger:
logger(
f"WARNING: #{len(paths_not_mapped)} {map_type} paths NOT "
f" mapped for: {to_resource.path!r}"
)
to_resource.save()

if relations_to_create:
rels = CodebaseRelation.objects.bulk_create(relations_to_create.values())
logger(
f"Created {len(rels)} mappings using "
f"{', '.join(map_types)} for: {to_resource.path!r}"
)
if logger:
logger(
f"Created {len(rels)} mappings using "
f"{', '.join(map_types)} for: {to_resource.path!r}"
)
else:
logger(f"No mappings using {', '.join(map_types)} for: {to_resource.path!r}")
if logger:
logger(
f"No mappings using {', '.join(map_types)} for: {to_resource.path!r}"
)


def process_paths_in_binary(
Expand Down Expand Up @@ -1944,9 +1949,17 @@ def map_elfs_with_dwarf_paths(project, logger=None):
f"with {from_resources.count():,d} from/ resources."
)

from_resources_index = pathmap.build_index(
from_resources.values_list("id", "path"), with_subpaths=True
)
# Build the path index, adding virtual aliases for .in template files
from_paths = []
for res_id, path in from_resources.values_list("id", "path"):
from_paths.append((res_id, path))
# If the source file is a template ending in '.in', also index its
# target name
if path.endswith(".in"):
target_path = path[:-3] # Removes the trailing '.in'
from_paths.append((res_id, target_path))

from_resources_index = pathmap.build_index(from_paths, with_subpaths=True)

if logger:
logger("Done building from/ resources index.")
Expand Down Expand Up @@ -2035,6 +2048,15 @@ def map_go_paths(project, logger=None):
)


def update_from_to_tag(project):
"""Update 'from' or 'to' tag to resources based on their path."""
for resource in project.codebaseresources.files():
if resource.path.startswith("from/"):
resource.update(tag="from")
elif resource.path.startswith("to/"):
resource.update(tag="to")


RUST_BINARY_OPTIONS = ["Rust"]
ELF_BINARY_OPTIONS = ["Python", "Go", "Elf"]
MACHO_BINARY_OPTIONS = ["Rust", "Go", "MacOS"]
Expand Down
7 changes: 7 additions & 0 deletions scanpipe/pipes/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@
def get_request_session(uri):
"""Return a Requests session setup with authentication and headers."""
session = requests.Session()

# Set a default User-Agent to avoid 403 Forbidden errors on strict
# registries that block default python-requests headers.
session.headers.update(
{"User-Agent": "ScanCode.io (https://github.com/aboutcode-org/scancode.io)"}
)

netloc = urlparse(uri).netloc

if credentials := scanpipe_settings.FETCH_BASIC_AUTH.get(netloc):
Expand Down
1 change: 1 addition & 0 deletions scanpipe/pipes/flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
REQUIRES_REVIEW = "requires-review"
REVIEW_DANGLING_LEGAL_FILE = "review-dangling-legal-file"
NOT_DEPLOYED = "not-deployed"
LICENSE_ISSUE = "license-mismatch-declared-vs-detected"
GENERATED = "generated-file"


Expand Down
Loading
Loading