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
416 changes: 202 additions & 214 deletions .github/workflows/README.md

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
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
# Weekly scan: Sundays at 4 AM UTC
- cron: '0 4 * * 0'
workflow_call:

permissions:
Expand Down
81 changes: 52 additions & 29 deletions .github/workflows/dependency-pinning-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
description: 'Comma-separated list of dependency types to check'
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value for dependency-types changed from 'actions,containers' to 'github-actions'. However, the parameter description on line 12 still says "Comma-separated list of dependency types to check" without specifying what valid values are (e.g., github-actions, npm, pip, containers).

Additionally, this creates an inconsistency: the default was checking both actions and containers, but now only checks github-actions. Is github-actions the new canonical name for what was previously called actions? If so, the old containers check is being silently dropped from the default.

Please either:

  1. Document the valid values in the description
  2. Clarify if containers checking is intentionally removed from the default
  3. Update the description to match the script's actual parameter name (IncludeTypes as used on line 92)
Suggested change
description: 'Comma-separated list of dependency types to check'
description: 'Comma-separated list of dependency types to check. Valid values: github-actions, npm, pip, containers. Default is github-actions. (Parameter is passed as IncludeTypes to the script.)'

Copilot uses AI. Check for mistakes.
required: false
type: string
default: 'actions,containers'
default: 'github-actions'
soft-fail:
description: 'Whether to continue on compliance violations'
required: false
Expand Down Expand Up @@ -71,45 +71,74 @@ jobs:
run: |
Write-Host "Validating dependency SHA pinning compliance..."

# Build parameter list
# Ensure logs directory exists
New-Item -ItemType Directory -Force -Path logs | Out-Null

# Build parameter list for JSON output (always generate)
$params = @{
Path = '.'
Recursive = $true
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['DependencyTypes'] = '${{ inputs.dependency-types }}'
$params['IncludeTypes'] = '${{ inputs.dependency-types }}'
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow input parameter is named dependency-types (line 10-15) but it's being passed to the PowerShell script as IncludeTypes (line 92). While this might work if the script parameter is actually named IncludeTypes, the naming inconsistency is confusing.

According to the script documentation in Test-DependencyPinning.ps1 (line 32-33 in this PR), the parameter is indeed .PARAMETER IncludeTypes with description "Comma-separated list of dependency types to check."

For clarity and consistency:

  1. Either rename the workflow input to include-types to match the script parameter naming
  2. Or add a comment explaining the parameter name mapping
  3. Update the workflow input description to match the script's parameter description exactly

Copilot uses AI. Check for mistakes.
}

# Pass compliance threshold to script (script handles enforcement)
if ('${{ inputs.threshold }}') {
$params['Threshold'] = [int]'${{ inputs.threshold }}'
}

# Run validation script
# Run validation script (JSON format)
& scripts/security/Test-DependencyPinning.ps1 @params
$jsonExitCode = $LASTEXITCODE
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script exit code from the JSON format run ($jsonExitCode = $LASTEXITCODE) is captured but never used. This means if the first run fails, the workflow will continue to generate SARIF format and then succeed even if it shouldn't have. The script's exit code should be checked and honored.

Add after line 102:

if ($jsonExitCode -ne 0) {
    Write-Host "JSON validation failed with exit code $jsonExitCode"
    exit $jsonExitCode
}
Suggested change
$jsonExitCode = $LASTEXITCODE
$jsonExitCode = $LASTEXITCODE
if ($jsonExitCode -ne 0) {
Write-Host "JSON validation failed with exit code $jsonExitCode"
exit $jsonExitCode
}

Copilot uses AI. Check for mistakes.

# 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
}
Comment on lines +100 to +111
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSON format script run captures the exit code ($jsonExitCode = $LASTEXITCODE on line 102) but never uses it. If the JSON validation fails, the workflow continues to generate SARIF format and process results as if everything succeeded. This is problematic because:

  1. A failed JSON run means the validation found issues and the script should have exited with code 1 (when FailOnUnpinned is true)
  2. Running SARIF generation after a failure may produce incorrect or incomplete results
  3. The workflow will ultimately succeed even though validation failed

The captured exit code should be checked and the workflow should fail immediately if non-zero:

& scripts/security/Test-DependencyPinning.ps1 @params
$jsonExitCode = $LASTEXITCODE

if ($jsonExitCode -ne 0) {
    Write-Host "JSON validation failed with exit code $jsonExitCode"
    exit $jsonExitCode
}

Copilot uses AI. Check for mistakes.

# 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))"
# 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"

# 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))"
}
}
}
else {
Write-Error "Failed to generate dependency pinning report"
exit 1
}

- name: Upload SARIF to Security tab
if: inputs.upload-sarif && always()
Expand Down Expand Up @@ -164,9 +193,3 @@ jobs:
"@
})
"@ | 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
47 changes: 47 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Main Branch CI

on:
push:
branches:
- main
workflow_dispatch:

