Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/security-audit/collect-lockfiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Used by .github/workflows/security-audit.yml after a resolve step.
#
# Copies every lockfile the resolve step generated into resolved/, keeping
# its repo path, so the osv-scanner job can lay them back over a fresh
# checkout. Most are gitignored (pubspec.lock, composer.lock, Gemfile.lock);
# the rest are merely untracked (requirements.txt, gradle.lockfile,
# Package.resolved), so both listings are needed.
set -euo pipefail

names='(^|/)(pubspec\.lock|composer\.lock|Gemfile\.lock|requirements\.txt|gradle\.lockfile|Package\.resolved)$'
{
git ls-files --others --ignored --exclude-standard | grep -E "$names" || true
git ls-files --others --exclude-standard | grep -E "$names" || true
} | grep -v -E '(^|/)(\.build|build|node_modules)/' | sort -u > /tmp/locks.txt

if [ ! -s /tmp/locks.txt ]; then
echo "::error::no lockfiles were generated"
exit 1
fi

while read -r f; do
mkdir -p "resolved/$(dirname "$f")"
cp "$f" "resolved/$f"
done < /tmp/locks.txt

echo "Generated lockfiles:"
cat /tmp/locks.txt
34 changes: 34 additions & 0 deletions .github/security-audit/lock-all.init.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Gradle init script used by .github/workflows/security-audit.yml.
//
// antd-java and antd-kotlin don't commit Gradle lockfiles, so osv-scanner
// has nothing to read. This script turns on dependency locking for every
// project from the outside (no build-file change) and registers a task that
// resolves every resolvable configuration, so
//
// ./gradlew --init-script lock-all.init.gradle resolveAndLockAll --write-locks
//
// writes a gradle.lockfile per project listing the exact versions a fresh
// build resolves today, test and example configurations included. The
// workflow then scans those lockfiles.
//
// -PauditConfigurations=a,b limits resolution to the named configurations.
// ffi/android uses it (releaseRuntimeClasspath, what the published AAR's
// consumers get): resolving every configuration of an Android library also
// pulls in Android build tooling that isn't part of the shipped artifact.

allprojects {
dependencyLocking {
lockAllConfigurations()
}

tasks.register("resolveAndLockAll") {
notCompatibleWithConfigurationCache("resolves configurations at execution time")
doFirst {
assert gradle.startParameter.writeDependencyLocks
def only = project.findProperty("auditConfigurations")?.toString()?.split(",")*.trim()
configurations
.findAll { it.canBeResolved && (only == null || only.contains(it.name)) }
.each { it.resolve() }
}
}
}
83 changes: 83 additions & 0 deletions .github/security-audit/nuget-audit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Used by .github/workflows/security-audit.yml (dotnet-audit job).
#
# Lists vulnerable NuGet packages, transitive included, for each project.
# osv-scanner only sees the direct PackageReferences in a .csproj, so this
# is the transitive check for .NET.
#
# Exit status: 1 if any blocking project has a vulnerable package OR could
# not be audited (restore or listing failed); 0 otherwise. Report-only
# projects (examples) only ever produce warnings.
#
# Every failure is checked explicitly instead of relying on `set -e`: bash
# disables errexit inside a function called from `f || ...`, so a failed
# restore used to fall through to the final grep and pass as clean.
#
# Tests: .github/security-audit/test-nuget-audit.sh (stub dotnet via DOTNET,
# project lists via NUGET_AUDIT_BLOCKING / NUGET_AUDIT_REPORT_ONLY).
set -uo pipefail

DOTNET="${DOTNET:-dotnet}"

blocking=(
antd-csharp/Antd.Sdk/Antd.Sdk.csproj
antd-csharp/Antd.Sdk.Tests/Antd.Sdk.Tests.csproj
ffi/csharp/AntFfi/AntFfi.csproj
ffi/csharp/AntFfi.Tests/AntFfi.Tests.csproj
)
report_only=(
antd-csharp/Examples/Examples.csproj
)
if [ -n "${NUGET_AUDIT_BLOCKING+set}" ]; then
read -r -a blocking <<< "$NUGET_AUDIT_BLOCKING"
fi
if [ -n "${NUGET_AUDIT_REPORT_ONLY+set}" ]; then
read -r -a report_only <<< "$NUGET_AUDIT_REPORT_ONLY"
fi

out="$(mktemp)"
trap 'rm -f "$out"' EXIT

