Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c239e85
chore: refactor workflows for improved parallelization
sapientpants Oct 3, 2025
fd7c733
fix: remove unused sbom generation
sapientpants Oct 3, 2025
0b33278
feat: add console output to Trivy scan for visibility
sapientpants Oct 3, 2025
102e51d
fix: switch Trivy to table format for console visibility
sapientpants Oct 3, 2025
474c4c1
Revert "fix: switch Trivy to table format for console visibility"
sapientpants Oct 3, 2025
9013170
fix: ensure Trivy SARIF upload completes before workflow fails
sapientpants Oct 3, 2025
0f25610
feat: upload Trivy SARIF results as downloadable artifact
sapientpants Oct 3, 2025
d2eca3e
refactor: consolidate Trivy scans into single step
sapientpants Oct 3, 2025
e0931c4
refactor: simplify Trivy scan workflow
sapientpants Oct 3, 2025
2951108
feat: enhance Trivy scanning with additional security checks
sapientpants Oct 3, 2025
f4da681
refactor: simplify Trivy artifact naming
sapientpants Oct 3, 2025
386ed87
chore: enable Trivy debug output and progress logging
sapientpants Oct 3, 2025
e601f6f
fix: only fail on fixable CRITICAL/HIGH vulnerabilities
sapientpants Oct 5, 2025
3a5697a
fix: fail on any fixable vulnerability regardless of severity
sapientpants Oct 5, 2025
3848fff
fix: upgrade OpenSSL to fix CVE-2025-9230, CVE-2025-9231, CVE-2025-9232
sapientpants Oct 6, 2025
56170aa
fix: configure Trivy to accept GPL and MPL licenses
sapientpants Oct 6, 2025
3cf6674
fix: add GPL and LGPL variants to Trivy license ignore list
sapientpants Oct 7, 2025
b9691de
fix: remove license scanner from Trivy configuration
sapientpants Oct 7, 2025
9158ef6
chore: update trivy
sapientpants Oct 8, 2025
205274a
chore: update trivy
sapientpants Oct 8, 2025
8be12ef
chore: refactor GitHub Actions workflow structure
sapientpants Oct 11, 2025
3cbc836
fix: create dedicated CodeQL workflow to resolve Security tab warnings
sapientpants Oct 11, 2025
528b1b2
chore: cleanup unused workflow files and update CodeQL schedule
sapientpants Oct 11, 2025
ed1da31
fix: add conditionals to prevent workflow steps when no version change
sapientpants Oct 11, 2025
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
106 changes: 18 additions & 88 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,41 +48,25 @@ jobs:
# Parallel security scans to identify vulnerabilities before release
# =============================================================================

# CodeQL: Static analysis for security vulnerabilities
# Scans TypeScript/JavaScript for common security issues (XSS, SQL injection, etc.)
codeql:
security:
uses: ./.github/workflows/reusable-security.yml
with:
generate-sbom: false # SBOM generated during release for consistency
run-osv-scan: false # OSV scan runs separately below
run-codeql: true # Enable CodeQL analysis

# OSV (Open Source Vulnerabilities) scanning
# Uses Google's official action for comprehensive dependency vulnerability checks
# UPDATE: Quarterly review for new scanner versions (currently v2.2.1)
vulnerability:
uses: google/osv-scanner-action/.github/workflows/[email protected]
with:
# Scan entire project including all manifests (package.json, pnpm-lock.yaml)
scan-args: |-
./
permissions:
security-events: write # Required to upload findings to Security tab
actions: read
contents: read

# =============================================================================
# UNIFIED BUILD PHASE
# Single build job that creates artifacts to be reused throughout the workflow
# =============================================================================

build-once:
name: Build TypeScript Once
build:
name: Build TypeScript
# Runs after validation passes
needs: [validate, codeql, vulnerability]
needs: [validate, security]
runs-on: ubuntu-latest
outputs:
artifact-name: dist-${{ github.sha }}
changed: ${{ steps.version.outputs.changed }}
version: ${{ steps.version.outputs.version }}
tag_sha: ${{ steps.tag.outputs.sha }}
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down Expand Up @@ -137,69 +121,15 @@ jobs:
build-manifest.json
retention-days: 1 # Only needed for this workflow run

# =============================================================================
# VERSION DETERMINATION PHASE
# Determines version and creates tag BEFORE building release artifacts
# =============================================================================

version:
# Now depends on build-once to have compiled code available
needs: [build-once]
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.version.outputs.changed }} # true if version changed
version: ${{ steps.version.outputs.version }} # semantic version number
tag_sha: ${{ steps.tag.outputs.sha }} # SHA of the created tag
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history needed for changeset detection
# SECURITY: Use RELEASE_TOKEN if available for protected branch pushes
# Falls back to GITHUB_TOKEN for standard permissions
token: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}

# =============================================================================
# ENVIRONMENT SETUP
# Consistent toolchain setup matching package.json requirements
# =============================================================================

- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.build-once.outputs.artifact-name }}

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.17.0
run_install: false
standalone: true

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

# =============================================================================
# VERSION MANAGEMENT
# Determines if release needed based on changesets
# =============================================================================

- name: Version packages
id: version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Custom script validates changesets and determines version
# Only checks for version changes, doesn't update files
# FAILS IF: feat/fix commits exist without changesets
# Outputs: changed=true/false, version=X.Y.Z
node .github/scripts/version-and-release.js --check-only
node .github/scripts/version-and-release.js

- name: Create and push tag
# Create tag BEFORE building artifacts so they're associated with the tag
Expand Down Expand Up @@ -230,13 +160,13 @@ jobs:

prepare-release-assets:
# Builds release artifacts using the tagged commit
needs: [version, build-once]
if: needs.version.outputs.changed == 'true'
needs: [build]
if: needs.build.outputs.changed == 'true'
uses: ./.github/workflows/reusable-prepare-assets.yml
with:
version: ${{ needs.version.outputs.version }}
tag_sha: ${{ needs.version.outputs.tag_sha }}
build_artifact: ${{ needs.build-once.outputs.artifact-name }}
version: ${{ needs.build.outputs.version }}
tag_sha: ${{ needs.build.outputs.tag_sha }}
build_artifact: ${{ needs.build.outputs.artifact-name }}
enable_docker: ${{ vars.ENABLE_DOCKER_RELEASE == 'true' }}
docker_platforms: 'linux/amd64,linux/arm64'
enable_npm: ${{ vars.ENABLE_NPM_RELEASE == 'true' }}
Expand All @@ -249,7 +179,7 @@ jobs:
commit-version:
name: Commit Version Changes
# Only runs after all assets are successfully prepared
needs: [prepare-release-assets, version, build-once]
needs: [prepare-release-assets, build]
runs-on: ubuntu-latest
outputs:
version: ${{ needs.prepare-release-assets.outputs.version }}
Expand All @@ -263,7 +193,7 @@ jobs:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.build-once.outputs.artifact-name }}
name: ${{ needs.build.outputs.artifact-name }}

- name: Install pnpm
# Required for updating package.json version
Expand Down Expand Up @@ -326,7 +256,7 @@ jobs:
create-release:
name: Create GitHub Release
# Only runs after version is committed
needs: [commit-version, prepare-release-assets, build-once, version]
needs: [commit-version, prepare-release-assets, build]
runs-on: ubuntu-latest
outputs:
released: ${{ steps.release.outputs.released }}
Expand All @@ -341,7 +271,7 @@ jobs:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.build-once.outputs.artifact-name }}
name: ${{ needs.build.outputs.artifact-name }}

- name: Install pnpm
uses: pnpm/action-setup@v4
Expand All @@ -366,7 +296,7 @@ jobs:
- name: Create release artifacts
run: |
VERSION="${{ needs.prepare-release-assets.outputs.version }}"
TAG_SHA="${{ needs.version.outputs.tag_sha }}"
TAG_SHA="${{ needs.build.outputs.tag_sha }}"
tar -czf dist-${VERSION}-${TAG_SHA:0:7}.tar.gz dist/
zip -r dist-${VERSION}-${TAG_SHA:0:7}.zip dist/

Expand Down
97 changes: 9 additions & 88 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,43 +40,14 @@ jobs:
uses: ./.github/workflows/reusable-validate.yml
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
validate-changesets: true

# CodeQL: Static security analysis for TypeScript/JavaScript
# Security: Static security analysis for TypeScript/JavaScript
# Scans for: XSS, injection attacks, insecure patterns
# Results appear in Security tab of the PR
codeql:
security:
uses: ./.github/workflows/reusable-security.yml
with:
generate-sbom: false # Skip SBOM for PRs (generated at release)
run-osv-scan: false # OSV runs separately below
run-codeql: true # Enable CodeQL scanning

# OSV: Dependency vulnerability scanning
# Uses Google's database of known vulnerabilities
# UPDATE: Check quarterly for new versions (currently v2.2.2)
vulnerability:
uses: google/osv-scanner-action/.github/workflows/[email protected]

# =============================================================================
# WORKFLOW VALIDATION
# Lint GitHub Actions workflow files for errors
# =============================================================================

# Actionlint: Validate GitHub Actions workflow syntax and best practices
# Catches: undefined outputs, typos, bash errors, incorrect action inputs
actionlint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run actionlint
# Uses the official actionlint Docker action
# Automatically detects all workflow files in .github/workflows/
uses: reviewdog/action-actionlint@v1
with:
fail_level: error # Fail the job if errors are found
reporter: github-pr-check # Report errors as PR checks

# =============================================================================
# DOCKER CONTAINER VALIDATION
Expand All @@ -94,47 +65,6 @@ jobs:
save-artifact: false # Don't save artifact for PRs
image-name: 'sonarqube-mcp-server-pr'

# =============================================================================
# CHANGESET VALIDATION
# Ensures features and fixes have proper changelog entries
# =============================================================================

