mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 22:27:22 +02:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfde0974b0 | ||
|
|
9f047fdf11 | ||
|
|
07ae2ca08d | ||
|
|
36969bdeff | ||
|
|
dfb6011083 | ||
|
|
997a486e22 | ||
|
|
4f64fbc004 | ||
|
|
87d6a288d7 | ||
|
|
20987d30d0 | ||
|
|
e2007d9bf2 | ||
|
|
4f0e32ab51 | ||
|
|
605391114c | ||
|
|
1caf243d29 | ||
|
|
48e91312e8 | ||
|
|
d81df67493 | ||
|
|
47cda2b5ef | ||
|
|
ae711963d1 | ||
|
|
f878197150 | ||
|
|
514efb03e0 | ||
|
|
1e4f424f09 | ||
|
|
b5f0877967 |
@@ -35,7 +35,7 @@ runs:
|
||||
- name: Restore tool cache
|
||||
if: inputs.tools == 'true'
|
||||
id: tool-cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tool
|
||||
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }}
|
||||
@@ -60,7 +60,7 @@ runs:
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y "${packages[@]}"
|
||||
- name: Restore ORAS cache from github actions
|
||||
if: inputs.download-test-fixture-cache == 'true'
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp/oras-cache
|
||||
key: ${{ inputs.cache-key-prefix }}-oras-cache
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# CodeQL scans for security vulnerabilities and coding errors across all
|
||||
# languages in this repo. Results appear in the "Security" tab under
|
||||
# "Code scanning alerts" and are enforced by branch protection rules.
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
@@ -8,74 +5,17 @@ on:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
# Weekly scheduled scan catches newly disclosed vulnerabilities in
|
||||
# existing code, not just changes introduced by PRs.
|
||||
schedule:
|
||||
- cron: '38 11 * * 3'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
name: Analyze
|
||||
uses: anchore/workflows/.github/workflows/codeql.yaml@e8cee3a5916cebb68cda68b54c180f43394c1910 # v0.5.0
|
||||
permissions:
|
||||
# Required to upload SARIF results to the "Security" tab.
|
||||
security-events: write
|
||||
# Required to fetch internal or private CodeQL packs.
|
||||
packages: read
|
||||
# Only required for workflows in private repositories.
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# GitHub Actions workflow linting — no build needed.
|
||||
- language: actions
|
||||
build-mode: none
|
||||
|
||||
# Go uses "manual" build mode so we control exactly what gets
|
||||
# compiled. The default "autobuild" finds the Makefile and runs
|
||||
# the full CI pipeline (lint, test, snapshot release, etc.),
|
||||
# which is far more work than CodeQL needs. All it requires is
|
||||
# compiled Go source so it can build a type-resolved code graph
|
||||
# for analysis.
|
||||
- language: go
|
||||
build-mode: manual
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Pin the Go toolchain to whatever go.mod declares so CodeQL
|
||||
# analyzes with the same version the project actually uses.
|
||||
# Only runs for the Go matrix entry.
|
||||
- name: Setup Go
|
||||
if: matrix.language == 'go'
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
|
||||
# Minimal build for Go: compile all packages so CodeQL gets a full
|
||||
# type-resolved code graph for analysis.
|
||||
- name: Build (Go)
|
||||
if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: go build ./...
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
# The category tag lets GitHub associate SARIF results with the
|
||||
# correct language when branch protection checks for required
|
||||
# code scanning results.
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
name: Dependabot Automation
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
run:
|
||||
uses: anchore/workflows/.github/workflows/dependabot-automation.yaml@main
|
||||
@@ -1,64 +0,0 @@
|
||||
# Note: this workflow has been disabled manually in the UI and will be replaced in short order
|
||||
|
||||
name: "Detect schema changes"
|
||||
|
||||
on:
|
||||
# IMPORTANT! This workflow is triggered by the `pull_request_target` event
|
||||
# which means that forked PRs will run with access secrets from the repo
|
||||
# it's forked from (the "target" repo).
|
||||
#
|
||||
# For this reason we only NEVER checkout the code from the pull request
|
||||
# (e.g. "ref: ${{ github.event.pull_request.head.sha }}") to prevent
|
||||
# accidentally running potentially untrusted code.
|
||||
#
|
||||
# By default the checkout will be:
|
||||
# - GITHUB_SHA: Last commit on the PR base branch
|
||||
# - GITHUB_REF: PR base branch
|
||||
#
|
||||
# ...unlike a typical PR where:
|
||||
# - GITHUB_SHA: Last merge commit on the GITHUB_REF branch
|
||||
# - GITHUB_REF: PR merge branch refs/pull/:prNumber/merge
|
||||
pull_request_target:
|
||||
|
||||
env:
|
||||
# note: this is used within hashFiles() so must be within the GITHUB_WORKSPACE path (or will silently fail)
|
||||
CI_COMMENT_FILE: .tmp/labeler-comment.txt
|
||||
# needs to be any string to uniquely identify the comment on a PR across multiple runs
|
||||
COMMENT_HEADER: "label-commentary"
|
||||
|
||||
jobs:
|
||||
label:
|
||||
name: "Label changes"
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: anchore/syft # IMPORTANT! An additional protection that this is checking out code from the expected repository
|
||||
ref: main # IMPORTANT! It is CRITICAL that this only ever considers the code from main and NEVER EVER from a fork.
|
||||
|
||||
- run: python .github/scripts/labeler.py
|
||||
env:
|
||||
# note: this token has write access to the repo
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_PR_NUMBER: ${{ github.event.number }}
|
||||
|
||||
- name: Delete existing comment
|
||||
if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }}
|
||||
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff #v3.0.3
|
||||
with:
|
||||
header: ${{ env.COMMENT_HEADER }}
|
||||
hide: true
|
||||
hide_classify: "OUTDATED"
|
||||
|
||||
- name: Add comment
|
||||
if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }}
|
||||
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff #v3.0.3
|
||||
with:
|
||||
header: ${{ env.COMMENT_HEADER }}
|
||||
path: ${{ env.CI_COMMENT_FILE }}
|
||||
@@ -1,18 +0,0 @@
|
||||
name: Add to OSS board
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- transferred
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
run:
|
||||
uses: "anchore/workflows/.github/workflows/oss-project-board-add.yaml@main"
|
||||
secrets:
|
||||
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
|
||||
+23
-108
@@ -1,7 +1,11 @@
|
||||
name: "Release"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
permissions: {}
|
||||
|
||||
# there should never be two releases in progress at the same time
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -19,115 +23,26 @@ on:
|
||||
- "install-script-only"
|
||||
|
||||
jobs:
|
||||
quality-gate:
|
||||
environment: release
|
||||
|
||||
version-available:
|
||||
if: ${{ github.event.inputs.phase == 'all' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: anchore/workflows/.github/workflows/check-version-available.yaml@8b2b1caf40e03933c6807e03b99e883e2ceb5ac8 # v0.4.0
|
||||
with:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Validate Apple notarization credentials
|
||||
run: .tool/quill submission list
|
||||
env:
|
||||
QUILL_NOTARY_ISSUER: ${{ secrets.APPLE_NOTARY_ISSUER }}
|
||||
QUILL_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
|
||||
QUILL_NOTARY_KEY: ${{ secrets.APPLE_NOTARY_KEY }}
|
||||
|
||||
- name: Check if running on main
|
||||
if: github.ref != 'refs/heads/main'
|
||||
# we are using the following flag when running `cosign blob-verify` for checksum signature verification:
|
||||
# --certificate-identity-regexp "https://github.com/anchore/.github/workflows/release.yaml@refs/heads/main"
|
||||
# if we are not on the main branch, the signature will not be verifiable since the suffix requires the main branch
|
||||
# at the time of when the OIDC token was issued on the Github Actions runner.
|
||||
run: echo "This can only be run on the main branch otherwise releases produced will not be verifiable with cosign" && exit 1
|
||||
|
||||
- name: Check if tag already exists
|
||||
# note: this will fail if the tag already exists
|
||||
run: |
|
||||
[[ "$VERSION" == v* ]] || (echo "version '$VERSION' does not have a 'v' prefix" && exit 1)
|
||||
git tag "$VERSION"
|
||||
env:
|
||||
VERSION: ${{ github.event.inputs.version }}
|
||||
|
||||
- name: Check static analysis results
|
||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||
id: static-analysis
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
|
||||
checkName: "Static analysis"
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check unit test results
|
||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||
id: unit
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
|
||||
checkName: "Unit tests"
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check integration test results
|
||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||
id: integration
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
|
||||
checkName: "Integration tests"
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check acceptance test results (linux)
|
||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||
id: acceptance-linux
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
|
||||
checkName: "Acceptance tests (Linux)"
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check acceptance test results (mac)
|
||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||
id: acceptance-mac
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
|
||||
checkName: "Acceptance tests (Mac)"
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check cli test results (linux)
|
||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||
id: cli-linux
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
|
||||
checkName: "CLI tests (Linux)"
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Quality gate
|
||||
if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.integration.outputs.conclusion != 'success' || steps.cli-linux.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success'
|
||||
env:
|
||||
STATIC_ANALYSIS_STATUS: ${{ steps.static-analysis.conclusion }}
|
||||
UNIT_TEST_STATUS: ${{ steps.unit.outputs.conclusion }}
|
||||
INTEGRATION_TEST_STATUS: ${{ steps.integration.outputs.conclusion }}
|
||||
ACCEPTANCE_LINUX_STATUS: ${{ steps.acceptance-linux.outputs.conclusion }}
|
||||
ACCEPTANCE_MAC_STATUS: ${{ steps.acceptance-mac.outputs.conclusion }}
|
||||
CLI_LINUX_STATUS: ${{ steps.cli-linux.outputs.conclusion }}
|
||||
run: |
|
||||
echo "Static Analysis Status: $STATIC_ANALYSIS_STATUS"
|
||||
echo "Unit Test Status: $UNIT_TEST_STATUS"
|
||||
echo "Integration Test Status: $INTEGRATION_TEST_STATUS"
|
||||
echo "Acceptance Test (Linux) Status: $ACCEPTANCE_LINUX_STATUS"
|
||||
echo "Acceptance Test (Mac) Status: $ACCEPTANCE_MAC_STATUS"
|
||||
echo "CLI Test (Linux) Status: $CLI_LINUX_STATUS"
|
||||
false
|
||||
check-gate:
|
||||
if: ${{ github.event.inputs.phase == 'all' }}
|
||||
permissions:
|
||||
checks: read # required for getting the status of specific check names
|
||||
uses: anchore/workflows/.github/workflows/check-gate.yaml@8b2b1caf40e03933c6807e03b99e883e2ceb5ac8 # v0.4.0
|
||||
with:
|
||||
# these are checks that should be run on pull-request and merges to main.
|
||||
# we do NOT want to kick off a release if these have not been verified on main.
|
||||
# Please see the validations.yaml workflow for the names that should be used here.
|
||||
checks: '["Acceptance tests (Linux)", "Acceptance tests (Mac)", "Build snapshot artifacts", "CLI tests (Linux)", "Integration tests", "Static analysis", "Unit tests"]'
|
||||
|
||||
release:
|
||||
needs: [ quality-gate ]
|
||||
needs: [ check-gate, version-available ]
|
||||
if: ${{ github.event.inputs.phase == 'all' }}
|
||||
# runs-on.com: compute instances for parallel builds
|
||||
# spot disabled: reliability for build workflows (used for releases too)
|
||||
@@ -193,7 +108,7 @@ jobs:
|
||||
artifact-name: sbom.spdx.json
|
||||
|
||||
- name: Notify Slack of new release
|
||||
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 #v3.0.1
|
||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c #v3.0.3
|
||||
continue-on-error: true
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
name: "Manage Awaiting Response Label"
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
uses: "anchore/workflows/.github/workflows/remove-awaiting-response-label.yaml@main"
|
||||
secrets:
|
||||
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
|
||||
@@ -10,8 +10,7 @@ on:
|
||||
- '.github/workflows/**'
|
||||
- '.github/actions/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
zizmor:
|
||||
@@ -26,7 +25,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run zizmor"
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
with:
|
||||
# there is a pass/fail gate as a repo ruleset (if there is no ruleset configured then the action will pass by default)
|
||||
advanced-security: true
|
||||
|
||||
@@ -13,8 +13,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
Static-Analysis:
|
||||
@@ -25,6 +24,8 @@ jobs:
|
||||
# s3-cache: faster actions cache
|
||||
# tmpfs: faster io-intensive workflows
|
||||
runs-on: &test-runner "runs-on=${{ github.run_id }}/cpu=4+8/ram=32+128/family=r5+r6+r7+r8+m4+m5+m6+m7+m8/spot=price-capacity-optimized/extras=s3-cache+tmpfs"
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
@@ -45,6 +46,8 @@ jobs:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Unit tests"
|
||||
runs-on: *test-runner
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
@@ -65,6 +68,8 @@ jobs:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Integration tests"
|
||||
runs-on: *test-runner
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
@@ -89,6 +94,8 @@ jobs:
|
||||
# s3-cache: faster actions cache
|
||||
# tmpfs: faster io-intensive workflows
|
||||
runs-on: "runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/extras=s3-cache+tmpfs"
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
|
||||
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
|
||||
@@ -120,6 +127,8 @@ jobs:
|
||||
name: "Acceptance tests (Linux)"
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: *test-runner
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
|
||||
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
|
||||
@@ -162,6 +171,8 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
# note: macos runners aren't supported yet for runs-on managed runners.
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||
@@ -197,6 +208,8 @@ jobs:
|
||||
name: "CLI tests (Linux)"
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: *test-runner
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
|
||||
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
|
||||
|
||||
@@ -41,8 +41,8 @@ require (
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.4.0
|
||||
github.com/gkampitakis/go-snaps v0.5.21
|
||||
github.com/go-git/go-billy/v5 v5.8.0
|
||||
github.com/go-git/go-git/v5 v5.18.0
|
||||
github.com/go-git/go-billy/v5 v5.9.0
|
||||
github.com/go-git/go-git/v5 v5.19.0
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/goccy/go-yaml v1.19.2
|
||||
@@ -62,6 +62,7 @@ require (
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.8
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||
github.com/klauspost/compress v1.18.5
|
||||
github.com/magiconair/properties v1.8.10
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/moby/sys/mountinfo v0.7.2
|
||||
@@ -92,7 +93,7 @@ require (
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
||||
golang.org/x/mod v0.35.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/time v0.15.0
|
||||
@@ -177,7 +178,7 @@ require (
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
|
||||
github.com/containerd/ttrpc v1.2.7 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v29.4.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
@@ -215,7 +216,7 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
@@ -257,7 +258,7 @@ require (
|
||||
github.com/pborman/indent v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
github.com/pkg/xattr v0.4.9 // indirect
|
||||
|
||||
@@ -325,8 +325,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -420,12 +420,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
|
||||
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
|
||||
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM=
|
||||
github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
|
||||
github.com/go-git/go-git/v5 v5.19.0 h1:+WkVUQZSy/F1Gb13udrMKjIM2PrzsNfDKFSfo5tkMtc=
|
||||
github.com/go-git/go-git/v5 v5.19.0/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -649,6 +649,8 @@ github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -790,8 +792,8 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -1048,8 +1050,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
||||
@@ -96,6 +96,20 @@ var binaryClassifierOverrides = map[string][]binaryClassifierOverride{
|
||||
CPEs: singleCPE("cpe:2.3:a:oracle:jdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
},
|
||||
"openssl-binary": {
|
||||
{
|
||||
Class: "openssl-binary-aws-lc",
|
||||
Package: "aws-lc",
|
||||
PURL: mustPURL("pkg:generic/aws-lc@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:amazon:aws_libcrypto:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "openssl-binary",
|
||||
Package: "openssl",
|
||||
PURL: mustPURL("pkg:generic/openssl@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func mustPURL(purl string) string {
|
||||
|
||||
@@ -427,6 +427,17 @@ catalogers:
|
||||
criteria:
|
||||
- '**/openssl'
|
||||
packages:
|
||||
- class: openssl-binary
|
||||
name: ""
|
||||
purl: pkg:/
|
||||
cpes: []
|
||||
type: BinaryPkg
|
||||
- class: openssl-binary-aws-lc
|
||||
name: aws-lc
|
||||
purl: pkg:generic/aws-lc
|
||||
cpes:
|
||||
- cpe:2.3:a:amazon:aws_libcrypto:*:*:*:*:*:*:*:*
|
||||
type: BinaryPkg
|
||||
- class: openssl-binary
|
||||
name: openssl
|
||||
purl: pkg:generic/openssl
|
||||
@@ -684,6 +695,16 @@ catalogers:
|
||||
cpes:
|
||||
- cpe:2.3:a:mongodb:mongodb:*:*:*:*:*:*:*:*
|
||||
type: BinaryPkg
|
||||
- method: glob
|
||||
criteria:
|
||||
- '**/nginx-ingress-controller'
|
||||
packages:
|
||||
- class: ingress-nginx-binary
|
||||
name: nginx-ingress-controller
|
||||
purl: pkg:generic/nginx-ingress-controller
|
||||
cpes:
|
||||
- cpe:2.3:a:kubernetes:ingress-nginx:*:*:*:*:*:*:*:*
|
||||
type: BinaryPkg
|
||||
- method: glob
|
||||
criteria:
|
||||
- '**/java'
|
||||
|
||||
@@ -1485,6 +1485,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("openssl-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "aws-lc/1.69.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "aws-lc",
|
||||
Version: "1.69.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/aws-lc@1.69.0",
|
||||
Locations: locations("openssl"),
|
||||
Metadata: metadata("openssl-binary-aws-lc"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "openldap/2.6.10/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -1754,6 +1765,25 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// release-candidate elixir image — pre-fix the matchers stripped the
|
||||
// "-rc.1" suffix from the elixir-library result and missed the
|
||||
// elixir-binary entirely (#4819).
|
||||
logicalFixture: "elixir/1.12.0-rc.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "elixir",
|
||||
Version: "1.12.0-rc.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/elixir@1.12.0-rc.1",
|
||||
Locations: locations("elixir", "lib/elixir/ebin/elixir.app"),
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("elixir-binary", "elixir"),
|
||||
match("elixir-library", "lib/elixir/ebin/elixir.app"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "istio_pilot-discovery/1.29.0-alpha.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -2227,6 +2257,105 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("envoy-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/1.15.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "1.15.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@1.15.1",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/1.11.8/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "1.11.8",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@1.11.8",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/1.9.6/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "1.9.6",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@1.9.6",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/1.7.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "1.7.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@1.7.1",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/1.12.0-beta.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "1.12.0-beta.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@1.12.0-beta.0",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/1.2.0-beta.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "1.2.0-beta.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@1.2.0-beta.1",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/1.0.0-alpha.2/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "1.0.0-alpha.2",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@1.0.0-alpha.2",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/0.34.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "0.34.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@0.34.0",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx-ingress-controller/0.33.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx-ingress-controller",
|
||||
Version: "0.33.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx-ingress-controller@0.33.0",
|
||||
Locations: locations("nginx-ingress-controller"),
|
||||
Metadata: metadata("ingress-nginx-binary"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -106,13 +106,19 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
{
|
||||
Class: "redis-binary",
|
||||
FileGlob: "**/redis-server",
|
||||
EvidenceMatcher: binutils.MatchAny(
|
||||
// matches most recent versions of redis (~v7), e.g. "7.0.14buildkitsandbox-1702957741000000000"
|
||||
m.FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
|
||||
// matches against older versions of redis (~v3 - v6), e.g. "4.0.11841ce7054bd9-1542359302000000000"
|
||||
m.FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
|
||||
// matches against older versions of redis (~v2), e.g. "Server started, Redis version 2.8.23"
|
||||
m.FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
EvidenceMatcher: binutils.MatchAll(
|
||||
// Negative Matchers to exclude valkey-server
|
||||
binutils.MatchNone(
|
||||
binutils.MatchPath("**/valkey-server"),
|
||||
),
|
||||
binutils.MatchAny(
|
||||
// matches most recent versions of redis (~v7), e.g. "7.0.14buildkitsandbox-1702957741000000000"
|
||||
m.FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
|
||||
// matches against older versions of redis (~v3 - v6), e.g. "4.0.11841ce7054bd9-1542359302000000000"
|
||||
m.FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
|
||||
// matches against older versions of redis (~v2), e.g. "Server started, Redis version 2.8.23"
|
||||
m.FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
),
|
||||
),
|
||||
Package: "redis",
|
||||
PURL: mustPURL("pkg:generic/redis@version"),
|
||||
@@ -558,14 +564,29 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
{
|
||||
Class: "openssl-binary",
|
||||
FileGlob: "**/openssl",
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]OpenSSL 3.1.4'
|
||||
// [NUL]OpenSSL 1.1.1w'
|
||||
`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+([a-z]+|-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`,
|
||||
),
|
||||
Package: "openssl",
|
||||
PURL: mustPURL("pkg:generic/openssl@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
EvidenceMatcher: binutils.BranchingEvidenceMatcher([]binutils.Classifier{
|
||||
{
|
||||
Class: "openssl-binary-aws-lc",
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]OpenSSL 1.1.1 (compatible; AWS-LC 1.69.0)[NUL]
|
||||
`AWS-LC (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\)\x00`,
|
||||
),
|
||||
Package: "aws-lc",
|
||||
PURL: mustPURL("pkg:generic/aws-lc@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:amazon:aws_libcrypto:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "openssl-binary",
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]OpenSSL 3.1.4'
|
||||
// [NUL]OpenSSL 1.1.1w'
|
||||
`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+([a-z]+|-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`,
|
||||
),
|
||||
Package: "openssl",
|
||||
PURL: mustPURL("pkg:generic/openssl@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
}...),
|
||||
},
|
||||
{
|
||||
Class: "openldap-search-binary",
|
||||
@@ -762,7 +783,9 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
Class: "elixir-binary",
|
||||
FileGlob: "**/elixir",
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)ELIXIR_VERSION=(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
// Capture optional pre-release suffix (-rc.1, -alpha.0, -beta.2,
|
||||
// etc.) so release-candidate elixir images (#4819) match.
|
||||
`(?m)ELIXIR_VERSION=(?P<version>[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z0-9]+(?:\.[0-9]+)?)?)`),
|
||||
Package: "elixir",
|
||||
PURL: mustPURL("pkg:generic/elixir@version"),
|
||||
CPEs: []cpe.CPE{
|
||||
@@ -773,7 +796,8 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
Class: "elixir-library",
|
||||
FileGlob: "**/elixir/ebin/elixir.app",
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\{vsn,"(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9]+)?)"\}`),
|
||||
// Same pre-release extension as elixir-binary above.
|
||||
`(?m)\{vsn,"(?P<version>[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z0-9]+(?:\.[0-9]+)?)?)"\}`),
|
||||
Package: "elixir",
|
||||
PURL: mustPURL("pkg:generic/elixir@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
@@ -905,6 +929,29 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
PURL: mustPURL("pkg:generic/mongodb@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:mongodb:mongodb:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "ingress-nginx-binary",
|
||||
FileGlob: "**/nginx-ingress-controller",
|
||||
EvidenceMatcher: binutils.MatchAny(
|
||||
// [NUL][NUL]v1.15.1[NUL][NUL]@e[ETX][NUL][NUL][NUL][NUL]go1.26.1[NUL][NUL][NUL]
|
||||
// �v1.15.1[NUL][NUL]�z[ETX][NUL][NUL][NUL][NUL]go1.24.4[NUL][NUL][NUL]
|
||||
m.FileContentsVersionMatcher(`v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+.{0,50}go[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9])?\.[0-9]+\x00+`),
|
||||
// �Lv1.9.6[NUL][NUL]$a�c[SOH][NUL][NUL][NUL]
|
||||
// [NUL][NUL]v0.34.0[NUL]......�$a�...[NUL]
|
||||
m.FileContentsVersionMatcher(`v(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9])?)\x00+.{0,800}\$a.{0,10}\x00+`),
|
||||
// [NUL][NUL]v1.7.1[NUL][NUL][NUL]...S=v<y5...
|
||||
// [NUL]0.33.0[NUL][NUL]...[NUL][NUL]...S=v<y5
|
||||
m.FileContentsVersionMatcher(`\x00+v?(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9])?)\x00+.{0,100}S=v<y5`),
|
||||
// [NUL][NUL]go1.22.8[NUL][NUL][NUL][NUL][NUL][NUL][NUL][NUL][NUL]v1.12.0-beta.0[NUL][NUL]
|
||||
m.FileContentsVersionMatcher(`\x00+go[0-9]+\.[0-9]+\.[0-9]+\x00+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9])?)\x00+`),
|
||||
// [NUL][NUL]v1.2.0-beta.1[NUL][NUL]
|
||||
// [NUL][NUL]v1.0.0-alpha.2[NUL][NUL]
|
||||
m.FileContentsVersionMatcher(`\x00+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+\-(alpha|beta)\.[0-9])\x00+`),
|
||||
),
|
||||
Package: "nginx-ingress-controller",
|
||||
PURL: mustPURL("pkg:generic/nginx-ingress-controller@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:kubernetes:ingress-nginx:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
}
|
||||
|
||||
return append(classifiers, defaultJavaClassifiers()...)
|
||||
|
||||
BIN
Binary file not shown.
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.12.0-rc.1
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
Usage: $(basename "$0") [options] [.exs file] [data]
|
||||
|
||||
## General options
|
||||
|
||||
-e "COMMAND" Evaluates the given command (*)
|
||||
-h, --help Prints this message (standalone)
|
||||
-r "FILE" Requires the given files/patterns (*)
|
||||
-S SCRIPT Finds and executes the given script in \$PATH
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{application,elixir,
|
||||
[{description,"elixir"},
|
||||
{vsn,"1.12.0-rc.1"},
|
||||
{modules,
|
||||
['Elixir.Access','Elixir.Agent.Server','Elixir.Agent',
|
||||
'Elixir.Application','Elixir.ArgumentError',
|
||||
elixir_overridable,elixir_parser,elixir_quote,elixir_rewrite,
|
||||
elixir_sup,elixir_tokenizer,elixir_utils,iex]},
|
||||
{registered,[elixir_sup,elixir_config,elixir_code_server]},
|
||||
{applications,[kernel,stdlib,compiler]},
|
||||
{mod,{elixir,[]}},
|
||||
{env,
|
||||
[{ansi_syntax_colors,
|
||||
[{atom,cyan},
|
||||
{binary,default_color},
|
||||
{operator,default_color}]},
|
||||
{check_endianness,true},
|
||||
{dbg_callback,{'Elixir.Macro',dbg,[]}},
|
||||
{time_zone_database,'Elixir.Calendar.UTCOnlyTimeZoneDatabase'}]}]}.
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+83
-2
@@ -625,12 +625,14 @@ from-images:
|
||||
paths:
|
||||
- /usr/local/bin/redis-server
|
||||
|
||||
- version: 9.0.0
|
||||
- name: valkey-server
|
||||
version: 9.0.0
|
||||
images:
|
||||
- ref: valkey/valkey:9.0.0@sha256:42ea97850708540d4e05f6241cfbd241c1ba502e64d9a42efb2c2e277a8ca9d6
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/bin/valkey-server
|
||||
- /usr/local/bin/redis-server
|
||||
|
||||
- version: 2.9.0
|
||||
images:
|
||||
@@ -917,11 +919,19 @@ from-images:
|
||||
- name: openssl
|
||||
version: 1.1.1w
|
||||
images:
|
||||
- ref: openresty/openresty:1.25.3.1-2-alpine@sha256:b1cb45b1556801b8cb1bc29ea78faf2eaf67926a37b052dd070866e44d7df07a
|
||||
- ref: openresty/openresty:1.25.3.1-2-alpine@sha256:6c24f25318e5ecf0c981bec584842af329552a538fe48a24ec25387374c652b2
|
||||
platform: linux/arm64
|
||||
paths:
|
||||
- /usr/local/openresty/openssl/bin/openssl
|
||||
|
||||
- name: aws-lc
|
||||
version: 1.69.0
|
||||
images:
|
||||
- ref: haproxytech/haproxy-alpine:3.3.8@sha256:ba237771cc7e734b4449829b09d107012c8d7578d77487b8453207e7ca851996
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /opt/aws-lc/bin/openssl
|
||||
|
||||
- name: openldap
|
||||
version: 2.6.10
|
||||
images:
|
||||
@@ -1379,3 +1389,74 @@ from-images:
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/bin/envoy
|
||||
|
||||
- version: 1.15.1
|
||||
images:
|
||||
- ref: registry.k8s.io/ingress-nginx/controller:v1.15.1@sha256:594ceea76b01c592858f803f9ff4d2cb40542cae2060410b2c95f75907d659e1
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 1.11.8
|
||||
images:
|
||||
- ref: registry.k8s.io/ingress-nginx/controller:v1.11.8@sha256:695d79381ee6af00c7f5c9fd434f50851d7d32838ad5b2c507e416cf2084fc79
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 1.9.6
|
||||
images:
|
||||
- ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:1405cc613bd95b2c6edd8b2a152510ae91c7e62aea4698500d23b2145960ab9c
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 1.8.4
|
||||
images:
|
||||
- ref: registry.k8s.io/ingress-nginx/controller:v1.8.4@sha256:8d8ddf32b83ca3e74bd5f66369fa60d85353e18ff55fa7691b321aa4716f5ba9
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 1.7.1
|
||||
images:
|
||||
- ref: registry.k8s.io/ingress-nginx/controller:v1.7.1@sha256:7244b95ea47bddcb8267c1e625fb163fc183ef55448855e3ac52a7b260a60407
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 1.12.0-beta.0
|
||||
images:
|
||||
- ref: registry.k8s.io/ingress-nginx/controller:v1.12.0-beta.0@sha256:9724476b928967173d501040631b23ba07f47073999e80e34b120e8db5f234d5
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 1.2.0-beta.1
|
||||
images:
|
||||
- ref: k8s.gcr.io/ingress-nginx/controller:v1.2.0-beta.1@sha256:7059739637c30865f74cae403fffa55c2cb6d9779cd15654480dd0e4f850d536
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 1.0.0-alpha.2
|
||||
images:
|
||||
- ref: k8s.gcr.io/ingress-nginx/controller:v1.0.0-alpha.2@sha256:04a0ad3a1279c2a58898e789eed767eafa138ee1e5b9b23a988c6e8485cf958d
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 0.34.0
|
||||
images:
|
||||
- ref: k8s.gcr.io/ingress-nginx/controller:v0.34.0@sha256:5e0dcdc3ddb076f9ad988c624b8c459b53235ace26b9b18c192dedd3a038173c
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
- version: 0.33.0
|
||||
images:
|
||||
- ref: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.33.0@sha256:ad8a1e9e44b4deb6ded8d677e9f0014178294e579d0710a48bca1e05838f2473
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /nginx-ingress-controller
|
||||
|
||||
|
||||
@@ -18,71 +18,60 @@ var (
|
||||
licensePattern = regexp.MustCompile(`^License: (?P<license>\S*)`)
|
||||
commonLicensePathPattern = regexp.MustCompile(`/usr/share/common-licenses/(?P<license>[0-9A-Za-z_.\-]+)`)
|
||||
licenseAgreementHeadingPattern = regexp.MustCompile(`(?i)^\s*(?P<license>LICENSE AGREEMENT(?: FOR .+?)?)\s*$`)
|
||||
formatHeaderPattern = regexp.MustCompile(`^Format:\s*https?://www\.debian\.org/doc/packaging-manuals/copyright-format/`)
|
||||
)
|
||||
|
||||
// heading-detection states. Replaces licenseFirstSentenceAfterHeadingPattern,
|
||||
// which only matched at the start of the file: a non-empty heading, a line
|
||||
// of dashes, blank lines, then text up to the first period.
|
||||
const (
|
||||
expectHeading = iota
|
||||
expectDashes
|
||||
skipBlanks
|
||||
captureLicense
|
||||
headingDone // matched or impossible — stop checking
|
||||
)
|
||||
|
||||
func parseLicensesFromCopyright(reader io.Reader) []string {
|
||||
findings := strset.New()
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
// State machine replacing licenseFirstSentenceAfterHeadingPattern.
|
||||
// That regex only matched at the start of the file: a non-empty heading,
|
||||
// a line of dashes, blank lines, then text up to the first period.
|
||||
const (
|
||||
expectHeading = iota
|
||||
expectDashes
|
||||
skipBlanks
|
||||
captureLicense
|
||||
headingDone // matched or impossible — stop checking
|
||||
)
|
||||
// per the DEP-5 spec, machine-readable copyright files MUST have a
|
||||
// Format field whose value is a URI for the specification. Only files
|
||||
// with this header should be parsed as machine-readable.
|
||||
// See: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
formatVerified := false
|
||||
headingState := expectHeading
|
||||
var licenseText strings.Builder
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
// per-line regex checks (applied to every line)
|
||||
if value := findLicenseClause(licensePattern, line); value != "" {
|
||||
findings.Add(value)
|
||||
}
|
||||
if value := findLicenseClause(commonLicensePathPattern, line); value != "" {
|
||||
findings.Add(value)
|
||||
}
|
||||
if value := findLicenseClause(licenseAgreementHeadingPattern, line); value != "" {
|
||||
findings.Add(value)
|
||||
if !formatVerified {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
if !formatHeaderPattern.MatchString(line) {
|
||||
return nil
|
||||
}
|
||||
formatVerified = true
|
||||
}
|
||||
|
||||
// multi-line heading detection (only at start of file)
|
||||
switch headingState {
|
||||
case expectHeading:
|
||||
if strings.TrimSpace(line) != "" {
|
||||
headingState = expectDashes
|
||||
} else {
|
||||
headingState = headingDone
|
||||
}
|
||||
case expectDashes:
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if len(trimmed) > 0 && strings.Trim(trimmed, "-") == "" {
|
||||
headingState = skipBlanks
|
||||
} else {
|
||||
headingState = headingDone
|
||||
}
|
||||
case skipBlanks:
|
||||
if strings.TrimSpace(line) != "" {
|
||||
headingState = captureLicense
|
||||
licenseText.WriteString(line)
|
||||
if value := extractUpToFirstPeriod(licenseText.String()); value != "" {
|
||||
findings.Add(value)
|
||||
headingState = headingDone
|
||||
}
|
||||
}
|
||||
case captureLicense:
|
||||
licenseText.WriteString(" ")
|
||||
licenseText.WriteString(line)
|
||||
if value := extractUpToFirstPeriod(licenseText.String()); value != "" {
|
||||
for _, p := range []*regexp.Regexp{licensePattern, commonLicensePathPattern, licenseAgreementHeadingPattern} {
|
||||
if value := findLicenseClause(p, line); value != "" {
|
||||
findings.Add(value)
|
||||
headingState = headingDone
|
||||
}
|
||||
}
|
||||
|
||||
var found string
|
||||
headingState, found = advanceHeadingState(headingState, line, &licenseText)
|
||||
if found != "" {
|
||||
findings.Add(found)
|
||||
}
|
||||
}
|
||||
|
||||
if !formatVerified {
|
||||
return nil
|
||||
}
|
||||
|
||||
results := findings.List()
|
||||
@@ -91,6 +80,40 @@ func parseLicensesFromCopyright(reader io.Reader) []string {
|
||||
return results
|
||||
}
|
||||
|
||||
func advanceHeadingState(state int, line string, licenseText *strings.Builder) (int, string) {
|
||||
switch state {
|
||||
case expectHeading:
|
||||
if strings.TrimSpace(line) != "" {
|
||||
return expectDashes, ""
|
||||
}
|
||||
return headingDone, ""
|
||||
case expectDashes:
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if len(trimmed) > 0 && strings.Trim(trimmed, "-") == "" {
|
||||
return skipBlanks, ""
|
||||
}
|
||||
return headingDone, ""
|
||||
case skipBlanks:
|
||||
if strings.TrimSpace(line) == "" {
|
||||
return state, ""
|
||||
}
|
||||
licenseText.WriteString(line)
|
||||
if value := extractUpToFirstPeriod(licenseText.String()); value != "" {
|
||||
return headingDone, value
|
||||
}
|
||||
return captureLicense, ""
|
||||
case captureLicense:
|
||||
licenseText.WriteString(" ")
|
||||
licenseText.WriteString(line)
|
||||
if value := extractUpToFirstPeriod(licenseText.String()); value != "" {
|
||||
return headingDone, value
|
||||
}
|
||||
return state, ""
|
||||
case headingDone:
|
||||
}
|
||||
return state, ""
|
||||
}
|
||||
|
||||
// extractUpToFirstPeriod returns the license text up to the first period,
|
||||
// processed through ensureIsSingleLicense, or "" if no period found yet.
|
||||
func extractUpToFirstPeriod(s string) string {
|
||||
|
||||
@@ -2,6 +2,7 @@ package debian
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -14,13 +15,14 @@ func TestParseLicensesFromCopyright(t *testing.T) {
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
fixture: "testdata/copyright/libc6",
|
||||
// note: there are other licenses in this file that are not matched --we don't do full text license identification yet
|
||||
expected: []string{"GPL-2", "LGPL-2.1"},
|
||||
// no Format header; not machine-readable, returns nil
|
||||
fixture: "testdata/copyright/libc6",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
// no Format header; not machine-readable, returns nil
|
||||
fixture: "testdata/copyright/trilicense",
|
||||
expected: []string{"GPL-2", "LGPL-2.1", "MPL-1.1"},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
fixture: "testdata/copyright/liblzma5",
|
||||
@@ -31,21 +33,25 @@ func TestParseLicensesFromCopyright(t *testing.T) {
|
||||
expected: []string{"GPL-1", "GPL-2", "LGPL-2.1"},
|
||||
},
|
||||
{
|
||||
fixture: "testdata/copyright/python",
|
||||
// note: this should not capture #, Permission, This, see ... however it's not clear how to fix this (this is probably good enough)
|
||||
expected: []string{"#", "Apache", "Apache-2", "Apache-2.0", "Expat", "GPL-2", "ISC", "LGPL-2.1+", "PSF-2", "Permission", "Python", "This", "see"},
|
||||
// no Format header; not machine-readable, returns nil
|
||||
// previously this captured nonsensical values like "#", "Permission", "This", "see"
|
||||
fixture: "testdata/copyright/python",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
// no Format header; not machine-readable, returns nil
|
||||
fixture: "testdata/copyright/cuda",
|
||||
expected: []string{"NVIDIA Software License Agreement and CUDA Supplement to Software License Agreement"},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
// no Format header; not machine-readable, returns nil
|
||||
fixture: "testdata/copyright/dev-kit",
|
||||
expected: []string{"LICENSE AGREEMENT FOR NVIDIA SOFTWARE DEVELOPMENT KITS"},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
// no Format header; not machine-readable, returns nil
|
||||
fixture: "testdata/copyright/microsoft",
|
||||
expected: []string{"LICENSE AGREEMENT FOR MICROSOFT PRODUCTS"},
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -63,3 +69,71 @@ func TestParseLicensesFromCopyright(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLicensesFromCopyright_FormatHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
machineReadable bool
|
||||
}{
|
||||
{
|
||||
name: "valid http Format header",
|
||||
content: "Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n",
|
||||
machineReadable: true,
|
||||
},
|
||||
{
|
||||
name: "valid https Format header",
|
||||
content: "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n",
|
||||
machineReadable: true,
|
||||
},
|
||||
{
|
||||
name: "blank lines before Format header",
|
||||
content: "\n\nFormat: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n",
|
||||
machineReadable: true,
|
||||
},
|
||||
{
|
||||
name: "no Format header",
|
||||
content: "This is the Debian prepackaged version of foo.\n",
|
||||
machineReadable: false,
|
||||
},
|
||||
{
|
||||
name: "Format header is not first non-blank line",
|
||||
content: "Some-Field: value\nFormat: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n",
|
||||
machineReadable: false,
|
||||
},
|
||||
{
|
||||
name: "empty content",
|
||||
content: "",
|
||||
machineReadable: false,
|
||||
},
|
||||
{
|
||||
name: "only blank lines",
|
||||
content: "\n\n\n",
|
||||
machineReadable: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := parseLicensesFromCopyright(strings.NewReader(test.content))
|
||||
// parseLicensesFromCopyright returns nil for non-machine-readable
|
||||
// files and a (possibly empty) slice otherwise.
|
||||
if test.machineReadable {
|
||||
require.NotNil(t, actual)
|
||||
} else {
|
||||
require.Nil(t, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLicensesFromCopyrightInline(t *testing.T) {
|
||||
// verify that a file with License: fields but no Format header returns nil
|
||||
content := `License: GPL-2
|
||||
License: LGPL-2.1
|
||||
`
|
||||
actual := parseLicensesFromCopyright(strings.NewReader(content))
|
||||
if actual != nil {
|
||||
t.Errorf("expected nil for non-machine-readable file, got %v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,7 +730,10 @@ func TestCataloger(t *testing.T) {
|
||||
if len(p.CPEs) == 0 {
|
||||
continue
|
||||
}
|
||||
assert.Contains(t, p.Name, "Microsoft.NETCore.App")
|
||||
if runtimeFamilyFromName(p.Name) != netRuntimeFamily {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, runtimeCPEs(p.Name, p.Version), p.CPEs)
|
||||
return
|
||||
}
|
||||
t.Error("expected at least one runtime package with a CPE")
|
||||
@@ -1186,6 +1189,7 @@ func TestCataloger(t *testing.T) {
|
||||
"runtime.linux-x64.Microsoft.NETCore.DotNetHostPolicy @ 2.2.8 (/usr/share/dotnet/shared/Microsoft.NETCore.App/2.2.8/Microsoft.NETCore.App.deps.json)", // a compile target reference
|
||||
},
|
||||
expectedRels: []string{
|
||||
"Microsoft.NETCore.App @ 2.2.8 (/usr/share/dotnet/shared/Microsoft.NETCore.App/2.2.8/Microsoft.NETCore.App.deps.json) [dependency-of] helloworld @ 1.0.0 (/app/helloworld.deps.json)",
|
||||
"Microsoft.NETCore.DotNetHostPolicy @ 2.2.8 (/usr/share/dotnet/shared/Microsoft.NETCore.App/2.2.8/Microsoft.NETCore.App.deps.json) [dependency-of] Microsoft.NETCore.App @ 2.2.8 (/usr/share/dotnet/shared/Microsoft.NETCore.App/2.2.8/Microsoft.NETCore.App.deps.json)",
|
||||
"Serilog @ 2.10.0 (/app/helloworld.deps.json) [dependency-of] Serilog.Sinks.Console @ 4.0.1 (/app/helloworld.deps.json)",
|
||||
"Serilog @ 2.10.0 (/app/helloworld.deps.json) [dependency-of] helloworld @ 1.0.0 (/app/helloworld.deps.json)",
|
||||
|
||||
@@ -108,7 +108,7 @@ func (c depsBinaryCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
||||
Name: "Microsoft.NETCore.App",
|
||||
Version: version,
|
||||
Type: pkg.DotnetPkg,
|
||||
CPEs: runtimeCPEs(version),
|
||||
CPEs: runtimeCPEs("Microsoft.NETCore.App", version),
|
||||
Locations: file.NewLocationSet(locs...),
|
||||
}
|
||||
pkgs = append(pkgs, rtp)
|
||||
|
||||
@@ -22,6 +22,14 @@ var (
|
||||
versionPunctuationRegex = regexp.MustCompile(`[.,]+`)
|
||||
)
|
||||
|
||||
type runtimeFamily string
|
||||
|
||||
const (
|
||||
unknownRuntimeFamily runtimeFamily = ""
|
||||
netRuntimeFamily runtimeFamily = "net"
|
||||
aspNetCoreRuntimeFamily runtimeFamily = "aspnet_core"
|
||||
)
|
||||
|
||||
// newDotnetDepsPackage creates a new Dotnet dependency package from a logicalDepsJSONPackage.
|
||||
// Note that the new logicalDepsJSONPackage now directly holds library and executable information.
|
||||
func newDotnetDepsPackage(lp logicalDepsJSONPackage, depsLocation file.Location) *pkg.Package {
|
||||
@@ -36,7 +44,7 @@ func newDotnetDepsPackage(lp logicalDepsJSONPackage, depsLocation file.Location)
|
||||
|
||||
var cpes []cpe.CPE
|
||||
if isRuntime(name) {
|
||||
cpes = runtimeCPEs(ver)
|
||||
cpes = runtimeCPEs(name, ver)
|
||||
}
|
||||
|
||||
p := &pkg.Package{
|
||||
@@ -56,52 +64,68 @@ func newDotnetDepsPackage(lp logicalDepsJSONPackage, depsLocation file.Location)
|
||||
}
|
||||
|
||||
func isRuntime(name string) bool {
|
||||
// found in a self-contained net8 app in the deps.json for the application
|
||||
selfContainedRuntimeDependency := strings.HasPrefix(name, "runtimepack.Microsoft.NETCore.App.Runtime")
|
||||
// found in net8 apps in the deps.json for the runtime
|
||||
explicitRuntimeDependency := strings.HasPrefix(name, "Microsoft.NETCore.App.Runtime")
|
||||
// found in net2 apps in the deps.json for the runtime
|
||||
producesARuntime := strings.HasPrefix(name, "runtime") && strings.HasSuffix(name, "Microsoft.NETCore.App")
|
||||
return selfContainedRuntimeDependency || explicitRuntimeDependency || producesARuntime
|
||||
return runtimeFamilyFromName(name) != unknownRuntimeFamily
|
||||
}
|
||||
|
||||
func runtimeCPEs(ver string) []cpe.CPE {
|
||||
// .NET Core Versions
|
||||
// 2016: .NET Core 1.0, cpe:2.3:a:microsoft:dotnet_core:1.0:*:*:*:*:*:*:*
|
||||
// 2016: .NET Core 1.1, cpe:2.3:a:microsoft:dotnet_core:1.1:*:*:*:*:*:*:*
|
||||
// 2017: .NET Core 2.0, cpe:2.3:a:microsoft:dotnet_core:2.0:*:*:*:*:*:*:*
|
||||
// 2018: .NET Core 2.1, cpe:2.3:a:microsoft:dotnet_core:2.1:*:*:*:*:*:*:*
|
||||
// 2018: .NET Core 2.2, cpe:2.3:a:microsoft:dotnet_core:2.2:*:*:*:*:*:*:*
|
||||
// 2019: .NET Core 3.0, cpe:2.3:a:microsoft:dotnet_core:3.0:*:*:*:*:*:*:*
|
||||
// 2019: .NET Core 3.1, cpe:2.3:a:microsoft:dotnet_core:3.1:*:*:*:*:*:*:*
|
||||
func runtimeFamilyFromName(name string) runtimeFamily {
|
||||
normalizedName := strings.ToLower(name)
|
||||
|
||||
// Unified .NET Versions
|
||||
// 2020: .NET 5.0, cpe:2.3:a:microsoft:dotnet:5.0:*:*:*:*:*:*:*
|
||||
// 2021: .NET 6.0, cpe:2.3:a:microsoft:dotnet:6.0:*:*:*:*:*:*:*
|
||||
// 2022: .NET 7.0, cpe:2.3:a:microsoft:dotnet:7.0:*:*:*:*:*:*:*
|
||||
// 2023: .NET 8.0, cpe:2.3:a:microsoft:dotnet:8.0:*:*:*:*:*:*:*
|
||||
// 2024: .NET 9.0, cpe:2.3:a:microsoft:dotnet:9.0:*:*:*:*:*:*:*
|
||||
// 2025 ...?
|
||||
// found in self-contained or framework-dependent apps in deps.json entries
|
||||
if strings.HasPrefix(normalizedName, "runtimepack.microsoft.aspnetcore.app.runtime") ||
|
||||
strings.HasPrefix(normalizedName, "microsoft.aspnetcore.app.runtime") ||
|
||||
normalizedName == "microsoft.aspnetcore.app" ||
|
||||
(strings.HasPrefix(normalizedName, "runtime") && strings.HasSuffix(normalizedName, "microsoft.aspnetcore.app")) {
|
||||
return aspNetCoreRuntimeFamily
|
||||
}
|
||||
|
||||
fields := strings.Split(ver, ".")
|
||||
majorVersion, err := strconv.Atoi(fields[0])
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Tracef("failed to parse .NET major version from %q", ver)
|
||||
// found in self-contained, framework-dependent, and synthesized runtime packages
|
||||
if strings.HasPrefix(normalizedName, "runtimepack.microsoft.netcore.app.runtime") ||
|
||||
strings.HasPrefix(normalizedName, "microsoft.netcore.app.runtime") ||
|
||||
normalizedName == "microsoft.netcore.app" ||
|
||||
(strings.HasPrefix(normalizedName, "runtime") && strings.HasSuffix(normalizedName, "microsoft.netcore.app")) {
|
||||
return netRuntimeFamily
|
||||
}
|
||||
|
||||
return unknownRuntimeFamily
|
||||
}
|
||||
|
||||
func runtimeCPEs(name, ver string) []cpe.CPE {
|
||||
family := runtimeFamilyFromName(name)
|
||||
if family == unknownRuntimeFamily {
|
||||
return nil
|
||||
}
|
||||
|
||||
var minorVersion int
|
||||
if len(fields) > 1 {
|
||||
minorVersion, err = strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Tracef("failed to parse .NET minor version from %q", ver)
|
||||
return nil
|
||||
}
|
||||
fields := strings.Split(ver, ".")
|
||||
if len(fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
productName := "dotnet"
|
||||
if majorVersion < 5 {
|
||||
productName = "dotnet_core"
|
||||
normalizedVersionFields := make([]string, 0, len(fields))
|
||||
majorVersion, err := strconv.Atoi(fields[0])
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Tracef("failed to parse .NET runtime major version from %q", ver)
|
||||
return nil
|
||||
}
|
||||
normalizedVersionFields = append(normalizedVersionFields, strconv.Itoa(majorVersion))
|
||||
|
||||
for _, field := range fields[1:] {
|
||||
value, err := strconv.Atoi(field)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Tracef("failed to parse .NET runtime version component %q from %q", field, ver)
|
||||
return nil
|
||||
}
|
||||
normalizedVersionFields = append(normalizedVersionFields, strconv.Itoa(value))
|
||||
}
|
||||
|
||||
if len(normalizedVersionFields) == 1 {
|
||||
normalizedVersionFields = append(normalizedVersionFields, "0")
|
||||
}
|
||||
|
||||
productName := ".net"
|
||||
if family == aspNetCoreRuntimeFamily {
|
||||
productName = "asp.net_core"
|
||||
} else if majorVersion < 5 {
|
||||
productName = ".net_core"
|
||||
}
|
||||
|
||||
return []cpe.CPE{
|
||||
@@ -110,7 +134,7 @@ func runtimeCPEs(ver string) []cpe.CPE {
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: productName,
|
||||
Version: fmt.Sprintf("%d.%d", majorVersion, minorVersion),
|
||||
Version: strings.Join(normalizedVersionFields, "."),
|
||||
},
|
||||
// we didn't find this in the underlying material, but this is the convention in NVD and we are certain this is a runtime package
|
||||
Source: cpe.DeclaredSource,
|
||||
|
||||
@@ -46,6 +46,56 @@ func Test_getDepsJSONFilePrefix(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newDotnetDepsPackage_AssignsRuntimeCPEs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nameVersion string
|
||||
expected []cpe.CPE
|
||||
}{
|
||||
{
|
||||
name: ".NET runtime package",
|
||||
nameVersion: "runtimepack.Microsoft.NETCore.App.Runtime.win-x64/10.0.4",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: ".net",
|
||||
Version: "10.0.4",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ASP.NET Core runtime package",
|
||||
nameVersion: "runtimepack.Microsoft.AspNetCore.App.Runtime.win-x64/10.0.4",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "asp.net_core",
|
||||
Version: "10.0.4",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := newDotnetDepsPackage(
|
||||
logicalDepsJSONPackage{NameVersion: tc.nameVersion},
|
||||
file.NewLocation("/app/test.deps.json"),
|
||||
)
|
||||
|
||||
assert.Equal(t, tc.expected, actual.CPEs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewDotnetBinaryPackage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -400,19 +450,21 @@ func Test_spaceNormalize(t *testing.T) {
|
||||
|
||||
func TestRuntimeCPEs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
expected []cpe.CPE
|
||||
name string
|
||||
packageName string
|
||||
version string
|
||||
expected []cpe.CPE
|
||||
}{
|
||||
{
|
||||
name: ".NET Core 1.0",
|
||||
version: "1.0",
|
||||
name: ".NET Core 1.0",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "1.0",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet_core",
|
||||
Product: ".net_core",
|
||||
Version: "1.0",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -420,14 +472,15 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".NET Core 2.1",
|
||||
version: "2.1",
|
||||
name: ".NET Core 2.1",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "2.1",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet_core",
|
||||
Product: ".net_core",
|
||||
Version: "2.1",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -435,14 +488,15 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".NET Core 3.1",
|
||||
version: "3.1",
|
||||
name: ".NET Core 3.1",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "3.1",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet_core",
|
||||
Product: ".net_core",
|
||||
Version: "3.1",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -450,14 +504,15 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".NET Core 4.9 (hypothetical)",
|
||||
version: "4.9",
|
||||
name: ".NET Core 4.9 (hypothetical)",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "4.9",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet_core",
|
||||
Product: ".net_core",
|
||||
Version: "4.9",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -465,14 +520,15 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".NET 5.0",
|
||||
version: "5.0",
|
||||
name: ".NET 5.0",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "5.0",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet",
|
||||
Product: ".net",
|
||||
Version: "5.0",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -480,14 +536,15 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".NET 6.0",
|
||||
version: "6.0",
|
||||
name: ".NET 6.0",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "6.0",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet",
|
||||
Product: ".net",
|
||||
Version: "6.0",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -495,14 +552,15 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".NET 8.0",
|
||||
version: "8.0",
|
||||
name: ".NET 8.0",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "8.0",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet",
|
||||
Product: ".net",
|
||||
Version: "8.0",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -510,14 +568,15 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".NET 10.0 (future version)",
|
||||
version: "10.0",
|
||||
name: ".NET 10.0 (future version)",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "10.0",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet",
|
||||
Product: ".net",
|
||||
Version: "10.0",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -525,14 +584,47 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Patch version should not be included",
|
||||
version: "6.0.21",
|
||||
name: "Patch version should be preserved",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "6.0.21",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet",
|
||||
Product: ".net",
|
||||
Version: "6.0.21",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ASP.NET Core runtime",
|
||||
packageName: "runtimepack.Microsoft.AspNetCore.App.Runtime.win-x64",
|
||||
version: "9.0.10",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "asp.net_core",
|
||||
Version: "9.0.10",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Assumed minor version",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "6",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: ".net",
|
||||
Version: "6.0",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
@@ -540,39 +632,26 @@ func TestRuntimeCPEs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Assumed minor version",
|
||||
version: "6",
|
||||
expected: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "microsoft",
|
||||
Product: "dotnet",
|
||||
Version: "6.0",
|
||||
},
|
||||
Source: cpe.DeclaredSource,
|
||||
},
|
||||
},
|
||||
name: "Invalid version format",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "invalid",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "Invalid version format",
|
||||
version: "invalid",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "Empty version",
|
||||
version: "",
|
||||
expected: nil,
|
||||
name: "Empty version",
|
||||
packageName: "Microsoft.NETCore.App",
|
||||
version: "",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := runtimeCPEs(tc.version)
|
||||
result := runtimeCPEs(tc.packageName, tc.version)
|
||||
|
||||
if !reflect.DeepEqual(result, tc.expected) {
|
||||
t.Errorf("runtimeCPEs(%q) = %+v; want %+v",
|
||||
tc.version, result, tc.expected)
|
||||
t.Errorf("runtimeCPEs(%q, %q) = %+v; want %+v",
|
||||
tc.packageName, tc.version, result, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,6 +120,20 @@ func MatchAll(matchers ...EvidenceMatcher) EvidenceMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// MatchNone succeeds only if the matcher returns no results.
|
||||
func MatchNone(matcher EvidenceMatcher) EvidenceMatcher {
|
||||
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
|
||||
match, err := matcher(classifier, context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if match != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return []pkg.Package{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type ContextualEvidenceMatchers struct {
|
||||
CatalogerName string
|
||||
}
|
||||
|
||||
@@ -243,3 +243,40 @@ func Test_SupportingEvidenceMatcher(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchNone(t *testing.T) {
|
||||
matchingMatcher := MatchPath("**")
|
||||
notMatchingMatcher := MatchPath("will-not-match")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
matcher EvidenceMatcher
|
||||
expected bool // true if MatchNone should succeed (inner failed)
|
||||
}{
|
||||
{
|
||||
name: "inner matches, MatchNone fails",
|
||||
matcher: MatchNone(matchingMatcher),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "inner fails, MatchNone succeeds",
|
||||
matcher: MatchNone(notMatchingMatcher),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pkgs, err := tt.matcher(Classifier{}, MatcherContext{
|
||||
Location: file.NewLocation("some/path"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if tt.expected {
|
||||
assert.NotNil(t, pkgs)
|
||||
assert.Empty(t, pkgs)
|
||||
} else {
|
||||
assert.Nil(t, pkgs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@
|
||||
"github.com/go-git/go-git": [
|
||||
"cpe:2.3:a:go-git_project:go-git:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/go-jose/go-jose/v4#pkg-constants": [
|
||||
"cpe:2.3:a:go-jose_project:go-jose:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"github.com/go-resty/resty/v2": [
|
||||
"cpe:2.3:a:resty_project:resty:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
@@ -151,6 +154,9 @@
|
||||
"github.com/rezmoss/axios4go": [
|
||||
"cpe:2.3:a:rezmoss:axios4go:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/russellhaering/goxmldsig": [
|
||||
"cpe:2.3:a:goxmldsig_project:goxmldsig:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"github.com/sap/cloud-security-client-go": [
|
||||
"cpe:2.3:a:sap:cloud-security-client-go:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
@@ -160,6 +166,9 @@
|
||||
"github.com/sigstore/gitsign": [
|
||||
"cpe:2.3:a:sigstore:gitsign:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/sipeed/picoclaw": [
|
||||
"cpe:2.3:a:sipeed:picoclaw:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/sirupsen/logrus": [
|
||||
"cpe:2.3:a:turbopuffer:logrus:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
@@ -169,12 +178,27 @@
|
||||
"github.com/stacklok/minder": [
|
||||
"cpe:2.3:a:lfprojects:minder:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/tomwright/dasel": [
|
||||
"cpe:2.3:a:tomwright:dasel:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/tomwright/dasel/v2": [
|
||||
"cpe:2.3:a:tomwright:dasel:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/tomwright/dasel/v3": [
|
||||
"cpe:2.3:a:tomwright:dasel:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/valyala/fasthttp": [
|
||||
"cpe:2.3:a:fasthttp_project:fasthttp:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"github.com/whilp/git-urls": [
|
||||
"cpe:2.3:a:git-urls_project:git-urls:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/yuin/goldmark": [
|
||||
"cpe:2.3:a:yuin:goldmark:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"go.opentelemetry.io/otel": [
|
||||
"cpe:2.3:a:opentelemetry:opentelemetry:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"golang.org/x/crypto": [
|
||||
"cpe:2.3:a:go:ssh:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
@@ -2222,6 +2246,9 @@
|
||||
"626": [
|
||||
"cpe:2.3:a:626_project:626:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@aborruso/ckan-mcp-server": [
|
||||
"cpe:2.3:a:ondata:ckan_mcp_server:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@actions/artifact": [
|
||||
"cpe:2.3:a:github:actions\\/artifact:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2248,6 +2275,9 @@
|
||||
"@anthropic-ai/claude-code": [
|
||||
"cpe:2.3:a:anthropic:claude_code:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@anthropic-ai/sdk": [
|
||||
"cpe:2.3:a:anthropic:claude_sdk_for_typescript:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"@apollo/gateway": [
|
||||
"cpe:2.3:a:apollographql:apollo_gateway:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2257,6 +2287,9 @@
|
||||
"@apollosproject/data-connector-rock": [
|
||||
"cpe:2.3:a:apollosapp:data-connector-rock:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@appium/support": [
|
||||
"cpe:2.3:a:appium:appium\\/support:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@astrojs/cloudflare": [
|
||||
"cpe:2.3:a:astro:\\@astrojs\\/cloudflare:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2284,12 +2317,21 @@
|
||||
"@backstage/backend-common": [
|
||||
"cpe:2.3:a:linuxfoundation:backstage_backend-common:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@backstage/integration": [
|
||||
"cpe:2.3:a:linuxfoundation:backstage\\/integration:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@backstage/plugin-auth-backend": [
|
||||
"cpe:2.3:a:linuxfoundation:auth_backend:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@backstage/plugin-scaffolder-backend": [
|
||||
"cpe:2.3:a:linuxfoundation:backstage\\/plugin-scaffolder-backend:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"@backstage/plugin-techdocs": [
|
||||
"cpe:2.3:a:linuxfoundation:\\@backstage\\/plugin-techdocs:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@backstage/plugin-techdocs-node": [
|
||||
"cpe:2.3:a:linuxfoundation:backstage_plugin-techdocs-node:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"@backstage/techdocs-common": [
|
||||
"cpe:2.3:a:linuxfoundation:\\@backstage\\/techdocs-common:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2350,6 +2392,9 @@
|
||||
"@evershop/evershop": [
|
||||
"cpe:2.3:a:evershop:evershop:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@fastify/middie": [
|
||||
"cpe:2.3:a:openjsf:\\@fastify\\/middie:*:*:*:*:*:fastify:*:*"
|
||||
],
|
||||
"@fastly/js-compute": [
|
||||
"cpe:2.3:a:fastly:js-compute:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2365,6 +2410,12 @@
|
||||
"@flourish/sdk": [
|
||||
"cpe:2.3:a:canva:flourish_sdk:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@frontmcp/adapters": [
|
||||
"cpe:2.3:a:agentfront:\\@frontmcp\\/adapters:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@frontmcp/sdk": [
|
||||
"cpe:2.3:a:agentfront:\\@frontmcp\\/sdk:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@github/paste-markdown": [
|
||||
"cpe:2.3:a:paste-markdown_project:paste-markdown:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2389,6 +2440,9 @@
|
||||
"@joeattardi/emoji-button": [
|
||||
"cpe:2.3:a:emoji_button_project:emoji_button:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@keystone-6/core": [
|
||||
"cpe:2.3:a:keystonejs:keystone:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@keystonejs/keystone": [
|
||||
"cpe:2.3:a:keystonejs:keystone:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2407,6 +2461,9 @@
|
||||
"@misskey-dev/summaly": [
|
||||
"cpe:2.3:a:misskey:summaly:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@mobilenext/mobile-mcp": [
|
||||
"cpe:2.3:a:mobilenexthq:mobile_mcp:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@nestjs/core": [
|
||||
"cpe:2.3:a:nestjs:nest:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2431,6 +2488,12 @@
|
||||
"@octokit/webhooks": [
|
||||
"cpe:2.3:a:octokit:webhooks:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@openclaw/voice-call": [
|
||||
"cpe:2.3:a:openclaw:openclaw\\/voice-call:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@opennextjs/cloudflare": [
|
||||
"cpe:2.3:a:opennextjs:opennext_for_cloudflare:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@openzeppelin/contracts": [
|
||||
"cpe:2.3:a:openzeppelin:contracts:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2456,6 +2519,9 @@
|
||||
"@rkesters/gnuplot": [
|
||||
"cpe:2.3:a:gnuplot_project:gnuplot:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@saltcorn/server": [
|
||||
"cpe:2.3:a:saltcorn:saltcorn:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"@sap/xssec": [
|
||||
"cpe:2.3:a:sap:\\@sap\\/xssec:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2749,6 +2815,9 @@
|
||||
"box2d-native": [
|
||||
"cpe:2.3:a:box2d-native_project:box2d-native:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"brace-expansion": [
|
||||
"cpe:2.3:a:juliangruber:brace-expansion:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"braces": [
|
||||
"cpe:2.3:a:braces_project:braces:*:*:*:*:*:node.js:*:*",
|
||||
"cpe:2.3:a:jonschlinkert:braces:*:*:*:*:*:node.js:*:*"
|
||||
@@ -2859,6 +2928,9 @@
|
||||
"class-transformer": [
|
||||
"cpe:2.3:a:class-transformer_project:class-transformer:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"clawdbot": [
|
||||
"cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"cli": [
|
||||
"cpe:2.3:a:cli_project:cli:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -3100,6 +3172,9 @@
|
||||
"defaults-deep": [
|
||||
"cpe:2.3:a:defaults-deep_project:defaults-deep:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"defu": [
|
||||
"cpe:2.3:a:unjs:defu:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"defuddle": [
|
||||
"cpe:2.3:a:kepano:defuddle:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -3182,6 +3257,9 @@
|
||||
"dotty": [
|
||||
"cpe:2.3:a:dotty_project:dotty:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"drizzle-orm": [
|
||||
"cpe:2.3:a:drizzle:drizzle:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"droppy": [
|
||||
"cpe:2.3:a:droppy_project:droppy:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -3216,7 +3294,8 @@
|
||||
"cpe:2.3:a:adonisjs:edge:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"effect": [
|
||||
"cpe:2.3:a:effect_project:effect:*:*:*:*:*:node.js:*:*"
|
||||
"cpe:2.3:a:effect_project:effect:*:*:*:*:*:node.js:*:*",
|
||||
"cpe:2.3:a:effectful:effect:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"egg-scripts": [
|
||||
"cpe:2.3:a:eggjs:egg-scripts:*:*:*:*:*:node.js:*:*"
|
||||
@@ -4069,6 +4148,9 @@
|
||||
"launchpad": [
|
||||
"cpe:2.3:a:bitovi:launchpad:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"leaflet": [
|
||||
"cpe:2.3:a:leafletjs:leaflet:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"less-openui5": [
|
||||
"cpe:2.3:a:less-openui5_project:less-openui5:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -4147,6 +4229,18 @@
|
||||
"lodash": [
|
||||
"cpe:2.3:a:lodash:lodash:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"lodash-amd": [
|
||||
"cpe:2.3:a:lodash:lodash-amd:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"lodash-es": [
|
||||
"cpe:2.3:a:lodash:lodash-es:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"lodash.template": [
|
||||
"cpe:2.3:a:lodash:lodash.template:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"lodash.unset": [
|
||||
"cpe:2.3:a:lodash:lodash.unset:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"log4js": [
|
||||
"cpe:2.3:a:log4js_project:log4js:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -4235,6 +4329,9 @@
|
||||
"materialize-css": [
|
||||
"cpe:2.3:a:materializecss:materialize:*:*:*:*:*:*:node.js:*"
|
||||
],
|
||||
"mathjs": [
|
||||
"cpe:2.3:a:mathjs:mathjs:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"matrix-appservice-bridge": [
|
||||
"cpe:2.3:a:matrix:matrix-appservice-bridge:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -4377,6 +4474,9 @@
|
||||
"mpath": [
|
||||
"cpe:2.3:a:mpath_project:mpath:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"mppx": [
|
||||
"cpe:2.3:a:wevm:mppx:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"mqtt-packet": [
|
||||
"cpe:2.3:a:mqtt-packet_project:mqtt-packet:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -4443,6 +4543,9 @@
|
||||
"nanoid": [
|
||||
"cpe:2.3:a:nanoid_project:nanoid:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"nanotar": [
|
||||
"cpe:2.3:a:unjs:nanotar:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"napi-postinstall": [
|
||||
"cpe:2.3:a:un-ts:napi-postinstall:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -4739,6 +4842,9 @@
|
||||
"open-graph": [
|
||||
"cpe:2.3:a:open-graph_project:open-graph:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"openclaw": [
|
||||
"cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"opencv": [
|
||||
"cpe:2.3:a:node-opencv_project:node-opencv:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -4802,6 +4908,9 @@
|
||||
"parsejson": [
|
||||
"cpe:2.3:a:parsejson_project:parsejson:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"parsoid": [
|
||||
"cpe:2.3:a:wikimedia:parsoid:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"passport": [
|
||||
"cpe:2.3:a:passport_project:passport:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -4817,6 +4926,9 @@
|
||||
"path-parse": [
|
||||
"cpe:2.3:a:path-parse_project:path-parse:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"path-to-regexp": [
|
||||
"cpe:2.3:a:pillarjs:path-to-regexp:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"pathval": [
|
||||
"cpe:2.3:a:chaijis:pathval:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5109,6 +5221,9 @@
|
||||
"robot-js": [
|
||||
"cpe:2.3:a:getrobot:robot-js:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"rollup": [
|
||||
"cpe:2.3:a:rollupjs:rollup:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"rollup-plugin-dev-server": [
|
||||
"cpe:2.3:a:rollup-plugin-dev-server_project:rollup-plugin-dev-server:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5130,6 +5245,9 @@
|
||||
"rtcmulticonnection-client": [
|
||||
"cpe:2.3:a:rtcmulticonnection-client_project:rtcmulticonnection-client:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"rwsdk": [
|
||||
"cpe:2.3:a:redwoodjs:redwoodsdk:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"s3-uploader": [
|
||||
"cpe:2.3:a:s3-uploader_project:s3-uploader:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5381,6 +5499,9 @@
|
||||
"sockjs": [
|
||||
"cpe:2.3:a:sockjs_project:sockjs:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"spin.js": [
|
||||
"cpe:2.3:a:spin.js:spin.js:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"split-html-to-chars": [
|
||||
"cpe:2.3:a:split-html-to-chars_project:split-html-to-chars:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5702,6 +5823,9 @@
|
||||
"utils-extend": [
|
||||
"cpe:2.3:a:utils-extend_project:utils-extend:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"uuid": [
|
||||
"cpe:2.3:a:uuidjs:uuid:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"uv-tj-demo": [
|
||||
"cpe:2.3:a:uv-tj-demo_project:uv-tj-demo:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5981,6 +6105,9 @@
|
||||
"Flask-Caching": [
|
||||
"cpe:2.3:a:flask-caching_project:flask-caching:*:*:*:*:*:flask:*:*"
|
||||
],
|
||||
"Flask-HTTPAuth": [
|
||||
"cpe:2.3:a:miguelgrinberg:flask-httpauth:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"Flask-Security-Too": [
|
||||
"cpe:2.3:a:flask-security-too_project:flask-security-too:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6020,6 +6147,9 @@
|
||||
"aiohttp-session": [
|
||||
"cpe:2.3:a:aio-libs:aiohttp_session:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"ajenti.plugin.core": [
|
||||
"cpe:2.3:a:ajenti:ajenti_plugin_core:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"aniso8601": [
|
||||
"cpe:2.3:a:aniso8601_project:aniso8601:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6068,6 +6198,9 @@
|
||||
"certifi": [
|
||||
"cpe:2.3:a:certifi:certifi:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"claude-agent-sdk": [
|
||||
"cpe:2.3:a:anthropic:claude_agent_sdk:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"cleo": [
|
||||
"cpe:2.3:a:python-poetry:cleo:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6093,6 +6226,9 @@
|
||||
"cpe:2.3:a:cryptography.io:cryptography:*:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:cryptography.io:cryptography:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"curl-cffi": [
|
||||
"cpe:2.3:a:lexiforest:curl_cffi:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"cvxopt": [
|
||||
"cpe:2.3:a:cvxopt_project:cvxopt:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
@@ -6126,6 +6262,9 @@
|
||||
"django-user-sessions": [
|
||||
"cpe:2.3:a:django-user-sessions_project:django-user-sessions:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"docling-core": [
|
||||
"cpe:2.3:a:docling:docling-core:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"drf-jwt": [
|
||||
"cpe:2.3:a:styria:django-rest-framework-json_web_tokens:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6144,6 +6283,9 @@
|
||||
"ecdsa": [
|
||||
"cpe:2.3:a:tlsfuzzer:ecdsa:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"emmett": [
|
||||
"cpe:2.3:a:emmett:emmett:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"enum34": [
|
||||
"cpe:2.3:a:python:enum34:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6156,6 +6298,9 @@
|
||||
"fastapi-api-key": [
|
||||
"cpe:2.3:a:athroniaeth:fastapi_api_key:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"fastfeedparser": [
|
||||
"cpe:2.3:a:kagi:fastfeedparser:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"feedgen": [
|
||||
"cpe:2.3:a:feedgen_project:feedgen:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
@@ -6217,6 +6362,9 @@
|
||||
"jwcrypto": [
|
||||
"cpe:2.3:a:latchset:jwcrypto:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"kedro": [
|
||||
"cpe:2.3:a:linuxfoundation:kedro:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"keep": [
|
||||
"cpe:2.3:a:keep_project:keep:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6232,9 +6380,15 @@
|
||||
"langchain-experimental": [
|
||||
"cpe:2.3:a:langchain:langchain_experimental:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"lerobot": [
|
||||
"cpe:2.3:a:huggingface:lerobot:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"loguru": [
|
||||
"cpe:2.3:a:loguru_project:loguru:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"lupa": [
|
||||
"cpe:2.3:a:scoder:lupa:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"lxml": [
|
||||
"cpe:2.3:a:lxml:lxml:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6247,6 +6401,12 @@
|
||||
"mage-ai": [
|
||||
"cpe:2.3:a:mage:mage-ai:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"magic-wormhole": [
|
||||
"cpe:2.3:a:magic_wormhole_project:magic_wormhole:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"marimo": [
|
||||
"cpe:2.3:a:coreweave:marimo:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"marshmallow": [
|
||||
"cpe:2.3:a:marshmallow_project:marshmallow:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
@@ -6274,6 +6434,9 @@
|
||||
"oncall": [
|
||||
"cpe:2.3:a:linkedin:oncall:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"onnx": [
|
||||
"cpe:2.3:a:linuxfoundation:onnx:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"openapi-python-client": [
|
||||
"cpe:2.3:a:openapi-python-client_project:openapi-python-client:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6307,9 +6470,15 @@
|
||||
"pdm": [
|
||||
"cpe:2.3:a:frostming:pdm:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"picklescan": [
|
||||
"cpe:2.3:a:mmaitre314:picklescan:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"pillow-heif": [
|
||||
"cpe:2.3:a:bigcat88:pillow-heif:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"pipecat-ai": [
|
||||
"cpe:2.3:a:pipecat:pipecat:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"pipreqs": [
|
||||
"cpe:2.3:a:pipreqs_project:pipreqs:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
@@ -6319,6 +6488,9 @@
|
||||
"poetry": [
|
||||
"cpe:2.3:a:python-poetry:poetry:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"praisonaiagents": [
|
||||
"cpe:2.3:a:praison:praisonaiagents:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"pretix": [
|
||||
"cpe:2.3:a:rami:pretix:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6367,6 +6539,9 @@
|
||||
"pydash": [
|
||||
"cpe:2.3:a:derrickgilland:pydash:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"pydicom": [
|
||||
"cpe:2.3:a:pydicom:pydicom:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"pyesasky": [
|
||||
"cpe:2.3:a:esa:pyesasky:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
@@ -6391,6 +6566,9 @@
|
||||
"pypolicyd-spf": [
|
||||
"cpe:2.3:a:pypolicyd-spf_project:pypolicyd-spf:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"python-dotenv": [
|
||||
"cpe:2.3:a:saurabh-kumar:python-dotenv:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"python-gnupg": [
|
||||
"cpe:2.3:a:python:python-gnupg:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6463,6 +6641,9 @@
|
||||
"setuptools": [
|
||||
"cpe:2.3:a:python:setuptools:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"sigstore": [
|
||||
"cpe:2.3:a:linuxfoundation:sigstore-python:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"simiki": [
|
||||
"cpe:2.3:a:simiki_project:simiki:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6490,12 +6671,18 @@
|
||||
"starlette": [
|
||||
"cpe:2.3:a:encode:starlette:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"strawberry-graphql": [
|
||||
"cpe:2.3:a:strawberry:strawberry_graphql:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"tablib": [
|
||||
"cpe:2.3:a:python:tablib:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"taguette": [
|
||||
"cpe:2.3:a:taguette:taguette:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"telnyx": [
|
||||
"cpe:2.3:a:telnyx:telnyx:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"tkvideoplayer": [
|
||||
"cpe:2.3:a:python:tkvideoplayer:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6511,6 +6698,9 @@
|
||||
"unearth": [
|
||||
"cpe:2.3:a:frostming:unearth:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"unstructured": [
|
||||
"cpe:2.3:a:unstructured:unstructured:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"untangle": [
|
||||
"cpe:2.3:a:untangle_project:untangle:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
@@ -6585,6 +6775,12 @@
|
||||
"bson": [
|
||||
"cpe:2.3:a:bson_project:bson:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"bsv-sdk": [
|
||||
"cpe:2.3:a:sgbett:bsv_ruby_sdk:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"bsv-wallet": [
|
||||
"cpe:2.3:a:sgbett:bsv-wallet:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"cap-strap": [
|
||||
"cpe:2.3:a:cap-strap_project:cap-strap:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
@@ -6718,11 +6914,15 @@
|
||||
"httparty": [
|
||||
"cpe:2.3:a:jnunemaker:httparty:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"icalendar": [
|
||||
"cpe:2.3:a:icalendar_project:icalendar:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"iodine": [
|
||||
"cpe:2.3:a:boazsegev:iodine:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"json": [
|
||||
"cpe:2.3:a:json_project:json:*:*:*:*:*:ruby:*:*"
|
||||
"cpe:2.3:a:json_project:json:*:*:*:*:*:ruby:*:*",
|
||||
"cpe:2.3:a:ruby-lang:json:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"json-jwt": [
|
||||
"cpe:2.3:a:json-jwt_project:json-jwt:*:*:*:*:*:ruby:*:*"
|
||||
@@ -6754,12 +6954,18 @@
|
||||
"lean-ruport": [
|
||||
"cpe:2.3:a:lean-ruport_project:lean-ruport:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"lodash-rails": [
|
||||
"cpe:2.3:a:lodash:lodash-rails:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"loofah": [
|
||||
"cpe:2.3:a:loofah_project:loofah:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"lynx": [
|
||||
"cpe:2.3:a:lynx_project:lynx:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"mcp": [
|
||||
"cpe:2.3:a:lfprojects:mcp_ruby_sdk:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"mechanize": [
|
||||
"cpe:2.3:a:mechanize_project:mechanize:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
@@ -7189,6 +7395,9 @@
|
||||
"containers": [
|
||||
"cpe:2.3:a:containers_project:containers:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"coreutils": [
|
||||
"cpe:2.3:a:uutils:coreutils:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"cranelift-codegen": [
|
||||
"cpe:2.3:a:bytecodealliance:cranelift-codegen:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -7264,6 +7473,9 @@
|
||||
"heapless": [
|
||||
"cpe:2.3:a:heapless_project:heapless:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"httpsig-hyper": [
|
||||
"cpe:2.3:a:junkurihara:httpsig-hyper:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"ic-stable-structures": [
|
||||
"cpe:2.3:a:dfinity:stable_structures:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -7309,6 +7521,9 @@
|
||||
"libp2p-deflate": [
|
||||
"cpe:2.3:a:libp2p:libp2p-deflate:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"libp2p-gossipsub": [
|
||||
"cpe:2.3:a:protocol:libp2p-gossipsub:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"libpulse-binding": [
|
||||
"cpe:2.3:a:libpulse-binding_project:libpulse-binding:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -7339,6 +7554,9 @@
|
||||
"lucet-runtime-internals": [
|
||||
"cpe:2.3:a:lucet-runtime-internals_project:lucet-runtime-internals:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"lz4_flex": [
|
||||
"cpe:2.3:a:pseitz:lz4_flex:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"magnetic": [
|
||||
"cpe:2.3:a:magnetic_project:magnetic:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -7480,6 +7698,9 @@
|
||||
"simple_asn1": [
|
||||
"cpe:2.3:a:simple_asn1_project:simple_asn1:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"skim": [
|
||||
"cpe:2.3:a:skim-rs:skim:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"slack_morphism": [
|
||||
"cpe:2.3:a:slack_morphism_project:slack_morphism:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -7606,6 +7827,18 @@
|
||||
"zebra-consensus": [
|
||||
"cpe:2.3:a:zfnd:zebra-consensus:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"zebra-rpc": [
|
||||
"cpe:2.3:a:zfnd:zebra-rpc:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"zebra-script": [
|
||||
"cpe:2.3:a:zfnd:zebra-script:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"zebrad": [
|
||||
"cpe:2.3:a:zfnd:zebrad:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"zeptoclaw": [
|
||||
"cpe:2.3:a:zeptoclaw:zeptoclaw:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"zeroize_derive": [
|
||||
"cpe:2.3:a:zeroize_derive_project:zeroize_derive:*:*:*:*:*:rust:*:*"
|
||||
]
|
||||
@@ -12356,6 +12589,9 @@
|
||||
"friends": [
|
||||
"cpe:2.3:a:alex.kirk:friends:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"front-end-editor": [
|
||||
"cpe:2.3:a:scribu:front-end_editor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"front-end-only-users": [
|
||||
"cpe:2.3:a:etoilewebdesign:front_end_users:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14098,6 +14334,10 @@
|
||||
"link-to-bible": [
|
||||
"cpe:2.3:a:bibleserver:link_to_bible:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"link-whisper": [
|
||||
"cpe:2.3:a:linkwhisper:link_whisper:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:linkwhisper:link_whisper:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"linked-variation": [
|
||||
"cpe:2.3:a:multidots:advanced_linked_variations_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17243,6 +17483,9 @@
|
||||
"sexy-contact-form": [
|
||||
"cpe:2.3:a:creative-solutions:creative_contact_form:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"sg-cachepress": [
|
||||
"cpe:2.3:a:siteground:speed_optimizer:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"sg-security": [
|
||||
"cpe:2.3:a:siteground:security_optimizer:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18171,6 +18414,9 @@
|
||||
"sprout-invoices": [
|
||||
"cpe:2.3:a:webventures:client_invoicing_by_sprout_invoices:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"sql-chart-builder": [
|
||||
"cpe:2.3:a:guaven:sql_chart_builder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"squeeze": [
|
||||
"cpe:2.3:a:squeeze_project:squeeze:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -21864,6 +22110,9 @@
|
||||
"wpshopgermany-protectedshops": [
|
||||
"cpe:2.3:a:maennchen1:wpshopgermany_-_protected_shops:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpshopify": [
|
||||
"cpe:2.3:a:shopwp_project:shopwp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpsimpletools-log-viewer": [
|
||||
"cpe:2.3:a:wpsimpletools:basic_log_viewer:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
|
||||
@@ -6,9 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
@@ -250,7 +248,9 @@ func parseYarnLockYaml(reader io.ReadCloser) ([]yarnPackage, error) {
|
||||
return nil, fmt.Errorf("failed to unmarshal yarn v2 lockfile: %w", err)
|
||||
}
|
||||
|
||||
packages := make(map[string]yarnPackage)
|
||||
var seenPkgs = strset.New()
|
||||
|
||||
pkgs := []yarnPackage{}
|
||||
for key, value := range lockfile {
|
||||
packageName := findPackageName(key)
|
||||
if packageName == "" {
|
||||
@@ -258,10 +258,16 @@ func parseYarnLockYaml(reader io.ReadCloser) ([]yarnPackage, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
packages[packageName] = yarnPackage{Name: packageName, Version: value.Version, Resolved: value.Resolution, Integrity: value.Checksum, Dependencies: value.Dependencies}
|
||||
var pkg = yarnPackage{Name: packageName, Version: value.Version, Resolved: value.Resolution, Integrity: value.Checksum, Dependencies: value.Dependencies}
|
||||
var nameVersion = pkg.Name + "@" + pkg.Version
|
||||
|
||||
if !seenPkgs.Has(nameVersion) {
|
||||
seenPkgs.Add(nameVersion)
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
return slices.Collect(maps.Values(packages)), nil
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func (a genericYarnLockAdapter) parseYarnLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
|
||||
@@ -204,6 +204,42 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestParseYarnBerryWithDuplicates(t *testing.T) {
|
||||
var expectedRelationships []artifact.Relationship
|
||||
fixture := "testdata/yarn-berry-dups/yarn.lock"
|
||||
locations := file.NewLocationSet(file.NewLocation(fixture))
|
||||
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "async",
|
||||
Version: "3.2.6",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/async@3.2.6",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "async@npm:3.2.6",
|
||||
Integrity: "10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "async",
|
||||
Version: "0.9.2",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/async@0.9.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "async@npm:0.9.2",
|
||||
Integrity: "10c0/22ac816db119a9b84ac7182fa969b2cceacfcfa278c3efb0ac6a94d1210a4429e42c8cf6e704039aa7662e4ba62f26cecf039c91d41ceb91355dc9672c9b9ac1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
adapter := newGenericYarnLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestParseYarnLock(t *testing.T) {
|
||||
var expectedRelationships []artifact.Relationship
|
||||
fixture := "testdata/yarn/yarn.lock"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"async@npm:^3.2.3":
|
||||
version: 3.2.6
|
||||
resolution: "async@npm:3.2.6"
|
||||
checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async@npm:0.9.2":
|
||||
version: 0.9.2
|
||||
resolution: "async@npm:0.9.2"
|
||||
checksum: 10c0/22ac816db119a9b84ac7182fa969b2cceacfcfa278c3efb0ac6a94d1210a4429e42c8cf6e704039aa7662e4ba62f26cecf039c91d41ceb91355dc9672c9b9ac1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -4,7 +4,7 @@ configs: # AUTO-GENERATED - config structs and their fields
|
||||
kernel.LinuxKernelCatalogerConfig:
|
||||
fields:
|
||||
- key: CatalogModules
|
||||
description: CatalogModules enables cataloging linux kernel modules (`*.ko` files) in addition to the kernel itself.
|
||||
description: CatalogModules enables cataloging linux kernel modules (`*.ko` and compressed `*.ko.gz`, `*.ko.xz`, `*.ko.zst` files) in addition to the kernel itself.
|
||||
app_key: linux-kernel.catalog-modules
|
||||
catalogers:
|
||||
- ecosystem: linux # MANUAL
|
||||
@@ -36,6 +36,9 @@ catalogers:
|
||||
- '**/zImage'
|
||||
- '**/zImage-*'
|
||||
- '**/lib/modules/**/*.ko'
|
||||
- '**/lib/modules/**/*.ko.gz'
|
||||
- '**/lib/modules/**/*.ko.xz'
|
||||
- '**/lib/modules/**/*.ko.zst'
|
||||
metadata_types: # AUTO-GENERATED
|
||||
- pkg.LinuxKernel
|
||||
- pkg.LinuxKernelModule
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
var _ pkg.Cataloger = (*linuxKernelCataloger)(nil)
|
||||
|
||||
type LinuxKernelCatalogerConfig struct {
|
||||
// CatalogModules enables cataloging linux kernel modules (`*.ko` files) in addition to the kernel itself.
|
||||
// CatalogModules enables cataloging linux kernel modules (`*.ko` and compressed `*.ko.gz`, `*.ko.xz`, `*.ko.zst` files) in addition to the kernel itself.
|
||||
// app-config: linux-kernel.catalog-modules
|
||||
CatalogModules bool `yaml:"catalog-modules" json:"catalog-modules" mapstructure:"catalog-modules"`
|
||||
}
|
||||
@@ -47,6 +47,9 @@ var kernelArchiveGlobs = []string{
|
||||
|
||||
var kernelModuleGlobs = []string{
|
||||
"**/lib/modules/**/*.ko",
|
||||
"**/lib/modules/**/*.ko.gz",
|
||||
"**/lib/modules/**/*.ko.xz",
|
||||
"**/lib/modules/**/*.ko.zst",
|
||||
}
|
||||
|
||||
// NewLinuxKernelCataloger returns a new kernel files cataloger object.
|
||||
|
||||
@@ -3,9 +3,16 @@ package kernel
|
||||
import (
|
||||
"context"
|
||||
"debug/elf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/archives"
|
||||
|
||||
intfile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
@@ -20,7 +27,14 @@ func parseLinuxKernelModuleFile(ctx context.Context, _ file.Resolver, _ *generic
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err)
|
||||
}
|
||||
metadata, err := parseLinuxKernelModuleMetadata(unionReader)
|
||||
|
||||
moduleReader, err := decompressedModuleReader(ctx, reader.RealPath, unionReader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to decompress kernel module %q: %w", reader.RealPath, err)
|
||||
}
|
||||
defer moduleReader.Close()
|
||||
|
||||
metadata, err := parseLinuxKernelModuleMetadata(moduleReader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse kernel module metadata: %w", err)
|
||||
}
|
||||
@@ -39,6 +53,91 @@ func parseLinuxKernelModuleFile(ctx context.Context, _ file.Resolver, _ *generic
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
// decompressedModuleReader returns a UnionReader over the decompressed contents of the kernel module
|
||||
// when the path indicates compression (e.g. .ko.gz, .ko.xz, .ko.zst). For plain .ko files the original
|
||||
// reader is returned unchanged. ELF parsing requires random access (io.ReaderAt + io.Seeker), so
|
||||
// compressed streams are spilled to a temp file rather than buffered in memory — kernel modules can be
|
||||
// tens of MB decompressed and large numbers of them are scanned per cataloger run. The caller owns
|
||||
// the returned reader and must Close it; the underlying reader (r) is not closed by Close on the
|
||||
// passthrough path — its lifecycle is the caller's.
|
||||
func decompressedModuleReader(ctx context.Context, path string, r unionreader.UnionReader) (unionreader.UnionReader, error) {
|
||||
// fast path: plain .ko files don't need format sniffing
|
||||
if strings.HasSuffix(path, ".ko") {
|
||||
return &nopCloseUnionReader{UnionReader: r}, nil
|
||||
}
|
||||
|
||||
format, stream, err := intfile.IdentifyArchive(ctx, path, r)
|
||||
if err != nil {
|
||||
if errors.Is(err, archives.NoMatch) {
|
||||
return passthrough(r)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to identify compression format: %w", err)
|
||||
}
|
||||
|
||||
decompressor, ok := format.(archives.Decompressor)
|
||||
if !ok {
|
||||
// not a single-stream compressed format (e.g. a tar/zip archive); treat as a plain .ko
|
||||
return passthrough(r)
|
||||
}
|
||||
|
||||
rc, err := decompressor.OpenReader(stream)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open %s decompression stream: %w", format.Extension(), err)
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
return nil, fmt.Errorf("no temp dir factory in context")
|
||||
}
|
||||
tempFile, fileCleanup, err := td.NewFile("syft-kmod-*.ko") //nolint:gocritic // cleanup outlives this function — runs from tempFileUnionReader.Close on the returned reader
|
||||
if err != nil {
|
||||
fileCleanup()
|
||||
return nil, fmt.Errorf("unable to create temp file for decompressed kernel module: %w", err)
|
||||
}
|
||||
tfr := &tempFileUnionReader{File: tempFile, cleanup: fileCleanup}
|
||||
|
||||
if _, err := io.Copy(tempFile, rc); err != nil {
|
||||
_ = tfr.Close()
|
||||
return nil, fmt.Errorf("unable to write decompressed kernel module: %w", err)
|
||||
}
|
||||
if _, err := tempFile.Seek(0, io.SeekStart); err != nil {
|
||||
_ = tfr.Close()
|
||||
return nil, fmt.Errorf("unable to rewind decompressed kernel module: %w", err)
|
||||
}
|
||||
|
||||
return tfr, nil
|
||||
}
|
||||
|
||||
// passthrough returns the original reader rewound to offset 0. IdentifyArchive consumes bytes to
|
||||
// sniff magic; we rewind explicitly so callers don't have to reason about the seeker's position.
|
||||
func passthrough(r unionreader.UnionReader) (unionreader.UnionReader, error) {
|
||||
if _, err := r.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("unable to rewind reader after format sniff: %w", err)
|
||||
}
|
||||
return &nopCloseUnionReader{UnionReader: r}, nil
|
||||
}
|
||||
|
||||
// nopCloseUnionReader wraps a UnionReader so that Close is a no-op. used on the passthrough path
|
||||
// where the underlying reader's lifecycle is owned by the caller, not by us.
|
||||
type nopCloseUnionReader struct {
|
||||
unionreader.UnionReader
|
||||
}
|
||||
|
||||
func (*nopCloseUnionReader) Close() error { return nil }
|
||||
|
||||
// tempFileUnionReader is a UnionReader backed by a temp file; Close closes the file and removes it.
|
||||
type tempFileUnionReader struct {
|
||||
*os.File
|
||||
cleanup func()
|
||||
}
|
||||
|
||||
func (t *tempFileUnionReader) Close() error {
|
||||
err := t.File.Close()
|
||||
t.cleanup()
|
||||
return err
|
||||
}
|
||||
|
||||
func parseLinuxKernelModuleMetadata(r unionreader.UnionReader) (p *pkg.LinuxKernelModule, err error) {
|
||||
// filename: /lib/modules/5.15.0-1031-aws/kernel/zfs/zzstd.ko
|
||||
// version: 1.4.5a
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ulikunitz/xz"
|
||||
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
func testContext(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
td := tmpdir.FromPath(t.TempDir())
|
||||
return tmpdir.WithValue(context.Background(), td)
|
||||
}
|
||||
|
||||
// minimalKOBytes constructs a minimal ELF64 LE relocatable object with a .modinfo
|
||||
// section containing the given null-terminated key=value entries.
|
||||
func minimalKOBytes(entries []string) []byte {
|
||||
// .modinfo section: each entry is key=value\0
|
||||
var modinfo bytes.Buffer
|
||||
for _, e := range entries {
|
||||
modinfo.WriteString(e)
|
||||
modinfo.WriteByte(0)
|
||||
}
|
||||
|
||||
// section header string table — embeds names of all sections back-to-back, leading null required.
|
||||
// offsets below index into this blob.
|
||||
shstrtab := []byte("\x00.modinfo\x00.shstrtab\x00")
|
||||
const (
|
||||
modinfoNameOff uint32 = 1
|
||||
shstrtabNameOff uint32 = 10
|
||||
)
|
||||
|
||||
const (
|
||||
ehdrSize uint64 = 64
|
||||
shdrSize uint64 = 64
|
||||
numSections uint16 = 3 // null + .modinfo + .shstrtab
|
||||
)
|
||||
|
||||
// layout: [ehdr][modinfo][shstrtab][pad to 8][section headers]
|
||||
var (
|
||||
modinfoOff = ehdrSize
|
||||
modinfoSize = uint64(modinfo.Len())
|
||||
shstrtabOff = modinfoOff + modinfoSize
|
||||
shstrtabSize = uint64(len(shstrtab))
|
||||
shdrsOff = alignUp(shstrtabOff+shstrtabSize, 8)
|
||||
)
|
||||
|
||||
header := elf.Header64{
|
||||
Ident: [16]byte{
|
||||
0x7f, 'E', 'L', 'F',
|
||||
byte(elf.ELFCLASS64),
|
||||
byte(elf.ELFDATA2LSB),
|
||||
byte(elf.EV_CURRENT),
|
||||
},
|
||||
Type: uint16(elf.ET_REL),
|
||||
Machine: uint16(elf.EM_X86_64),
|
||||
Version: uint32(elf.EV_CURRENT),
|
||||
Shoff: shdrsOff,
|
||||
Ehsize: uint16(ehdrSize),
|
||||
Shentsize: uint16(shdrSize),
|
||||
Shnum: numSections,
|
||||
Shstrndx: numSections - 1, // .shstrtab is last
|
||||
}
|
||||
|
||||
sections := []elf.Section64{
|
||||
{}, // SHN_UNDEF
|
||||
{
|
||||
Name: modinfoNameOff,
|
||||
Type: uint32(elf.SHT_PROGBITS),
|
||||
Off: modinfoOff,
|
||||
Size: modinfoSize,
|
||||
Addralign: 1,
|
||||
},
|
||||
{
|
||||
Name: shstrtabNameOff,
|
||||
Type: uint32(elf.SHT_STRTAB),
|
||||
Off: shstrtabOff,
|
||||
Size: shstrtabSize,
|
||||
Addralign: 1,
|
||||
},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = binary.Write(&buf, binary.LittleEndian, header)
|
||||
buf.Write(modinfo.Bytes())
|
||||
buf.Write(shstrtab)
|
||||
for uint64(buf.Len()) < shdrsOff {
|
||||
buf.WriteByte(0)
|
||||
}
|
||||
for _, s := range sections {
|
||||
_ = binary.Write(&buf, binary.LittleEndian, s)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func alignUp(v, align uint64) uint64 {
|
||||
if v%align == 0 {
|
||||
return v
|
||||
}
|
||||
return v + (align - v%align)
|
||||
}
|
||||
|
||||
func gzCompress(data []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
w := gzip.NewWriter(&buf)
|
||||
_, _ = w.Write(data)
|
||||
_ = w.Close()
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func xzCompress(data []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
w, _ := xz.NewWriter(&buf)
|
||||
_, _ = w.Write(data)
|
||||
_ = w.Close()
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func zstCompress(data []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
w, _ := zstd.NewWriter(&buf)
|
||||
_, _ = w.Write(data)
|
||||
_ = w.Close()
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// makeLocationReadCloser wraps a byte slice as a file.LocationReadCloser with the given path.
|
||||
func makeLocationReadCloser(path string, data []byte) file.LocationReadCloser {
|
||||
return file.LocationReadCloser{
|
||||
Location: file.NewVirtualLocation(path, path),
|
||||
ReadCloser: io.NopCloser(bytes.NewReader(data)),
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLinuxKernelModuleFile_Compressed(t *testing.T) {
|
||||
modinfo := []string{
|
||||
"name=dummy_mod",
|
||||
"version=1.2.3",
|
||||
"vermagic=6.1.0-rc1 SMP mod_unload",
|
||||
"license=GPL v2",
|
||||
}
|
||||
koBytes := minimalKOBytes(modinfo)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
data []byte
|
||||
wantName string
|
||||
wantVer string
|
||||
wantKV string // expected KernelVersion from vermagic
|
||||
}{
|
||||
{
|
||||
name: "uncompressed .ko",
|
||||
path: "/lib/modules/6.1.0-rc1/kernel/dummy_mod.ko",
|
||||
data: koBytes,
|
||||
wantName: "dummy_mod",
|
||||
wantVer: "1.2.3",
|
||||
wantKV: "6.1.0-rc1",
|
||||
},
|
||||
{
|
||||
name: "gzip-compressed .ko.gz",
|
||||
path: "/lib/modules/6.1.0-rc1/kernel/dummy_mod.ko.gz",
|
||||
data: gzCompress(koBytes),
|
||||
wantName: "dummy_mod",
|
||||
wantVer: "1.2.3",
|
||||
wantKV: "6.1.0-rc1",
|
||||
},
|
||||
{
|
||||
name: "xz-compressed .ko.xz",
|
||||
path: "/lib/modules/6.1.0-rc1/kernel/dummy_mod.ko.xz",
|
||||
data: xzCompress(koBytes),
|
||||
wantName: "dummy_mod",
|
||||
wantVer: "1.2.3",
|
||||
wantKV: "6.1.0-rc1",
|
||||
},
|
||||
{
|
||||
name: "zstd-compressed .ko.zst",
|
||||
path: "/lib/modules/6.1.0-rc1/kernel/dummy_mod.ko.zst",
|
||||
data: zstCompress(koBytes),
|
||||
wantName: "dummy_mod",
|
||||
wantVer: "1.2.3",
|
||||
wantKV: "6.1.0-rc1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader := makeLocationReadCloser(tt.path, tt.data)
|
||||
pkgs, rels, err := parseLinuxKernelModuleFile(testContext(t), nil, &generic.Environment{}, reader)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, pkgs, 1)
|
||||
assert.Empty(t, rels)
|
||||
assert.Equal(t, tt.wantName, pkgs[0].Name)
|
||||
assert.Equal(t, tt.wantVer, pkgs[0].Version)
|
||||
|
||||
meta, ok := pkgs[0].Metadata.(pkg.LinuxKernelModule)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, tt.wantKV, meta.KernelVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecompressedModuleReader(t *testing.T) {
|
||||
koBytes := minimalKOBytes([]string{"name=test", "vermagic=5.15.0 SMP mod_unload"})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
data []byte
|
||||
}{
|
||||
{"uncompressed", "/lib/modules/5.15.0/kernel/test.ko", koBytes},
|
||||
{"gz", "/lib/modules/5.15.0/kernel/test.ko.gz", gzCompress(koBytes)},
|
||||
{"xz", "/lib/modules/5.15.0/kernel/test.ko.xz", xzCompress(koBytes)},
|
||||
{"zst", "/lib/modules/5.15.0/kernel/test.ko.zst", zstCompress(koBytes)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
br := bytes.NewReader(tt.data)
|
||||
wrapped := struct {
|
||||
io.ReadCloser
|
||||
io.ReaderAt
|
||||
io.Seeker
|
||||
}{
|
||||
ReadCloser: io.NopCloser(br),
|
||||
ReaderAt: br,
|
||||
Seeker: br,
|
||||
}
|
||||
got, err := decompressedModuleReader(testContext(t), tt.path, wrapped)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
t.Cleanup(func() { _ = got.Close() })
|
||||
|
||||
b, err := io.ReadAll(got)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, koBytes, b, "decompressed bytes should match original .ko bytes")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecompressedModuleReader_TempFileRemovedOnClose(t *testing.T) {
|
||||
koBytes := minimalKOBytes([]string{"name=test", "vermagic=5.15.0 SMP"})
|
||||
data := gzCompress(koBytes)
|
||||
|
||||
br := bytes.NewReader(data)
|
||||
wrapped := struct {
|
||||
io.ReadCloser
|
||||
io.ReaderAt
|
||||
io.Seeker
|
||||
}{
|
||||
ReadCloser: io.NopCloser(br),
|
||||
ReaderAt: br,
|
||||
Seeker: br,
|
||||
}
|
||||
|
||||
got, err := decompressedModuleReader(testContext(t), "/test.ko.gz", wrapped)
|
||||
require.NoError(t, err)
|
||||
|
||||
tfr, ok := got.(*tempFileUnionReader)
|
||||
require.True(t, ok, "expected compressed path to spill to a temp file")
|
||||
path := tfr.File.Name()
|
||||
|
||||
_, err = os.Stat(path)
|
||||
require.NoError(t, err, "temp file should exist before Close")
|
||||
|
||||
require.NoError(t, got.Close())
|
||||
|
||||
_, err = os.Stat(path)
|
||||
assert.True(t, os.IsNotExist(err), "temp file should be removed after Close, got err=%v", err)
|
||||
}
|
||||
@@ -73,63 +73,63 @@ func Test_InterpreterCataloger(t *testing.T) {
|
||||
fixture: "image-apache",
|
||||
expectedPkgs: []string{
|
||||
// interpreters
|
||||
"libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
|
||||
// extensions
|
||||
"calendar @ 8.2.30 (/usr/lib/php/20220829/calendar.so)",
|
||||
"ctype @ 8.2.30 (/usr/lib/php/20220829/ctype.so)",
|
||||
"exif @ 8.2.30 (/usr/lib/php/20220829/exif.so)",
|
||||
"ffi @ 8.2.30 (/usr/lib/php/20220829/ffi.so)",
|
||||
"fileinfo @ 8.2.30 (/usr/lib/php/20220829/fileinfo.so)",
|
||||
"ftp @ 8.2.30 (/usr/lib/php/20220829/ftp.so)",
|
||||
"gettext @ 8.2.30 (/usr/lib/php/20220829/gettext.so)",
|
||||
"iconv @ 8.2.30 (/usr/lib/php/20220829/iconv.so)",
|
||||
"mysqli @ 8.2.30 (/usr/lib/php/20220829/mysqli.so)",
|
||||
"opcache @ 8.2.30 (/usr/lib/php/20220829/opcache.so)",
|
||||
"pdo @ 8.2.30 (/usr/lib/php/20220829/pdo.so)",
|
||||
"pdo_mysql @ 8.2.30 (/usr/lib/php/20220829/pdo_mysql.so)",
|
||||
"phar @ 8.2.30 (/usr/lib/php/20220829/phar.so)",
|
||||
"posix @ 8.2.30 (/usr/lib/php/20220829/posix.so)",
|
||||
"readline @ 8.2.30 (/usr/lib/php/20220829/readline.so)",
|
||||
"shmop @ 8.2.30 (/usr/lib/php/20220829/shmop.so)",
|
||||
"simplexml @ 8.2.30 (/usr/lib/php/20220829/simplexml.so)",
|
||||
"sockets @ 8.2.30 (/usr/lib/php/20220829/sockets.so)",
|
||||
"sysvmsg @ 8.2.30 (/usr/lib/php/20220829/sysvmsg.so)",
|
||||
"sysvsem @ 8.2.30 (/usr/lib/php/20220829/sysvsem.so)",
|
||||
"sysvshm @ 8.2.30 (/usr/lib/php/20220829/sysvshm.so)",
|
||||
"tokenizer @ 8.2.30 (/usr/lib/php/20220829/tokenizer.so)",
|
||||
"xml @ 8.2.30 (/usr/lib/php/20220829/xml.so)",
|
||||
"xmlreader @ 8.2.30 (/usr/lib/php/20220829/xmlreader.so)",
|
||||
"xmlwriter @ 8.2.30 (/usr/lib/php/20220829/xmlwriter.so)",
|
||||
"xsl @ 8.2.30 (/usr/lib/php/20220829/xsl.so)",
|
||||
"calendar @ 8.2.31 (/usr/lib/php/20220829/calendar.so)",
|
||||
"ctype @ 8.2.31 (/usr/lib/php/20220829/ctype.so)",
|
||||
"exif @ 8.2.31 (/usr/lib/php/20220829/exif.so)",
|
||||
"ffi @ 8.2.31 (/usr/lib/php/20220829/ffi.so)",
|
||||
"fileinfo @ 8.2.31 (/usr/lib/php/20220829/fileinfo.so)",
|
||||
"ftp @ 8.2.31 (/usr/lib/php/20220829/ftp.so)",
|
||||
"gettext @ 8.2.31 (/usr/lib/php/20220829/gettext.so)",
|
||||
"iconv @ 8.2.31 (/usr/lib/php/20220829/iconv.so)",
|
||||
"mysqli @ 8.2.31 (/usr/lib/php/20220829/mysqli.so)",
|
||||
"opcache @ 8.2.31 (/usr/lib/php/20220829/opcache.so)",
|
||||
"pdo @ 8.2.31 (/usr/lib/php/20220829/pdo.so)",
|
||||
"pdo_mysql @ 8.2.31 (/usr/lib/php/20220829/pdo_mysql.so)",
|
||||
"phar @ 8.2.31 (/usr/lib/php/20220829/phar.so)",
|
||||
"posix @ 8.2.31 (/usr/lib/php/20220829/posix.so)",
|
||||
"readline @ 8.2.31 (/usr/lib/php/20220829/readline.so)",
|
||||
"shmop @ 8.2.31 (/usr/lib/php/20220829/shmop.so)",
|
||||
"simplexml @ 8.2.31 (/usr/lib/php/20220829/simplexml.so)",
|
||||
"sockets @ 8.2.31 (/usr/lib/php/20220829/sockets.so)",
|
||||
"sysvmsg @ 8.2.31 (/usr/lib/php/20220829/sysvmsg.so)",
|
||||
"sysvsem @ 8.2.31 (/usr/lib/php/20220829/sysvsem.so)",
|
||||
"sysvshm @ 8.2.31 (/usr/lib/php/20220829/sysvshm.so)",
|
||||
"tokenizer @ 8.2.31 (/usr/lib/php/20220829/tokenizer.so)",
|
||||
"xml @ 8.2.31 (/usr/lib/php/20220829/xml.so)",
|
||||
"xmlreader @ 8.2.31 (/usr/lib/php/20220829/xmlreader.so)",
|
||||
"xmlwriter @ 8.2.31 (/usr/lib/php/20220829/xmlwriter.so)",
|
||||
"xsl @ 8.2.31 (/usr/lib/php/20220829/xsl.so)",
|
||||
},
|
||||
expectedRels: []string{
|
||||
"calendar @ 8.2.30 (/usr/lib/php/20220829/calendar.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ctype @ 8.2.30 (/usr/lib/php/20220829/ctype.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"exif @ 8.2.30 (/usr/lib/php/20220829/exif.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ffi @ 8.2.30 (/usr/lib/php/20220829/ffi.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"fileinfo @ 8.2.30 (/usr/lib/php/20220829/fileinfo.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ftp @ 8.2.30 (/usr/lib/php/20220829/ftp.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"gettext @ 8.2.30 (/usr/lib/php/20220829/gettext.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"iconv @ 8.2.30 (/usr/lib/php/20220829/iconv.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"mysqli @ 8.2.30 (/usr/lib/php/20220829/mysqli.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"opcache @ 8.2.30 (/usr/lib/php/20220829/opcache.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"pdo @ 8.2.30 (/usr/lib/php/20220829/pdo.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"pdo_mysql @ 8.2.30 (/usr/lib/php/20220829/pdo_mysql.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"phar @ 8.2.30 (/usr/lib/php/20220829/phar.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"posix @ 8.2.30 (/usr/lib/php/20220829/posix.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"readline @ 8.2.30 (/usr/lib/php/20220829/readline.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"shmop @ 8.2.30 (/usr/lib/php/20220829/shmop.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"simplexml @ 8.2.30 (/usr/lib/php/20220829/simplexml.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sockets @ 8.2.30 (/usr/lib/php/20220829/sockets.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvmsg @ 8.2.30 (/usr/lib/php/20220829/sysvmsg.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvsem @ 8.2.30 (/usr/lib/php/20220829/sysvsem.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvshm @ 8.2.30 (/usr/lib/php/20220829/sysvshm.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"tokenizer @ 8.2.30 (/usr/lib/php/20220829/tokenizer.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xml @ 8.2.30 (/usr/lib/php/20220829/xml.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xmlreader @ 8.2.30 (/usr/lib/php/20220829/xmlreader.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xmlwriter @ 8.2.30 (/usr/lib/php/20220829/xmlwriter.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xsl @ 8.2.30 (/usr/lib/php/20220829/xsl.so) [dependency-of] libphp @ 8.2.30 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"calendar @ 8.2.31 (/usr/lib/php/20220829/calendar.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ctype @ 8.2.31 (/usr/lib/php/20220829/ctype.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"exif @ 8.2.31 (/usr/lib/php/20220829/exif.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ffi @ 8.2.31 (/usr/lib/php/20220829/ffi.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"fileinfo @ 8.2.31 (/usr/lib/php/20220829/fileinfo.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ftp @ 8.2.31 (/usr/lib/php/20220829/ftp.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"gettext @ 8.2.31 (/usr/lib/php/20220829/gettext.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"iconv @ 8.2.31 (/usr/lib/php/20220829/iconv.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"mysqli @ 8.2.31 (/usr/lib/php/20220829/mysqli.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"opcache @ 8.2.31 (/usr/lib/php/20220829/opcache.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"pdo @ 8.2.31 (/usr/lib/php/20220829/pdo.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"pdo_mysql @ 8.2.31 (/usr/lib/php/20220829/pdo_mysql.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"phar @ 8.2.31 (/usr/lib/php/20220829/phar.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"posix @ 8.2.31 (/usr/lib/php/20220829/posix.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"readline @ 8.2.31 (/usr/lib/php/20220829/readline.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"shmop @ 8.2.31 (/usr/lib/php/20220829/shmop.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"simplexml @ 8.2.31 (/usr/lib/php/20220829/simplexml.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sockets @ 8.2.31 (/usr/lib/php/20220829/sockets.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvmsg @ 8.2.31 (/usr/lib/php/20220829/sysvmsg.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvsem @ 8.2.31 (/usr/lib/php/20220829/sysvsem.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvshm @ 8.2.31 (/usr/lib/php/20220829/sysvshm.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"tokenizer @ 8.2.31 (/usr/lib/php/20220829/tokenizer.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xml @ 8.2.31 (/usr/lib/php/20220829/xml.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xmlreader @ 8.2.31 (/usr/lib/php/20220829/xmlreader.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xmlwriter @ 8.2.31 (/usr/lib/php/20220829/xmlwriter.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xsl @ 8.2.31 (/usr/lib/php/20220829/xsl.so) [dependency-of] libphp @ 8.2.31 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+8
-1
@@ -1,6 +1,13 @@
|
||||
FROM --platform=linux/amd64 httpd:2.4.63-bookworm AS builder
|
||||
|
||||
RUN apt update -y && apt install -y libapache2-mod-php php8.2-memcache php8.2-memcache php8.2-xml php8.2-mysqli php8.2-opcache
|
||||
# pin php8.2 packages to avoid version drift when debian publishes security updates
|
||||
ARG PHP_VERSION=8.2.31-1~deb12u1
|
||||
RUN apt update -y && apt install -y \
|
||||
libapache2-mod-php8.2=${PHP_VERSION} \
|
||||
php8.2-common=${PHP_VERSION} \
|
||||
php8.2-xml=${PHP_VERSION} \
|
||||
php8.2-mysql=${PHP_VERSION} \
|
||||
php8.2-opcache=${PHP_VERSION}
|
||||
|
||||
FROM busybox:latest
|
||||
|
||||
|
||||
@@ -170,8 +170,8 @@ func (rp requirementsParser) parseRequirementsTxt(ctx context.Context, _ file.Re
|
||||
}
|
||||
|
||||
func parseVersion(version string, guessFromConstraint bool) string {
|
||||
if isPinnedConstraint(version) {
|
||||
return strings.TrimSpace(strings.ReplaceAll(version, "==", ""))
|
||||
if version := parsePinnedVersion(version); version != "" {
|
||||
return version
|
||||
}
|
||||
|
||||
if guessFromConstraint {
|
||||
@@ -181,15 +181,26 @@ func parseVersion(version string, guessFromConstraint bool) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func isPinnedConstraint(version string) bool {
|
||||
return strings.Contains(version, "==") && !strings.ContainsAny(version, "*,<>!")
|
||||
func parsePinnedVersion(version string) string {
|
||||
version = strings.TrimSpace(version)
|
||||
if strings.ContainsAny(version, "*,<>!") {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, operator := range []string{"===", "=="} {
|
||||
if strings.HasPrefix(version, operator) && !strings.HasPrefix(version, operator+"=") {
|
||||
return strings.TrimSpace(strings.TrimPrefix(version, operator))
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func guessVersion(constraint string) string {
|
||||
// handle "2.8.*" -> "2.8.0"
|
||||
constraint = strings.ReplaceAll(constraint, "*", "0")
|
||||
if isPinnedConstraint(constraint) {
|
||||
return strings.TrimSpace(strings.ReplaceAll(constraint, "==", ""))
|
||||
if version := parsePinnedVersion(constraint); version != "" {
|
||||
return version
|
||||
}
|
||||
|
||||
constraints := strings.Split(constraint, ",")
|
||||
|
||||
@@ -29,6 +29,18 @@ func TestParseRequirementsTxt(t *testing.T) {
|
||||
VersionConstraint: "== 4.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "urllib3",
|
||||
Version: "1.26.20",
|
||||
PURL: "pkg:pypi/urllib3@1.26.20",
|
||||
Locations: locations,
|
||||
Language: pkg.Python,
|
||||
Type: pkg.PythonPkg,
|
||||
Metadata: pkg.PythonRequirementsEntry{
|
||||
Name: "urllib3",
|
||||
VersionConstraint: "===1.26.20",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
@@ -294,6 +306,14 @@ func Test_newRequirement(t *testing.T) {
|
||||
VersionConstraint: "==2.8",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "arbitrary equality",
|
||||
raw: "urllib3===1.26.20",
|
||||
want: &unprocessedRequirement{
|
||||
Name: "urllib3",
|
||||
VersionConstraint: "===1.26.20",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "comment + constraint",
|
||||
raw: "Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*",
|
||||
@@ -363,6 +383,11 @@ func Test_parseVersion(t *testing.T) {
|
||||
version: " == 1.0.0 ",
|
||||
want: "1.0.0",
|
||||
},
|
||||
{
|
||||
name: "arbitrary equality constraint",
|
||||
version: " === 1.26.20 ",
|
||||
want: "1.26.20",
|
||||
},
|
||||
{
|
||||
name: "resolve lowest, simple constraint",
|
||||
version: " >= 1.0.0 ",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
flask == 4.0.0
|
||||
urllib3===1.26.20
|
||||
# a line that is ignored
|
||||
sqlalchemy >= 1.0.0, <= 2.0.0, != 3.0.0, <= 3.0.0
|
||||
foo == 1.0.0 # a comment that needs to be ignored
|
||||
|
||||
+1
-1
@@ -220,7 +220,7 @@ func TypeByName(name string) Type {
|
||||
return CondaPkg
|
||||
case packageurl.TypePub:
|
||||
return DartPubPkg
|
||||
case "dotnet": // here to support legacy use cases
|
||||
case "dotnet", packageurl.TypeNuget: // "dotnet" is here to support legacy use cases; "nuget" is the canonical purl type
|
||||
return DotnetPkg
|
||||
case packageurl.TypeCocoapods:
|
||||
return CocoapodsPkg
|
||||
|
||||
@@ -58,6 +58,11 @@ func TestTypeFromPURL(t *testing.T) {
|
||||
purl: "pkg:dotnet/Microsoft.CodeAnalysis.Razor@2.2.0",
|
||||
expected: DotnetPkg,
|
||||
},
|
||||
{
|
||||
name: "nuget purl maps to dotnet",
|
||||
purl: "pkg:nuget/Minio@7.0.0",
|
||||
expected: DotnetPkg,
|
||||
},
|
||||
{
|
||||
purl: "pkg:composer/laravel/laravel@5.5.0",
|
||||
expected: PhpComposerPkg,
|
||||
|
||||
@@ -145,6 +145,9 @@ func GetDirectoryExclusionFunctions(root string, exclusions []string) ([]fileres
|
||||
// check exclusions for supported paths, these are all relative to the "scan root"
|
||||
if strings.HasPrefix(exclusion, "./") || strings.HasPrefix(exclusion, "*/") || strings.HasPrefix(exclusion, "**/") {
|
||||
exclusion = strings.TrimPrefix(exclusion, "./")
|
||||
// a trailing slash signals a directory but is otherwise discarded by doublestar.Match,
|
||||
// causing the pattern to silently match nothing (see issue #4839)
|
||||
exclusion = strings.TrimSuffix(exclusion, "/")
|
||||
exclusions[idx] = root + exclusion
|
||||
} else {
|
||||
errors = append(errors, exclusion)
|
||||
|
||||
@@ -175,6 +175,18 @@ func Test_DirectorySource_Exclusions(t *testing.T) {
|
||||
},
|
||||
exclusions: []string{"./target"},
|
||||
},
|
||||
{
|
||||
input: "testdata/image-simple",
|
||||
desc: "exclude explicit directory with trailing slash (issue #4839)",
|
||||
glob: "**",
|
||||
expected: []string{
|
||||
"Dockerfile",
|
||||
"file-1.txt",
|
||||
"file-2.txt",
|
||||
//"target/really/nested/file-3.txt", // explicitly skipped
|
||||
},
|
||||
exclusions: []string{"./target/"},
|
||||
},
|
||||
{
|
||||
input: "testdata/image-simple",
|
||||
desc: "exclude explicit file relative to the root",
|
||||
@@ -329,6 +341,14 @@ func Test_getDirectoryExclusionFunctions_crossPlatform(t *testing.T) {
|
||||
finfo: file.ManualInfo{ModeValue: os.ModeDir},
|
||||
walkHint: fs.SkipDir,
|
||||
},
|
||||
{
|
||||
desc: "directory exclusion with trailing slash (issue #4839)",
|
||||
root: "/usr/var",
|
||||
path: "/usr/var/lib",
|
||||
exclude: "./lib/",
|
||||
finfo: file.ManualInfo{ModeValue: os.ModeDir},
|
||||
walkHint: fs.SkipDir,
|
||||
},
|
||||
{
|
||||
desc: "no file info",
|
||||
root: "/",
|
||||
|
||||
Reference in New Issue
Block a user