Skip to content

BUILD-11310 Add report-ci-insights action (per-PR CI metrics comment)#298

Draft
mikolaj-matuszny-ext-sonarsource wants to merge 7 commits into
masterfrom
BUILD-11310-report-ci-insights
Draft

BUILD-11310 Add report-ci-insights action (per-PR CI metrics comment)#298
mikolaj-matuszny-ext-sonarsource wants to merge 7 commits into
masterfrom
BUILD-11310-report-ci-insights

Conversation

@mikolaj-matuszny-ext-sonarsource

@mikolaj-matuszny-ext-sonarsource mikolaj-matuszny-ext-sonarsource commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What

New report-ci-insights composite action (M4.3). A repository adds it as one dedicated job; it aggregates per-job CI metrics across the workflow run and posts a single sticky PR comment with a compact headline over folded detail.

Ticket: BUILD-11310 · Epic: BUILD-11068

How it gets the data (the interesting part)

Both "obvious" transports were empirically ruled out: the hook can't upload an artifact (no runtime token in the hook env — BUILD-11309), and job summaries aren't readable cross-job via any token API. The working transport: the report job (needs: [all]) downloads each completed sibling's job log via GET /repos/{r}/actions/jobs/{id}/logs (GITHUB_TOKEN + actions: read) and recovers the metrics JSON the runner hook prints to stdout, wrapped in sentinels (producer PRs: infra#422, this-repo#297). Proven end-to-end in a spike.

Usage

jobs:
  build: ...
  test: ...
  report-ci-insights:
    needs: [build, test]
    if: always() && github.event_name == 'pull_request'
    runs-on: sonar-xs
    permissions:
      actions: read           # download sibling job logs
      pull-requests: write    # post/update the sticky comment
    steps:
      - uses: SonarSource/ci-github-actions/report-ci-insights@v1

Behaviour

  • Headline (always visible): run totals (CPU-s, worst peak memory + job, network, cache) + a flags line only when something is wrong (OOM kills / CPU throttling).
  • Folded detail: per-job table + cache table in <details>. Folding rules: columns with no data are dropped; the Flags column/line appears only when flags exist; the cache fold renders only when a job reported cache.
  • Sticky: one comment, matched by a hidden marker, updated idempotently on re-runs.
  • Fail-open: any error → ::warning:: + exit 0; never fails the consumer workflow. Skips the report job itself, non-completed jobs, log-download failures, jobs without metrics, and corrupt-JSON records.

Tests

32 shellspec examples (mocked gh) covering sentinel extraction (incl. decoy/hardening), all collect skip paths, folding by absence-assertion, both upsert branches, and main orchestration. Added report-ci-insights to the .shellspec kcov include-pattern.

Reviews

Built per-phase TDD with spec-compliance review each phase, plus a whole-action code-quality review whose two Important findings (validate recovered JSON; escape | in markdown cells) are fixed with regression tests.

Scope

ARC + WarpBuild Linux runners (where the metrics hook runs); PRs only.

@sonarqubecloud

Copy link
Copy Markdown

Agentic Analysis: Early Results

Agentic Analysis and Context Augmentation are available on your project. Here are some issues that could have been prevented. Follow the links to learn how to put them into action.

12 issue(s) found across 1 file(s):

Rule File Line Message
shelldre:S1192 spec/report-ci-insights_spec.sh 324 Define a constant instead of using the literal '%s\t%s\n' 13 times.
shelldre:S1192 spec/report-ci-insights_spec.sh 399 Define a constant instead of using the literal 'PATCHED 999' 5 times.
shelldre:S1192 spec/report-ci-insights_spec.sh 400 Define a constant instead of using the literal 'POSTED' 6 times.
shelldre:S7682 spec/report-ci-insights_spec.sh 417 Add an explicit return statement at the end of the function.
shelldre:S7682 spec/report-ci-insights_spec.sh 418 Add an explicit return statement at the end of the function.
shelldre:S7682 spec/report-ci-insights_spec.sh 419 Add an explicit return statement at the end of the function.
shelldre:S7682 spec/report-ci-insights_spec.sh 420 Add an explicit return statement at the end of the function.
shelldre:S7682 spec/report-ci-insights_spec.sh 421 Add an explicit return statement at the end of the function.
shelldre:S7679 spec/report-ci-insights_spec.sh 421 Assign this positional parameter to a local variable.
shelldre:S7682 spec/report-ci-insights_spec.sh 428 Add an explicit return statement at the end of the function.
shelldre:S7682 spec/report-ci-insights_spec.sh 439 Add an explicit return statement at the end of the function.
shelldre:S7682 spec/report-ci-insights_spec.sh 451 Add an explicit return statement at the end of the function.

Analyzed by SonarQube Agentic Analysis in 2.8 s

@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Jun 12, 2026

Copy link
Copy Markdown