# Changeset check - runs in parallel with other jobs
changeset:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history needed to compare with main

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.17.0 # Pinned: Match package.json packageManager
run_install: false
standalone: true

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22 # Pinned: Match package.json engines.node
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Fetch main branch
# Need main branch to compare changesets
run: git fetch origin main:main

- name: Changeset status
# Validates that changesets exist for features/fixes
# FAILS IF: feat/fix commits exist without changesets
# To fix: Run 'pnpm changeset' and commit the generated file
# For non-code changes: Run 'pnpm changeset --empty'
run: pnpm changeset:status

# =============================================================================
# FINAL STATUS CHECK
# Single job to verify all parallel checks succeeded
Expand All @@ -143,7 +73,7 @@ jobs:
# Final status check - ensures all jobs passed
# Required for branch protection rules
pr-status:
needs: [validate, codeql, vulnerability, actionlint, changeset, docker]
needs: [validate, security, docker]
if: always() # Run even if previous jobs failed
runs-on: ubuntu-latest
steps:
Expand All @@ -152,11 +82,8 @@ jobs:
# This single check can be used as a required status check
# FAILS IF: Any validation job failed
# Common failures:
# - validate: Tests fail, coverage below 80%, lint errors
# - codeql: Security vulnerabilities detected
# - vulnerability: Vulnerable dependencies found
# - actionlint: Workflow syntax errors or best practice violations
# - changeset: Missing changeset for feat/fix commits
# - validate: Tests fail, coverage below 80%, lint errors, workflow errors, missing changesets
# - security: Security vulnerabilities, vulnerable dependencies, audit failures
# - docker: Container vulnerabilities or build failures (when enabled)
run: |
# Check Docker job status
Expand All @@ -176,18 +103,12 @@ jobs:
fi

if [ "${{ needs.validate.result }}" != "success" ] || \
[ "${{ needs.codeql.result }}" != "success" ] || \
[ "${{ needs.vulnerability.result }}" != "success" ] || \
[ "${{ needs.actionlint.result }}" != "success" ] || \
[ "${{ needs.changeset.result }}" != "success" ] || \
[ "${{ needs.security.result }}" != "success" ] || \
[ "$DOCKER_FAILED" == "true" ]; then
echo "❌ PR validation failed"
# Check individual job results for debugging
echo "Validate: ${{ needs.validate.result }}"
echo "CodeQL: ${{ needs.codeql.result }}"
echo "Vulnerability: ${{ needs.vulnerability.result }}"
echo "Actionlint: ${{ needs.actionlint.result }}"
echo "Changeset: ${{ needs.changeset.result }}"
echo "Security: ${{ needs.security.result }}"
echo "Docker: ${{ needs.docker.result }}"
exit 1
fi
Expand Down
27 changes: 20 additions & 7 deletions .github/workflows/reusable-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,30 +176,43 @@ jobs:

- name: Run Trivy vulnerability scanner
# SECURITY: Scan image for vulnerabilities before any distribution
# FAILS IF: Critical or high vulnerabilities found (configurable)
# NOTE: Multi-platform OCI exports cannot be scanned from tar files
# Will fail workflow if CRITICAL or HIGH vulnerabilities found
if: steps.build-config.outputs.can_load == 'true' || !contains(inputs.platforms, ',')
uses: aquasecurity/[email protected]
with:
input: ${{ steps.scan-config.outputs.scan_input }}
image-ref: ${{ steps.scan-config.outputs.scan_image_ref }}
format: 'sarif' # GitHub Security format
exit-code: '1' # Fail if vulnerabilities found
format: 'sarif'
hide-progress: false
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH' # Fail on serious vulnerabilities
ignore-unfixed: false # Include unfixed CVEs
exit-code: '1' # Fail workflow if vulnerabilities found
trivyignores: '.trivyignore' # Use ignore file for false positives
severity: 'CRITICAL,HIGH'
scanners: 'vuln,secret,misconfig,license'
ignore-unfixed: false
trivyignores: '.trivyignore'
version: 'latest'
env:
TRIVY_DEBUG: 'true'

- name: Upload Trivy results to GitHub Security
# Always upload results, even if scan fails
# Results viewable at: Security > Code scanning alerts
# Only upload if Trivy actually ran (not for multi-platform builds)
if: always() && (steps.build-config.outputs.can_load == 'true' || !contains(inputs.platforms, ','))
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
category: 'container-scan-${{ github.event_name }}'

- name: Upload Trivy SARIF as artifact
# Upload SARIF file as artifact for debugging and inspection
if: always() && (steps.build-config.outputs.can_load == 'true' || !contains(inputs.platforms, ','))
uses: actions/upload-artifact@v4
with:
name: trivy-${{ github.sha }}
path: trivy-results.sarif
retention-days: 7

# =============================================================================
# ARTIFACT STORAGE
# Save Docker image for reuse in publish workflow
Expand Down
Loading
Loading