# .gitea/workflows/sast-scan.yml # Static Application Security Testing (SAST) Workflow # Sprint: CI/CD Enhancement - Security Scanning (Tier 2) # # Purpose: Detect security vulnerabilities in source code through static analysis # - Code injection vulnerabilities # - Authentication/authorization issues # - Cryptographic weaknesses # - Data exposure risks # - OWASP Top 10 detection # # Supported Languages: C#/.NET, JavaScript/TypeScript, Python, YAML, Dockerfile # # PLACEHOLDER: Choose your SAST scanner implementation below # Options: # 1. Semgrep - Fast, open-source, good .NET support # 2. CodeQL - GitHub's analysis engine # 3. SonarQube - Enterprise-grade with dashboards # 4. Snyk Code - Commercial with good accuracy name: SAST Scanning on: push: branches: [main, develop] paths: - 'src/**' - '*.csproj' - '*.cs' - '*.ts' - '*.js' - '*.py' - 'Dockerfile*' pull_request: paths: - 'src/**' - '*.csproj' - '*.cs' - '*.ts' - '*.js' - '*.py' - 'Dockerfile*' schedule: - cron: '30 3 * * 1' # Weekly on Monday at 3:30 AM UTC workflow_dispatch: inputs: scan_level: description: 'Scan thoroughness level' type: choice options: - quick - standard - comprehensive default: standard fail_on_findings: description: 'Fail workflow on findings' type: boolean default: true env: DOTNET_VERSION: '10.0.100' TZ: UTC jobs: # =========================================================================== # PLACEHOLDER SAST IMPLEMENTATION # =========================================================================== # # IMPORTANT: Configure your preferred SAST tool by uncommenting ONE of the # implementation options below. Each option includes the necessary steps # and configuration for that specific tool. # # =========================================================================== sast-scan: name: SAST Analysis runs-on: ubuntu-22.04 timeout-minutes: 30 permissions: security-events: write contents: read steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 # ========================================================================= # PLACEHOLDER: Uncomment your preferred SAST tool configuration # ========================================================================= - name: SAST Scan Placeholder run: | echo "::notice::SAST scanning placeholder - configure your scanner below" echo "" echo "Available SAST options:" echo "" echo "1. SEMGREP (Recommended for open-source)" echo " Uncomment the Semgrep section below" echo " - Fast, accurate, good .NET support" echo " - Free for open-source projects" echo "" echo "2. CODEQL (GitHub native)" echo " Uncomment the CodeQL section below" echo " - Deep analysis capabilities" echo " - Native GitHub integration" echo "" echo "3. SONARQUBE (Enterprise)" echo " Uncomment the SonarQube section below" echo " - Comprehensive dashboards" echo " - Technical debt tracking" echo "" echo "4. SNYK CODE (Commercial)" echo " Uncomment the Snyk section below" echo " - High accuracy" echo " - Good IDE integration" # ========================================================================= # OPTION 1: SEMGREP # ========================================================================= # Uncomment the following section to use Semgrep: # # - name: Run Semgrep # uses: returntocorp/semgrep-action@v1 # with: # config: >- # p/default # p/security-audit # p/owasp-top-ten # p/csharp # p/javascript # p/typescript # p/python # p/docker # env: # SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} # ========================================================================= # OPTION 2: CODEQL # ========================================================================= # Uncomment the following section to use CodeQL: # # - name: Initialize CodeQL # uses: github/codeql-action/init@v3 # with: # languages: csharp, javascript # queries: security-and-quality # # - name: Build for CodeQL # run: | # dotnet build src/StellaOps.sln --configuration Release # # - name: Perform CodeQL Analysis # uses: github/codeql-action/analyze@v3 # with: # category: "/language:csharp" # ========================================================================= # OPTION 3: SONARQUBE # ========================================================================= # Uncomment the following section to use SonarQube: # # - name: SonarQube Scan # uses: SonarSource/sonarqube-scan-action@master # env: # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} # with: # args: > # -Dsonar.projectKey=stellaops # -Dsonar.sources=src/ # -Dsonar.exclusions=**/bin/**,**/obj/**,**/node_modules/** # ========================================================================= # OPTION 4: SNYK CODE # ========================================================================= # Uncomment the following section to use Snyk Code: # # - name: Setup Snyk # uses: snyk/actions/setup@master # # - name: Snyk Code Test # run: snyk code test --sarif-file-output=snyk-code.sarif # env: # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} # continue-on-error: true # # - name: Upload Snyk results # uses: github/codeql-action/upload-sarif@v3 # with: # sarif_file: snyk-code.sarif # =========================================================================== # .NET SECURITY ANALYSIS (built-in) # =========================================================================== dotnet-security: name: .NET Security Analysis runs-on: ubuntu-22.04 timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - name: Restore packages run: dotnet restore src/StellaOps.sln - name: Run Security Code Analysis run: | # Enable nullable reference types warnings as errors for security dotnet build src/StellaOps.sln \ --configuration Release \ --no-restore \ /p:TreatWarningsAsErrors=false \ /p:EnableNETAnalyzers=true \ /p:AnalysisLevel=latest \ /warnaserror:CA2100,CA2109,CA2119,CA2153,CA2300,CA2301,CA2302,CA2305,CA2310,CA2311,CA2312,CA2315,CA2321,CA2322,CA2326,CA2327,CA2328,CA2329,CA2330,CA2350,CA2351,CA2352,CA2353,CA2354,CA2355,CA2356,CA2361,CA2362,CA3001,CA3002,CA3003,CA3004,CA3005,CA3006,CA3007,CA3008,CA3009,CA3010,CA3011,CA3012,CA3061,CA3075,CA3076,CA3077,CA3147,CA5350,CA5351,CA5358,CA5359,CA5360,CA5361,CA5362,CA5363,CA5364,CA5365,CA5366,CA5367,CA5368,CA5369,CA5370,CA5371,CA5372,CA5373,CA5374,CA5375,CA5376,CA5377,CA5378,CA5379,CA5380,CA5381,CA5382,CA5383,CA5384,CA5385,CA5386,CA5387,CA5388,CA5389,CA5390,CA5391,CA5392,CA5393,CA5394,CA5395,CA5396,CA5397,CA5398,CA5399,CA5400,CA5401,CA5402,CA5403 \ 2>&1 | tee build-security.log || true - name: Parse security warnings run: | echo "### .NET Security Analysis" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Count security warnings SECURITY_WARNINGS=$(grep -E "warning CA[235][0-9]{3}" build-security.log | wc -l || echo "0") echo "- Security warnings found: $SECURITY_WARNINGS" >> $GITHUB_STEP_SUMMARY if [[ $SECURITY_WARNINGS -gt 0 ]]; then echo "" >> $GITHUB_STEP_SUMMARY echo "
Security Warnings" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY grep -E "warning CA[235][0-9]{3}" build-security.log | head -50 >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY fi - name: Upload security log uses: actions/upload-artifact@v4 if: always() with: name: sast-dotnet-security-log path: build-security.log retention-days: 14 # =========================================================================== # DEPENDENCY VULNERABILITY CHECK # =========================================================================== dependency-check: name: Dependency Vulnerabilities runs-on: ubuntu-22.04 timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - name: Run vulnerability audit run: | echo "### Dependency Vulnerability Audit" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Check for known vulnerabilities in NuGet packages dotnet list src/StellaOps.sln package --vulnerable --include-transitive 2>&1 | tee vuln-report.txt || true # Parse results VULN_COUNT=$(grep -c "has the following vulnerable packages" vuln-report.txt || echo "0") if [[ $VULN_COUNT -gt 0 ]]; then echo "::warning::Found $VULN_COUNT projects with vulnerable dependencies" echo "- Projects with vulnerabilities: $VULN_COUNT" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
Vulnerability Report" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY cat vuln-report.txt >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY else echo "No known vulnerabilities found in dependencies." >> $GITHUB_STEP_SUMMARY fi - name: Upload vulnerability report uses: actions/upload-artifact@v4 if: always() with: name: sast-vulnerability-report path: vuln-report.txt retention-days: 14 # =========================================================================== # DOCKERFILE SECURITY LINTING # =========================================================================== dockerfile-lint: name: Dockerfile Security runs-on: ubuntu-22.04 timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v4 - name: Find Dockerfiles id: find run: | DOCKERFILES=$(find . -name "Dockerfile*" -type f ! -path "./node_modules/*" | jq -R -s -c 'split("\n") | map(select(length > 0))') COUNT=$(echo "$DOCKERFILES" | jq 'length') echo "files=$DOCKERFILES" >> $GITHUB_OUTPUT echo "count=$COUNT" >> $GITHUB_OUTPUT echo "Found $COUNT Dockerfiles" - name: Install Hadolint if: steps.find.outputs.count != '0' run: | wget -qO hadolint https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64 chmod +x hadolint sudo mv hadolint /usr/local/bin/ - name: Lint Dockerfiles if: steps.find.outputs.count != '0' run: | echo "### Dockerfile Security Lint" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY TOTAL_ISSUES=0 for dockerfile in $(echo '${{ steps.find.outputs.files }}' | jq -r '.[]'); do echo "Linting: $dockerfile" ISSUES=$(hadolint --format json "$dockerfile" 2>/dev/null || echo "[]") ISSUE_COUNT=$(echo "$ISSUES" | jq 'length') TOTAL_ISSUES=$((TOTAL_ISSUES + ISSUE_COUNT)) if [[ $ISSUE_COUNT -gt 0 ]]; then echo "- **$dockerfile**: $ISSUE_COUNT issues" >> $GITHUB_STEP_SUMMARY fi done echo "" >> $GITHUB_STEP_SUMMARY echo "**Total issues found: $TOTAL_ISSUES**" >> $GITHUB_STEP_SUMMARY if [[ $TOTAL_ISSUES -gt 0 ]] && [[ "${{ github.event.inputs.fail_on_findings }}" == "true" ]]; then echo "::warning::Found $TOTAL_ISSUES Dockerfile security issues" fi # =========================================================================== # SUMMARY # =========================================================================== summary: name: SAST Summary runs-on: ubuntu-22.04 needs: [sast-scan, dotnet-security, dependency-check, dockerfile-lint] if: always() steps: - name: Generate summary run: | echo "## SAST Scan Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY echo "| SAST Analysis | ${{ needs.sast-scan.result }} |" >> $GITHUB_STEP_SUMMARY echo "| .NET Security | ${{ needs.dotnet-security.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Dependency Check | ${{ needs.dependency-check.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Dockerfile Lint | ${{ needs.dockerfile-lint.result }} |" >> $GITHUB_STEP_SUMMARY - name: Check for failures if: | github.event.inputs.fail_on_findings == 'true' && (needs.sast-scan.result == 'failure' || needs.dotnet-security.result == 'failure' || needs.dependency-check.result == 'failure') run: exit 1