-
Notifications
You must be signed in to change notification settings - Fork 37
feat(workflows): add security reusable workflows #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
3cbf38a
feat(workflows): add security reusable workflows
WilliamBerryiii 21c29da
fix(workflows): address Copilot review comments in security workflows
WilliamBerryiii 89d30c4
fix(workflows): improve gitleaks-scan workflow installation and execu…
WilliamBerryiii 9a1cf1d
Merge branch 'main' into feature/security-workflows
WilliamBerryiii 7b2ed41
feat(security): implement reusable workflow pattern and audit permiss…
WilliamBerryiii 5dee11e
merge: resolve README conflict by combining validation and security w…
WilliamBerryiii fcf3317
fix(security): implement PR #28 security workflow fixes
WilliamBerryiii 89e4b66
fix(security): remove invalid secrets context check from gitleaks wor…
WilliamBerryiii 96c94de
refactor(security): replace gitleaks-action with reusable workflow
WilliamBerryiii ccb75d7
refactor(security): rename gitleaks.yml to security-scan.yml for exte…
WilliamBerryiii 9a42922
fix(scripts): replace hardcoded staleness threshold with dynamic inpu…
WilliamBerryiii 70ca689
feat(security): implement GitHub native security tooling
WilliamBerryiii 5514b41
chore(security): remove Harden Runner from security workflows
WilliamBerryiii d4fd074
fix(security): correct hardcoded thresholds and documentation mismatches
WilliamBerryiii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| name: CodeQL Security Analysis | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ main, develop, feature/* ] | ||
| pull_request: | ||
| branches: [ main, develop ] | ||
| schedule: | ||
| - cron: '0 4 * * 0' # Sundays at 4 AM UTC | ||
| workflow_call: | ||
|
|
||
| permissions: | ||
| contents: read | ||
| security-events: write | ||
|
|
||
| jobs: | ||
| analyze: | ||
| name: CodeQL Analysis | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| security-events: write | ||
| actions: read | ||
|
|
||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| language: [ 'javascript' ] | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v4.2.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Initialize CodeQL | ||
| uses: github/codeql-action/init@95b1867cf797beb28ce725a6f25268e2d3304672 # v3.27.0 | ||
| with: | ||
| languages: ${{ matrix.language }} | ||
| queries: security-extended,security-and-quality | ||
|
|
||
| - name: Autobuild | ||
| uses: github/codeql-action/autobuild@95b1867cf797beb28ce725a6f25268e2d3304672 # v3.27.0 | ||
|
|
||
| - name: Perform CodeQL Analysis | ||
| uses: github/codeql-action/analyze@95b1867cf797beb28ce725a6f25268e2d3304672 # v3.27.0 | ||
| with: | ||
| category: "/language:${{ matrix.language }}" | ||
|
|
||
| - name: Add job summary | ||
| if: always() | ||
| run: | | ||
| echo "## CodeQL Security Analysis Complete" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Language:** ${{ matrix.language }}" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Queries:** security-extended, security-and-quality" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "📊 View results in the Security tab under Code Scanning" >> $GITHUB_STEP_SUMMARY |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| 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: 'actions,containers' | ||
| 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 | ||
| security-events: write | ||
|
|
||
| jobs: | ||
| scan: | ||
| name: Validate SHA Pinning Compliance | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| security-events: write | ||
| outputs: | ||
| compliance-score: ${{ steps.pinning.outputs.compliance-score }} | ||
| unpinned-count: ${{ steps.pinning.outputs.unpinned-count }} | ||
| is-compliant: ${{ steps.pinning.outputs.is-compliant }} | ||
| steps: | ||
| - name: Harden Runner | ||
| uses: step-security/harden-runner@92c522aaa6f53af082553dedc1596c80b71aba33 # v2.10.2 | ||
| with: | ||
| egress-policy: audit | ||
|
|
||
| - name: Checkout code | ||
| uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v4.2.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Run SHA Pinning Validation | ||
| id: pinning | ||
| shell: pwsh | ||
| run: | | ||
| Write-Host "Validating dependency SHA pinning compliance..." | ||
|
|
||
| # Build parameter list | ||
| $params = @{ | ||
| Path = '.' | ||
| Format = 'json' | ||
| OutputPath = 'logs/dependency-pinning-results.json' | ||
| } | ||
|
|
||
| if ('${{ inputs.dependency-types }}') { | ||
| $params['DependencyTypes'] = '${{ inputs.dependency-types }}' | ||
| } | ||
|
|
||
| if ('${{ inputs.threshold }}') { | ||
WilliamBerryiii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| $params['Threshold'] = [int]'${{ inputs.threshold }}' | ||
| } | ||
|
|
||
| # Run validation script | ||
| & scripts/security/Test-DependencyPinning.ps1 @params | ||
|
|
||
| # Extract metrics from report | ||
| $report = Get-Content logs/dependency-pinning-results.json | ConvertFrom-Json | ||
| $complianceScore = $report.ComplianceScore | ||
| $unpinnedCount = $report.UnpinnedDependencies | ||
| $threshold = [int]'${{ inputs.threshold }}' | ||
| $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" | ||
|
|
||
| # Fire GitHub Actions warnings for each violation | ||
| if ($unpinnedCount -gt 0) { | ||
| foreach ($violation in $report.Violations) { | ||
| Write-Output "::warning file=$($violation.File),line=$($violation.Line)::Unpinned $($violation.Type) dependency: $($violation.Name)@$($violation.Version) (Severity: $($violation.Severity))" | ||
| } | ||
| } | ||
|
|
||
| - name: Upload SARIF to Security tab | ||
| if: inputs.upload-sarif && always() | ||
| uses: github/codeql-action/upload-sarif@95b1867cf797beb28ce725a6f25268e2d3304672 # 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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4.4.3 | ||
| 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 SHA-pinned.** | ||
|
|
||
| Review the warnings in the workflow log and pin dependencies to specific SHA commits. | ||
|
|
||
| "@ | ||
| } else { | ||
| @" | ||
|
|
||
| ### ✅ All Dependencies Pinned | ||
|
|
||
| All dependencies are properly SHA-pinned. | ||
|
|
||
| "@ | ||
| }) | ||
| "@ | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding UTF8 | ||
|
|
||
| - name: Fail job if non-compliant | ||
| if: steps.pinning.outputs.is-compliant == 'false' && !inputs.soft-fail | ||
| run: | | ||
| echo "Dependency pinning scan failed - compliance threshold not met" | ||
| exit 1 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| name: Dependency Review | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [ main, develop ] | ||
| workflow_call: | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| dependency-review: | ||
| name: Review Dependencies | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v4.2.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Dependency Review | ||
| uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # v4.3.4 | ||
| with: | ||
| fail-on-severity: moderate | ||
| comment-summary-in-pr: always |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| name: Security Scan | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ main, develop ] | ||
| pull_request: | ||
| branches: [ main, develop ] | ||
|
|
||
| permissions: | ||
| contents: read | ||
| security-events: write | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| codeql: | ||
| name: CodeQL Security Analysis | ||
| uses: ./.github/workflows/codeql-analysis.yml | ||
| permissions: | ||
| contents: read | ||
| security-events: write | ||
| actions: read | ||
|
|
||
| dependency-review: | ||
| name: Dependency Review | ||
| if: github.event_name == 'pull_request' | ||
| uses: ./.github/workflows/dependency-review.yml | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| name: SHA Staleness Check | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| max-age-days: | ||
| description: 'Maximum SHA age in days before considered stale' | ||
| required: false | ||
| type: number | ||
| default: 30 | ||
| workflow_call: | ||
| inputs: | ||
| max-age-days: | ||
| description: 'Maximum SHA age in days before considered stale' | ||
| required: false | ||
| type: number | ||
| default: 30 | ||
| outputs: | ||
| stale-count: | ||
| description: 'Number of stale SHA pins found' | ||
| value: ${{ jobs.check-staleness.outputs.stale-count }} | ||
| has-stale: | ||
| description: 'Whether any stale SHA pins were found' | ||
| value: ${{ jobs.check-staleness.outputs.has-stale }} | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| check-staleness: | ||
| name: Check Action SHA Staleness | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| outputs: | ||
| stale-count: ${{ steps.staleness.outputs.stale-count }} | ||
| has-stale: ${{ steps.staleness.outputs.has-stale }} | ||
| steps: | ||
| - name: Harden Runner | ||
| uses: step-security/harden-runner@92c522aaa6f53af082553dedc1596c80b71aba33 # v2.10.2 | ||
| with: | ||
| egress-policy: audit | ||
|
|
||
| - name: Checkout code | ||
| uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v4.2.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Run SHA Staleness Check | ||
| id: staleness | ||
| shell: pwsh | ||
WilliamBerryiii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| $thresholdDays = if ('${{ inputs.max-age-days }}') { | ||
| [int]'${{ inputs.max-age-days }}' | ||
| } else { | ||
| 30 | ||
| } | ||
WilliamBerryiii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Write-Host "Running SHA staleness check with $thresholdDays day threshold..." | ||
|
|
||
| # Run staleness check and capture output | ||
| & scripts/security/Test-SHAStaleness.ps1 ` | ||
| -MaxAge $thresholdDays ` | ||
| -OutputFormat github ` | ||
| -OutputPath logs/sha-staleness-results.json | ||
|
|
||
| # Extract stale count from JSON report | ||
| if (Test-Path logs/sha-staleness-results.json) { | ||
| $report = Get-Content logs/sha-staleness-results.json | ConvertFrom-Json | ||
| $staleCount = ($report.Dependencies | Where-Object { $_.DaysOld -gt $thresholdDays } | Measure-Object).Count | ||
| "stale-count=$staleCount" >> $env:GITHUB_OUTPUT | ||
| "has-stale=$(if ($staleCount -gt 0) { 'true' } else { 'false' })" >> $env:GITHUB_OUTPUT | ||
|
|
||
| Write-Host "Stale Dependencies Found: $staleCount" | ||
|
|
||
| # Fire warnings for each stale dependency | ||
| foreach ($staleDep in $report.Dependencies) { | ||
| if ($staleDep.DaysOld -gt $thresholdDays) { | ||
| Write-Output "::warning file=$($staleDep.File)::Stale SHA detected: $($staleDep.Action)@$($staleDep.CurrentVersion) is $($staleDep.DaysOld) days old (latest: $($staleDep.LatestVersion))" | ||
| } | ||
| } | ||
| } else { | ||
| "stale-count=0" >> $env:GITHUB_OUTPUT | ||
| "has-stale=false" >> $env:GITHUB_OUTPUT | ||
| } | ||
|
|
||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Warning "Some GitHub Actions have stale SHA pins. Review the warnings above." | ||
| Write-Host "::warning::GitHub Actions with stale SHA pins detected. This is informational only - no action required immediately." | ||
| } else { | ||
| Write-Host "All GitHub Actions SHA pins are up to date." | ||
| } | ||
|
|
||
| - name: Upload staleness report | ||
| if: always() | ||
| uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4.4.3 | ||
| with: | ||
| name: sha-staleness-results | ||
| path: logs/sha-staleness-results.json | ||
| retention-days: 90 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.