Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ on:
required: false
default: true
description: 'Publish JUnit results as a check + upload test/coverage artifacts.'
coverage:
type: boolean
required: false
default: true
description: 'Post a coverage comment on the pull request from JaCoCo-format XML reports (works with JaCoCo and Kover). No-op when no reports match, so it is safe on by default; set false to disable entirely.'
coverage-xml:
type: string
required: false
default: '**/build/reports/jacoco/test/jacocoTestReport.xml'
description: 'Newline- or comma-separated glob(s) for JaCoCo-format coverage XML reports.'
submodules:
type: string
required: false
Expand Down Expand Up @@ -75,6 +85,127 @@ jobs:
check-name: Test Results (${{ inputs.image-name }})
artifact-name: test-reports-${{ inputs.image-name }}

- name: Generate coverage summary
id: coverage
if: >-
inputs.coverage
&& github.event_name == 'pull_request'
&& github.event.pull_request.head.repo.full_name == github.repository
&& steps.changes.outputs.source_code == 'true'
shell: bash
env:
COVERAGE_XML: ${{ inputs.coverage-xml }}
COVERAGE_TITLE: Coverage
COVERAGE_MARKER: '<!-- jvm-coverage-summary -->'
run: |
python3 - <<'PY'
import glob
import os
import xml.etree.ElementTree as ET

patterns = [
p.strip()
for p in os.environ["COVERAGE_XML"].replace(",", "\n").splitlines()
if p.strip()
]
files = sorted({f for pat in patterns for f in glob.glob(pat, recursive=True)})

if not files:
print("::notice::No coverage XML matched; skipping the coverage comment.")
raise SystemExit(0)

title = os.environ["COVERAGE_TITLE"]
marker = os.environ["COVERAGE_MARKER"]

def percentage(covered, missed):
total = covered + missed
return "n/a" if total == 0 else f"{covered / total * 100:.2f}%"

def counters(root):
return {
c.attrib["type"]: (int(c.attrib["covered"]), int(c.attrib["missed"]))
for c in root.findall("counter")
}

rows = []
totals = {}
for path in files:
root = ET.parse(path).getroot()
name = root.attrib.get("name") or path
module = counters(root)
line = module.get("LINE", (0, 0))
Comment on lines +133 to +136

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 | ⚡ Quick win

Handle malformed coverage XML without failing the whole workflow.

A single malformed/misdetected XML causes ET.parse(path) to raise and fail the job. Since this feature is enabled by default and intended to be no-op-safe, this should degrade gracefully (warn + skip bad file), not hard-fail CI.

Suggested patch
           rows = []
           totals = {}
+          parsed_any = False
           for path in files:
-              root = ET.parse(path).getroot()
+              try:
+                  root = ET.parse(path).getroot()
+              except ET.ParseError:
+                  print(f"::warning file={path}::Invalid coverage XML; skipping.")
+                  continue
               name = root.attrib.get("name") or path
               module = counters(root)
               line = module.get("LINE", (0, 0))
               branch = module.get("BRANCH", (0, 0))
               rows.append(f"| {name} | {percentage(*line)} | {percentage(*branch)} |")
+              parsed_any = True
               for counter_type, (covered, missed) in module.items():
                   acc_covered, acc_missed = totals.get(counter_type, (0, 0))
                   totals[counter_type] = (acc_covered + covered, acc_missed + missed)
+
+          if not parsed_any:
+              print("::notice::No valid coverage XML parsed; skipping the coverage comment.")
+              raise SystemExit(0)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 133 - 136, The ET.parse(path) call in
the coverage XML parsing section lacks error handling and will raise an
exception if the XML is malformed, causing the entire workflow to fail. Wrap the
ET.parse(path) statement and subsequent operations (including the attrib.get,
counters call, and line retrieval) in a try-except block that catches XML
parsing errors. When an exception occurs, log a warning message that includes
the file path and the actual error details, then skip processing that file by
continuing to the next iteration. This allows the workflow to gracefully degrade
by warning about bad files while continuing to process valid coverage files.

branch = module.get("BRANCH", (0, 0))
rows.append(f"| {name} | {percentage(*line)} | {percentage(*branch)} |")
for counter_type, (covered, missed) in module.items():
acc_covered, acc_missed = totals.get(counter_type, (0, 0))
totals[counter_type] = (acc_covered + covered, acc_missed + missed)

total_line = totals.get("LINE", (0, 0))
total_branch = totals.get("BRANCH", (0, 0))
summary = "\n".join(
[
marker,
f"## {title}",
"",
f"**Total** — Lines {percentage(*total_line)} · "
f"Branches {percentage(*total_branch)}",
"",
"| Module | Lines | Branches |",
"| --- | ---: | ---: |",
*rows,
"",
]
)
with open("jvm-coverage-summary.md", "w", encoding="utf-8") as handle:
handle.write(summary)
PY

if [[ -f jvm-coverage-summary.md ]]; then
echo "has_reports=true" >> "$GITHUB_OUTPUT"
else
echo "has_reports=false" >> "$GITHUB_OUTPUT"
fi

- name: Comment coverage on the pull request
if: ${{ steps.coverage.outputs.has_reports == 'true' }}
uses: actions/github-script@v9

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:

#!/bin/bash
# Verify unpinned action refs in workflow files
rg -nP '^\s*uses:\s*[^@]+@[A-Za-z0-9._-]+$' .github/workflows

Repository: yonatankarp/github-actions

Length of output: 2086


🏁 Script executed:

sed -n '165,180p' .github/workflows/ci.yml

Repository: yonatankarp/github-actions

Length of output: 674


🏁 Script executed:

rg 'actions/github-script' .github/workflows/ci.yml -n

Repository: yonatankarp/github-actions

Length of output: 113


🌐 Web query:

GitHub Actions pinning commit SHA security best practice actions/github-script v9

💡 Result:

Pinning GitHub Actions to a full-length commit SHA is a security best practice that protects against supply chain attacks, such as malicious code being injected into an existing tag or branch [1][2][3]. Because tags and branches are mutable references that can be moved or force-pushed, a compromised action repository could silently inject malicious changes into your workflows [4][3]. A full commit SHA is immutable, ensuring that your workflow runs exactly the code you intend [5][2][6]. For actions/github-script v9, which was released in April 2026 [7][8], the recommended security approach is to pin to its specific full commit SHA [5][2]. The current SHA for version 9.0.0 is 3a2844b7e9c422d3c10d287c895573f7108da1b3 [7][8]. When pinning to a SHA, it is a standard practice to include the version tag as a comment for readability and to facilitate automated updates by tools like Dependabot or Renovate [2][9][3]: - uses: actions/github-script@3a2844b # v9.0.0 Key considerations for this practice include: 1. Enforcement: Many organizations now enforce SHA pinning via policy to prevent the use of mutable tags [10][5][2]. If your organization enables this, workflows using tags will fail [5][2]. 2. Maintenance: Using Dependabot or Renovate with appropriate presets (e.g., helpers:pinGitHubActionDigests) allows you to automate the process of updating these pinned SHAs, balancing security with ease of maintenance [2][4][11]. 3. Dependency Trees: Security policies that enforce SHA pinning may check the entire dependency tree of your actions, including internal sub-actions referenced by the action you are using [5]. 4. V9 Specifics: Note that actions/github-script v9 introduced breaking changes, including an upgrade to @actions/github v9 (which is ESM-only) and the requirement to use an injected getOctokit function instead of requiring @actions/github [12][7][13]. When updating to v9, ensure your scripts are compatible with these changes [12][13].

Citations:


🌐 Web query:

actions/github-script v9 commit SHA release

💡 Result:

The actions/github-script v9.0.0 release is associated with the commit SHA 3a2844b7e9c422d3c10d287c895573f7108da1b3 [1][2]. This release, published on April 9, 2026, upgraded the action to use @actions/github v9 [3][1]. Notable changes in v9 include: - The addition of a getOctokit factory function available directly in the script context [1][4]. - An upgrade to an ESM-only version of @actions/github, which means require('@actions/github') no longer works in scripts [1][5]. - The requirement to use the injected getOctokit function instead of importing it from @actions/github [1][6]. - The automatic addition of an orchestration ID to the user-agent string for request tracing [4][5].

Citations:


Pin actions/github-script to a full commit SHA.

Using @v9 is mutable and weakens supply-chain guarantees. Pin to the exact commit SHA 3a2844b7e9c422d3c10d287c895573f7108da1b3 (with # v9.0.0 comment for readability) to protect against tag manipulation and supply-chain attacks.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 171-171: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 171, The actions/github-script action is
using a mutable version tag (`@v9`) which weakens supply-chain security. Replace
the version tag `@v9` with the full commit SHA
3a2844b7e9c422d3c10d287c895573f7108da1b3 in the uses field, and add a comment on
the same line (# v9.0.0) for readability to indicate which version this commit
corresponds to.

Source: Linters/SAST tools

env:
MARKER: '<!-- jvm-coverage-summary -->'
with:
script: |
const fs = require('fs');
const marker = process.env.MARKER;
const body = fs.readFileSync('jvm-coverage-summary.md', 'utf8');
const { owner, repo } = context.repo;
const issue_number = context.issue.number;

const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find((comment) =>
['github-actions', 'github-actions[bot]'].includes(comment.user?.login) &&
comment.body?.includes(marker)
);

if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});
}

- if: inputs.dockerfile-path != '' && steps.changes.outputs.source_code == 'true'
uses: yonatankarp/github-actions/.github/actions/build-docker-image@v2
with:
Expand Down
Loading