From ea4ffcabfe681ddadc0a0e9da6bbfd5fbe307d25 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Fri, 20 Mar 2026 17:56:31 -0300 Subject: [PATCH 01/14] ci(codeql): add CodeQL analysis workflow for this repository --- .github/workflows/self-codeql.yml | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/self-codeql.yml diff --git a/.github/workflows/self-codeql.yml b/.github/workflows/self-codeql.yml new file mode 100644 index 00000000..ecf3249e --- /dev/null +++ b/.github/workflows/self-codeql.yml @@ -0,0 +1,45 @@ +name: Self — CodeQL + +on: + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main + schedule: + # Run every Monday at 06:00 UTC + - cron: '0 6 * * 1' + workflow_dispatch: + +permissions: + contents: read + security-events: write + actions: read + +jobs: + analyze: + name: CodeQL Analysis + runs-on: blacksmith-4vcpu-ubuntu-2404 + + strategy: + fail-fast: false + matrix: + language: + - actions + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: '/language:${{ matrix.language }}' From 540b7dbcdb817897f453dfc396eeafe3f19a0558 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Mon, 23 Mar 2026 15:22:24 -0300 Subject: [PATCH 02/14] ci(codeql): extract CodeQL steps into composites and improve workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract init and analyze steps into reusable composite actions under src/security/. Remove schedule trigger (moving to future schedule-job), remove unnecessary matrix strategy, and add YAML path filters. New composites: - src/security/codeql-init — initializes CodeQL with security-extended suite - src/security/codeql-analyze — performs analysis and uploads SARIF results --- .github/workflows/self-codeql.yml | 23 +++++----- src/security/codeql-analyze/README.md | 42 ++++++++++++++++++ src/security/codeql-analyze/action.yml | 20 +++++++++ src/security/codeql-init/README.md | 61 ++++++++++++++++++++++++++ src/security/codeql-init/action.yml | 25 +++++++++++ 5 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 src/security/codeql-analyze/README.md create mode 100644 src/security/codeql-analyze/action.yml create mode 100644 src/security/codeql-init/README.md create mode 100644 src/security/codeql-init/action.yml diff --git a/.github/workflows/self-codeql.yml b/.github/workflows/self-codeql.yml index ecf3249e..af9338d3 100644 --- a/.github/workflows/self-codeql.yml +++ b/.github/workflows/self-codeql.yml @@ -5,13 +5,16 @@ on: branches: - develop - main + paths: + - '**.yml' + - '**.yaml' pull_request: branches: - develop - main - schedule: - # Run every Monday at 06:00 UTC - - cron: '0 6 * * 1' + paths: + - '**.yml' + - '**.yaml' workflow_dispatch: permissions: @@ -24,22 +27,16 @@ jobs: name: CodeQL Analysis runs-on: blacksmith-4vcpu-ubuntu-2404 - strategy: - fail-fast: false - matrix: - language: - - actions - steps: - name: Checkout repository uses: actions/checkout@v6 - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: ./src/security/codeql-init with: - languages: ${{ matrix.language }} + languages: actions - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: ./src/security/codeql-analyze with: - category: '/language:${{ matrix.language }}' + category: '/language:actions' diff --git a/src/security/codeql-analyze/README.md b/src/security/codeql-analyze/README.md new file mode 100644 index 00000000..10e5a087 --- /dev/null +++ b/src/security/codeql-analyze/README.md @@ -0,0 +1,42 @@ + + + + + +
Lerian

codeql-analyze

+ +Composite action that performs CodeQL analysis and uploads SARIF results to the GitHub Security tab. Designed to be paired with [`codeql-init`](../codeql-init/). + +## Inputs + +| Input | Description | Required | Default | +|---|---|:---:|---| +| `category` | Category for SARIF results used for deduplication (e.g., `/language:actions`) | Yes | — | +| `output` | Output directory for SARIF files | No | `../results` | + +## Usage + +### Basic (paired with codeql-init) + +```yaml +steps: + - uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.x.x + with: + languages: actions + + - name: Perform CodeQL Analysis + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@v1.x.x + with: + category: '/language:actions' +``` + +## Permissions required + +```yaml +permissions: + contents: read + security-events: write # required for SARIF upload to GitHub Security tab +``` diff --git a/src/security/codeql-analyze/action.yml b/src/security/codeql-analyze/action.yml new file mode 100644 index 00000000..ee2e7904 --- /dev/null +++ b/src/security/codeql-analyze/action.yml @@ -0,0 +1,20 @@ +name: CodeQL Analyze +description: Perform CodeQL analysis and upload SARIF results to GitHub Security tab + +inputs: + category: + description: 'Category for SARIF results used for deduplication (e.g., /language:actions)' + required: true + output: + description: 'Output directory for SARIF files' + required: false + default: '../results' + +runs: + using: composite + steps: + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: ${{ inputs.category }} + output: ${{ inputs.output }} diff --git a/src/security/codeql-init/README.md b/src/security/codeql-init/README.md new file mode 100644 index 00000000..e08ac3a2 --- /dev/null +++ b/src/security/codeql-init/README.md @@ -0,0 +1,61 @@ + + + + + +
Lerian

codeql-init

