Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
49915c0
feat(security): add CodeQL analysis and pre-release version gate to P…
bedatty Apr 9, 2026
aad6ec3
fix(security): configure private Go module access for CodeQL autobuild
bedatty Apr 9, 2026
046ef64
fix(security): add actions:read permission for CodeQL status reporting
bedatty Apr 9, 2026
537771d
fix(security): disable SARIF upload by default in codeql-analyze comp…
bedatty Apr 9, 2026
f89fd13
feat(security): add codeql_upload_sarif input for Security tab integr…
bedatty Apr 9, 2026
927fb87
feat(security): make prerelease gate branch-aware (block on rc/main, …
bedatty Apr 9, 2026
2edd134
feat(security): broaden prerelease check to block all unstable versio…
bedatty Apr 9, 2026
b0ff96d
fix(prerelease-check): also scan repo root for monorepos with shared …
bedatty Apr 13, 2026
2e7960b
feat(prerelease-check): post PR comment with unstable version findings
bedatty Apr 13, 2026
279a4bc
fix(prerelease-check): consolidate PR comments with shared marker + c…
bedatty Apr 13, 2026
c320241
refactor(security): integrate prerelease findings into consolidated s…
bedatty Apr 14, 2026
dfea6f2
fix(security): point pr-security-reporter to feature branch with prer…
bedatty Apr 14, 2026
864a8a5
fix(prerelease-check): expose artifact-file output for reporter consu…
bedatty Apr 14, 2026
c5fd5ce
fix(ci): pin codeql actions by SHA, fix yaml lint errors
bedatty Apr 14, 2026
3409b28
fix(security): address CodeRabbit review findings
bedatty Apr 14, 2026
5abeb48
fix(ci): enable CodeQL SARIF upload in self-pr-validation
bedatty Apr 14, 2026
d947c6b
fix(ci): pin actions/checkout to SHA in self-pr-validation
bedatty Apr 14, 2026
596d0f5
fix(security): treat prerelease artifact contract violations as scan …
bedatty Apr 14, 2026
617b198
fix(security): bound prerelease table rows and truncate content
bedatty Apr 14, 2026
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
151 changes: 128 additions & 23 deletions .github/workflows/pr-security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,34 @@
description: 'Use the component working_dir as Docker build context instead of repo root. Useful for independent modules (e.g., tools with their own go.mod).'
type: boolean
default: false
enable_codeql:
description: 'Enable CodeQL static analysis. Requires codeql_languages to be set.'
type: boolean
default: false
codeql_languages:
description: 'Languages to analyze with CodeQL (comma-separated, e.g., "go", "javascript-typescript", "actions")'
type: string
required: false
default: ''
Comment thread
bedatty marked this conversation as resolved.
codeql_fail_on_findings:
description: 'Fail the workflow when CodeQL detects security issues'
type: boolean
default: true
Comment thread
bedatty marked this conversation as resolved.
codeql_upload_sarif:
description: 'Upload CodeQL SARIF results to the GitHub Security tab. Requires Code Security (GHAS) enabled on the repo.'
type: boolean
default: false
enable_prerelease_check:
description: 'Block dependencies pinned to pre-release versions (-beta, -rc)'
type: boolean
default: true
Comment thread
bedatty marked this conversation as resolved.
prerelease_block_branches:
description: 'Comma-separated list of PR target branches where pre-release versions cause a hard failure. On other branches, findings are reported as warnings only.'
type: string
default: 'release-candidate,main'

permissions:
actions: read # Required for CodeQL status reporting
id-token: write # Required for OIDC authentication
contents: read # Required to checkout the repository
pull-requests: write # Allows commenting on PRs
Expand All @@ -100,7 +126,7 @@
# ----------------- Detect Changes & Build Matrix -----------------
- name: Get changed paths
id: changed-paths
uses: LerianStudio/github-actions-shared-workflows/src/config/changed-paths@v1.18.0
uses: LerianStudio/github-actions-shared-workflows/src/config/changed-paths@v1.23.1
with:
filter-paths: ${{ inputs.filter_paths }}
shared-paths: ${{ inputs.shared_paths }}
Expand Down Expand Up @@ -150,7 +176,7 @@
- name: Trivy Filesystem Scan
id: fs-scan
if: always()
uses: LerianStudio/github-actions-shared-workflows/src/security/trivy-fs-scan@v1.18.0
uses: LerianStudio/github-actions-shared-workflows/src/security/trivy-fs-scan@v1.23.1
with:
scan-ref: ${{ matrix.working_dir }}
app-name: ${{ env.APP_NAME }}
Expand All @@ -175,7 +201,7 @@
- name: Trivy Image Scan
id: image-scan
if: always() && inputs.enable_docker_scan
uses: LerianStudio/github-actions-shared-workflows/src/security/trivy-image-scan@v1.18.0
uses: LerianStudio/github-actions-shared-workflows/src/security/trivy-image-scan@v1.23.1
with:
image-ref: '${{ env.DOCKERHUB_ORG }}/${{ env.APP_NAME }}:pr-scan-${{ github.sha }}'
app-name: ${{ env.APP_NAME }}
Expand All @@ -185,15 +211,24 @@
- name: Dockerfile Compliance Checks
id: dockerfile-checks
if: always() && inputs.enable_docker_scan && inputs.enable_health_score
uses: LerianStudio/github-actions-shared-workflows/src/security/dockerfile-checks@v1.18.0
uses: LerianStudio/github-actions-shared-workflows/src/security/dockerfile-checks@v1.23.1
with:
dockerfile-path: ${{ env.DOCKERFILE_PATH }}

# ----------------- Pre-release Version Gate -----------------
- name: Pre-release Version Check
id: prerelease-check
if: always() && inputs.enable_prerelease_check
uses: LerianStudio/github-actions-shared-workflows/src/security/prerelease-check@feat/pr-security-scan-codeql-prerelease

Check warning on line 222 in .github/workflows/pr-security-scan.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/prerelease-check@feat/pr-security-scan-codeql-prerelease
with:
scan-ref: ${{ matrix.working_dir }}
app-name: ${{ env.APP_NAME }}
Comment thread
bedatty marked this conversation as resolved.

# ----------------- Results & Security Gate -----------------
- name: Post Security Scan Results to PR
id: post-results
if: always() && github.event_name == 'pull_request'
uses: LerianStudio/github-actions-shared-workflows/src/security/pr-security-reporter@v1.18.0
uses: LerianStudio/github-actions-shared-workflows/src/security/pr-security-reporter@v1.23.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || secrets.GITHUB_TOKEN }}
app-name: ${{ env.APP_NAME }}
Expand All @@ -202,32 +237,102 @@
dockerfile-has-non-root-user: ${{ steps.dockerfile-checks.outputs.has-non-root-user || 'false' }}
fail-on-findings: 'true'
Comment thread
bedatty marked this conversation as resolved.