# Minimal permissions for security
permissions:
contents: read

jobs:
spell-check:
name: Spell Check
uses: ./.github/workflows/spell-check.yml
permissions:
contents: read
with:
soft-fail: false

markdown-lint:
name: Markdown Lint
uses: ./.github/workflows/markdown-lint.yml
permissions:
contents: read
with:
soft-fail: false

table-format:
name: Table Format Check
uses: ./.github/workflows/table-format.yml
permissions:
contents: read
with:
soft-fail: false

dependency-pinning-scan:
name: Dependency Pinning Scan
uses: ./.github/workflows/dependency-pinning-scan.yml
permissions:
contents: read
security-events: write
with:
soft-fail: false
upload-sarif: true
upload-artifact: true
11 changes: 5 additions & 6 deletions .github/workflows/markdown-link-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ jobs:
New-Item -ItemType Directory -Force -Path logs | Out-Null

- name: Run markdown link check
id: link-check
shell: pwsh
run: |
& scripts/linting/Markdown-Link-Check.ps1
continue-on-error: true
continue-on-error: ${{ inputs.soft-fail }}

- name: Upload markdown link check results
if: always()
Expand All @@ -58,10 +59,8 @@ jobs:
retention-days: 30

- name: Check results and fail if needed
if: ${{ !inputs.soft-fail }}
if: ${{ !inputs.soft-fail && steps.link-check.outcome == 'failure' }}
shell: pwsh
run: |
if ($env:MARKDOWN_LINK_CHECK_FAILED -eq 'true') {
Write-Host "Markdown link check failed and soft-fail is false. Failing the job."
exit 1
}
Write-Host "Markdown link check failed and soft-fail is false. Failing the job."
exit 1
85 changes: 85 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: PR Validation

on:
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
- develop
workflow_dispatch:

# Minimal permissions for security
permissions:
contents: read

jobs:
spell-check:
name: Spell Check
uses: ./.github/workflows/spell-check.yml
permissions:
contents: read
with:
soft-fail: false

markdown-lint:
name: Markdown Lint
uses: ./.github/workflows/markdown-lint.yml
permissions:
contents: read
with:
soft-fail: false

table-format:
name: Table Format Check
uses: ./.github/workflows/table-format.yml
permissions:
contents: read
with:
soft-fail: false

psscriptanalyzer:
name: PowerShell Lint
uses: ./.github/workflows/ps-script-analyzer.yml
permissions:
contents: read
with:
soft-fail: false
changed-files-only: true

frontmatter-validation:
name: Frontmatter Validation
uses: ./.github/workflows/frontmatter-validation.yml
permissions:
contents: read
with:
soft-fail: false
changed-files-only: true
skip-footer-validation: false
warnings-as-errors: true

link-lang-check:
name: Link Language Check
uses: ./.github/workflows/link-lang-check.yml
permissions:
contents: read
with:
soft-fail: false

markdown-link-check:
name: Markdown Link Check
uses: ./.github/workflows/markdown-link-check.yml
permissions:
contents: read
with:
soft-fail: true

dependency-pinning-check:
name: Validate Dependency Pinning
uses: ./.github/workflows/dependency-pinning-scan.yml
permissions:
contents: read
security-events: write
with:
soft-fail: false
upload-sarif: true
upload-artifact: false
1 change: 1 addition & 0 deletions .github/workflows/ps-script-analyzer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ jobs:
logs/psscriptanalyzer-results.json
logs/psscriptanalyzer-summary.json
retention-days: 30
if-no-files-found: ignore

- name: Check results
if: "!inputs.soft-fail"
Expand Down
12 changes: 1 addition & 11 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

permissions:
contents: read
Expand All @@ -18,12 +16,4 @@ jobs:
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
actions: read
6 changes: 3 additions & 3 deletions .github/workflows/weekly-security-maintenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
max-age-days: ${{ inputs.max-age-days || 30 }}

# Job 3: Run CodeQL security analysis
codeql-scan:
codeql-analysis:
name: CodeQL Security Analysis
uses: ./.github/workflows/codeql-analysis.yml
permissions:
Expand All @@ -49,7 +49,7 @@ jobs:
# Job 4: Generate consolidated summary
summary:
name: Security Maintenance Summary
needs: [validate-pinning, check-staleness, codeql-scan]
needs: [validate-pinning, check-staleness, codeql-analysis]
if: always()
runs-on: ubuntu-latest
permissions:
Expand All @@ -61,7 +61,7 @@ jobs:
$pinningScore = '${{ needs.validate-pinning.outputs.compliance-score }}'
$unpinnedCount = '${{ needs.validate-pinning.outputs.unpinned-count }}'
$staleCount = '${{ needs.check-staleness.outputs.stale-count }}'
$codeqlResult = '${{ needs.codeql-scan.result }}'
$codeqlResult = '${{ needs.codeql-analysis.result }}'

# Determine overall status
$hasIssues = $false
Expand Down
Loading
Loading