عنوان:

‫ارزیابی و پایش سلامت وابستگی‌های NuGet در پروژه‌های NET.


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۴/۲۱ ۰۹:۰۵
آدرس: www.dntips.ir
در توسعه نرم‌افزارهای مدرن مبتنی بر اکوسیستم .NET، استفاده از بسته‌های شخص ثالث (Third-Party Packages) از طریق مدیریت بسته NuGet به ابزاری جدایی‌ناپذیر تبدیل شده است. با این حال، تکیه بر این وابستگی‌ها چالش‌های امنیتی و فنی متعددی را به همراه دارد. وجود آسیب‌پذیری‌های امنیتی شناخته‌شده (CVEs)، منسوخ شدن بسته‌ها (Deprecation) و عدم به‌روزرسانی به نسخه‌های پایدار جدید (Outdated Packages)، پایداری و امنیت کل سیستم را تهدید می‌کند.
بنابراین، پیاده‌سازی یک مکانیزم خودکار جهت پایش و سنجش سلامت این وابستگی‌ها در فرآیندهای یکپارچه‌سازی مداوم (CI/CD)، نقشی کلیدی در حفظ کیفیت نرم‌افزار ایفا می‌کند. در این مقاله، به معرفی ابزارها و دستورات داخلی .NET CLI برای بررسی وضعیت سلامت بسته‌ها پرداخته و یک اسکریپت جامع در دو نگارش متناسب با محیط‌های ویندوز (PowerShell) و لینوکس/مک (Bash) ارائه می‌دهیم که علاوه بر تولید گزارش چاپی، قابلیت مسدودسازی فرآیند Build را در صورت کشف آسیب‌پذیری دارا می‌باشند.

تشریح فنی دستورات پایه‌ای NET Core SDK.
ابزار خط فرمان دات‌نت (CLI) دستورات قدرتمندی را برای بررسی وضعیت بسته‌های متصل به پروژه یا راهکار (Solution) فراهم کرده است که در اسکریپت‌های نهایی از آن‌ها بهره گرفته‌ایم:
  • --vulnerable: لیست بسته‌هایی که دارای آسیب‌پذیری‌های امنیتی ثبت شده هستند را استخراج می‌کند. استفاده از سوئیچ --include-transitive تضمین می‌کند که وابستگی‌های غیرمستقیم (بسته‌هایی که خود توسط بسته‌های اصلی فراخوانی شده‌اند) نیز مورد بررسی قرار گیرند.
  • --deprecated: بسته‌هایی را نشان می‌دهد که توسط توسعه‌دهنده اصلی منسوخ اعلام شده‌اند و استفاده از آن‌ها دیگر توصیه نمی‌شود.
  • --outdated: وضعیت نسخه‌های مصرفی را با آخرین نسخه‌های منتشر شده در سرور NuGet مقایسه کرده و بسته‌های قدیمی را لیست می‌کند.

اسکریپت یکپارچه پایش سلامت بسته‌ها (NuGet Health Check)
در ادامه، دو نسخه اسکریپت کاملاً هماهنگ و تست شده ارائه شده است. هر دو نسخه به طور هوشمند فایل پروژه یا راهکار (پشتیبانی از ساختار کلاسیک .sln و ساختار جدید .slnx) را شناسایی کرده، خروجی گزارش را به صورت یک فایل متنی با برچسب زمان (Timestamp) ذخیره می‌کنند و در صورت وجود خطای امنیتی، با بازگرداندن Exit Code 1 مانع از ادامه یافتن فرآیند CI/CD می‌شوند.

نگارش اول: مخصوص محیط‌های ویندوز (PowerShell)
پاورشل ویندوز پتانسیل بالایی در مدیریت رشته‌ها دارد، بنابراین اسکریپت زیر به صورت بومی (Native) بدون نیاز به ابزارهای جانبی لینوکسی در ویندوز اجرا می‌شود:
# nuget-health-check.ps1
# اجرا از ریشه مخزن - نیازمند .NET SDK 8 یا بالاتر

$ErrorActionPreference = "Stop"

# پیدا کردن خودکار اولین فایل Solution (پشتیبانی از .sln و .slnx)
$Solution = Get-ChildItem -Filter *.slnx | Select-Object -First 1
if ($null -eq $Solution) {
    $Solution = Get-ChildItem -Filter *.sln | Select-Object -First 1
}

if ($null -eq $Solution) {
    Write-Error "خطا: هیچ فایل راهکاری (.sln یا .slnx) در این پوشه یافت نشد."
    Exit 1
}

$Timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ"
$ReportPath = "reports/nuget-health-$(Get-Date -Format 'yyyy-MM-dd').txt"

# ایجاد پوشه گزارش در صورت عدم وجود
if (-not (Test-Path "reports")) { New-Item -ItemType Directory -Path "reports" | Out-Null }

$ReportContent = New-Object System.Text.StringBuilder
$null = $ReportContent.AppendLine("========================================================")
$null = $ReportContent.AppendLine(" NuGet Dependency Health Report")
$null = $ReportContent.AppendLine(" Generated : $Timestamp")
$null = $ReportContent.AppendLine(" Solution  : $($Solution.Name)")
$null = $ReportContent.AppendLine("========================================================")
$null = $ReportContent.AppendLine("")