## To be fixed
# - name: Upload Secret Scan Results - Repository (SARIF) to GitHub Security Tab
# uses: github/codeql-action/upload-sarif@v3
# if: always()
# continue-on-error: true
# with:
# sarif_file: 'trivy-secret-scan-repo-${{ env.APP_NAME }}.sarif'

# - name: Upload Vulnerability Scan Results - Docker Image (SARIF) to GitHub Security Tab
# uses: github/codeql-action/upload-sarif@v3
# if: always()
# continue-on-error: true
# with:
# sarif_file: 'trivy-vulnerability-scan-docker-${{ env.APP_NAME }}.sarif'
- name: Gate - Fail on Pre-release Versions
if: always() && inputs.enable_prerelease_check && steps.prerelease-check.outputs.has-findings == 'true'
env:
BLOCK_BRANCHES: ${{ inputs.prerelease_block_branches }}
TARGET_BRANCH: ${{ github.base_ref }}
FINDINGS_COUNT: ${{ steps.prerelease-check.outputs.findings-count }}
run: |
SHOULD_BLOCK=false
IFS=',' read -ra BRANCHES <<< "$BLOCK_BRANCHES"
for branch in "${BRANCHES[@]}"; do
branch=$(echo "$branch" | xargs)
if [ "$TARGET_BRANCH" = "$branch" ]; then
SHOULD_BLOCK=true
break
fi
done

if [ "$SHOULD_BLOCK" = "true" ]; then
echo "::error::Pre-release version pins detected ($FINDINGS_COUNT finding(s)). Target branch '$TARGET_BRANCH' does not allow beta or release candidate dependencies."
exit 1
else
echo "::warning::Pre-release version pins detected ($FINDINGS_COUNT finding(s)). Allowed on '$TARGET_BRANCH' — will be blocked on: $BLOCK_BRANCHES."
fi