+ +Composite action that initializes CodeQL analysis for specified languages. Uses the `security-extended` query suite by default for broader coverage. Designed to be paired with [`codeql-analyze`](../codeql-analyze/). + +## Inputs + +| Input | Description | Required | Default | +|---|---|:---:|---| +| `languages` | Languages to analyze (comma-separated, e.g., `actions`, `javascript-typescript`, `go`) | Yes | — | +| `queries` | Query suite to use (`security-standard`, `security-extended`, `security-and-quality`) | No | `security-extended` | +| `config-file` | Path to CodeQL configuration file | No | — | + +## Usage + +### Basic (GitHub Actions YAML analysis) + +```yaml +jobs: + codeql: + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + contents: read + security-events: write + actions: read + steps: + - uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.x.x + with: + languages: actions + + - name: Analyze + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@v1.x.x + with: + category: '/language:actions' +``` + +### Multiple languages + +```yaml +- name: Initialize CodeQL + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.x.x + with: + languages: javascript-typescript + queries: security-and-quality +``` + +## Permissions required + +```yaml +permissions: + contents: read + security-events: write + actions: read # required for workflows using private repositories +``` diff --git a/src/security/codeql-init/action.yml b/src/security/codeql-init/action.yml new file mode 100644 index 00000000..a2442047 --- /dev/null +++ b/src/security/codeql-init/action.yml @@ -0,0 +1,25 @@ +name: CodeQL Init +description: Initialize CodeQL analysis for specified languages with configurable query suite + +inputs: + languages: + description: 'Languages to analyze (comma-separated, e.g., actions, javascript-typescript, go)' + required: true + queries: + description: 'Query suite to use (security-standard, security-extended, security-and-quality)' + required: false + default: 'security-extended' + config-file: + description: 'Path to CodeQL configuration file' + required: false + default: '' + +runs: + using: composite + steps: + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ inputs.languages }} + queries: ${{ inputs.queries }} + config-file: ${{ inputs.config-file || '' }} From 63acc0460c53587bca490bc162cdba55a6ab5c10 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Mon, 23 Mar 2026 16:06:27 -0300 Subject: [PATCH 03/14] refactor(codeql): use changed-workflows composite and remove push trigger Reuse existing changed-workflows composite for file detection instead of inline git diff. CodeQL analysis now only runs when action/workflow YAML files are changed in PRs. Full repo scan will move to a future schedule-job. --- .github/workflows/self-codeql.yml | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/.github/workflows/self-codeql.yml b/.github/workflows/self-codeql.yml index af9338d3..132c8de7 100644 --- a/.github/workflows/self-codeql.yml +++ b/.github/workflows/self-codeql.yml @@ -1,13 +1,6 @@ name: Self — CodeQL on: - push: - branches: - - develop - - main - paths: - - '**.yml' - - '**.yaml' pull_request: branches: - develop @@ -23,8 +16,30 @@ permissions: actions: read jobs: + # ----------------- Changed Files Detection ----------------- + changed-files: + name: Detect Changed Files + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + contents: read + pull-requests: read + outputs: + action_files: ${{ steps.detect.outputs.action-files }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Detect changed files + id: detect + uses: ./src/config/changed-workflows + with: + github-token: ${{ github.token }} + + # ----------------- CodeQL Analysis ----------------- analyze: name: CodeQL Analysis + needs: changed-files + if: needs.changed-files.outputs.action_files != '' runs-on: blacksmith-4vcpu-ubuntu-2404 steps: From 138d109a5d2bcb91bfd8fec779647370e6a07650 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 09:14:15 -0300 Subject: [PATCH 04/14] docs(codeql-analyze): add actions:read to permissions section Align codeql-analyze README permissions with codeql-init for consistency, since both composites are used together. --- src/security/codeql-analyze/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/security/codeql-analyze/README.md b/src/security/codeql-analyze/README.md index 10e5a087..b010c65c 100644 --- a/src/security/codeql-analyze/README.md +++ b/src/security/codeql-analyze/README.md @@ -39,4 +39,5 @@ steps: permissions: contents: read security-events: write # required for SARIF upload to GitHub Security tab + actions: read # required for workflows using private repositories ``` From 83e544ac2a0f311a2612a16f842cc4b3c592321d Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 09:35:41 -0300 Subject: [PATCH 05/14] docs(codeql): document wrapped upstream actions in READMEs Add references to github/codeql-action/init and github/codeql-action/analyze in composite README descriptions for transparency. --- src/security/codeql-analyze/README.md | 2 +- src/security/codeql-init/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/security/codeql-analyze/README.md b/src/security/codeql-analyze/README.md index b010c65c..749206b3 100644 --- a/src/security/codeql-analyze/README.md +++ b/src/security/codeql-analyze/README.md @@ -5,7 +5,7 @@ -Composite action that performs CodeQL analysis and uploads SARIF results to the GitHub Security tab. Designed to be paired with [`codeql-init`](../codeql-init/). +Composite action that wraps [`github/codeql-action/analyze`](https://github.com/github/codeql-action) to perform CodeQL analysis and upload SARIF results to the GitHub Security tab. Designed to be paired with [`codeql-init`](../codeql-init/). ## Inputs diff --git a/src/security/codeql-init/README.md b/src/security/codeql-init/README.md index e08ac3a2..94ae1605 100644 --- a/src/security/codeql-init/README.md +++ b/src/security/codeql-init/README.md @@ -5,7 +5,7 @@ -Composite action that initializes CodeQL analysis for specified languages. Uses the `security-extended` query suite by default for broader coverage. Designed to be paired with [`codeql-analyze`](../codeql-analyze/). +Composite action that wraps [`github/codeql-action/init`](https://github.com/github/codeql-action) to initialize CodeQL analysis for specified languages. Uses the `security-extended` query suite by default for broader coverage. Designed to be paired with [`codeql-analyze`](../codeql-analyze/). ## Inputs From 60a3f28f0c1bb3cbe2ebcd69892c1d70116bf2fe Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 10:03:58 -0300 Subject: [PATCH 06/14] feat(security): add CodeQL to pr-security-scan and consolidate self-codeql into self-pr-validation Add CodeQL as optional feature in pr-security-scan.yml with new inputs (enable_codeql, codeql_languages, codeql_queries). Create codeql-reporter composite that reads SARIF and posts findings as PR comments. Remove standalone self-codeql.yml and move CodeQL analysis into self-pr-validation.yml as a job alongside existing linters. --- .github/workflows/pr-security-scan.yml | 54 +++++- .github/workflows/self-codeql.yml | 57 ------ .github/workflows/self-pr-validation.yml | 35 ++++ src/security/codeql-reporter/README.md | 60 +++++++ src/security/codeql-reporter/action.yml | 217 +++++++++++++++++++++++ 5 files changed, 362 insertions(+), 61 deletions(-) delete mode 100644 .github/workflows/self-codeql.yml create mode 100644 src/security/codeql-reporter/README.md create mode 100644 src/security/codeql-reporter/action.yml diff --git a/.github/workflows/pr-security-scan.yml b/.github/workflows/pr-security-scan.yml index ca8a86ce..948f6720 100644 --- a/.github/workflows/pr-security-scan.yml +++ b/.github/workflows/pr-security-scan.yml @@ -67,12 +67,25 @@ on: description: 'Enable Docker Hub Health Score compliance checks (non-root user, CVEs, licenses)' type: boolean default: true + enable_codeql: + description: 'Enable CodeQL static analysis. Runs as a separate job in parallel with other security scans' + type: boolean + default: false + codeql_languages: + description: 'Comma-separated CodeQL languages to analyze (e.g., go, javascript-typescript, actions). Required when enable_codeql is true' + type: string + default: '' + codeql_queries: + description: 'CodeQL query suite to use (security-standard, security-extended, security-and-quality)' + type: string + default: 'security-extended' permissions: id-token: write # Required for OIDC authentication contents: read # Required to checkout the repository pull-requests: write # Allows commenting on PRs security-events: write # Required for security scanning + actions: read # Required for CodeQL on private repositories jobs: prepare_matrix: @@ -207,17 +220,50 @@ jobs: # with: # sarif_file: 'trivy-vulnerability-scan-docker-${{ env.APP_NAME }}.sarif' + # ----------------- CodeQL Analysis ----------------- + codeql_analysis: + name: CodeQL Analysis + if: inputs.enable_codeql && inputs.codeql_languages != '' + runs-on: ${{ inputs.runner_type }} + permissions: + contents: read + security-events: write + pull-requests: write + actions: read + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.18.0 + with: + languages: ${{ inputs.codeql_languages }} + queries: ${{ inputs.codeql_queries }} + + - name: Perform CodeQL Analysis + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@v1.18.0 + with: + category: '/language:${{ inputs.codeql_languages }}' + + - name: Post CodeQL Results to PR + if: always() && github.event_name == 'pull_request' + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-reporter@v1.18.0 + with: + github-token: ${{ secrets.MANAGE_TOKEN || secrets.GITHUB_TOKEN }} + languages: ${{ inputs.codeql_languages }} + fail-on-findings: 'false' + # ----------------- Slack Notification ----------------- notify: name: Notify - needs: [prepare_matrix, security_scan] - if: always() && needs.prepare_matrix.outputs.matrix != '[]' + needs: [prepare_matrix, security_scan, codeql_analysis] + if: always() && (needs.prepare_matrix.outputs.matrix != '[]' || needs.codeql_analysis.result != 'skipped') runs-on: ${{ inputs.runner_type }} steps: - name: Slack Notification uses: LerianStudio/github-actions-shared-workflows/src/notify/slack-notify@v1.18.0 with: webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - status: ${{ needs.security_scan.result }} + status: ${{ (needs.security_scan.result == 'failure' || needs.codeql_analysis.result == 'failure') && 'failure' || 'success' }} workflow-name: "PR Security Scan" - failed-jobs: ${{ needs.security_scan.result == 'failure' && 'Security Scan' || '' }} + failed-jobs: ${{ needs.security_scan.result == 'failure' && 'Security Scan' || '' }}${{ needs.codeql_analysis.result == 'failure' && ', CodeQL Analysis' || '' }} diff --git a/.github/workflows/self-codeql.yml b/.github/workflows/self-codeql.yml deleted file mode 100644 index 132c8de7..00000000 --- a/.github/workflows/self-codeql.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Self — CodeQL - -on: - pull_request: - branches: - - develop - - main - paths: - - '**.yml' - - '**.yaml' - workflow_dispatch: - -permissions: - contents: read - security-events: write - actions: read - -jobs: - # ----------------- Changed Files Detection ----------------- - changed-files: - name: Detect Changed Files - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - pull-requests: read - outputs: - action_files: ${{ steps.detect.outputs.action-files }} - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Detect changed files - id: detect - uses: ./src/config/changed-workflows - with: - github-token: ${{ github.token }} - - # ----------------- CodeQL Analysis ----------------- - analyze: - name: CodeQL Analysis - needs: changed-files - if: needs.changed-files.outputs.action_files != '' - runs-on: blacksmith-4vcpu-ubuntu-2404 - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Initialize CodeQL - uses: ./src/security/codeql-init - with: - languages: actions - - - name: Perform CodeQL Analysis - uses: ./src/security/codeql-analyze - with: - category: '/language:actions' diff --git a/.github/workflows/self-pr-validation.yml b/.github/workflows/self-pr-validation.yml index c98093f3..9f5fe9ab 100644 --- a/.github/workflows/self-pr-validation.yml +++ b/.github/workflows/self-pr-validation.yml @@ -14,6 +14,9 @@ on: permissions: contents: read checks: read + security-events: write + pull-requests: write + actions: read jobs: # ----------------- PR Validation ----------------- @@ -176,6 +179,38 @@ jobs: with: files: ${{ needs.changed-files.outputs.composite_files }} + # ----------------- CodeQL Analysis ----------------- + codeql: + name: CodeQL Analysis + runs-on: blacksmith-4vcpu-ubuntu-2404 + needs: changed-files + if: needs.changed-files.outputs.action_files != '' + permissions: + contents: read + security-events: write + pull-requests: write + actions: read + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: ./src/security/codeql-init + with: + languages: actions + + - name: Perform CodeQL Analysis + uses: ./src/security/codeql-analyze + with: + category: '/language:actions' + + - name: Post CodeQL Results to PR + if: always() && github.event_name == 'pull_request' + uses: ./src/security/codeql-reporter + with: + github-token: ${{ secrets.MANAGE_TOKEN || github.token }} + languages: actions + # ----------------- Lint Report ----------------- lint-report: name: Lint Report diff --git a/src/security/codeql-reporter/README.md b/src/security/codeql-reporter/README.md new file mode 100644 index 00000000..c0428750 --- /dev/null +++ b/src/security/codeql-reporter/README.md @@ -0,0 +1,60 @@ + + + + + +
Lerian

