Skip to content
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 on lines +79 to +87
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the workflow file
git ls-files | grep -E "\.github/workflows/pr-security-scan\.yml"

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 126


🏁 Script executed:

# Read the workflow file to examine the codeql input definitions and codeql_scan job conditions
# Lines 79-87 show the inputs, lines 237-240 are mentioned as related
cat -n .github/workflows/pr-security-scan.yml | sed -n '1,300p'

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 15834


🏁 Script executed:

# Check the codeql_scan job to see its conditions and logic
ast-grep --pattern 'codeql_scan:'

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 70


Add validation to fail fast when CodeQL is enabled without languages.

The codeql_scan job condition (line 240) silently skips when enable_codeql: true but codeql_languages is empty, masking caller misconfiguration. The description correctly states the requirement, but there is no validation enforcing it. Add a validation job that runs when inputs.enable_codeql && inputs.codeql_languages == '' and exits with a clear error about the missing language configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr-security-scan.yml around lines 79 - 87, The workflow
allows enable_codeql to be true with an empty codeql_languages, which causes the
codeql_scan job to silently skip; add an early validation job (e.g.,
validate_codeql_inputs) that runs before codeql_scan and uses an if condition
checking inputs.enable_codeql is true and inputs.codeql_languages is empty, and
in that job run a single step that prints a clear error (mentioning
enable_codeql and codeql_languages) and exits non‑zero to fail fast; update job
dependencies so codeql_scan depends_on this validation to guarantee callers get
an explicit error rather than silent skipping.

codeql_fail_on_findings:
description: 'Fail the workflow when CodeQL detects security issues'
type: boolean
default: true
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 on lines +96 to +99
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Defaulting enable_prerelease_check to true is a caller-breaking rollout.

This reusable workflow now hard-fails PRs for existing consumers that do not know about the new input yet. "Can be disabled" does not help callers already pinned to a shared ref, because they would have to change their workflow first. If this is meant to be non-breaking, ship it default-off initially or only behind a new version boundary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr-security-scan.yml around lines 92 - 95, The default for
the reusable workflow input enable_prerelease_check currently defaults to true
which will break existing callers; change its default to false (or gate it
behind a new major/minor version boundary) so the prerelease blocking is opt-in
for existing consumers, updating the input declaration for
enable_prerelease_check accordingly to avoid hard-failing PRs for callers that
don’t supply the new input.

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 on lines +218 to +225
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Read the pr-security-scan.yml file to check the refs at the mentioned lines
cat -n .github/workflows/pr-security-scan.yml | sed -n '200,290p'

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 5201


🏁 Script executed:

# Search for all feature-branch refs in the file
grep -n "feat/pr-security-scan-codeql-prerelease" .github/workflows/pr-security-scan.yml

# Check what branches exist and what the default branch is
git branch -a | head -20

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 849


🏁 Script executed:

# Check for any README or docs about branch/ref strategy
find . -maxdepth 2 -name "*.md" -type f | head -10

# Look for other uses of stable refs in workflows to see the pattern
grep -r "uses:.*github-actions-shared-workflows.*@" .github/workflows/ | grep -v "feat/" | head -5

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 976


🏁 Script executed:

# Check the default branch configuration and what the review comment learnings say
cat README.md | head -50

# Check for version tags to understand the tagging strategy
git tag -l | tail -10

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 1600


🏁 Script executed:

# Check what version tags are used for other composites in the same workflow
grep -n "uses:.*github-actions-shared-workflows.*@" .github/workflows/pr-security-scan.yml | head -15

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 1338


🏁 Script executed:

# Check if the new composites exist and what version tags they might have
for composite in prerelease-check codeql-config codeql-init codeql-analyze codeql-reporter; do
  if [ -f "src/security/$composite/action.yml" ]; then
    echo "=== $composite exists ==="
  else
    echo "=== $composite MISSING ==="
  fi
done

# Check what branches these new composites are on
git log --oneline -n 20 -- src/security/prerelease-check/ 2>/dev/null | head -5
git log --oneline -n 20 -- src/security/codeql-config/ 2>/dev/null | head -5

Repository: LerianStudio/github-actions-shared-workflows

Length of output: 422


Switch feature-branch refs to semantic version tags before merge.

The five uses: entries reference @feat/pr-security-scan-codeql-prerelease. Once this branch is deleted or diverges, external callers of pr-security-scan.yml will fail to resolve these composites. Update to match the semantic versioning pattern used throughout the workflow (e.g., @v1.23.1 or the appropriate release version for these new composites):