# ----------------- CodeQL Analysis -----------------
codeql_scan:
needs: prepare_matrix
if: inputs.enable_codeql && inputs.codeql_languages != '' && needs.prepare_matrix.outputs.matrix != '[]'
runs-on: ${{ inputs.runner_type }}
steps:
# ----------------- Setup -----------------
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Extract changed paths from matrix
id: extract-paths
env:
MATRIX: ${{ needs.prepare_matrix.outputs.matrix }}
run: |
PATHS=$(echo "$MATRIX" | jq -r '.[].working_dir' | paste -sd ',' -)
echo "paths=$PATHS" >> "$GITHUB_OUTPUT"

# ----------------- CodeQL Config -----------------
- name: Generate CodeQL Config
id: codeql-config
uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@feat/pr-security-scan-codeql-prerelease

Check warning on line 285 in .github/workflows/pr-security-scan.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@feat/pr-security-scan-codeql-prerelease
with:
changed-paths: ${{ steps.extract-paths.outputs.paths }}

# ----------------- CodeQL Analysis -----------------
- name: Initialize CodeQL
if: steps.codeql-config.outputs.skip != 'true'
uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@feat/pr-security-scan-codeql-prerelease

Check warning on line 292 in .github/workflows/pr-security-scan.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@feat/pr-security-scan-codeql-prerelease
with:
languages: ${{ inputs.codeql_languages }}
config-file: ${{ steps.codeql-config.outputs.config-file }}

- name: Configure private Go modules access
if: steps.codeql-config.outputs.skip != 'true'
env:
TOKEN: ${{ secrets.MANAGE_TOKEN || secrets.GITHUB_TOKEN }}
run: |
git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "https://github.com/"
echo "GOPRIVATE=github.com/LerianStudio/*" >> "$GITHUB_ENV"
Comment thread
bedatty marked this conversation as resolved.

- name: Autobuild
if: steps.codeql-config.outputs.skip != 'true'
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4

- name: Perform CodeQL Analysis
if: steps.codeql-config.outputs.skip != 'true'
uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@feat/pr-security-scan-codeql-prerelease

Check warning on line 311 in .github/workflows/pr-security-scan.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@feat/pr-security-scan-codeql-prerelease
with:
category: '/language:${{ inputs.codeql_languages }}'
upload: ${{ inputs.codeql_upload_sarif }}

# ----------------- Results & Security Gate -----------------
- name: Post CodeQL Results to PR
if: always() && github.event_name == 'pull_request' && steps.codeql-config.outputs.skip != 'true'
uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-reporter@feat/pr-security-scan-codeql-prerelease

Check warning on line 319 in .github/workflows/pr-security-scan.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-reporter@feat/pr-security-scan-codeql-prerelease
with:
github-token: ${{ secrets.MANAGE_TOKEN || secrets.GITHUB_TOKEN }}
languages: ${{ inputs.codeql_languages }}
fail-on-findings: ${{ inputs.codeql_fail_on_findings }}

# ----------------- Slack Notification -----------------
notify:
name: Notify
needs: [prepare_matrix, security_scan]
needs: [prepare_matrix, security_scan, codeql_scan]
if: always() && needs.prepare_matrix.outputs.matrix != '[]'
runs-on: ${{ inputs.runner_type }}
steps:
- name: Slack Notification
uses: LerianStudio/github-actions-shared-workflows/src/notify/slack-notify@v1.18.0
uses: LerianStudio/github-actions-shared-workflows/src/notify/slack-notify@v1.23.1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
status: ${{ needs.security_scan.result }}
status: ${{ (needs.security_scan.result == 'failure' || needs.codeql_scan.result == 'failure') && 'failure' || needs.security_scan.result }}
workflow-name: "PR Security Scan"
failed-jobs: ${{ needs.security_scan.result == 'failure' && 'Security Scan' || '' }}
failed-jobs: ${{ needs.security_scan.result == 'failure' && needs.codeql_scan.result == 'failure' && 'Security Scan, CodeQL Scan' || needs.security_scan.result == 'failure' && 'Security Scan' || needs.codeql_scan.result == 'failure' && 'CodeQL Scan' || '' }}
85 changes: 68 additions & 17 deletions docs/pr-security-scan-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Reusable workflow for comprehensive security scanning on pull requests. Supports