codeql-reporter

+ +Composite action that reads CodeQL SARIF output and posts a formatted security report as a PR comment. Uses an idempotent comment strategy (updates existing comment on re-runs). Designed to run after [`codeql-analyze`](../codeql-analyze/). + +## Inputs + +| Input | Description | Required | Default | +|---|---|:---:|---| +| `github-token` | GitHub token with `pull-requests:write` and `issues:write` permissions | Yes | — | +| `sarif-path` | Path to the CodeQL SARIF output directory | No | `../results` | +| `languages` | Comma-separated list of languages analyzed (for display) | Yes | — | +| `fail-on-findings` | Fail the step when security findings are detected (`true`/`false`) | No | `false` | + +## Outputs + +| Output | Description | +|---|---| +| `has-findings` | `true` if any CodeQL findings were detected | +| `findings-count` | Total number of CodeQL findings | + +## Usage + +### After CodeQL analysis (paired with codeql-init and codeql-analyze) + +```yaml +steps: + - uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.x.x + with: + languages: go + + - name: Perform CodeQL Analysis + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@v1.x.x + with: + category: '/language:go' + + - name: Post CodeQL Results to PR + if: always() && github.event_name == 'pull_request' + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-reporter@v1.x.x + with: + github-token: ${{ github.token }} + languages: go +``` + +## Permissions required + +```yaml +permissions: + contents: read + security-events: write + pull-requests: write + actions: read +``` diff --git a/src/security/codeql-reporter/action.yml b/src/security/codeql-reporter/action.yml new file mode 100644 index 00000000..bf1a6c6e --- /dev/null +++ b/src/security/codeql-reporter/action.yml @@ -0,0 +1,217 @@ +name: CodeQL Reporter +description: Reads CodeQL SARIF output and posts a formatted security report as a PR comment. + +inputs: + github-token: + description: GitHub token with pull-requests:write and issues:write permissions + required: true + sarif-path: + description: Path to the CodeQL SARIF output directory + required: false + default: '../results' + languages: + description: Comma-separated list of languages analyzed (for display purposes) + required: true + fail-on-findings: + description: 'Fail the step with exit code 1 when security findings are detected (true/false)' + required: false + default: 'false' + +outputs: + has-findings: + description: True if any CodeQL findings were detected + value: ${{ steps.parse-outputs.outputs.has_findings }} + findings-count: + description: Total number of CodeQL findings + value: ${{ steps.parse-outputs.outputs.findings_count }} + +runs: + using: composite + steps: + - name: Post CodeQL report to PR + id: report + uses: actions/github-script@v8 + env: + SARIF_PATH: ${{ inputs.sarif-path }} + LANGUAGES: ${{ inputs.languages }} + with: + github-token: ${{ inputs.github-token }} + result-encoding: string + script: | + const fs = require('fs'); + const path = require('path'); + + const sarifDir = process.env.SARIF_PATH; + const languages = process.env.LANGUAGES; + + let body = ''; + let hasFindings = false; + let findingsCount = 0; + + // ── Helpers ── + const SEVERITY_ICONS = { + error: '\u{1F534}', + warning: '\u{1F7E1}', + note: '\u{1F535}', + none: '\u26AA' + }; + + const SEVERITY_LABELS = { + error: 'High/Critical', + warning: 'Medium', + note: 'Low', + none: 'Info' + }; + + const SEVERITY_ORDER = { error: 0, warning: 1, note: 2, none: 3 }; + + const md = (value) => + String(value ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ').replace(/`/g, '\\`'); + + const truncate = (str, max) => + str.length > max ? str.substring(0, max - 3) + '...' : str; + + // ── Read SARIF files ── + function readSarifFiles() { + const findings = []; + + if (!fs.existsSync(sarifDir)) { + core.warning(`SARIF directory not found: ${sarifDir}`); + return findings; + } + + const files = fs.readdirSync(sarifDir).filter(f => f.endsWith('.sarif')); + + for (const file of files) { + try { + const sarif = JSON.parse(fs.readFileSync(path.join(sarifDir, file), 'utf8')); + + for (const run of (sarif.runs || [])) { + const rules = new Map(); + for (const rule of (run.tool?.driver?.rules || [])) { + rules.set(rule.id, rule); + } + + for (const result of (run.results || [])) { + const rule = rules.get(result.ruleId) || {}; + const location = result.locations?.[0]?.physicalLocation; + + findings.push({ + rule: result.ruleId || 'unknown', + severity: result.level || 'warning', + message: result.message?.text || rule.shortDescription?.text || 'No description', + file: location?.artifactLocation?.uri || 'unknown', + line: location?.region?.startLine || 0, + description: rule.fullDescription?.text || rule.shortDescription?.text || '', + tags: rule.properties?.tags || [], + }); + } + } + } catch (e) { + core.warning(`Failed to parse SARIF file ${file}: ${e.message}`); + } + } + + return findings; + } + + // ── Build Report ── + const findings = readSarifFiles(); + findingsCount = findings.length; + + body += `## \u{1F6E1}\uFE0F CodeQL Analysis Results\n\n`; + body += `**Languages analyzed:** \`${languages}\`\n\n`; + + if (findings.length === 0) { + body += `\u2705 No security issues found.\n\n`; + } else { + hasFindings = true; + + findings.sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 3) - (SEVERITY_ORDER[b.severity] ?? 3)); + + // Summary counts + const counts = {}; + for (const f of findings) { + const label = SEVERITY_LABELS[f.severity] || 'Info'; + counts[label] = (counts[label] || 0) + 1; + } + + body += `**Found ${findings.length} issue(s):** `; + body += Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(', '); + body += `\n\n`; + + // Findings table + const MAX = 30; + body += `| Severity | Rule | File | Message |\n`; + body += `|----------|------|------|----------|\n`; + + for (const f of findings.slice(0, MAX)) { + const icon = SEVERITY_ICONS[f.severity] || '\u26AA'; + const label = SEVERITY_LABELS[f.severity] || 'Info'; + const fileLink = f.line > 0 ? `\`${md(f.file)}:${f.line}\`` : `\`${md(f.file)}\``; + body += `| ${icon} ${label} | \`${md(f.rule)}\` | ${fileLink} | ${md(truncate(f.message, 80))} |\n`; + } + + if (findings.length > MAX) { + body += `\n_... and ${findings.length - MAX} more findings._\n`; + } + + body += '\n'; + } + + // ── Useful Links ── + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + const securityUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/security/code-scanning`; + body += `---\n\n`; + body += `\u{1F50D} [View full scan logs](${runUrl}) | \u{1F6E1}\uFE0F [Security tab](${securityUrl})\n\n`; + + // ── Post Comment ── + const marker = ``; + body = marker + '\n' + body; + + try { + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + per_page: 100, + }); + + const existing = comments.find(c => c.body?.includes(marker)); + const params = { + owner: context.repo.owner, + repo: context.repo.repo, + body, + }; + + if (existing) { + await github.rest.issues.updateComment({ ...params, comment_id: existing.id }); + } else { + await github.rest.issues.createComment({ ...params, issue_number: context.issue.number }); + } + } catch (e) { + core.warning(`Could not post PR CodeQL comment: ${e.message}`); + } + + return JSON.stringify({ has_findings: hasFindings, findings_count: findingsCount }); + + - name: Parse outputs + id: parse-outputs + shell: bash + run: | + RESULT='${{ steps.report.outputs.result }}' + HAS_FINDINGS=$(echo "$RESULT" | jq -r '.has_findings // false') + FINDINGS_COUNT=$(echo "$RESULT" | jq -r '.findings_count // 0') + echo "has_findings=$HAS_FINDINGS" >> "$GITHUB_OUTPUT" + echo "findings_count=$FINDINGS_COUNT" >> "$GITHUB_OUTPUT" + echo "Parsed outputs: has_findings=$HAS_FINDINGS, findings_count=$FINDINGS_COUNT" + + # ----------------- Security Gate ----------------- + - name: Gate - Fail on CodeQL Findings + if: inputs.fail-on-findings == 'true' + shell: bash + run: | + if [ "${{ steps.parse-outputs.outputs.has_findings }}" = "true" ]; then + echo "::error::CodeQL found ${{ steps.parse-outputs.outputs.findings_count }} security issue(s). Check the PR comment for details." + exit 1 + fi From 4b033f1b15e60a9aa7c8fa4a33ac8525b84c2ed5 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 10:21:55 -0300 Subject: [PATCH 07/14] refactor(security): extract CodeQL config generation into codeql-config composite Create src/security/codeql-config composite that generates a dynamic CodeQL configuration scoped to changed paths. Supports both file lists (extracts parent dirs) and directory lists (uses as-is). Update self-pr-validation.yml and pr-security-scan.yml to use the new composite instead of inline shell scripts. --- .github/workflows/pr-security-scan.yml | 31 +++++++++- .github/workflows/self-pr-validation.yml | 11 +++- src/security/codeql-config/README.md | 57 ++++++++++++++++++ src/security/codeql-config/action.yml | 75 ++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 src/security/codeql-config/README.md create mode 100644 src/security/codeql-config/action.yml diff --git a/.github/workflows/pr-security-scan.yml b/.github/workflows/pr-security-scan.yml index 948f6720..903ddde1 100644 --- a/.github/workflows/pr-security-scan.yml +++ b/.github/workflows/pr-security-scan.yml @@ -223,6 +223,7 @@ jobs: # ----------------- CodeQL Analysis ----------------- codeql_analysis: name: CodeQL Analysis + needs: prepare_matrix if: inputs.enable_codeql && inputs.codeql_languages != '' runs-on: ${{ inputs.runner_type }} permissions: @@ -233,20 +234,48 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Extract changed directories from matrix + id: extract-dirs + shell: bash + run: | + MATRIX='${{ needs.prepare_matrix.outputs.matrix }}' + if [ "$MATRIX" != "[]" ] && [ -n "$MATRIX" ]; then + DIRS=$(echo "$MATRIX" | jq -r '.[].working_dir' 2>/dev/null | tr '\n' ',' | sed 's/,$//') + fi + + # Fallback for single-app repos (working_dir is ".") + if [ -z "$DIRS" ] || [ "$DIRS" = "." ]; then + DIRS=$(git diff --name-only --diff-filter=ACMR origin/${{ github.event.pull_request.base.ref || 'develop' }}...HEAD | head -200 | tr '\n' ',' | sed 's/,$//') + fi + + echo "paths=$DIRS" >> "$GITHUB_OUTPUT" + + - name: Generate CodeQL config for changed paths + id: codeql-config + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@v1.18.0 + with: + changed-paths: ${{ steps.extract-dirs.outputs.paths }} + path-mode: ${{ needs.prepare_matrix.outputs.matrix != '[]' && 'dirs' || 'files' }} - name: Initialize CodeQL + if: steps.codeql-config.outputs.skip != 'true' uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.18.0 with: languages: ${{ inputs.codeql_languages }} queries: ${{ inputs.codeql_queries }} + config-file: ${{ steps.codeql-config.outputs.config-file }} - name: Perform CodeQL Analysis + if: steps.codeql-config.outputs.skip != 'true' uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@v1.18.0 with: category: '/language:${{ inputs.codeql_languages }}' - name: Post CodeQL Results to PR - if: always() && github.event_name == 'pull_request' + if: always() && github.event_name == 'pull_request' && steps.codeql-config.outputs.skip != 'true' uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-reporter@v1.18.0 with: github-token: ${{ secrets.MANAGE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/self-pr-validation.yml b/.github/workflows/self-pr-validation.yml index 9f5fe9ab..7ce43123 100644 --- a/.github/workflows/self-pr-validation.yml +++ b/.github/workflows/self-pr-validation.yml @@ -194,18 +194,27 @@ jobs: - name: Checkout uses: actions/checkout@v6 + - name: Generate CodeQL config for changed files + id: codeql-config + uses: ./src/security/codeql-config + with: + changed-paths: ${{ needs.changed-files.outputs.action_files }} + - name: Initialize CodeQL + if: steps.codeql-config.outputs.skip != 'true' uses: ./src/security/codeql-init with: languages: actions + config-file: ${{ steps.codeql-config.outputs.config-file }} - name: Perform CodeQL Analysis + if: steps.codeql-config.outputs.skip != 'true' uses: ./src/security/codeql-analyze with: category: '/language:actions' - name: Post CodeQL Results to PR - if: always() && github.event_name == 'pull_request' + if: always() && github.event_name == 'pull_request' && steps.codeql-config.outputs.skip != 'true' uses: ./src/security/codeql-reporter with: github-token: ${{ secrets.MANAGE_TOKEN || github.token }} diff --git a/src/security/codeql-config/README.md b/src/security/codeql-config/README.md new file mode 100644 index 00000000..ff7c21e9 --- /dev/null +++ b/src/security/codeql-config/README.md @@ -0,0 +1,57 @@ + + + + + +
Lerian