Suggested ref update
-        uses: LerianStudio/github-actions-shared-workflows/src/security/prerelease-check@feat/pr-security-scan-codeql-prerelease
+        uses: LerianStudio/github-actions-shared-workflows/src/security/prerelease-check@v1.24.0
...
-        uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@feat/pr-security-scan-codeql-prerelease
+        uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@v1.24.0
...
-        uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@feat/pr-security-scan-codeql-prerelease
+        uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.24.0
...
-        uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@feat/pr-security-scan-codeql-prerelease
+        uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@v1.24.0
...
-        uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-reporter@feat/pr-security-scan-codeql-prerelease
+        uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-reporter@v1.24.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr-security-scan.yml around lines 209 - 216, The workflow
references a feature branch tag in the composite actions (the `uses:` entries
like
LerianStudio/github-actions-shared-workflows/src/security/prerelease-check@feat/pr-security-scan-codeql-prerelease)
which will break once that branch is deleted; update each `uses:` to point to a
released semantic version tag (e.g., change
`@feat/pr-security-scan-codeql-prerelease` to the appropriate `@vX.Y.Z` release)
so the Pre-release Version Check (`id: prerelease-check`) and the other four
composite action references resolve reliably after merges.


# ----------------- 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'

## 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 on lines +297 to +303
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Parameterize private Go module access instead of hardcoding LerianStudio.

GOPRIVATE=github.com/LerianStudio/* makes this shared workflow specific to one org/host. Any caller that needs private Go modules outside that scope will still fail CodeQL autobuild. Move the host/org pattern behind inputs, or derive it from repository context, before shipping this as a reusable workflow. As per coding guidelines: "Never hardcode tokens, org names, or internal URLs — always use inputs or secrets".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr-security-scan.yml around lines 270 - 276, The step
"Configure private Go modules access" currently hardcodes
GOPRIVATE=github.com/LerianStudio/* and makes the reusable workflow
org-specific; change this to accept a parameter (e.g., an input like
private_go_pattern or private_go_host) or derive the pattern from the GitHub
context (github.repository_owner / github.server_url) and use that value when
setting the git config and GOPRIVATE; update references to the git config line
and the GOPRIVATE assignment so they interpolate the input/context value instead
of the literal "LerianStudio". Ensure the new input has a sensible default and
is documented so callers can override it for other orgs/hosts.


- 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 on lines +163 to +165
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Security-tab upload is documented as unconditional, but it is opt-in.

The workflow only uploads CodeQL SARIF when codeql_upload_sarif: true is set. The current wording says results are uploaded to the GitHub Security tab whenever CodeQL is enabled, which will mislead callers using the default configuration.

Also applies to: 199-199, 547-547

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/pr-security-scan-workflow.md` around lines 163 - 165, Update the
documentation text that currently states "Results are posted as a separate PR
comment and uploaded to the GitHub Security tab" to clarify that SARIF upload to
the Security tab is opt-in and only happens when the configuration flag
codeql_upload_sarif: true is set; change the wording around the CodeQL section
(references to "CodeQL analysis" and the upload behavior) to explicitly note the
default is not to upload SARIF and callers must enable codeql_upload_sarif to
have results uploaded, and apply the same correction at the other occurrences
mentioned.


### 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 on lines +167 to +180
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The prerelease gate is branch-aware, not unconditional.

This section currently says the PR fails if any prerelease pins are found, but the workflow only exits non-zero on branches listed in prerelease_block_branches; other branches emit a warning. Update the prose to match the actual gate semantics.

Also applies to: 307-311

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/pr-security-scan-workflow.md` around lines 167 - 180, Update the prose
describing the prerelease gate to reflect that it is branch-aware: state that
when enable_prerelease_check is true the workflow scans go.mod, package.json,
and Dockerfile for prerelease suffixes (e.g., -beta, -rc) and emits a warning on
non-blocking branches, and only fails the PR (exits non‑zero) for branches
listed in prerelease_block_branches; adjust the wording around the “With
Pre-release Version Gate” section and the corresponding lines mentioned so they
reference the enable_prerelease_check input and the prerelease_block_branches
behavior instead of saying it unconditionally fails the PR.


## 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 on lines +12 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

upload default introduces an unintended behavior change for existing callers.

Line 15 defaults upload to 'false', and Line 25 always forwards it. Callers that omit this new input now disable SARIF upload by default (including the current workflow invocations shown in .github/workflows/self-pr-validation.yml Line 206-Line 210 and .github/workflows/pr-security-scan.yml Line 283-Line 287). Set the default to 'true' or pass upload: 'true' explicitly at call sites that must publish to Security tab.

Suggested fix
   upload:
     description: 'Upload SARIF to GitHub Security tab (requires Code Security / GHAS enabled on the repo)'
     required: false
-    default: 'false'
+    default: 'true'

Also applies to: 25-25

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/security/codeql-analyze/action.yml` around lines 12 - 15, The new action
input 'upload' in action.yml defaults to 'false', which changes behavior for
callers that omit it; update the default to 'true' (or ensure callers explicitly
pass 'upload: \"true\"') so SARIF upload remains enabled by default. Modify the
inputs block for the 'upload' input in action.yml to set default: 'true' (or
update any workflows invoking the action to pass upload: 'true' where necessary)
and keep the value forwarded as before.


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