- **Secret scanning**: Trivy filesystem scan for exposed secrets (scans only changed component folder)
- **Vulnerability scanning**: Docker image vulnerability detection (optional)
- **CodeQL static analysis**: GitHub CodeQL for semantic code analysis (opt-in via `enable_codeql`)
- **Pre-release version gate**: Blocks dependencies pinned to `-beta` or `-rc` versions (enabled by default)
- **CLI/Non-Docker support**: Skip Docker scanning for projects without Dockerfile via `enable_docker_scan: false`
- **Monorepo support**: Automatic detection of changed components
- **Component-scoped scanning**: Only scans the specific component folder that changed, not entire repo
Expand Down Expand Up @@ -138,9 +140,9 @@ This will:
- ❌ Skip Docker vulnerability scanning
- ❌ Skip Docker Scout analysis

### Docker Scout Analysis
### With CodeQL Analysis

Enable Docker Scout for additional vulnerability scoring and CVE analysis on your Docker images:
Enable CodeQL for semantic static analysis on top of the standard security scans:

```yaml
name: PR Security Scan
Expand All @@ -153,16 +155,29 @@ jobs:
uses: LerianStudio/github-actions-shared-workflows/.github/workflows/pr-security-scan.yml@v1.0.0
with:
runner_type: "blacksmith-4vcpu-ubuntu-2404"
enable_docker_scout: true
enable_codeql: true
codeql_languages: 'go'
secrets: inherit
```

This will run all standard scans plus Docker Scout quickview and CVE analysis.
This will run all standard scans plus CodeQL analysis scoped to changed paths. Results are posted as a separate PR comment and uploaded to the GitHub Security tab.

**Requirements:**
- Docker Hub account with Scout access (Free, Team, or Business)
- `DOCKER_USERNAME` and `DOCKER_PASSWORD` secrets configured
- `enable_docker_scan` must also be `true` (default) — Scout reuses the same image built for Trivy scanning
**Supported languages:** `go`, `javascript-typescript`, `actions`, `python`, `java-kotlin`, `csharp`, `ruby`, `swift`, `cpp`
Comment thread
bedatty marked this conversation as resolved.
Outdated

### With Pre-release Version Gate

Pre-release checks are enabled by default. To disable:

```yaml
jobs:
security-scan:
uses: LerianStudio/github-actions-shared-workflows/.github/workflows/pr-security-scan.yml@v1.0.0
with:
enable_prerelease_check: false
secrets: inherit
```

When enabled, the workflow scans `go.mod`, `package.json`, and `Dockerfile` for version pins containing `-beta` or `-rc` suffixes and fails the PR if any are found.
Comment thread
bedatty marked this conversation as resolved.
Outdated

## Inputs

Expand All @@ -177,7 +192,13 @@ This will run all standard scans plus Docker Scout quickview and CVE analysis.
| `docker_registry` | string | `docker.io` | Docker registry URL |
| `dockerfile_name` | string | `Dockerfile` | Name of the Dockerfile |
| `enable_docker_scan` | boolean | `true` | Enable Docker image build and vulnerability scanning. Set to `false` for projects without Dockerfile (e.g., CLI tools) |
| `enable_docker_scout` | boolean | `false` | Enable Docker Scout image analysis for vulnerability scoring. Requires Docker Hub with Scout access |
| `enable_health_score` | boolean | `true` | Enable Docker Hub Health Score compliance checks (non-root user, CVEs, licenses) |
| `enable_codeql` | boolean | `false` | Enable CodeQL static analysis. Requires `codeql_languages` to be set |
| `codeql_languages` | string | `''` | Languages to analyze with CodeQL (comma-separated, e.g., `go`, `javascript-typescript`, `actions`) |
| `codeql_fail_on_findings` | boolean | `true` | Fail the workflow when CodeQL detects security issues |
| `codeql_upload_sarif` | boolean | `false` | Upload CodeQL SARIF results to the GitHub Security tab. Requires Code Security (GHAS) enabled on the repo |
| `enable_prerelease_check` | boolean | `true` | Block dependencies pinned to pre-release versions (`-beta`, `-rc`) |
| `prerelease_block_branches` | string | `release-candidate,main` | Comma-separated PR target branches where pre-release versions cause a hard failure. On other branches, findings are reported as warnings only |

## Secrets