codeql-config

+ +Composite action that generates a dynamic CodeQL configuration file scoped to changed paths. Ensures CodeQL analyzes only modified files in PRs instead of the entire repository. Designed to run before [`codeql-init`](../codeql-init/). + +## Inputs + +| Input | Description | Required | Default | +|---|---|:---:|---| +| `changed-paths` | Comma or newline-separated list of changed file paths or directories | Yes | — | +| `output-file` | Path where the generated config file will be written | No | `.github/codeql-config-pr.yml` | +| `path-mode` | How to interpret paths: `files` extracts parent directories, `dirs` uses paths as-is | No | `files` | + +## Outputs + +| Output | Description | +|---|---| +| `config-file` | Path to the generated CodeQL config file | +| `skip` | `true` if no paths were resolved and CodeQL should be skipped | + +## Usage + +### With changed-workflows (this repo) + +```yaml +- name: Generate CodeQL config + id: codeql-config + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@v1.x.x + with: + changed-paths: ${{ needs.changed-files.outputs.action_files }} + +- name: Initialize CodeQL + if: steps.codeql-config.outputs.skip != 'true' + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.x.x + with: + languages: actions + config-file: ${{ steps.codeql-config.outputs.config-file }} +``` + +### With monorepo matrix directories + +```yaml +- name: Generate CodeQL config + id: codeql-config + uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@v1.x.x + with: + changed-paths: ${{ steps.extract-dirs.outputs.dirs }} + path-mode: dirs +``` + +## Permissions required + +No special permissions needed — this composite only generates a file. diff --git a/src/security/codeql-config/action.yml b/src/security/codeql-config/action.yml new file mode 100644 index 00000000..39756e87 --- /dev/null +++ b/src/security/codeql-config/action.yml @@ -0,0 +1,75 @@ +name: CodeQL Config Generator +description: Generates a dynamic CodeQL configuration file scoped to changed paths for PR-only analysis. + +inputs: + changed-paths: + description: 'Comma or newline-separated list of changed file paths or directories to scope the analysis' + required: true + output-file: + description: 'Path where the generated CodeQL config file will be written' + required: false + default: '.github/codeql-config-pr.yml' + path-mode: + description: 'How to interpret changed-paths: "files" extracts parent directories, "dirs" uses paths as-is' + required: false + default: 'files' + +outputs: + config-file: + description: 'Path to the generated CodeQL config file' + value: ${{ steps.generate.outputs.config-file }} + skip: + description: 'true if no paths were resolved and CodeQL should be skipped' + value: ${{ steps.generate.outputs.skip }} + +runs: + using: composite + steps: + - name: Generate CodeQL config + id: generate + shell: bash + run: | + CONFIG_FILE="${{ inputs.output-file }}" + PATH_MODE="${{ inputs.path-mode }}" + RAW_PATHS="${{ inputs.changed-paths }}" + + # Normalize separators (comma or newline) to newline + PATHS=$(echo "$RAW_PATHS" | tr ',' '\n' | sed '/^[[:space:]]*$/d' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + if [ -z "$PATHS" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "No paths provided — skipping CodeQL config generation" + exit 0 + fi + + # Extract parent directories if path-mode is "files" + if [ "$PATH_MODE" = "files" ]; then + PATHS=$(echo "$PATHS" | while IFS= read -r file; do + dirname "$file" + done | sort -u) + fi + + # Remove duplicates and empty lines + PATHS=$(echo "$PATHS" | sort -u | sed '/^[[:space:]]*$/d') + + if [ -z "$PATHS" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "No paths resolved — skipping CodeQL config generation" + exit 0 + fi + + # Generate config + { + echo "paths:" + echo "$PATHS" | while IFS= read -r p; do + echo " - '$p'" + done + } > "$CONFIG_FILE" + + echo "skip=false" >> "$GITHUB_OUTPUT" + echo "config-file=$CONFIG_FILE" >> "$GITHUB_OUTPUT" + + count=$(echo "$PATHS" | wc -l | tr -d ' ') + echo "::group::CodeQL config — $count path(s)" + cat "$CONFIG_FILE" + echo "::endgroup::" From 5089c6b112e1ecf1067e71b07127a85b98a24415 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 11:09:22 -0300 Subject: [PATCH 08/14] fix(security): address code injection risks and improve robustness Move ${{ }} interpolations to env: blocks in pr-security-scan.yml and codeql-config composite to prevent shell injection. Fix path-mode detection in pr-security-scan to correctly switch between dirs/files mode based on actual fallback behavior. Remove overly broad permissions from self-pr-validation.yml workflow level. Add SARIF availability detection in codeql-reporter to avoid false negatives. --- .github/workflows/pr-security-scan.yml | 13 +++++++++---- .github/workflows/self-pr-validation.yml | 3 --- src/security/codeql-config/action.yml | 10 +++++----- src/security/codeql-reporter/action.yml | 12 +++++++++++- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pr-security-scan.yml b/.github/workflows/pr-security-scan.yml index 903ddde1..08f76a48 100644 --- a/.github/workflows/pr-security-scan.yml +++ b/.github/workflows/pr-security-scan.yml @@ -240,25 +240,30 @@ jobs: - name: Extract changed directories from matrix id: extract-dirs shell: bash + env: + MATRIX: ${{ needs.prepare_matrix.outputs.matrix }} + BASE_REF: ${{ github.event.pull_request.base.ref || 'develop' }} run: | - MATRIX='${{ needs.prepare_matrix.outputs.matrix }}' + PATH_MODE=dirs if [ "$MATRIX" != "[]" ] && [ -n "$MATRIX" ]; then - DIRS=$(echo "$MATRIX" | jq -r '.[].working_dir' 2>/dev/null | tr '\n' ',' | sed 's/,$//') + DIRS=$(printf '%s' "$MATRIX" | jq -r '.[].working_dir' 2>/dev/null | tr '\n' ',' | sed 's/,$//') fi # Fallback for single-app repos (working_dir is ".") if [ -z "$DIRS" ] || [ "$DIRS" = "." ]; then - DIRS=$(git diff --name-only --diff-filter=ACMR origin/${{ github.event.pull_request.base.ref || 'develop' }}...HEAD | head -200 | tr '\n' ',' | sed 's/,$//') + PATH_MODE=files + DIRS=$(git diff --name-only --diff-filter=ACMR "origin/$BASE_REF"...HEAD | head -200 | tr '\n' ',' | sed 's/,$//') fi echo "paths=$DIRS" >> "$GITHUB_OUTPUT" + echo "path-mode=$PATH_MODE" >> "$GITHUB_OUTPUT" - name: Generate CodeQL config for changed paths id: codeql-config uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@v1.18.0 with: changed-paths: ${{ steps.extract-dirs.outputs.paths }} - path-mode: ${{ needs.prepare_matrix.outputs.matrix != '[]' && 'dirs' || 'files' }} + path-mode: ${{ steps.extract-dirs.outputs.path-mode }} - name: Initialize CodeQL if: steps.codeql-config.outputs.skip != 'true' diff --git a/.github/workflows/self-pr-validation.yml b/.github/workflows/self-pr-validation.yml index 7ce43123..f7ff2e48 100644 --- a/.github/workflows/self-pr-validation.yml +++ b/.github/workflows/self-pr-validation.yml @@ -14,9 +14,6 @@ on: permissions: contents: read checks: read - security-events: write - pull-requests: write - actions: read jobs: # ----------------- PR Validation ----------------- diff --git a/src/security/codeql-config/action.yml b/src/security/codeql-config/action.yml index 39756e87..53049278 100644 --- a/src/security/codeql-config/action.yml +++ b/src/security/codeql-config/action.yml @@ -28,13 +28,13 @@ runs: - name: Generate CodeQL config id: generate shell: bash + env: + CONFIG_FILE: ${{ inputs.output-file }} + PATH_MODE: ${{ inputs.path-mode }} + RAW_PATHS: ${{ inputs.changed-paths }} run: | - CONFIG_FILE="${{ inputs.output-file }}" - PATH_MODE="${{ inputs.path-mode }}" - RAW_PATHS="${{ inputs.changed-paths }}" - # Normalize separators (comma or newline) to newline - PATHS=$(echo "$RAW_PATHS" | tr ',' '\n' | sed '/^[[:space:]]*$/d' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + PATHS=$(printf '%s' "$RAW_PATHS" | tr ',' '\n' | sed '/^[[:space:]]*$/d' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if [ -z "$PATHS" ]; then echo "skip=true" >> "$GITHUB_OUTPUT" diff --git a/src/security/codeql-reporter/action.yml b/src/security/codeql-reporter/action.yml index bf1a6c6e..b5e6b933 100644 --- a/src/security/codeql-reporter/action.yml +++ b/src/security/codeql-reporter/action.yml @@ -47,6 +47,7 @@ runs: let body = ''; let hasFindings = false; let findingsCount = 0; + let sarifAvailable = true; // ── Helpers ── const SEVERITY_ICONS = { @@ -76,12 +77,19 @@ runs: const findings = []; if (!fs.existsSync(sarifDir)) { + sarifAvailable = false; core.warning(`SARIF directory not found: ${sarifDir}`); return findings; } const files = fs.readdirSync(sarifDir).filter(f => f.endsWith('.sarif')); + if (files.length === 0) { + sarifAvailable = false; + core.warning('No SARIF files found in output directory'); + return findings; + } + for (const file of files) { try { const sarif = JSON.parse(fs.readFileSync(path.join(sarifDir, file), 'utf8')); @@ -122,7 +130,9 @@ runs: body += `## \u{1F6E1}\uFE0F CodeQL Analysis Results\n\n`; body += `**Languages analyzed:** \`${languages}\`\n\n`; - if (findings.length === 0) { + if (!sarifAvailable) { + body += `\u26A0\uFE0F CodeQL did not produce readable SARIF output. Check the analysis step logs.\n\n`; + } else if (findings.length === 0) { body += `\u2705 No security issues found.\n\n`; } else { hasFindings = true; From 816be6bfafbf782277397688eb98aa9ac17d995a Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 11:43:53 -0300 Subject: [PATCH 09/14] fix(codeql-reporter): mark SARIF unavailable on parse errors and move outputs to env Set sarifAvailable = false when SARIF parsing fails to prevent false negatives. Move step output interpolations to env: blocks for consistency with the injection prevention pattern. --- src/security/codeql-reporter/action.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/security/codeql-reporter/action.yml b/src/security/codeql-reporter/action.yml index b5e6b933..52b5d8ac 100644 --- a/src/security/codeql-reporter/action.yml +++ b/src/security/codeql-reporter/action.yml @@ -116,6 +116,7 @@ runs: } } } catch (e) { + sarifAvailable = false; core.warning(`Failed to parse SARIF file ${file}: ${e.message}`); } } @@ -208,8 +209,9 @@ runs: - name: Parse outputs id: parse-outputs shell: bash + env: + RESULT: ${{ steps.report.outputs.result }} run: | - RESULT='${{ steps.report.outputs.result }}' HAS_FINDINGS=$(echo "$RESULT" | jq -r '.has_findings // false') FINDINGS_COUNT=$(echo "$RESULT" | jq -r '.findings_count // 0') echo "has_findings=$HAS_FINDINGS" >> "$GITHUB_OUTPUT" @@ -220,8 +222,11 @@ runs: - name: Gate - Fail on CodeQL Findings if: inputs.fail-on-findings == 'true' shell: bash + env: + HAS_FINDINGS: ${{ steps.parse-outputs.outputs.has_findings }} + FINDINGS_COUNT: ${{ steps.parse-outputs.outputs.findings_count }} run: | - if [ "${{ steps.parse-outputs.outputs.has_findings }}" = "true" ]; then - echo "::error::CodeQL found ${{ steps.parse-outputs.outputs.findings_count }} security issue(s). Check the PR comment for details." + if [ "$HAS_FINDINGS" = "true" ]; then + echo "::error::CodeQL found $FINDINGS_COUNT security issue(s). Check the PR comment for details." exit 1 fi From 79d40ae20706f21676de73bde4456791f2ab60da Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 11:56:27 -0300 Subject: [PATCH 10/14] fix(codeql-reporter): separate parse errors from SARIF unavailability Track parse errors independently so a corrupt SARIF file does not suppress findings from other valid SARIF files. Only show the unavailable warning when no SARIF directory or files exist at all. --- src/security/codeql-reporter/action.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/security/codeql-reporter/action.yml b/src/security/codeql-reporter/action.yml index 52b5d8ac..c8d99e80 100644 --- a/src/security/codeql-reporter/action.yml +++ b/src/security/codeql-reporter/action.yml @@ -48,6 +48,7 @@ runs: let hasFindings = false; let findingsCount = 0; let sarifAvailable = true; + let hadParseErrors = false; // ── Helpers ── const SEVERITY_ICONS = { @@ -116,7 +117,7 @@ runs: } } } catch (e) { - sarifAvailable = false; + hadParseErrors = true; core.warning(`Failed to parse SARIF file ${file}: ${e.message}`); } } @@ -133,6 +134,8 @@ runs: if (!sarifAvailable) { body += `\u26A0\uFE0F CodeQL did not produce readable SARIF output. Check the analysis step logs.\n\n`; + } else if (hadParseErrors && findings.length === 0) { + body += `\u26A0\uFE0F Some SARIF files could not be parsed and no findings were recovered. Check the analysis step logs.\n\n`; } else if (findings.length === 0) { body += `\u2705 No security issues found.\n\n`; } else { From 1dee19714f44269c91b0e81c033ba708df1e4b23 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 12:00:59 -0300 Subject: [PATCH 11/14] revert(pr-security-scan): remove CodeQL changes from reusable workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove CodeQL integration from pr-security-scan.yml — will be added in a separate PR with proper planning for external consumers. --- .github/workflows/pr-security-scan.yml | 88 ++------------------------ 1 file changed, 4 insertions(+), 84 deletions(-) diff --git a/.github/workflows/pr-security-scan.yml b/.github/workflows/pr-security-scan.yml index 08f76a48..ca8a86ce 100644 --- a/.github/workflows/pr-security-scan.yml +++ b/.github/workflows/pr-security-scan.yml @@ -67,25 +67,12 @@ on: description: 'Enable Docker Hub Health Score compliance checks (non-root user, CVEs, licenses)' type: boolean default: true - enable_codeql: - description: 'Enable CodeQL static analysis. Runs as a separate job in parallel with other security scans' - type: boolean - default: false - codeql_languages: - description: 'Comma-separated CodeQL languages to analyze (e.g., go, javascript-typescript, actions). Required when enable_codeql is true' - type: string - default: '' - codeql_queries: - description: 'CodeQL query suite to use (security-standard, security-extended, security-and-quality)' - type: string - default: 'security-extended' permissions: id-token: write # Required for OIDC authentication contents: read # Required to checkout the repository pull-requests: write # Allows commenting on PRs security-events: write # Required for security scanning - actions: read # Required for CodeQL on private repositories jobs: prepare_matrix: @@ -220,84 +207,17 @@ jobs: # with: # sarif_file: 'trivy-vulnerability-scan-docker-${{ env.APP_NAME }}.sarif' - # ----------------- CodeQL Analysis ----------------- - codeql_analysis: - name: CodeQL Analysis - needs: prepare_matrix - if: inputs.enable_codeql && inputs.codeql_languages != '' - runs-on: ${{ inputs.runner_type }} - permissions: - contents: read - security-events: write - pull-requests: write - actions: read - steps: - - name: Checkout Repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Extract changed directories from matrix - id: extract-dirs - shell: bash - env: - MATRIX: ${{ needs.prepare_matrix.outputs.matrix }} - BASE_REF: ${{ github.event.pull_request.base.ref || 'develop' }} - run: | - PATH_MODE=dirs - if [ "$MATRIX" != "[]" ] && [ -n "$MATRIX" ]; then - DIRS=$(printf '%s' "$MATRIX" | jq -r '.[].working_dir' 2>/dev/null | tr '\n' ',' | sed 's/,$//') - fi - - # Fallback for single-app repos (working_dir is ".") - if [ -z "$DIRS" ] || [ "$DIRS" = "." ]; then - PATH_MODE=files - DIRS=$(git diff --name-only --diff-filter=ACMR "origin/$BASE_REF"...HEAD | head -200 | tr '\n' ',' | sed 's/,$//') - fi - - echo "paths=$DIRS" >> "$GITHUB_OUTPUT" - echo "path-mode=$PATH_MODE" >> "$GITHUB_OUTPUT" - - - name: Generate CodeQL config for changed paths - id: codeql-config - uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@v1.18.0 - with: - changed-paths: ${{ steps.extract-dirs.outputs.paths }} - path-mode: ${{ steps.extract-dirs.outputs.path-mode }} - - - name: Initialize CodeQL - if: steps.codeql-config.outputs.skip != 'true' - uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-init@v1.18.0 - with: - languages: ${{ inputs.codeql_languages }} - queries: ${{ inputs.codeql_queries }} - config-file: ${{ steps.codeql-config.outputs.config-file }} - - - name: Perform CodeQL Analysis - if: steps.codeql-config.outputs.skip != 'true' - uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-analyze@v1.18.0 - with: - category: '/language:${{ inputs.codeql_languages }}' - - - 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@v1.18.0 - with: - github-token: ${{ secrets.MANAGE_TOKEN || secrets.GITHUB_TOKEN }} - languages: ${{ inputs.codeql_languages }} - fail-on-findings: 'false' - # ----------------- Slack Notification ----------------- notify: name: Notify - needs: [prepare_matrix, security_scan, codeql_analysis] - if: always() && (needs.prepare_matrix.outputs.matrix != '[]' || needs.codeql_analysis.result != 'skipped') + needs: [prepare_matrix, security_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 with: webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - status: ${{ (needs.security_scan.result == 'failure' || needs.codeql_analysis.result == 'failure') && 'failure' || 'success' }} + status: ${{ needs.security_scan.result }} workflow-name: "PR Security Scan" - failed-jobs: ${{ needs.security_scan.result == 'failure' && 'Security Scan' || '' }}${{ needs.codeql_analysis.result == 'failure' && ', CodeQL Analysis' || '' }} + failed-jobs: ${{ needs.security_scan.result == 'failure' && 'Security Scan' || '' }} From eab2c6eb8c08223e2f982fb231b37f3543149180 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 12:06:37 -0300 Subject: [PATCH 12/14] fix(codeql-config): use exact file paths instead of parent directories The dirname extraction was causing CodeQL to scan entire directories (e.g., all of .github/workflows/) instead of only the changed files. Now uses exact file paths in the config, ensuring CodeQL only analyzes the specific files that were modified in the PR. --- src/security/codeql-config/action.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/security/codeql-config/action.yml b/src/security/codeql-config/action.yml index 53049278..59a7b4d1 100644 --- a/src/security/codeql-config/action.yml +++ b/src/security/codeql-config/action.yml @@ -10,7 +10,7 @@ inputs: required: false default: '.github/codeql-config-pr.yml' path-mode: - description: 'How to interpret changed-paths: "files" extracts parent directories, "dirs" uses paths as-is' + description: 'How to interpret changed-paths: "files" uses exact file paths, "dirs" uses paths as directories' required: false default: 'files' @@ -42,13 +42,6 @@ runs: exit 0 fi - # Extract parent directories if path-mode is "files" - if [ "$PATH_MODE" = "files" ]; then - PATHS=$(echo "$PATHS" | while IFS= read -r file; do - dirname "$file" - done | sort -u) - fi - # Remove duplicates and empty lines PATHS=$(echo "$PATHS" | sort -u | sed '/^[[:space:]]*$/d') From 9a886004d0180de5eb6d85e3a0e52f663f5bb9f0 Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 12:18:26 -0300 Subject: [PATCH 13/14] fix(codeql-reporter): add PR filter to security tab link Pre-filter the Security tab URL with pr:+is:open so the link opens directly to the findings for the current PR. --- src/security/codeql-reporter/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/security/codeql-reporter/action.yml b/src/security/codeql-reporter/action.yml index c8d99e80..89bd2215 100644 --- a/src/security/codeql-reporter/action.yml +++ b/src/security/codeql-reporter/action.yml @@ -175,7 +175,7 @@ runs: // ── Useful Links ── const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - const securityUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/security/code-scanning`; + const securityUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/security/code-scanning?query=pr%3A${context.issue.number}+is%3Aopen`; body += `---\n\n`; body += `\u{1F50D} [View full scan logs](${runUrl}) | \u{1F6E1}\uFE0F [Security tab](${securityUrl})\n\n`; From 9cdf3c05608d6d71905988a894c7a4412b5ab40b Mon Sep 17 00:00:00 2001 From: Lucas Bedatty Date: Tue, 24 Mar 2026 14:04:17 -0300 Subject: [PATCH 14/14] fix(security): warn on partial parse errors and remove dead path-mode input Add warning in codeql-reporter when findings exist but some SARIF files failed to parse. Remove unused path-mode input and PATH_MODE env var from codeql-config (became dead code after dirname removal). --- src/security/codeql-config/README.md | 14 +------------- src/security/codeql-config/action.yml | 6 ------ src/security/codeql-reporter/action.yml | 4 ++++ 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/security/codeql-config/README.md b/src/security/codeql-config/README.md index ff7c21e9..78f11440 100644 --- a/src/security/codeql-config/README.md +++ b/src/security/codeql-config/README.md @@ -11,9 +11,8 @@ Composite action that generates a dynamic CodeQL configuration file scoped to ch | Input | Description | Required | Default | |---|---|:---:|---| -| `changed-paths` | Comma or newline-separated list of changed file paths or directories | Yes | — | +| `changed-paths` | Comma or newline-separated list of changed file paths | Yes | — | | `output-file` | Path where the generated config file will be written | No | `.github/codeql-config-pr.yml` | -| `path-mode` | How to interpret paths: `files` extracts parent directories, `dirs` uses paths as-is | No | `files` | ## Outputs @@ -41,17 +40,6 @@ Composite action that generates a dynamic CodeQL configuration file scoped to ch config-file: ${{ steps.codeql-config.outputs.config-file }} ``` -### With monorepo matrix directories - -```yaml -- name: Generate CodeQL config - id: codeql-config - uses: LerianStudio/github-actions-shared-workflows/src/security/codeql-config@v1.x.x - with: - changed-paths: ${{ steps.extract-dirs.outputs.dirs }} - path-mode: dirs -``` - ## Permissions required No special permissions needed — this composite only generates a file. diff --git a/src/security/codeql-config/action.yml b/src/security/codeql-config/action.yml index 59a7b4d1..9d0f0e1e 100644 --- a/src/security/codeql-config/action.yml +++ b/src/security/codeql-config/action.yml @@ -9,11 +9,6 @@ inputs: description: 'Path where the generated CodeQL config file will be written' required: false default: '.github/codeql-config-pr.yml' - path-mode: - description: 'How to interpret changed-paths: "files" uses exact file paths, "dirs" uses paths as directories' - required: false - default: 'files' - outputs: config-file: description: 'Path to the generated CodeQL config file' @@ -30,7 +25,6 @@ runs: shell: bash env: CONFIG_FILE: ${{ inputs.output-file }} - PATH_MODE: ${{ inputs.path-mode }} RAW_PATHS: ${{ inputs.changed-paths }} run: | # Normalize separators (comma or newline) to newline diff --git a/src/security/codeql-reporter/action.yml b/src/security/codeql-reporter/action.yml index 89bd2215..398041db 100644 --- a/src/security/codeql-reporter/action.yml +++ b/src/security/codeql-reporter/action.yml @@ -141,6 +141,10 @@ runs: } else { hasFindings = true; + if (hadParseErrors) { + body += `\u26A0\uFE0F Some SARIF files could not be parsed. Findings below may be incomplete.\n\n`; + } + findings.sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 3) - (SEVERITY_ORDER[b.severity] ?? 3)); // Summary counts