# 0 = clean, 1 = vulnerable packages found, 2 = the audit could not run.
check() {
local project="$1" list_rc=0
if ! "$DOTNET" restore "$project" >/dev/null; then
echo "dotnet restore failed for $project" >&2
return 2
fi
"$DOTNET" list "$project" package --vulnerable --include-transitive >"$out" || list_rc=$?
cat "$out"
if grep -q "has the following vulnerable packages" "$out"; then
return 1
fi
if [ "$list_rc" -ne 0 ]; then
echo "dotnet list package failed for $project (exit $list_rc)" >&2
return 2
fi
return 0
}

failed=0
for p in ${blocking[@]+"${blocking[@]}"}; do
echo "::group::$p"
rc=0
check "$p" || rc=$?
echo "::endgroup::"
case "$rc" in
0) ;;
1) echo "::error file=$p::vulnerable NuGet packages"; failed=1 ;;
*) echo "::error file=$p::NuGet audit could not run (restore or package listing failed)"; failed=1 ;;
esac
done
for p in ${report_only[@]+"${report_only[@]}"}; do
echo "::group::$p (report only)"
rc=0
check "$p" || rc=$?
echo "::endgroup::"
case "$rc" in
0) ;;
1) echo "::warning file=$p::vulnerable NuGet packages (examples are report-only)" ;;
*) echo "::warning file=$p::NuGet audit could not run (examples are report-only)" ;;
esac
done
exit "$failed"
60 changes: 60 additions & 0 deletions .github/security-audit/test-nuget-audit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Regression tests for nuget-audit.sh, run by security-audit.yml before the
# real audit. A stub `dotnet` returns controlled exit codes and output, so
# every path is exercised without the .NET SDK, including the ones that
# used to pass as clean (restore or listing failures).
set -euo pipefail

here="$(cd "$(dirname "$0")" && pwd)"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

cat > "$tmp/dotnet" <<'STUB'
#!/usr/bin/env bash
case "$1" in
restore) exit "${STUB_RESTORE_RC:-0}" ;;
list)
if [ "${STUB_LIST_OUT:-clean}" = vuln ]; then
echo "Project \`Fake\` has the following vulnerable packages"
echo " > Some.Package 1.0.0 High https://github.com/advisories/GHSA-xxxx"
else
echo "The given project \`Fake\` has no vulnerable packages given the current sources."
fi
exit "${STUB_LIST_RC:-0}" ;;
esac
exit 99
STUB
chmod +x "$tmp/dotnet"

fails=0
# run <name> <expected exit> <expected annotation substring or ""> [VAR=value ...]
run() {
local name="$1" want="$2" grepfor="$3"; shift 3
local got=0
env DOTNET="$tmp/dotnet" "$@" bash "$here/nuget-audit.sh" >"$tmp/out" 2>&1 || got=$?
if [ "$got" -ne "$want" ]; then
echo "FAIL $name: exit $got, want $want"; cat "$tmp/out"; fails=$((fails + 1)); return
fi
if [ -n "$grepfor" ] && ! grep -q -- "$grepfor" "$tmp/out"; then
echo "FAIL $name: output lacks '$grepfor'"; cat "$tmp/out"; fails=$((fails + 1)); return
fi
echo "ok $name (exit $got)"
}

B=(NUGET_AUDIT_BLOCKING=Fake.csproj NUGET_AUDIT_REPORT_ONLY=)
R=(NUGET_AUDIT_BLOCKING= NUGET_AUDIT_REPORT_ONLY=Example.csproj)

run "blocking: clean" 0 "" "${B[@]}"
run "blocking: vulnerable" 1 "::error file=Fake.csproj::vulnerable" "${B[@]}" STUB_LIST_OUT=vuln
run "blocking: restore fails" 1 "could not run" "${B[@]}" STUB_RESTORE_RC=17
run "blocking: list fails" 1 "could not run" "${B[@]}" STUB_LIST_RC=23
run "blocking: restore and list fail" 1 "could not run" "${B[@]}" STUB_RESTORE_RC=17 STUB_LIST_RC=23
run "blocking: list fails, vulns printed" 1 "vulnerable NuGet packages" "${B[@]}" STUB_LIST_RC=1 STUB_LIST_OUT=vuln
run "report-only: vulnerable warns" 0 "::warning file=Example.csproj::vulnerable" "${R[@]}" STUB_LIST_OUT=vuln
run "report-only: restore fails warns" 0 "::warning file=Example.csproj::NuGet audit could not run" "${R[@]}" STUB_RESTORE_RC=17

if [ "$fails" -ne 0 ]; then
echo "$fails nuget-audit.sh test(s) failed"
exit 1
fi
echo "all nuget-audit.sh tests passed"
Loading
Loading