name: Dependency Pinning Scan on: workflow_call: inputs: threshold: description: 'Compliance threshold percentage (0-100)' required: false type: number default: 95 dependency-types: description: 'Comma-separated list of dependency types to check' required: false type: string default: 'github-actions,npm,workflow-npm-commands' soft-fail: description: 'Whether to continue on compliance violations' required: false type: boolean default: false upload-sarif: description: 'Whether to upload SARIF results to Security tab' required: false type: boolean default: false upload-artifact: description: 'Whether to upload results as artifact' required: false type: boolean default: true outputs: compliance-score: description: 'Compliance score percentage' value: ${{ jobs.scan.outputs.compliance-score }} unpinned-count: description: 'Number of unpinned dependencies found' value: ${{ jobs.scan.outputs.unpinned-count }} is-compliant: description: 'Whether repository meets compliance threshold' value: ${{ jobs.scan.outputs.is-compliant }} permissions: contents: read jobs: scan: name: Validate SHA Pinning Compliance runs-on: ubuntu-latest permissions: contents: read security-events: write # Required for SARIF upload to Security tab outputs: compliance-score: ${{ steps.pinning.outputs.compliance-score }} unpinned-count: ${{ steps.pinning.outputs.unpinned-count }} is-compliant: ${{ steps.pinning.outputs.is-compliant }} steps: - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Run Dependency Pinning Validation id: pinning shell: pwsh run: | Write-Host "Validating dependency SHA pinning compliance..." # Ensure logs directory exists New-Item -ItemType Directory -Force -Path logs | Out-Null # Build parameter list for JSON output (always generate) $params = @{ Path = '.' Format = 'json' OutputPath = 'logs/dependency-pinning-results.json' } # Enable failure on threshold violations unless soft-fail is requested if ('${{ inputs.soft-fail }}' -ne 'true') { $params['FailOnUnpinned'] = $true } # Pass dependency types filter to script if ('${{ inputs.dependency-types }}') { $params['IncludeTypes'] = '${{ inputs.dependency-types }}' } # Pass compliance threshold to script (script handles enforcement) if ('${{ inputs.threshold }}') { $params['Threshold'] = [int]'${{ inputs.threshold }}' } # Run validation script (JSON format) & scripts/security/Test-DependencyPinning.ps1 @params $jsonExitCode = $LASTEXITCODE # Generate SARIF format if requested if ('${{ inputs.upload-sarif }}' -eq 'true') { Write-Host "Generating SARIF format for Security tab..." $params['Format'] = 'sarif' $params['OutputPath'] = 'logs/dependency-pinning-results.sarif' & scripts/security/Test-DependencyPinning.ps1 @params } # Extract metrics from JSON report if (Test-Path logs/dependency-pinning-results.json) { $report = Get-Content logs/dependency-pinning-results.json | ConvertFrom-Json $complianceScore = $report.ComplianceScore $unpinnedCount = $report.UnpinnedDependencies # Extract threshold from report metadata (script calculated compliance) $threshold = $report.Metadata.ComplianceThreshold $isCompliant = $complianceScore -ge $threshold "compliance-score=$complianceScore" >> $env:GITHUB_OUTPUT "unpinned-count=$unpinnedCount" >> $env:GITHUB_OUTPUT "is-compliant=$($isCompliant.ToString().ToLower())" >> $env:GITHUB_OUTPUT Write-Host "Compliance Score: $complianceScore%" Write-Host "Unpinned Dependencies: $unpinnedCount" Write-Host "Is Compliant (>=$threshold%): $isCompliant" } else { Write-Error "Failed to generate dependency pinning report" exit 1 } - name: Upload SARIF to Security tab if: inputs.upload-sarif && always() uses: github/codeql-action/upload-sarif@ce729e4d353d580e6cacd6a8cf2921b72e5e310a # v3.27.0 with: sarif_file: logs/dependency-pinning-results.sarif category: dependency-pinning continue-on-error: true - name: Upload validation report if: inputs.upload-artifact && always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: dependency-pinning-results path: logs/dependency-pinning-results.json retention-days: 90 - name: Add job summary if: always() shell: pwsh run: | $complianceScore = '${{ steps.pinning.outputs.compliance-score }}' $unpinnedCount = '${{ steps.pinning.outputs.unpinned-count }}' $isCompliant = '${{ steps.pinning.outputs.is-compliant }}' @" ## Dependency Pinning Scan Results | Metric | Value | |--------|-------| | Compliance Score | $complianceScore% | | Unpinned Dependencies | $unpinnedCount | | Status | $(if ($isCompliant -eq 'true') { '✅ Compliant' } else { '⚠️ Non-Compliant' }) | $(if ($unpinnedCount -ne '0') { @" ### ⚠️ Action Required **$unpinnedCount dependencies are not properly pinned.** Review the warnings in the workflow log and pin dependencies to specific SHA commits. "@ } else { @" ### ✅ All Dependencies Pinned All dependencies are properly pinned. "@ }) "@ | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding UTF8