$null = $ReportContent.AppendLine("--- Packages with known CVEs (including transitive) ---")
$VulnerableOutput = dotnet list $Solution.FullName package --vulnerable --include-transitive 2>&1
$null = $ReportContent.AppendLine($VulnerableOutput)
$null = $ReportContent.AppendLine("")

$null = $ReportContent.AppendLine("--- Packages that have newer versions available ---")
$OutdatedOutput = dotnet list $Solution.FullName package --outdated 2>&1
$null = $ReportContent.AppendLine($OutdatedOutput)
$null = $ReportContent.AppendLine("")

$null = $ReportContent.AppendLine("--- Packages marked deprecated by their authors ---")
$DeprecatedOutput = dotnet list $Solution.FullName package --deprecated 2>&1
$null = $ReportContent.AppendLine($DeprecatedOutput)
$null = $ReportContent.AppendLine("")

$null = $ReportContent.AppendLine("========================================================")
$null = $ReportContent.AppendLine(" Report complete. Review all findings and create ADRs")
$null = $ReportContent.AppendLine(" for any deferral decisions before closing this report.")
$null = $ReportContent.AppendLine("========================================================")

# خروجی روی کنسول و ذخیره در فایل
Write-Output $ReportContent.ToString()
$ReportContent.ToString() | Out-File -FilePath $ReportPath -Encoding utf8

# بررسی وجود آسیب‌پذیری برای متوقف کردن گیت CI
$VulnCount = ($VulnerableOutput | Select-String -Pattern ">\s+").Count
if ($VulnCount -gt 0) {
    Write-Host "ERROR: $VulnCount vulnerable package(s) found. Resolve before merging." -ForegroundColor Red
    Exit 1
}

نگارش دوم: مخصوص محیط‌های لینوکس، مک و داکر (Bash)
این اسکریپت با سینتکس استاندارد bash توسعه یافته و برای استفاده در رانرهای لینوکسی، کانتینرها و محیط‌های ابری بهینه‌سازی شده است:
#!/usr/bin/env bash
# scripts/nuget-health-check.sh
# اجرا از ریشه مخزن - نیازمند .NET SDK 8 یا بالاتر (Linux, macOS, WSL)

set -euo pipefail

# پیدا کردن خودکار فایل راهکار (.slnx یا .sln)
SOLUTION="${1:-$(ls *.slnx *.sln 2>/dev/null | head -1)}"
[[ -z "$SOLUTION" ]] && { echo "Error: No solution file (.sln or .slnx) found."; exit 1; }

TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
mkdir -p reports
REPORT_FILE="reports/nuget-health-$(date +%F).txt"

{
    echo "========================================================"
    echo " NuGet Dependency Health Report"
    echo " Generated : $TIMESTAMP"
    echo " Solution  : $SOLUTION"
    echo "========================================================"
    echo ""

    echo "--- Packages with known CVEs (including transitive) ---"
    dotnet list "$SOLUTION" package --vulnerable --include-transitive 2>/dev/null || true

    echo ""
    echo "--- Packages that have newer versions available ---"
    dotnet list "$SOLUTION" package --outdated 2>/dev/null || true

    echo ""
    echo "--- Packages marked deprecated by their authors ---"
    dotnet list "$SOLUTION" package --deprecated 2>/dev/null || true

    echo ""
    echo "========================================================"
    echo " Report complete. Review all findings and create ADRs"
    echo " for any deferral decisions before closing this report."
    echo "========================================================"
} | tee "$REPORT_FILE"

# بررسی آسیب‌پذیری‌ها جهت مسدودسازی گیت CI
VULN_COUNT=$(dotnet list "$SOLUTION" package --vulnerable --include-transitive 2>/dev/null | grep -c "^   >" || true)
if [ "$VULN_COUNT" -gt 0 ]; then
  echo "ERROR: $VULN_COUNT vulnerable package(s) found. Resolve before merging." >&2
  exit 1
fi

نتیجه‌گیری و توصیه‌ها
مدیریت سلامت وابستگی‌ها فرآیندی ایستا نیست، بلکه باید به عنوان بخشی پویا از چرخه حیات توسعه نرم‌افزار (SDLC) در نظر گرفته شود. با ادغام اسکریپت‌های فوق در خط لوله CI/CD (مانند GitHub Actions، GitLab CI یا Azure Pipelines)، تیم‌های توسعه می‌توانند قبل از ورود هرگونه کد آسیب‌پذیر به شاخه اصلی پروژه، از وجود آن مطلع شده و از بروز فجایع امنیتی جلوگیری کنند.
توصیه معماری (ADR): پیشنهاد می‌شود در صورت مواجهه با بسته‌های قدیمی یا منسوخ که در حال حاضر به دلایل فنی (مانند عدم سازگاری کد) امکان ارتقای آن آنی وجود ندارد، تصمیمات به صورت مستند در قالب ساختارهای ثبت تصمیمات معماری (Architecture Decision Records) در پروژه نگهداری شوند تا بدهی فنی (Technical Debt) تیم کاملاً شفاف و قابل رهگیری باشد.