Skip to content

feat: multi-ecosystem support (12 GHSA ecosystems) + src/ refactor #46

feat: multi-ecosystem support (12 GHSA ecosystems) + src/ refactor

feat: multi-ecosystem support (12 GHSA ecosystems) + src/ refactor #46

Workflow file for this run

name: Test Package Checker
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
jobs:
test-fixtures:
name: Run Tests on Fixtures
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Make script executable
run: chmod +x script.sh
- name: Run all tests
run: |
set +e # Don't exit on first error
FAILED_TESTS=0
PASSED_TESTS=0
echo "=========================================="
echo "Running Package Checker Tests"
echo "=========================================="
echo ""
# Test npm project
echo "📦 Testing npm project..."
cd test-fixtures/npm-project
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 1 ]; then
echo "✅ npm project: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ npm project: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test yarn project
echo "📦 Testing yarn project..."
cd test-fixtures/yarn-project
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 1 ]; then
echo "✅ yarn project: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ yarn project: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test yarn berry project
echo "📦 Testing yarn berry project..."
cd test-fixtures/yarn-berry-project
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 1 ]; then
echo "✅ yarn berry project: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ yarn berry project: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test pnpm project
echo "📦 Testing pnpm project..."
cd test-fixtures/pnpm-project
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 1 ]; then
echo "✅ pnpm project: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ pnpm project: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test bun project
echo "📦 Testing bun project..."
cd test-fixtures/bun-project
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 1 ]; then
echo "✅ bun project: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ bun project: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test deno project
echo "📦 Testing deno project..."
cd test-fixtures/deno-project
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 1 ]; then
echo "✅ deno project: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ deno project: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test npm-shrinkwrap project
echo "📦 Testing npm-shrinkwrap project..."
cd test-fixtures/npm-shrinkwrap-project
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 1 ]; then
echo "✅ npm-shrinkwrap project: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ npm-shrinkwrap project: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test rust project (Cargo.lock) against the shared multi-ecosystem
# test feed: exactly one vulnerable package (time@0.1.45), serde
# must NOT be flagged (its test entry is a version that doesn't match).
echo "📦 Testing rust project..."
cd test-fixtures/rust-project
RUST_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl)
RUST_EXIT=$?
if [ "$RUST_EXIT" -eq 1 ] && echo "$RUST_OUTPUT" | grep -q "time@0.1.45" && ! echo "$RUST_OUTPUT" | grep -q "serde"; then
echo "✅ rust project: Found time@0.1.45 as expected, serde not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ rust project: Expected time@0.1.45 vulnerable and serde unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test rust project against the real GHSA cargo feed, if committed
# (data/ghsa-cargo.purl is generated by the feed-update workflow).
if [ -f data/ghsa-cargo.purl ]; then
echo "📦 Testing rust project against real GHSA cargo feed..."
cd test-fixtures/rust-project
../../script.sh --source ../../data/ghsa-cargo.purl
if [ $? -eq 1 ]; then
echo "✅ rust project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ rust project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test go project (go.sum/go.mod) against the shared multi-ecosystem
# test feed: exactly one vulnerable package (golang.org/x/text@0.3.5),
# BurntSushi/toml must NOT be flagged (its test range doesn't match).
echo "📦 Testing go project..."
cd test-fixtures/go-project
GO_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl)
GO_EXIT=$?
if [ "$GO_EXIT" -eq 1 ] && echo "$GO_OUTPUT" | grep -q "golang.org/x/text@0.3.5" && ! echo "$GO_OUTPUT" | grep -q "toml"; then
echo "✅ go project: Found golang.org/x/text@0.3.5 as expected, toml not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ go project: Expected golang.org/x/text@0.3.5 vulnerable and toml unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test go project against the real GHSA golang feed, if committed
# (data/ghsa-golang.purl is generated by the feed-update workflow).
if [ -f data/ghsa-golang.purl ]; then
echo "📦 Testing go project against real GHSA golang feed..."
cd test-fixtures/go-project
../../script.sh --source ../../data/ghsa-golang.purl
if [ $? -eq 1 ]; then
echo "✅ go project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ go project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test python project (requirements.txt) against the shared
# multi-ecosystem test feed: exactly one vulnerable package
# (django@3.2, matched only after PEP 503 lower-casing of "Django");
# requests/certifi/uvicorn must NOT be flagged (safe versions, extras
# stripped), and the -r include and >= line must be skipped.
echo "📦 Testing python project (requirements.txt)..."
cd test-fixtures/python-project
PY_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl)
PY_EXIT=$?
if [ "$PY_EXIT" -eq 1 ] && echo "$PY_OUTPUT" | grep -q "django@3.2" && ! echo "$PY_OUTPUT" | grep -qE "requests|certifi|uvicorn|boto3"; then
echo "✅ python project: Found django@3.2 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ python project: Expected django@3.2 vulnerable and requests/certifi/uvicorn unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test poetry project (poetry.lock via the shared TOML parser) against
# the shared multi-ecosystem test feed: exactly one vulnerable package
# (pillow@8.0.0, matched after lower-casing "Pillow"); requests/certifi
# must NOT be flagged; the [package.dependencies] subtable keyed
# name/version must NOT leak a bogus package (subtable hardening).
echo "📦 Testing poetry project (poetry.lock)..."
cd test-fixtures/poetry-project
PO_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl)
PO_EXIT=$?
if [ "$PO_EXIT" -eq 1 ] && echo "$PO_OUTPUT" | grep -q "pillow@8.0.0" && ! echo "$PO_OUTPUT" | grep -qE "requests|certifi|@>=1.0|@>=2.0"; then
echo "✅ poetry project: Found pillow@8.0.0 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ poetry project: Expected pillow@8.0.0 vulnerable and no bogus/safe package flagged"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test pipenv project (Pipfile.lock JSON) against the shared feed:
# exactly one vulnerable package (django-rest-framework@3.0.0, matched
# only after collapsing the underscores in "django_rest_framework");
# certifi/requests must NOT be flagged and the VCS entry without a
# version must be skipped.
echo "📦 Testing pipenv project (Pipfile.lock)..."
cd test-fixtures/pipenv-project
PL_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl)
PL_EXIT=$?
if [ "$PL_EXIT" -eq 1 ] && echo "$PL_OUTPUT" | grep -q "django-rest-framework@3.0.0" && ! echo "$PL_OUTPUT" | grep -qE "certifi|internal-tool|@2.32.3"; then
echo "✅ pipenv project: Found django-rest-framework@3.0.0 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ pipenv project: Expected django-rest-framework@3.0.0 vulnerable and certifi/requests/VCS unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test python + poetry projects against the real GHSA pypi feed, if
# committed (data/ghsa-pypi.purl is generated by the feed-update
# workflow). django@3.2 and pillow@8.0.0 are covered by real advisories.
if [ -f data/ghsa-pypi.purl ]; then
echo "📦 Testing python project against real GHSA pypi feed..."
cd test-fixtures/python-project
../../script.sh --source ../../data/ghsa-pypi.purl
if [ $? -eq 1 ]; then
echo "✅ python project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ python project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
echo "📦 Testing poetry project against real GHSA pypi feed..."
cd test-fixtures/poetry-project
../../script.sh --source ../../data/ghsa-pypi.purl
if [ $? -eq 1 ]; then
echo "✅ poetry project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ poetry project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test ruby project (Gemfile.lock) against the shared multi-ecosystem
# test feed: exactly one vulnerable package (rack@2.2.3); rake, json
# and the platform-suffixed nokogiri must NOT be flagged (safe
# versions), and the GIT/PATH-sourced specs (rspec-support,
# local_tool) must be skipped entirely (no rubygems version to check).
echo "📦 Testing ruby project (Gemfile.lock)..."
cd test-fixtures/ruby-project
RB_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --lockfile-types ruby)
RB_EXIT=$?
if [ "$RB_EXIT" -eq 1 ] && echo "$RB_OUTPUT" | grep -q "rack@2.2.3" && ! echo "$RB_OUTPUT" | grep -qE "rake@|json@|nokogiri@|rspec-support|local_tool"; then
echo "✅ ruby project: Found rack@2.2.3 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ ruby project: Expected rack@2.2.3 vulnerable and rake/json/nokogiri/GIT+PATH specs unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test ruby project against the real GHSA gem feed, if committed
# (data/ghsa-gem.purl is generated by the feed-update workflow).
if [ -f data/ghsa-gem.purl ]; then
echo "📦 Testing ruby project against real GHSA gem feed..."
cd test-fixtures/ruby-project
../../script.sh --source ../../data/ghsa-gem.purl
if [ $? -eq 1 ]; then
echo "✅ ruby project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ ruby project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test php project (composer.lock: "packages" + "packages-dev")
# against the shared multi-ecosystem test feed: exactly one
# vulnerable package (guzzlehttp/guzzle@7.4.0); monolog/monolog
# (safe version), psr/log (v-prefixed "v3.0.0" in the lockfile,
# proving the leading-v strip; not in any feed) and the
# packages-dev entry phpunit/phpunit (proving the dev section is
# parsed) must NOT be flagged.
echo "📦 Testing php project (composer.lock)..."
cd test-fixtures/php-project
PHP_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --lockfile-types php)
PHP_EXIT=$?
if [ "$PHP_EXIT" -eq 1 ] && echo "$PHP_OUTPUT" | grep -q "guzzlehttp/guzzle@7.4.0" && ! echo "$PHP_OUTPUT" | grep -qE "monolog/monolog|psr/log|phpunit/phpunit"; then
echo "✅ php project: Found guzzlehttp/guzzle@7.4.0 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ php project: Expected guzzlehttp/guzzle@7.4.0 vulnerable and monolog/psr/phpunit unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test php project against the real GHSA composer feed, if
# committed (data/ghsa-composer.purl is generated by the
# feed-update workflow).
if [ -f data/ghsa-composer.purl ]; then
echo "📦 Testing php project against real GHSA composer feed..."
cd test-fixtures/php-project
../../script.sh --source ../../data/ghsa-composer.purl
if [ $? -eq 1 ]; then
echo "✅ php project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ php project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test maven project (gradle.lockfile + pom.xml coexisting in ONE
# directory — proving multi-file dispatch) against the shared
# multi-ecosystem test feed: exactly one vulnerable package,
# org.apache.logging.log4j:log4j-core@2.14.1 (log4shell, matched by the
# >=2.0 <2.15.0 range; note the groupId:artifactId colon in the name).
# guava (33.2.0-jre, not < 20.0) and slf4j-api from the gradle.lockfile,
# plus commons-lang3 (3.14.0) from the pom, are safe; jackson-databind's
# ${property} version in the pom is skipped (no manifest-grade property
# resolution).
echo "📦 Testing maven project (gradle.lockfile + pom.xml)..."
cd test-fixtures/maven-project
MVN_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --lockfile-types maven)
MVN_EXIT=$?
if [ "$MVN_EXIT" -eq 1 ] && echo "$MVN_OUTPUT" | grep -q "org.apache.logging.log4j:log4j-core@2.14.1" && ! echo "$MVN_OUTPUT" | grep -qE "guava|slf4j-api|commons-lang3|jackson-databind"; then
echo "✅ maven project: Found log4j-core@2.14.1 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ maven project: Expected log4j-core@2.14.1 vulnerable and guava/slf4j/commons-lang3/jackson unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test maven project against the real GHSA maven feed, if committed
# (data/ghsa-maven.purl is generated by the feed-update workflow).
# log4shell (GHSA-jfh8-c2jp-5v3q, log4j-core >=2.13.0 <2.15.0) is
# definitely present, so 2.14.1 must be flagged.
if [ -f data/ghsa-maven.purl ]; then
echo "📦 Testing maven project against real GHSA maven feed..."
cd test-fixtures/maven-project
../../script.sh --source ../../data/ghsa-maven.purl
if [ $? -eq 1 ]; then
echo "✅ maven project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ maven project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test nuget project (packages.lock.json: two frameworks, net8.0 +
# net9.0, proving cross-framework dedupe) against the shared
# multi-ecosystem test feed: exactly one vulnerable package,
# newtonsoft.json@12.0.2 (name lowercased per NuGet canon, and
# reported only ONCE despite appearing in both framework blocks).
# serilog@3.1.1 is safe (not < 2.0.0), the Transitive entry
# serilog.sinks.console is unaffected, and the Project-type entry
# contoso.shared carries no resolved version and must be skipped
# entirely (no manifest-grade csproj resolution - tier 2, skipped).
echo "📦 Testing nuget project (packages.lock.json)..."
cd test-fixtures/nuget-project
NUGET_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --lockfile-types nuget)
NUGET_EXIT=$?
if [ "$NUGET_EXIT" -eq 1 ] && echo "$NUGET_OUTPUT" | grep -q "newtonsoft.json@12.0.2" && echo "$NUGET_OUTPUT" | grep -q "Found 1 vulnerable package(s) in 1 location(s)" && ! echo "$NUGET_OUTPUT" | grep -qE "serilog@3.1.1|serilog.sinks.console|contoso.shared"; then
echo "✅ nuget project: Found newtonsoft.json@12.0.2 exactly once as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ nuget project: Expected newtonsoft.json@12.0.2 vulnerable exactly once and serilog/transitive/project entries unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test nuget project against the real GHSA nuget feed, if committed
# (data/ghsa-nuget.purl is generated by the feed-update workflow).
# GHSA-5crp-9r3c-p9vr / GHSA-8rfx-6mr3-5jh3 (newtonsoft.json
# <13.0.1) are definitely present, so 12.0.2 must be flagged.
if [ -f data/ghsa-nuget.purl ]; then
echo "📦 Testing nuget project against real GHSA nuget feed..."
cd test-fixtures/nuget-project
../../script.sh --source ../../data/ghsa-nuget.purl
if [ $? -eq 1 ]; then
echo "✅ nuget project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ nuget project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test dart project (pubspec.lock) against the shared multi-ecosystem
# test feed: exactly one vulnerable package, dio@4.0.6 (matches the
# <5.0.0 range). http@1.2.0 is safe (not < 0.13.3), the git-sourced
# internal_widgets entry is skipped (no pub.dev release to check),
# and the sdk-sourced flutter pseudo-package carries no hosted
# version at all (source: sdk, not source: hosted, skipped entirely).
echo "📦 Testing dart project (pubspec.lock)..."
cd test-fixtures/dart-project
DART_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --lockfile-types dart)
DART_EXIT=$?
if [ "$DART_EXIT" -eq 1 ] && echo "$DART_OUTPUT" | grep -q "dio@4.0.6" && ! echo "$DART_OUTPUT" | grep -qE "http@1.2.0|internal_widgets|flutter@0.0.0"; then
echo "✅ dart project: Found dio@4.0.6 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ dart project: Expected dio@4.0.6 vulnerable and http/git/sdk entries unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test dart project against the real GHSA pub feed, if committed
# (data/ghsa-pub.purl is generated by the feed-update workflow).
# GHSA-jwpw-q68h-r678 / GHSA-9324-jv53-9cc8 (dio <5.0.0) are
# definitely present, so 4.0.6 must be flagged.
if [ -f data/ghsa-pub.purl ]; then
echo "📦 Testing dart project against real GHSA pub feed..."
cd test-fixtures/dart-project
../../script.sh --source ../../data/ghsa-pub.purl
if [ $? -eq 1 ]; then
echo "✅ dart project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ dart project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test elixir project (mix.lock) against the shared multi-ecosystem
# test feed: exactly one vulnerable package, sweet_xml@0.6.6
# (matches the <0.7.0 range). paginator@1.2.0 is safe (not < 1.0.0),
# the git-sourced internal_auth entry is skipped (no hex.pm release
# to check), and jason/telemetry are unlisted (safe by absence).
echo "📦 Testing elixir project (mix.lock)..."
cd test-fixtures/elixir-project
HEX_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --lockfile-types hex)
HEX_EXIT=$?
if [ "$HEX_EXIT" -eq 1 ] && echo "$HEX_OUTPUT" | grep -q "sweet_xml@0.6.6" && ! echo "$HEX_OUTPUT" | grep -qE "paginator@1.2.0|internal_auth|jason@1.4.1|telemetry@1.2.1"; then
echo "✅ elixir project: Found sweet_xml@0.6.6 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ elixir project: Expected sweet_xml@0.6.6 vulnerable and paginator/git/unlisted entries unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test elixir project against the real GHSA hex feed, if committed
# (data/ghsa-hex.purl is generated by the feed-update workflow).
# GHSA-qpmc-wprv-x746 (sweet_xml <0.7.0) is definitely present, so
# 0.6.6 must be flagged.
if [ -f data/ghsa-hex.purl ]; then
echo "📦 Testing elixir project against real GHSA hex feed..."
cd test-fixtures/elixir-project
../../script.sh --source ../../data/ghsa-hex.purl
if [ $? -eq 1 ]; then
echo "✅ elixir project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ elixir project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test swift project (Package.resolved) against the shared
# multi-ecosystem test feed: exactly one vulnerable package,
# github.com/apple/swift-nio@2.10.0 (matches the <2.30.0 range).
# The pin is deliberately mixed-case
# ("https://GitHub.com/Apple/Swift-NIO.git") to prove URL
# canonicalization (scheme-strip + .git-strip + lowercase); it
# must still be reported under the lowercased name.
# github.com/apple/swift-log@1.14.0 is safe (not < 1.0.0), and the
# branch-pinned swift-atomics entry is skipped (no released
# version to check).
echo "📦 Testing swift project (Package.resolved)..."
cd test-fixtures/swift-project
SWIFT_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --lockfile-types swift)
SWIFT_EXIT=$?
if [ "$SWIFT_EXIT" -eq 1 ] && echo "$SWIFT_OUTPUT" | grep -q "github.com/apple/swift-nio@2.10.0" && ! echo "$SWIFT_OUTPUT" | grep -qE "swift-log@1.14.0|swift-atomics"; then
echo "✅ swift project: Found github.com/apple/swift-nio@2.10.0 as expected, others not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ swift project: Expected github.com/apple/swift-nio@2.10.0 vulnerable (lowercased) and swift-log/swift-atomics unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test swift project against the real GHSA swift feed, if
# committed (data/ghsa-swift.purl is generated by the
# feed-update workflow). GHSA-mgc4-wqv7-4pxm
# (github.com/apple/swift-nio >=2.0.0 <2.13.1) is definitely
# present, so 2.10.0 must be flagged. Guard on `[ -s ... ]`
# instead of `[ -f ... ]` if this feed can ever be committed
# empty (0 lines) - an empty file would otherwise pass `-f` and
# the smoke test would then legitimately fail to find anything.
if [ -f data/ghsa-swift.purl ]; then
echo "📦 Testing swift project against real GHSA swift feed..."
cd test-fixtures/swift-project
../../script.sh --source ../../data/ghsa-swift.purl
if [ $? -eq 1 ]; then
echo "✅ swift project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ swift project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test GitHub Actions project (.github/workflows/*.yml) — the ONLY
# ecosystem discovered by PATH, not a lockfile basename. Scanned
# against the shared multi-ecosystem test feed. Exactly one vulnerable
# action: tj-actions/changed-files@v35 (leading `v` stripped to 35,
# matches the <46.0.1 range, GHSA-mrrh-fwg8-r2c3). It must be reported
# with the LOWERCASED owner/repo name under the [githubactions]
# prefix. actions/checkout@v4 is a safe decoy (feed range <1.0.0, and
# 4 is not < 1); the quoted setup-python, the SHA-pinned cache
# (comment ignored), the owner/repo/subpath reusable-workflow call,
# the ./local step and the docker:// step are all safe or skipped.
# --lockfile-types actions isolates workflow scanning, and the fixture
# `cd` keeps the repo's own .github/workflows out of scope.
echo "📦 Testing GitHub Actions project (workflow uses:)..."
cd test-fixtures/actions-project
ACTIONS_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --lockfile-types actions)
ACTIONS_EXIT=$?
if [ "$ACTIONS_EXIT" -eq 1 ] && echo "$ACTIONS_OUTPUT" | grep -q "\[githubactions\] tj-actions/changed-files@35" && ! echo "$ACTIONS_OUTPUT" | grep -qE "checkout|setup-node|setup-python|upload-artifact|cache|shared-workflows|alpine|notify"; then
echo "✅ actions project: Found tj-actions/changed-files@35 as expected ([githubactions], lowercased); safe/skipped steps not flagged"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ actions project: Expected tj-actions/changed-files@35 vulnerable and checkout/setup/subpath/local/docker unaffected"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test actions project against the real GHSA githubactions feed, if
# committed (data/ghsa-githubactions.purl is generated by the
# feed-update workflow). tj-actions/changed-files GHSA-mrrh-fwg8-r2c3
# (<46.0.1) and GHSA-mcph-m25j-8j63 (<41) both cover v35, so it must
# be flagged. Guard on `[ -s ... ]` so a committed-empty feed (0
# lines) does not pass and then fail to find anything.
if [ -s data/ghsa-githubactions.purl ]; then
echo "📦 Testing actions project against real GHSA githubactions feed..."
cd test-fixtures/actions-project
../../script.sh --source ../../data/ghsa-githubactions.purl --lockfile-types actions
if [ $? -eq 1 ]; then
echo "✅ actions project (real feed): Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ actions project (real feed): Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
fi
# Test monorepo
echo "📦 Testing monorepo..."
cd test-fixtures/monorepo
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 1 ]; then
echo "✅ monorepo: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ monorepo: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test safe project (should NOT find vulnerabilities)
echo "📦 Testing safe project (should pass)..."
cd test-fixtures/safe-project
../../script.sh --source ../test-vulnerabilities.json
if [ $? -eq 0 ]; then
echo "✅ safe project: No vulnerabilities found as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ safe project: Should NOT have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Polyglot project: ONE directory holding an npm lockfile, a Python
# requirements.txt and a Go go.sum. A single scan against the shared
# multi-ecosystem feed must flag all three (lodash@4.17.20,
# django@3.2, golang.org/x/text@0.3.5) each with its ecosystem prefix,
# and the JSON export must carry all three distinct "ecosystem" values.
echo "📦 Testing polyglot project (npm + pypi + golang in one scan)..."
cd test-fixtures/polyglot-project
POLY_OUTPUT=$(../../script.sh --source ../test-vulnerabilities-multi.purl --export-json poly.json)
POLY_EXIT=$?
POLY_JSON=$(cat poly.json 2>/dev/null)
rm -f poly.json
if [ "$POLY_EXIT" -eq 1 ] \
&& echo "$POLY_OUTPUT" | grep -q "lodash@4.17.20" \
&& echo "$POLY_OUTPUT" | grep -q "django@3.2" \
&& echo "$POLY_OUTPUT" | grep -q "golang.org/x/text@0.3.5" \
&& echo "$POLY_JSON" | grep -q '"ecosystem": "npm"' \
&& echo "$POLY_JSON" | grep -q '"ecosystem": "pypi"' \
&& echo "$POLY_JSON" | grep -q '"ecosystem": "golang"'; then
echo "✅ polyglot project: npm+pypi+golang all detected and exported"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ polyglot project: expected npm+pypi+golang all detected and in JSON export"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test with CSV source
echo "📦 Testing with CSV source..."
cd test-fixtures/npm-project
../../script.sh --source ../test-vulnerabilities.csv
if [ $? -eq 1 ]; then
echo "✅ CSV source: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ CSV source: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ../..
echo ""
# Test with config file
echo "📦 Testing with config file..."
cd test-fixtures
../script.sh --config .package-checker.config.json
if [ $? -eq 1 ]; then
echo "✅ Config file: Found vulnerabilities as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ Config file: Should have found vulnerabilities"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
cd ..
echo ""
# Test metadata collision (correct advisory displayed when multiple advisories affect same package)
echo "📦 Testing metadata collision..."
test-fixtures/metadata-collision-project/test-metadata-collision.sh
if [ $? -eq 0 ]; then
echo "✅ metadata collision: Correct advisory metadata as expected"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ metadata collision: Wrong advisory metadata displayed"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
echo ""
# Comprehensive bug fix tests (multi-vuln, build metadata, empty range, pre-release, workspace)
echo "📦 Testing all bug fixes..."
test-fixtures/test-all-bugs.sh
if [ $? -eq 0 ]; then
echo "✅ bug fixes: All bug fix tests passed"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ bug fixes: Some bug fix tests failed"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
echo ""
# Version comparator harness (semver-2 + Go pseudo-versions + npm dispatch fallback)
echo "📦 Testing version comparators..."
bash test-fixtures/test-version-compare.sh
if [ $? -eq 0 ]; then
echo "✅ version comparators: All comparator rows passed"
PASSED_TESTS=$((PASSED_TESTS + 1))
else
echo "❌ version comparators: Some comparator rows failed"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
echo ""
# Summary
echo "=========================================="
echo "Test Summary"
echo "=========================================="
echo "✅ Passed: $PASSED_TESTS"
echo "❌ Failed: $FAILED_TESTS"
echo "=========================================="
if [ $FAILED_TESTS -gt 0 ]; then
echo "Some tests failed!"
exit 1
else
echo "All tests passed!"
exit 0
fi
verify-build:
name: Verify script.sh matches src/
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Rebuild from src
run: bash build.sh
- name: Check for drift
run: |
if ! git diff --exit-code script.sh; then
echo ""
echo "ERROR: script.sh does not match src/."
echo "Someone edited script.sh directly, or forgot to run ./build.sh after editing src/."
echo "Fix: run ./build.sh and commit the result together with your src/ changes."
exit 1
fi