Expand Down Expand Up @@ -219,14 +240,26 @@ For each component in the matrix:
1. **Docker Login**: Authenticate to registry (avoids rate limits)
2. **Checkout Repository**: Clone the code
3. **Setup Docker Buildx**: Enable multi-platform builds *(skipped if `enable_docker_scan: false`)*
4. **Trivy Secret Scan (Table)**: Scan filesystem for secrets - **fails on detection**
5. **Trivy Secret Scan (SARIF)**: Generate SARIF report
6. **Build Docker Image**: Build image for vulnerability scanning *(skipped if `enable_docker_scan: false`)*
7. **Trivy Vulnerability Scan (Table)**: Scan image for vulnerabilities *(skipped if `enable_docker_scan: false`)*
8. **Trivy Vulnerability Scan (SARIF)**: Generate SARIF report *(skipped if `enable_docker_scan: false`)*
9. **Docker Scout Analysis**: Quickview and CVE analysis *(skipped unless `enable_docker_scout: true` AND `enable_docker_scan: true`)*
4. **Trivy Filesystem Scan**: Scan filesystem for secrets and vulnerabilities
5. **Build Docker Image**: Build image for vulnerability scanning *(skipped if `enable_docker_scan: false`)*
6. **Trivy Image Scan**: Scan image for vulnerabilities and licenses *(skipped if `enable_docker_scan: false`)*
7. **Dockerfile Compliance Checks**: Non-root user and health score checks *(skipped unless `enable_health_score: true` AND `enable_docker_scan: true`)*
8. **Pre-release Version Check**: Scan for `-beta`/`-rc` version pins *(skipped if `enable_prerelease_check: false`)*
9. **Post Security Scan Results**: PR comment with consolidated findings

> **Note**: When `enable_docker_scan: false`, only filesystem scanning and pre-release checks run.

> **Note**: When `enable_docker_scan: false`, only filesystem secret scanning runs. This is useful for CLI tools and projects without Dockerfiles.
### Job 3: codeql_scan *(optional)*

Runs when `enable_codeql: true` and `codeql_languages` is set:

1. **Checkout Repository**: Clone the code
2. **Extract Changed Paths**: Derive scoped paths from the component matrix
3. **Generate CodeQL Config**: Scope analysis to changed paths
4. **Initialize CodeQL**: Set up CodeQL with configured languages and query suite
5. **Autobuild**: Automatically build the project for compiled languages
6. **Perform CodeQL Analysis**: Run semantic analysis and upload SARIF
7. **Post CodeQL Results**: PR comment with findings table and security gate

## Security Scans

Expand Down Expand Up @@ -259,6 +292,24 @@ For each component in the matrix:

**Exit behavior**: `exit-code: 0` (informative only, doesn't fail workflow)

### CodeQL Analysis

**What it does**: Runs GitHub CodeQL semantic analysis for security vulnerabilities and code quality issues

**Scope**: Automatically scoped to changed paths in the PR (via `codeql-config` composite)

**Query suite**: `security-extended` (default) — covers OWASP Top 10, CWE Top 25, and more

**Exit behavior**: Configurable via `codeql_fail_on_findings` (default: fails on findings)

### Pre-release Version Gate

**What it does**: Scans `go.mod`, `package.json`, and `Dockerfile` for version pins containing `-beta` or `-rc` suffixes

**Pattern matched**: `X.Y.Z-beta.*` and `X.Y.Z-rc.*` (any semver followed by a pre-release identifier)

**Exit behavior**: `exit-code: 1` on branches listed in `prerelease_block_branches` (default: `release-candidate,main`). On other branches (e.g., `develop`), findings are reported as warnings only.

## Monorepo Type 2 Behavior

### Backend Changes
Expand Down Expand Up @@ -493,7 +544,7 @@ Generated for each scan type:
- `trivy-secret-scan-repo-{app-name}.sarif`
- `trivy-vulnerability-scan-docker-{app-name}.sarif`

Can be uploaded to GitHub Security tab (currently commented out in workflow).
Uploaded to GitHub Security tab via CodeQL when `enable_codeql` is enabled.

## Related Workflows

Expand Down
5 changes: 5 additions & 0 deletions src/security/codeql-analyze/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
description: 'Output directory for SARIF files'
required: false
default: '../results'
upload:
description: 'Upload SARIF to GitHub Security tab (requires Code Security / GHAS enabled on the repo)'
required: false
default: 'false'
Comment thread
bedatty marked this conversation as resolved.

runs:
using: composite
steps:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4

Check failure on line 21 in src/security/codeql-analyze/action.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

External action not pinned by SHA: uses: github/codeql-action/analyze@v4 (use full commit SHA with a # vX.Y.Z comment)
with:
category: ${{ inputs.category }}
output: ${{ inputs.output }}
upload: ${{ inputs.upload }}
Loading
Loading