BUILD-11310

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
9 New issues

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Comment thread report-ci-insights/lib.sh
Comment on lines +193 to +196
# Escape pipes in the (author-controlled) job name so it can't inject columns.
name=${name//|/\\|}
local row="| ${name} |"
if (( has_cpu )); then row="${row} $(_rci_cpu_cell "$json") |"; fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Security: Rendered metric fields escape only pipes, not newlines/HTML

render_table escapes | in the job name (line 196) and render_cache_fold escapes | in the cache key and backend (lines 254-255), but no other characters are sanitized. These values are produced by jq -r, which converts JSON `` escapes into literal newlines, and they are emitted directly into the markdown table rows of the sticky PR comment.

The metrics JSON is recovered from sibling job stdout (a runner hook the producing job prints), so a field value such as a cache key of "foo malicious row" or one containing HTML like </details> will (a) break the markdown table layout, and (b) inject attacker-influenced markdown/HTML into a comment posted with pull-requests: write. Pipe-escaping alone does not prevent this. Risk is limited because forked-PR GITHUB_TOKEN is read-only, but for same-repo PRs the report is posted with write permission.

Suggested fix: strip/escape newlines (and ideally other control chars) in the same place pipes are escaped, e.g. replace embedded newlines with a space or <br> before building the row. Apply to name, key, and backend.

Collapse embedded CR/LF in author-influenced cells so they cannot break the markdown table or inject extra lines into the comment.:

# in render_table, after the pipe escape:
name=${name//|/\|}
name=${name//$'
'/ }
name=${name//$'\r'/ }

# in render_cache_fold, after the pipe escapes:
key=${key//|/\|}; key=${key//$'
'/ }; key=${key//$'\r'/ }
backend=${backend//|/\|}; backend=${backend//$'
'/ }; backend=${backend//$'\r'/ }
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 0 resolved / 1 findings

Introduces a composite action to aggregate and report CI metrics in a sticky PR comment, including robust sentinel extraction and log-based retrieval. Consider expanding sanitization in the rendering logic to include newlines and HTML characters, as currently only pipe symbols are escaped.

💡 Security: Rendered metric fields escape only pipes, not newlines/HTML

📄 report-ci-insights/lib.sh:193-196 📄 report-ci-insights/lib.sh:247-256

render_table escapes | in the job name (line 196) and render_cache_fold escapes | in the cache key and backend (lines 254-255), but no other characters are sanitized. These values are produced by jq -r, which converts JSON `` escapes into literal newlines, and they are emitted directly into the markdown table rows of the sticky PR comment.

The metrics JSON is recovered from sibling job stdout (a runner hook the producing job prints), so a field value such as a cache key of "foo malicious row" or one containing HTML like </details> will (a) break the markdown table layout, and (b) inject attacker-influenced markdown/HTML into a comment posted with pull-requests: write. Pipe-escaping alone does not prevent this. Risk is limited because forked-PR GITHUB_TOKEN is read-only, but for same-repo PRs the report is posted with write permission.

Suggested fix: strip/escape newlines (and ideally other control chars) in the same place pipes are escaped, e.g. replace embedded newlines with a space or <br> before building the row. Apply to name, key, and backend.

Collapse embedded CR/LF in author-influenced cells so they cannot break the markdown table or inject extra lines into the comment.
# in render_table, after the pipe escape:
name=${name//|/\|}
name=${name//$'
'/ }
name=${name//$'\r'/ }

# in render_cache_fold, after the pipe escapes:
key=${key//|/\|}; key=${key//$'
'/ }; key=${key//$'\r'/ }
backend=${backend//|/\|}; backend=${backend//$'
'/ }; backend=${backend//$'\r'/ }
🤖 Prompt for agents
Code Review: Introduces a composite action to aggregate and report CI metrics in a sticky PR comment, including robust sentinel extraction and log-based retrieval. Consider expanding sanitization in the rendering logic to include newlines and HTML characters, as currently only pipe symbols are escaped.

1. 💡 Security: Rendered metric fields escape only pipes, not newlines/HTML
   Files: report-ci-insights/lib.sh:193-196, report-ci-insights/lib.sh:247-256

   render_table escapes `|` in the job `name` (line 196) and render_cache_fold escapes `|` in the cache `key` and `backend` (lines 254-255), but no other characters are sanitized. These values are produced by `jq -r`, which converts JSON `
   ` escapes into literal newlines, and they are emitted directly into the markdown table rows of the sticky PR comment.
   
   The metrics JSON is recovered from sibling job stdout (a runner hook the producing job prints), so a field value such as a cache `key` of `"foo
   malicious row"` or one containing HTML like `</details>` will (a) break the markdown table layout, and (b) inject attacker-influenced markdown/HTML into a comment posted with `pull-requests: write`. Pipe-escaping alone does not prevent this. Risk is limited because forked-PR `GITHUB_TOKEN` is read-only, but for same-repo PRs the report is posted with write permission.
   
   Suggested fix: strip/escape newlines (and ideally other control chars) in the same place pipes are escaped, e.g. replace embedded newlines with a space or `<br>` before building the row. Apply to `name`, `key`, and `backend`.

   Fix (Collapse embedded CR/LF in author-influenced cells so they cannot break the markdown table or inject extra lines into the comment.):
   # in render_table, after the pipe escape:
   name=${name//|/\|}
   name=${name//$'
   '/ }
   name=${name//$'\r'/ }
   
   # in render_cache_fold, after the pipe escapes:
   key=${key//|/\|}; key=${key//$'
   '/ }; key=${key//$'\r'/ }
   backend=${backend//|/\|}; backend=${backend//$'
   '/ }; backend=${backend//$'\r'/ }

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@mikolaj-matuszny-ext-sonarsource mikolaj-matuszny-ext-sonarsource marked this pull request as draft June 12, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant