diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 3de86f5e..ea1411e4 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,172 +1,180 @@ --- -title: GitHub Workflows -description: Documentation for GitHub Actions workflows in the HVE Core project +title: GitHub Actions Workflows +description: Modular CI/CD workflow architecture for validation, security scanning, and automated maintenance author: HVE Core Team ms.date: 2025-11-12 ms.topic: reference keywords: - github actions - - workflows - ci/cd + - workflows + - security scanning - automation + - reusable workflows - validation - security -estimated_reading_time: 20 +estimated_reading_time: 25 --- -# GitHub Actions Workflows - -This directory contains GitHub Actions workflow definitions for continuous integration, code quality validation, security scanning, and automated maintenance in the HVE Core project. +This directory contains GitHub Actions workflows for continuous integration, security scanning, and automated maintenance of the `hve-core` repository. ## Overview Workflows run automatically on pull requests, pushes to protected branches, and scheduled intervals. They enforce code quality standards, validate documentation, perform security scans, and ensure consistency across the codebase. -## Workflow Organization - -### Naming Conventions +## Architecture -Workflows follow a consistent naming pattern to indicate their purpose and usage: +Modular reusable workflows following Single Responsibility Principle. Each workflow handles one specific tool or validation task. -* **`*-scan.yml`**: Security scanning workflows (reusable or standalone) - * Example: `gitleaks-scan.yml`, `checkov-scan.yml`, `dependency-pinning-scan.yml` - * Purpose: Run security scanners and produce SARIF outputs for the Security tab - * Typically support `workflow_call` trigger for composition - -* **`*-check.yml`**: Validation and compliance checking workflows - * Example: `sha-staleness-check.yml` - * Purpose: Validate code/configuration quality or security posture - * May run on schedule or be called by orchestrator workflows +## Workflow Organization -* **`*-lint.yml`**: Code quality and formatting workflows - * Example: `markdown-lint.yml`, `spell-check.yml` - * Purpose: Enforce code style and formatting standards - * Typically run on pull requests +### Naming Conventions -* **Orchestrator workflows**: Compose multiple reusable workflows - * Example: `weekly-security-maintenance.yml` - * Purpose: Run multiple related checks and generate consolidated reports - * Typically run on schedule or manual trigger +| Pattern | Purpose | Example | +|---------------|----------------------------------|---------------------------| +| `*-scan.yml` | Security scanning, SARIF outputs | `codeql-analysis.yml` | +| `*-check.yml` | Validation, compliance checking | `markdown-link-check.yml` | +| `*-lint.yml` | Code quality, formatting | `markdown-lint.yml` | +| Orchestrators | Compose multiple workflows | `pr-validation.yml` | ### Workflow Types -**Reusable Workflows** (`workflow_call` trigger) +**Reusable** (`workflow_call`): Called by other workflows, accept inputs, expose outputs, single-task focused. -* Designed to be called by other workflows -* Accept inputs via `workflow_call.inputs` -* Expose outputs via `workflow_call.outputs` -* Should be self-contained and focused on a single task -* Include appropriate permissions declarations +**Standalone** (`schedule`/`push`/`pull_request`): Run on events, may compose reusable workflows. -**Standalone Workflows** (`schedule`, `workflow_dispatch`, `push`, `pull_request` triggers) +## Orchestrator Workflows -* Run independently based on event triggers -* May call reusable workflows for composition -* Should minimize duplication by using reusable workflows +Compose multiple reusable workflows for comprehensive validation and security scanning. -## Current Workflows +| Workflow | Triggers | Jobs | Mode | Purpose | +|-----------------------------------|-----------------------------------------|-----------------------------------------------------------------|----------------------------|--------------------------------------| +| `pr-validation.yml` | PR to main/develop (open, push, reopen) | 9 jobs (8 reusable workflows + 1 inline) | Strict validation | Pre-merge quality gate with security | +| `main.yml` | Push to main | 5 jobs (5 reusable workflows) | Strict mode, SARIF uploads | Post-merge validation | +| `weekly-security-maintenance.yml` | Schedule (Sun 2AM UTC) | 4 (validate-pinning, check-staleness, codeql-analysis, summary) | Soft-fail warnings | Weekly security posture | -### Security Workflows +**pr-validation.yml jobs**: codeql-analysis, spell-check, markdown-lint, table-format, psscriptanalyzer, frontmatter-validation, link-lang-check, markdown-link-check, dependency-pinning-check -| Workflow | Type | Purpose | Triggers | -|----------|------|---------|----------| -| `weekly-security-maintenance.yml` | Orchestrator | Weekly security posture check | `schedule`, `workflow_dispatch` | -| `dependency-pinning-scan.yml` | Reusable | Validate SHA pinning compliance | `workflow_call` | -| `sha-staleness-check.yml` | Reusable | Check for stale SHA pins | `workflow_call`, `workflow_dispatch` | -| `codeql-analysis.yml` | Reusable | CodeQL security analysis | `push`, `pull_request`, `schedule`, `workflow_call` | -| `dependency-review.yml` | Reusable | Dependency vulnerability review | `pull_request`, `workflow_call` | -| `security-scan.yml` | Standalone | Security scanning orchestrator | `push`, `pull_request` | +**main.yml jobs**: spell-check, markdown-lint, table-format, codeql-analysis, dependency-pinning-scan -### Validation Workflows +## Reusable Workflows -| Workflow | Purpose | Triggers | Configuration | -|----------|---------|----------|---------------| -| `ps-script-analyzer.yml` | PowerShell static analysis | PR (*.ps1, *.psm1), dispatch | `scripts/linting/PSScriptAnalyzer.psd1` | -| `markdown-lint.yml` | Markdown formatting standards | PR (*.md), dispatch | `.markdownlint.json` | -| `frontmatter-validation.yml` | YAML frontmatter validation | PR (*.md), dispatch | Script hardcoded | -| `markdown-link-check.yml` | Link validation | PR (*.md), dispatch | `scripts/linting/markdown-link-check.config.json` | -| `link-lang-check.yml` | Detect language-specific URLs | PR (*.md), dispatch | Script regex | -| `spell-check.yml` | Spell checking | PR, dispatch | `.cspell.json` | -| `table-format.yml` | Markdown table formatting | PR (*.md), dispatch | N/A | +### Validation Workflows -## Using Reusable Workflows +| Workflow | Tool | Purpose | Key Inputs | Artifacts | +|------------------------------|--------------------------|--------------------------------------|-----------------------------------------------------------------------------------------------------------------|--------------------------------| +| `spell-check.yml` | cspell | Validate spelling across all files | `soft-fail` (false) | spell-check-results | +| `markdown-lint.yml` | markdownlint-cli | Enforce markdown standards | `soft-fail` (false) | markdown-lint-results | +| `table-format.yml` | markdown-table-formatter | Verify table formatting (check-only) | `soft-fail` (false) | table-format-results | +| `ps-script-analyzer.yml` | PSScriptAnalyzer | PowerShell static analysis | `soft-fail` (false), `changed-files-only` (true) | psscriptanalyzer-results | +| `frontmatter-validation.yml` | Custom PS script | YAML frontmatter validation | `soft-fail` (false), `changed-files-only` (true), `skip-footer-validation` (false), `warnings-as-errors` (true) | frontmatter-validation-results | +| `link-lang-check.yml` | Custom PS script | Detect language-specific URLs | `soft-fail` (false) | link-lang-check-results | +| `markdown-link-check.yml` | markdown-link-check | Validate links (internal/external) | `soft-fail` (true) | markdown-link-check-results | -### Basic Usage +All validation workflows use `permissions: contents: read`, publish PR annotations, and retain artifacts for 30 days. -Call a reusable workflow from another workflow using the `uses` keyword: +**Usage example**: ```yaml jobs: - security-scan: - name: CodeQL Security Analysis - uses: ./.github/workflows/codeql-analysis.yml - permissions: - contents: read - security-events: write - actions: read + spell-check: + uses: ./.github/workflows/spell-check.yml + with: + soft-fail: false ``` -### Passing Inputs +## Workflow Result Publishing Strategy -Provide inputs to reusable workflows using the `with` keyword: +Each modular workflow implements comprehensive 4-channel result publishing: -```yaml -jobs: - pinning-check: - uses: ./.github/workflows/dependency-pinning-scan.yml - with: - threshold: 95 - dependency-types: 'actions,containers' - soft-fail: true - upload-sarif: true - upload-artifact: true -``` +1. **PR Annotations**: Warnings/errors appear on Files Changed tab +2. **Artifacts**: Raw output files retained for 30 days +3. **SARIF Reports**: Security tab integration (security workflows only) +4. **Job Summaries**: Rich markdown summaries in Actions tab -### Accessing Outputs +## Security Best Practices -Access outputs from reusable workflows in downstream jobs: +All workflows in this repository follow security best practices: -```yaml -jobs: - security-scan: - uses: ./.github/workflows/dependency-pinning-scan.yml - with: - soft-fail: true +### SHA Pinning - summary: - needs: security-scan - runs-on: ubuntu-latest - steps: - - name: Check compliance - run: | - echo "Compliance: ${{ needs.security-scan.outputs.compliance-score }}%" - echo "Unpinned: ${{ needs.security-scan.outputs.unpinned-count }}" -``` +* All GitHub Actions use full 40-character commit SHAs +* Comments include semantic version tags for human readability +* Example: `uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2` + +### Minimal Permissions + +* Workflows use minimal permissions by default (`contents: read`) +* Additional permissions granted only when required for specific jobs +* Example: `security-events: write` only for SARIF uploads + +### Credential Protection + +* `persist-credentials: false` used in checkouts to prevent credential leakage +* Secrets inherited explicitly with `secrets: inherit` +* No hardcoded tokens or credentials + +### Network Hardening + +* `step-security/harden-runner` used in all jobs for egress policy auditing +* Egress policy set to `audit` mode for visibility + +## Maintenance + +### Updating SHA Pins + +The repository includes PowerShell scripts in `scripts/security/` for SHA pinning maintenance: + +* `Update-ActionSHAPinning.ps1` - Update GitHub Actions SHA pins +* `Update-DockerSHAPinning.ps1` - Update Docker image SHA pins +* `Update-ShellScriptSHAPinning.ps1` - Update shell script dependencies +* `Test-SHAStaleness.ps1` - Check for stale SHA pins +* `Test-DependencyPinning.ps1` - Validate SHA pinning compliance + +### Dependabot Integration + +Dependabot is configured to automatically create PRs for: -## Workflow Details +* GitHub Actions updates +* npm package updates +* Other dependency updates -### Security Workflows +The SHA staleness check workflow complements Dependabot by monitoring for stale pins between updates. -#### `weekly-security-maintenance.yml` +## Security Workflows -**Purpose**: Orchestrates weekly security posture checks +### Reusable Security Workflows -**Schedule**: Weekly on Sundays at 02:00 UTC +#### `codeql-analysis.yml` + +**Purpose**: Performs comprehensive security analysis using GitHub CodeQL + +**Triggers**: `schedule` (Sundays at 4 AM UTC), `workflow_call` + +**Features**: + +* **Languages**: JavaScript/TypeScript analysis +* **Queries**: security-extended and security-and-quality query suites +* **Coverage**: Detects SQL injection, XSS, command injection, path traversal, and 200+ other vulnerabilities +* **Integration**: Results appear in Security > Code Scanning tab +* **Auto-build**: Automatically detects and builds JavaScript/TypeScript projects -**Jobs**: +**Outputs**: SARIF results uploaded to GitHub Security tab, job summary with analysis details -* `validate-pinning`: Checks SHA pinning compliance (100% threshold) -* `check-staleness`: Identifies stale SHA pins (>30 days) -* `codeql-scan`: Runs CodeQL security analysis -* `summary`: Generates consolidated report +#### `dependency-review.yml` -**Outputs**: Consolidated job summaries, JSON reports +**Purpose**: Reviews dependency changes in pull requests for known vulnerabilities -**Security Coverage**: +**Triggers**: `pull_request`, `workflow_call` -* **CodeQL Analysis**: JavaScript/TypeScript code security scanning with security-extended queries -* **GitHub Secret Scanning**: Automatic detection of 200+ secret patterns (enabled via Security tab) -* **Dependabot Alerts**: Automatic vulnerability detection for npm dependencies (enabled via Security tab) +**Features**: + +* **Threshold**: Fails on moderate or higher severity vulnerabilities +* **PR Comments**: Automatically comments on PRs with vulnerability summary +* **Coverage**: Checks npm packages against GitHub Advisory Database +* **Integration**: Works with Dependabot alerts and security advisories + +**Behavior**: Blocks PRs introducing vulnerable dependencies (moderate+ severity) #### `dependency-pinning-scan.yml` @@ -207,85 +215,102 @@ jobs: * High: 181-365 days * Critical: >365 days -#### `codeql-analysis.yml` - -**Purpose**: Performs comprehensive security analysis using GitHub CodeQL - -**Triggers**: `push`, `pull_request`, `schedule` (Sundays at 4 AM UTC), `workflow_call` - -**Features**: - -* **Languages**: JavaScript/TypeScript analysis -* **Queries**: security-extended and security-and-quality query suites -* **Coverage**: Detects SQL injection, XSS, command injection, path traversal, and 200+ other vulnerabilities -* **Integration**: Results appear in Security > Code Scanning tab -* **Autobuild**: Automatically detects and builds JavaScript/TypeScript projects - -**Outputs**: SARIF results uploaded to GitHub Security tab, job summary with analysis details - -#### `dependency-review.yml` +## Architecture Decisions -**Purpose**: Reviews dependency changes in pull requests for known vulnerabilities +### CodeQL Execution Strategy -**Triggers**: `pull_request`, `workflow_call` - -**Features**: - -* **Threshold**: Fails on moderate or higher severity vulnerabilities -* **PR Comments**: Automatically comments on PRs with vulnerability summary -* **Coverage**: Checks npm packages against GitHub Advisory Database -* **Integration**: Works with Dependabot alerts and security advisories +**Previous Behavior:** CodeQL previously ran as both a standalone workflow (on PR/push events) AND within orchestrator workflows, causing duplicate analyses on the same commits and wasting GitHub Actions minutes. -**Behavior**: Blocks PRs introducing vulnerable dependencies (moderate+ severity) +**Current Architecture:** CodeQL now runs exclusively through orchestrator workflows to prevent duplicate runs and ensure consistent security scanning: -### Validation Workflows +* **CodeQL PR validation**: Runs via `pr-validation.yml` on all PR activity (open, push, reopen) +* **Main branch**: Runs via `main.yml` on every push to main +* **Weekly scan**: Standalone scheduled run every Sunday at 4 AM UTC for continuous security monitoring -#### `ps-script-analyzer.yml` +This architecture ensures: -**Purpose**: Static analysis of PowerShell scripts using PSScriptAnalyzer +* CodeQL does not run duplicate analyses on the same commit (previously executed both standalone and within orchestrators) +* Comprehensive security coverage across all code paths +* Clear ownership of when and why CodeQL executes +* Reduced GitHub Actions minutes consumption -**Features**: +**Workflow Execution Matrix**: -* Analyzes only changed PowerShell files -* Creates GitHub annotations for violations -* Exports JSON results and markdown summary -* Uploads artifacts with 30-day retention +| Event | Workflows That Run | CodeQL Included | +|--------------------------------------|----------------------------------------------------------|--------------------| +| Open PR to main/develop | `pr-validation.yml` (9 jobs) | ✅ Yes | +| Push to PR branch | `pr-validation.yml` (9 jobs) | ✅ Yes | +| Merge to main | `main.yml` (5 jobs) | ✅ Yes | +| Sunday 4AM UTC | `codeql-analysis.yml`, `weekly-security-maintenance.yml` | ✅ Yes (standalone) | +| Feature branch push (no open PR)[^1] | None | ❌ No | -**Exit Behavior**: Fails on Error or Warning severity issues +[^1]: Feature branches without an open PR are not validated. Open a PR to main or develop to trigger validation workflows. -#### `frontmatter-validation.yml` +## Adding New Workflows -**Purpose**: Validates YAML frontmatter in markdown files +To add a new workflow to the repository: -**Required Fields**: +1. Create `{tool-name}.yml` following existing patterns +2. Implement 4-channel result publishing (annotations, artifacts, SARIF if security, summaries) +3. Add harden-runner and SHA pinning +4. Use minimal permissions +5. Add soft-fail input support +6. Update `pr-validation.yml` and `main.yml` to include new job +7. Document in this README -* `title`, `description`, `author`, `ms.date`, `ms.topic`, `keywords`, `estimated_reading_time` +## Using Reusable Workflows -**Features**: +### Basic Usage -* Validates frontmatter format -* Checks footer format and copyright notice -* Creates GitHub annotations -* Exports JSON statistics +Call a reusable workflow from another workflow using the `uses` keyword: -#### `markdown-link-check.yml` +```yaml +jobs: + security-scan: + name: CodeQL Security Analysis + uses: ./.github/workflows/codeql-analysis.yml + permissions: + contents: read + security-events: write + actions: read +``` -**Purpose**: Validates all links in markdown files +### Passing Inputs -**Features**: +Provide inputs to reusable workflows using the `with` keyword: -* Checks internal and external links -* Retries failed links -* Creates GitHub annotations -* Generates detailed summaries +```yaml +jobs: + pinning-check: + uses: ./.github/workflows/dependency-pinning-scan.yml + with: + threshold: 95 + dependency-types: 'actions,containers' + soft-fail: true + upload-sarif: true + upload-artifact: true +``` -**Exit Behavior**: Soft-fail (sets failure status but continues) +### Accessing Outputs -#### `link-lang-check.yml` +Access outputs from reusable workflows in downstream jobs: -**Purpose**: Detects URLs with language paths (e.g., `/en-us/`) +```yaml +jobs: + security-scan: + uses: ./.github/workflows/dependency-pinning-scan.yml + with: + soft-fail: true -**Exit Behavior**: Warning only (does not fail workflow) + summary: + needs: security-scan + runs-on: ubuntu-latest + steps: + - name: Check compliance + run: | + echo "Compliance: ${{ needs.security-scan.outputs.compliance-score }}%" + echo "Unpinned: ${{ needs.security-scan.outputs.unpinned-count }}" +``` ## Common Patterns @@ -513,51 +538,14 @@ Use `continue-on-error: true` to prevent workflow failure on SARIF upload issues ## Configuration Files -| File | Purpose | Used By | -|------|---------|---------| -| `scripts/linting/PSScriptAnalyzer.psd1` | PowerShell linting rules | ps-script-analyzer.yml | -| `.markdownlint.json` | Markdown formatting rules | markdown-lint.yml | -| `scripts/linting/markdown-link-check.config.json` | Link checking configuration | markdown-link-check.yml | -| `.cspell.json` | Spell checking configuration | spell-check.yml | -| `.github/instructions/markdown.instructions.md` | Markdown style guide | All markdown workflows | -| `.github/instructions/commit-message.instructions.md` | Commit message standards | All workflows (informative) | - -## Maintenance - -### Updating SHA Pins - -Keep action SHA pins up-to-date using the provided script: - -```powershell -# Update all stale SHA pins -scripts/security/Update-ActionSHAPinning.ps1 -Path .github/workflows -UpdateStale - -# Dry-run to see what would be updated -scripts/security/Update-ActionSHAPinning.ps1 -Path .github/workflows -WhatIf -``` - -### Adding New Workflows - -When adding a new workflow: - -1. Follow the naming convention (`*-scan.yml`, `*-check.yml`, or `*-lint.yml`) -2. Pin all actions to SHA commits -3. Include Harden Runner as the first step (security workflows) -4. Document inputs, outputs, and purpose -5. Add appropriate triggers (pull_request paths, workflow_dispatch) -6. Implement artifact uploads with 30-day retention -7. Create GitHub annotations for violations -8. Generate step summary with results -9. Support local testing with corresponding script -10. Update this README with the new workflow entry -11. Test thoroughly before merging - -## Related Documentation - -* [Linting Scripts Documentation](../../scripts/linting/README.md) -* [Security Scripts Documentation](../../scripts/security/) -* [Scripts Documentation](../../scripts/README.md) -* [Contributing Guidelines](../../CONTRIBUTING.md) +| File | Purpose | Used By | +|-------------------------------------------------------|------------------------------|-----------------------------| +| `scripts/linting/PSScriptAnalyzer.psd1` | PowerShell linting rules | `ps-script-analyzer.yml` | +| `.markdownlint.json` | Markdown formatting rules | `markdown-lint.yml` | +| `scripts/linting/markdown-link-check.config.json` | Link checking configuration | `markdown-link-check.yml` | +| `.cspell.json` | Spell checking configuration | `spell-check.yml` | +| `.github/instructions/markdown.instructions.md` | Markdown style guide | All markdown workflows | +| `.github/instructions/commit-message.instructions.md` | Commit message standards | All workflows (informative) | ## Resources diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 41e6ccce..6ffc495f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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: diff --git a/.github/workflows/dependency-pinning-scan.yml b/.github/workflows/dependency-pinning-scan.yml index 7b3ad2e3..b3696c34 100644 --- a/.github/workflows/dependency-pinning-scan.yml +++ b/.github/workflows/dependency-pinning-scan.yml @@ -12,7 +12,7 @@ on: description: 'Comma-separated list of dependency types to check' required: false type: string - default: 'actions,containers' + default: 'github-actions' soft-fail: description: 'Whether to continue on compliance violations' required: false @@ -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 }}' } + # 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 + + # 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 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() @@ -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 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..7e7398de --- /dev/null +++ b/.github/workflows/main.yml @@ -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 diff --git a/.github/workflows/markdown-link-check.yml b/.github/workflows/markdown-link-check.yml index e8374bdb..959e8f58 100644 --- a/.github/workflows/markdown-link-check.yml +++ b/.github/workflows/markdown-link-check.yml @@ -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() @@ -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 diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 00000000..a6aadc8d --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -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 diff --git a/.github/workflows/ps-script-analyzer.yml b/.github/workflows/ps-script-analyzer.yml index 18b204d8..96d80ec5 100644 --- a/.github/workflows/ps-script-analyzer.yml +++ b/.github/workflows/ps-script-analyzer.yml @@ -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" diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 7bd46742..4262ebc7 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -3,8 +3,6 @@ name: Security Scan on: push: branches: [ main, develop ] - pull_request: - branches: [ main, develop ] permissions: contents: read @@ -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 \ No newline at end of file diff --git a/.github/workflows/weekly-security-maintenance.yml b/.github/workflows/weekly-security-maintenance.yml index 2dba099e..fa5a2b49 100644 --- a/.github/workflows/weekly-security-maintenance.yml +++ b/.github/workflows/weekly-security-maintenance.yml @@ -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: @@ -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: @@ -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 diff --git a/package-lock.json b/package-lock.json index 54ec9c9f..da067606 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,16 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "cspell": "^9.3.0", - "markdown-link-check": "^3.12.2", + "cspell": "9.3.1", + "markdown-link-check": "3.14.1", "markdown-table-formatter": "^1.6.0", - "markdownlint-cli": "^0.45.0" + "markdownlint-cli": "0.45.0" } }, "node_modules/@cspell/cspell-bundled-dicts": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.3.0.tgz", - "integrity": "sha512-rf5SAdg1EmgCBIo/zFEAIjydlO+VtVLR6oUVeD4rIE7zIPrPdJb0OMUGsxfOH7uePyhcfIxPYTjneQla0e5Ljw==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.3.1.tgz", + "integrity": "sha512-vL94iLjEzPTBAoc4v4iY87jUNDYvhG7S3Lkxc9Jdcyk+aeXnoqYK7mCRFOSPSbB2pT2bugX6S6ZaLKVMpY73gA==", "dev": true, "license": "MIT", "dependencies": { @@ -86,22 +86,22 @@ } }, "node_modules/@cspell/cspell-json-reporter": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.3.0.tgz", - "integrity": "sha512-bvpLNS73/SN3969ksipVSYDdMYsxYZqeBDkcA2yKJZ9eHRY/tHUbjTTIvyvlCLODzUiop2RFGrvDPcjyam/FoQ==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.3.1.tgz", + "integrity": "sha512-XvMupq2jV3lRMEaiFXrsfR3xrvMQ4Im194dRZ02D2qdtYtKV9jErms/OhGmfs1YNLrQaTyDKAAyZLRxhJSmL3g==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-types": "9.3.0" + "@cspell/cspell-types": "9.3.1" }, "engines": { "node": ">=20" } }, "node_modules/@cspell/cspell-pipe": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.3.0.tgz", - "integrity": "sha512-LEl2TYvXnGk/D+Vjq5DBsNQuLsmn3/2QV9dxteK5e323pJRcpEosq76elzsYpVLuB3s1hp2pFQhppWk69Gx2CQ==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.3.1.tgz", + "integrity": "sha512-MqCoUDwq2z4dn5fYMFrLYHjQyueqhvCNyztPS2ifhXJiEyr/YV61cLvQh/HoZlFmBSL7ViMXjejtL29LTLOEzA==", "dev": true, "license": "MIT", "engines": { @@ -109,9 +109,9 @@ } }, "node_modules/@cspell/cspell-resolver": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.3.0.tgz", - "integrity": "sha512-AOIC6xV2XuEWKy/K/CUBHQ7caavDnjb2zflP25j7fGMWpS/GIGKLPjgGQUHFbgNSSy9UY2ZGV7MVCsnD7moW5Q==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.3.1.tgz", + "integrity": "sha512-HpgvmgZO+fCF9syPAX+XJRPYya4w3UFA5T8Uj0Ic19tjwoCgtj2F1SMAqr3iah97xH/9bh9tSHdfa7HIWD1J+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -122,9 +122,9 @@ } }, "node_modules/@cspell/cspell-service-bus": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.3.0.tgz", - "integrity": "sha512-e9ZfG1VJ/kbzh2LBW/TJImEWcEUuBMYSDnIYbQCsclKAWyF3g8eLGxoX7xDbZ82USo4IU2tqYmrDvPcUD8CHxg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.3.1.tgz", + "integrity": "sha512-mbCuHzcLIrvEOAcWxFmF+cgdIEWEs8bEkUTPA62EjQcQ8RzH82jVUPYDqPGJ7bThoinG/Xfk90EqHgh1b1kEOw==", "dev": true, "license": "MIT", "engines": { @@ -132,9 +132,9 @@ } }, "node_modules/@cspell/cspell-types": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.3.0.tgz", - "integrity": "sha512-WN3R4nqQ7BEgS8UEoPIwNBRuSFT/8GImqXmlx7EHmzBAhevWjF3IDF+OEtokRJ95qik6a4xqJJTJgi624UxDFg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.3.1.tgz", + "integrity": "sha512-6KBVCN5dEk1+p0RP27DCjmtVNUmn0q+Zovthr35dmKOom2vNgAzFapneXIlir6jWSdKZ8b/5qbwbdhL0ATai5w==", "dev": true, "license": "MIT", "engines": { @@ -560,13 +560,13 @@ "license": "MIT" }, "node_modules/@cspell/dynamic-import": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.3.0.tgz", - "integrity": "sha512-c+coQC+bM1PuHX/Blg1mp2ODPZmMnWzXrZwX/JKpnY0uxcAjUrcmGjdjsV1/S/7ph9OJHvZL5KITCRBgFD1Kqg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.3.1.tgz", + "integrity": "sha512-pjdCtlXio1Zov2Xd74CNdhwQ0OQU1+fYbT1YrdYJFplW+OeHze9eEPRgCKzMRSXr3s8La+dfrdtWVr0LhLTTvA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "9.3.0", + "@cspell/url": "9.3.1", "import-meta-resolve": "^4.2.0" }, "engines": { @@ -574,9 +574,9 @@ } }, "node_modules/@cspell/filetypes": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.3.0.tgz", - "integrity": "sha512-pH413zKkMd5lh40HyLNgYfTacIryxsHDhxWShyxS7M+8W9MPl+tmzIHlSEvEVcKUUe3Qpowxo6b6Xkg7taRp/A==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.3.1.tgz", + "integrity": "sha512-8VghfXnR2SIBs7jFG0G2MI6ixQM0tcnFU/WqgxZJPOjPSX+kpCuzePijG3ueiMhIWztHg+NM+nQiQGREcuX0vA==", "dev": true, "license": "MIT", "engines": { @@ -584,9 +584,9 @@ } }, "node_modules/@cspell/strong-weak-map": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.3.0.tgz", - "integrity": "sha512-s8/QwCPWvWRXZsAzUbWzv/cgL0xjo7p+5QveThFmD3vODX3IlrC+MfnLpEWQAyTQ7CqB5bRuBVAR2cU9gpzZTA==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.3.1.tgz", + "integrity": "sha512-HNFyN9AXI2b6pC6p/VhJgDPw0rg0CTVHhQcleb3e2RsU72QnNv9DltcYR59y1igwJ+w5VP2sYh2TWYvBPTeMlg==", "dev": true, "license": "MIT", "engines": { @@ -594,9 +594,9 @@ } }, "node_modules/@cspell/url": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.3.0.tgz", - "integrity": "sha512-EY4Niv1apHP9RN1mMRP/AHm6xr14fhK+PXnytang6SVwX+tbAEYwwlnFjoEDO6ygPsqs5BBiQ4N7TiSlfmXfmw==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.3.1.tgz", + "integrity": "sha512-4MlTvq2neLV9IRDNIxcA6ef6bvUqqA8avbotnmD4X6p1IzMOvVLvQ8t6UMr4pKzpe+c5Ph33Y+C+mcwK3rk/BQ==", "dev": true, "license": "MIT", "engines": { @@ -1050,26 +1050,26 @@ } }, "node_modules/cspell": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.3.0.tgz", - "integrity": "sha512-YyXjOS3MAF6d0XggxHZtkyde6Yf0VgXkrFvR8C6jfxcnY0SJrJDKLiOppmm4ol+oWlvt1Dir1neGJW13xN+dUg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.3.1.tgz", + "integrity": "sha512-E6hbLdBx0GO4AVm/MxXhw/k4rPCqlvTx4OQUT7VtRdM6DsAhf+CZzuyXlzfkXESlUUNj0VGaZPPMC0e0NLsfsg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-json-reporter": "9.3.0", - "@cspell/cspell-pipe": "9.3.0", - "@cspell/cspell-types": "9.3.0", - "@cspell/dynamic-import": "9.3.0", - "@cspell/url": "9.3.0", + "@cspell/cspell-json-reporter": "9.3.1", + "@cspell/cspell-pipe": "9.3.1", + "@cspell/cspell-types": "9.3.1", + "@cspell/dynamic-import": "9.3.1", + "@cspell/url": "9.3.1", "chalk": "^5.6.2", "chalk-template": "^1.1.2", "commander": "^14.0.2", - "cspell-config-lib": "9.3.0", - "cspell-dictionary": "9.3.0", - "cspell-gitignore": "9.3.0", - "cspell-glob": "9.3.0", - "cspell-io": "9.3.0", - "cspell-lib": "9.3.0", + "cspell-config-lib": "9.3.1", + "cspell-dictionary": "9.3.1", + "cspell-gitignore": "9.3.1", + "cspell-glob": "9.3.1", + "cspell-io": "9.3.1", + "cspell-lib": "9.3.1", "fast-json-stable-stringify": "^2.1.0", "flatted": "^3.3.3", "semver": "^7.7.3", @@ -1087,13 +1087,13 @@ } }, "node_modules/cspell-config-lib": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.3.0.tgz", - "integrity": "sha512-YyKMBwRIo8Sh3D9roHWWpW9KnQCkeWOoPJkcLEA3q+UldspkqpjQ8A8bUvigLgVg4dBQosiEUdda3MUZT79Lyg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.3.1.tgz", + "integrity": "sha512-Mdm7FtXkiBzVigGY4jd/DVELai8XUkgV7E74l14VVnveyBHE1EnYD8g4COVE8qglCuSQnTtsuI1gqBlJkcLSzg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-types": "9.3.0", + "@cspell/cspell-types": "9.3.1", "comment-json": "^4.4.1", "smol-toml": "^1.4.2", "yaml": "^2.8.1" @@ -1103,9 +1103,9 @@ } }, "node_modules/cspell-config-lib/node_modules/smol-toml": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.4.2.tgz", - "integrity": "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.5.2.tgz", + "integrity": "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1116,15 +1116,15 @@ } }, "node_modules/cspell-dictionary": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.3.0.tgz", - "integrity": "sha512-+gS59D0ly/UfTDsjKavWCDTSukmQ3HO0Xy+t4+pwgkVOa8kXzhERoXxT0V3v71TJS+kFRHfsWeGekfaVB4n2ng==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.3.1.tgz", + "integrity": "sha512-px5qCUZqfCG2bBjkxSueLFRHCW0Vl2Joszfj36IPAyZJCO+OjBzHvXcitbFwwy5LDfxyXTTY307Asumzi5IAqA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "9.3.0", - "@cspell/cspell-types": "9.3.0", - "cspell-trie-lib": "9.3.0", + "@cspell/cspell-pipe": "9.3.1", + "@cspell/cspell-types": "9.3.1", + "cspell-trie-lib": "9.3.1", "fast-equals": "^5.3.2" }, "engines": { @@ -1132,15 +1132,15 @@ } }, "node_modules/cspell-gitignore": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.3.0.tgz", - "integrity": "sha512-AdI8WLKGNtTni1P+fbepDBh6u7Mv22diwtqMQoasDPeafArmQHpTp9gc8FgNnQO9tQASB7ZMjIOnLFNvmML+nw==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.3.1.tgz", + "integrity": "sha512-C56uKvx71QtsKu6JBxZDFYZHxx8ILh0mLYDStmXPRpGDYsDCC19sEnd+z8+HTXJZ1i5jxIqitQKtiCSXTREA+g==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "9.3.0", - "cspell-glob": "9.3.0", - "cspell-io": "9.3.0" + "@cspell/url": "9.3.1", + "cspell-glob": "9.3.1", + "cspell-io": "9.3.1" }, "bin": { "cspell-gitignore": "bin.mjs" @@ -1150,13 +1150,13 @@ } }, "node_modules/cspell-glob": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.3.0.tgz", - "integrity": "sha512-Mp1T4Y3utyIB5dgQk+XksdmS97il8TfFI9byMUR6Mprml/I+QglXj38bHX/++DeApfFHr+5y5DgqxmRn3/wnKA==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.3.1.tgz", + "integrity": "sha512-pyo8ySo90U4WaayjrnefU7kPA1pFL8ok4BDnlKJ5MwRqzVPIwV003Op0hnRYEEUdNyjRR4kU6GshMEkTrSlB7Q==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "9.3.0", + "@cspell/url": "9.3.1", "picomatch": "^4.0.3" }, "engines": { @@ -1164,14 +1164,14 @@ } }, "node_modules/cspell-grammar": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.3.0.tgz", - "integrity": "sha512-X6VrCto78Xm72st+3YZ13qk5jw5sS9QOcS4x3KO41T90YoOMXbMsSH7HPaNrhqaa1rtEKk43kFQp33OLPvsI3Q==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.3.1.tgz", + "integrity": "sha512-SZR5IfrMZK0pgVP5U48yoHvkfiCbmGkwwTGGomEXpVYev/7fG9wupZKt2YXfvATiuQmcZ9hFW4fPLZbpJckPfA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "9.3.0", - "@cspell/cspell-types": "9.3.0" + "@cspell/cspell-pipe": "9.3.1", + "@cspell/cspell-types": "9.3.1" }, "bin": { "cspell-grammar": "bin.mjs" @@ -1181,43 +1181,43 @@ } }, "node_modules/cspell-io": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.3.0.tgz", - "integrity": "sha512-39Gp7asqdsrLvZ9L3BUXYX5wE6gAuvxeklguB4hjz+7i7Jhz02CcjFXDd1VhIA6tJ4hRTHWtgqaoaFv6qQF/vg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.3.1.tgz", + "integrity": "sha512-ZL5IVJiNHU3bkJh1+Zgmx5i0NaUIondJZ7vIlYlO55Llz8mtIoSp7Cn2j9tURfRP/Q0BZOE6M841Tiich0mqPA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-service-bus": "9.3.0", - "@cspell/url": "9.3.0" + "@cspell/cspell-service-bus": "9.3.1", + "@cspell/url": "9.3.1" }, "engines": { "node": ">=20" } }, "node_modules/cspell-lib": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.3.0.tgz", - "integrity": "sha512-MM71PponJHWn/tt93hYBSvBVeyivjWjaiROfQ4UlUGw7TIwysAyywH3XNYUnydaHcbjWTk7W12JbEVa8sQaBIQ==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.3.1.tgz", + "integrity": "sha512-3P+PW6EZgztP0eUDHeUzi4ro6IqH927n59BAR6djo58eAMgwbyZUYtXYXVOxlyhWqiVjL/hjb8hiqzTt1YQFEg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-bundled-dicts": "9.3.0", - "@cspell/cspell-pipe": "9.3.0", - "@cspell/cspell-resolver": "9.3.0", - "@cspell/cspell-types": "9.3.0", - "@cspell/dynamic-import": "9.3.0", - "@cspell/filetypes": "9.3.0", - "@cspell/strong-weak-map": "9.3.0", - "@cspell/url": "9.3.0", + "@cspell/cspell-bundled-dicts": "9.3.1", + "@cspell/cspell-pipe": "9.3.1", + "@cspell/cspell-resolver": "9.3.1", + "@cspell/cspell-types": "9.3.1", + "@cspell/dynamic-import": "9.3.1", + "@cspell/filetypes": "9.3.1", + "@cspell/strong-weak-map": "9.3.1", + "@cspell/url": "9.3.1", "clear-module": "^4.1.2", - "cspell-config-lib": "9.3.0", - "cspell-dictionary": "9.3.0", - "cspell-glob": "9.3.0", - "cspell-grammar": "9.3.0", - "cspell-io": "9.3.0", - "cspell-trie-lib": "9.3.0", + "cspell-config-lib": "9.3.1", + "cspell-dictionary": "9.3.1", + "cspell-glob": "9.3.1", + "cspell-grammar": "9.3.1", + "cspell-io": "9.3.1", + "cspell-trie-lib": "9.3.1", "env-paths": "^3.0.0", - "gensequence": "^7.0.0", + "gensequence": "^8.0.8", "import-fresh": "^3.3.1", "resolve-from": "^5.0.0", "vscode-languageserver-textdocument": "^1.0.12", @@ -1229,15 +1229,15 @@ } }, "node_modules/cspell-trie-lib": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.3.0.tgz", - "integrity": "sha512-/hLujE3Gp36hhgJChvp6C3uJdBo5hGOqtyal6HjNmn+K27GXRrrn1L+5RoKaDMgFao9Ks5ccca9NtjQTw1EcoA==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.3.1.tgz", + "integrity": "sha512-PfHk6hX2e+OF4t3qxA/Y95FScEAPM7fQGsDaq+U0AqT8vsdtVou+VVS43ILBiCDYBDn2WUjWBTKYBGk2t1oKGQ==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "9.3.0", - "@cspell/cspell-types": "9.3.0", - "gensequence": "^7.0.0" + "@cspell/cspell-pipe": "9.3.1", + "@cspell/cspell-types": "9.3.1", + "gensequence": "^8.0.8" }, "engines": { "node": ">=20" @@ -1541,9 +1541,9 @@ } }, "node_modules/fast-equals": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", - "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", + "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", "dev": true, "license": "MIT", "engines": { @@ -1629,13 +1629,13 @@ } }, "node_modules/gensequence": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz", - "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-8.0.8.tgz", + "integrity": "sha512-omMVniXEXpdx/vKxGnPRoO2394Otlze28TyxECbFVyoSpZ9H3EO7lemjcB12OpQJzRW4e5tt/dL1rOxry6aMHg==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/get-uri": { @@ -1973,9 +1973,9 @@ } }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 486ca0fc..b7966717 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,10 @@ "precommit": "npm run security:scan:staged" }, "devDependencies": { - "cspell": "^9.3.0", - "markdownlint-cli": "^0.45.0", + "cspell": "9.3.1", + "markdown-link-check": "3.14.1", "markdown-table-formatter": "^1.6.0", - "markdown-link-check": "^3.12.2" + "markdownlint-cli": "0.45.0" }, "repository": { "type": "git", diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e6337fd3..00000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Python dependencies for development container -# Used by devcontainer postCreateCommand - -# Infrastructure as Code security scanning -checkov>=3.2.0 diff --git a/scripts/security/Test-DependencyPinning.ps1 b/scripts/security/Test-DependencyPinning.ps1 index 97cba127..a9381f5d 100644 --- a/scripts/security/Test-DependencyPinning.ps1 +++ b/scripts/security/Test-DependencyPinning.ps1 @@ -32,6 +32,11 @@ Comma-separated list of dependency types to check. Options: github-actions, npm, pip. Default is all types. +.PARAMETER Threshold + Minimum compliance score percentage required for passing grade (0-100). + Script will exit with code 1 if compliance falls below threshold when -FailOnUnpinned is set. + Default is 95%. + .PARAMETER Remediate Generate remediation suggestions with specific SHA pins for unpinned dependencies. @@ -47,6 +52,18 @@ ./Test-DependencyPinning.ps1 -IncludeTypes "github-actions,pip" -Remediate Check only GitHub Actions and pip dependencies with remediation suggestions. +.EXAMPLE + ./Test-DependencyPinning.ps1 -Threshold 90 -FailOnUnpinned + Enforce 90% compliance threshold and fail build if not met. + +.EXAMPLE + ./Test-DependencyPinning.ps1 -Threshold 100 -IncludeTypes "github-actions" + Require 100% SHA pinning for GitHub Actions only. + +.EXAMPLE + ./Test-DependencyPinning.ps1 -Threshold 80 + Report compliance against 80% threshold but continue on violations. + .NOTES Requires: - PowerShell 7.0 or later for cross-platform compatibility @@ -87,6 +104,10 @@ param( [Parameter(Mandatory = $false)] [string]$IncludeTypes = "github-actions,npm,pip", + [Parameter(Mandatory = $false)] + [ValidateRange(0, 100)] + [int]$Threshold = 95, + [Parameter(Mandatory = $false)] [switch]$Remediate ) @@ -432,6 +453,7 @@ function Get-ComplianceReportData { IncludedTypes = $IncludeTypes ExcludedPaths = $ExcludePaths RemediationEnabled = $Remediate.IsPresent + ComplianceThreshold = $Threshold } return $report @@ -680,13 +702,24 @@ try { if ($report.UnpinnedDependencies -gt 0) { Write-PinningLog "$($report.UnpinnedDependencies) dependencies require SHA pinning for security compliance" -Level Warning - if ($FailOnUnpinned) { - Write-PinningLog "Failing build due to dependency pinning violations (-FailOnViolations enabled)" -Level Error - exit 1 + # Check threshold compliance + if ($report.ComplianceScore -lt $Threshold) { + Write-PinningLog "Compliance score $($report.ComplianceScore)% is below threshold $Threshold%" -Level Error + + if ($FailOnUnpinned) { + Write-PinningLog "Failing build due to compliance threshold violation (-FailOnUnpinned enabled)" -Level Error + exit 1 + } + else { + Write-PinningLog "Threshold violation detected but continuing (soft-fail mode)" -Level Warning + } + } + else { + Write-PinningLog "Compliance score $($report.ComplianceScore)% meets threshold $Threshold%" -Level Info } } else { - Write-PinningLog "All dependencies are properly pinned! ✅" -Level Success + Write-PinningLog "All dependencies are properly pinned! ✅ (100% compliance, exceeds $Threshold% threshold)" -Level Success } }