Skip to content

feat(lighthouse): add reusable lighthouse audit workflow#89

Merged
tehw0lf merged 1 commit into
mainfrom
feature/lighthouse-scan
Jun 24, 2026
Merged

feat(lighthouse): add reusable lighthouse audit workflow#89
tehw0lf merged 1 commit into
mainfrom
feature/lighthouse-scan

Conversation

@tehw0lf

@tehw0lf tehw0lf commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds reusable lighthouse-scan.yml workflow callable from any repo
  • Runs Lighthouse audit (Performance, Accessibility, Best Practices, SEO)
  • Posts results as GitHub Step Summary with score table and links to full reports
  • Uploads artifacts via temporary public storage

Inputs

  • target_url (required): URL to audit
  • urls (optional): additional URLs as JSON array
  • runner (optional): defaults to ubuntu-latest

Test plan

  • Merge this PR first so the reusable workflow is available at @main
  • Then merge the tehwol.fi PR that calls this workflow
  • Trigger manually via workflow_dispatch to verify report output

Summary by CodeRabbit

  • New Features
    • Added a reusable Lighthouse scan workflow that can audit one required site URL and optional additional URLs.
    • The workflow now returns a scan result status and provides a formatted summary with key performance, accessibility, best practices, and SEO scores, plus links to full reports.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a reusable GitHub Actions workflow for Lighthouse scans. It accepts a required target URL, optional extra URLs, and runner selection, runs the Lighthouse CI action, exposes the audit outcome, and writes a Markdown summary with per-URL scores and report links.

Changes

Reusable Lighthouse scan workflow

Layer / File(s) Summary
Workflow contract and job setup
.github/workflows/lighthouse-scan.yml
Defines the reusable workflow inputs and output, then configures the Lighthouse job with runner selection, timeout, permissions, and output mapping.
URL assembly, scan, and summary
.github/workflows/lighthouse-scan.yml
Builds the URL list, runs the Lighthouse CI action with artifact upload enabled, and writes the Markdown report summary and link from the emitted manifest.

Code Review Effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A bunny hopped by the Lighthouse beam,
With JSON URLs in a neat little stream.
Scores in a table, bright and true,
Reports linked up in a shiny view,
Hooray for scans that sparkle and gleam! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a reusable Lighthouse audit workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/lighthouse-scan

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with 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.

Inline comments:
In @.github/workflows/lighthouse-scan.yml:
- Around line 58-68: The Lighthouse report summary step is expanding step
outputs directly inside the shell script, which can create quoting/injection
issues. Update the “format report summary” step to pass
steps.audit.outputs.manifest and steps.audit.outputs.links through env, then
reference those env vars inside the run block instead of inline interpolation.
Keep the existing jq processing and summary formatting intact while using the
step name “format report summary” and steps.audit as the location anchors.
- Around line 38-48: The “build urls list” step in lighthouse-scan.yml is
vulnerable because inputs.target_url and inputs.urls are expanded directly into
the shell script; move those values into env for the step and have the script
read the env vars instead. Update the URLs construction logic in the same step
to pass the values to jq using --arg/related safe argument handling so `build
urls list` treats them as data, not executable shell content.
- Around line 30-34: The lighthouse-scan workflow job has an unnecessary
permission scope: remove the unused issues: write grant and keep the job at
least-privilege with contents: read only. Update the permissions block in
lighthouse-scan.yml accordingly, since the job only reports via
$GITHUB_STEP_SUMMARY and does not use any issue-writing actions.
- Line 52: The Lighthouse CI workflow step currently uses the mutable
treosh/lighthouse-ci-action@v12 tag, so update the action reference in the
lighthouse scan job to the v12.6.2 commit SHA instead. Keep the same uses entry
for treosh/lighthouse-ci-action, but replace the version tag with the pinned
commit so the workflow is reproducible and immutable.
- Line 36: Update the checkout step in the lighthouse scan workflow to use the
pinned actions/checkout commit for v6.0.3 instead of the mutable v6 tag, and
disable credential persistence for this job by setting persist-credentials to
false. Make the change in the actions/checkout usage so the workflow is
reproducible and does not leave the token in git config.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 80e9d665-b9d2-4ef2-bc2d-96d4e199e1a1

📥 Commits

Reviewing files that changed from the base of the PR and between 4001c83 and 224aadc.

📒 Files selected for processing (1)
  • .github/workflows/lighthouse-scan.yml

Comment on lines +30 to +34
permissions:
contents: read
issues: write
outputs:
lighthouse_result: ${{ steps.audit.outcome }}

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 & Privacy | 🟠 Major | ⚡ Quick win

Drop the unused issues: write permission.

The job never creates or comments on issues (it only writes to $GITHUB_STEP_SUMMARY), so issues: write is an unnecessary privilege escalation. Reducing it to contents: read keeps the job at least-privilege.

🔒 Proposed fix
     permissions:
       contents: read
-      issues: write

As per coding guidelines: "Apply minimal permissions principle to all workflow jobs - use contents: read by default and only escalate permissions when needed".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
permissions:
contents: read
issues: write
outputs:
lighthouse_result: ${{ steps.audit.outcome }}
permissions:
contents: read
outputs:
lighthouse_result: ${{ steps.audit.outcome }}
🤖 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/lighthouse-scan.yml around lines 30 - 34, The
lighthouse-scan workflow job has an unnecessary permission scope: remove the
unused issues: write grant and keep the job at least-privilege with contents:
read only. Update the permissions block in lighthouse-scan.yml accordingly,
since the job only reports via $GITHUB_STEP_SUMMARY and does not use any
issue-writing actions.

Sources: Coding guidelines, Linters/SAST tools

outputs:
lighthouse_result: ${{ steps.audit.outcome }}
steps:
- uses: actions/checkout@v6

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 & Privacy | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What is the latest release tag and commit SHA for actions/checkout v6?

💡 Result:

The latest release tag for actions/checkout v6 is v6.0.3 [1][2][3]. The commit SHA associated with the v6.0.3 tag is df4cb1c069e1874edd31b4311f1884172cec0e10 [4][5].

Citations:


🏁 Script executed:

sed -n '1,160p' .github/workflows/lighthouse-scan.yml

Repository: tehw0lf/workflows

Length of output: 2518


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/lighthouse-scan.yml')
print(p.read_text())
PY

Repository: tehw0lf/workflows

Length of output: 2519


🏁 Script executed:

awk 'NR>=1 && NR<=200 {print NR ":" $0}' .github/workflows/lighthouse-scan.yml

Repository: tehw0lf/workflows

Length of output: 2713


Pin actions/checkout and disable credential persistence.

actions/checkout@v6 is a mutable tag; pin it to df4cb1c069e1874edd31b4311f1884172cec0e10 (v6.0.3). Since this job doesn’t push, set persist-credentials: false to keep the token out of .git/config.

🔒 Proposed fix
-      - uses: actions/checkout@v6
+      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 36-36: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 36-36: 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/lighthouse-scan.yml at line 36, Update the checkout step
in the lighthouse scan workflow to use the pinned actions/checkout commit for
v6.0.3 instead of the mutable v6 tag, and disable credential persistence for
this job by setting persist-credentials to false. Make the change in the
actions/checkout usage so the workflow is reproducible and does not leave the
token in git config.

Source: Linters/SAST tools

Comment on lines +38 to +48
- name: build urls list
id: urls
run: |
BASE_URL="${{ inputs.target_url }}"
EXTRA='${{ inputs.urls }}'
if [ -n "$EXTRA" ]; then
URLS=$(echo "$EXTRA" | jq -c ". + [\"$BASE_URL\"]")
else
URLS="[\"$BASE_URL\"]"
fi
echo "urls=$URLS" >> $GITHUB_OUTPUT

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 & Privacy | 🔴 Critical | ⚡ Quick win

Code injection: pass inputs via env, not template expansion.

${{ inputs.target_url }} and ${{ inputs.urls }} are expanded directly into the shell script before it runs, so a crafted URL (e.g. containing $(...) or backticks) executes arbitrary commands on the runner. Bind them to environment variables and feed them to jq with --arg so the values are treated as data.

🔒 Proposed fix
       - name: build urls list
         id: urls
+        env:
+          BASE_URL: ${{ inputs.target_url }}
+          EXTRA: ${{ inputs.urls }}
         run: |
-          BASE_URL="${{ inputs.target_url }}"
-          EXTRA='${{ inputs.urls }}'
           if [ -n "$EXTRA" ]; then
-            URLS=$(echo "$EXTRA" | jq -c ". + [\"$BASE_URL\"]")
+            URLS=$(echo "$EXTRA" | jq -c --arg base "$BASE_URL" '. + [$base]')
           else
-            URLS="[\"$BASE_URL\"]"
+            URLS=$(jq -cn --arg base "$BASE_URL" '[$base]')
           fi
           echo "urls=$URLS" >> $GITHUB_OUTPUT
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: build urls list
id: urls
run: |
BASE_URL="${{ inputs.target_url }}"
EXTRA='${{ inputs.urls }}'
if [ -n "$EXTRA" ]; then
URLS=$(echo "$EXTRA" | jq -c ". + [\"$BASE_URL\"]")
else
URLS="[\"$BASE_URL\"]"
fi
echo "urls=$URLS" >> $GITHUB_OUTPUT
- name: build urls list
id: urls
env:
BASE_URL: ${{ inputs.target_url }}
EXTRA: ${{ inputs.urls }}
run: |
if [ -n "$EXTRA" ]; then
URLS=$(echo "$EXTRA" | jq -c --arg base "$BASE_URL" '. + [$base]')
else
URLS=$(jq -cn --arg base "$BASE_URL" '[$base]')
fi
echo "urls=$URLS" >> $GITHUB_OUTPUT
🧰 Tools
🪛 zizmor (1.26.1)

[error] 41-41: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 42-42: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/lighthouse-scan.yml around lines 38 - 48, The “build urls
list” step in lighthouse-scan.yml is vulnerable because inputs.target_url and
inputs.urls are expanded directly into the shell script; move those values into
env for the step and have the script read the env vars instead. Update the URLs
construction logic in the same step to pass the values to jq using --arg/related
safe argument handling so `build urls list` treats them as data, not executable
shell content.

Source: Linters/SAST tools


- name: run lighthouse
id: audit
uses: treosh/lighthouse-ci-action@v12

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 & Privacy | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Latest release tag and commit SHA for treosh/lighthouse-ci-action v12

💡 Result:

Latest release tag for treosh/lighthouse-ci-action v12 (as of 2026-06-24): 12.6.2 [1][2]. Commit SHA for tag 12.6.2: 3e7e23f8e4a1e7f1c9a9c8b8f6c9d8a7b6c5d4e3 (short SHA shown as 3e7e23f) [3].

Citations:


Pin treosh/lighthouse-ci-action to a commit SHA.

@v12 is a mutable tag; pin this step to the v12.6.2 commit instead.

🔒 Proposed fix
-        uses: treosh/lighthouse-ci-action@v12
+        uses: treosh/lighthouse-ci-action@3e7e23f8e4a1e7f1c9a9c8b8f6c9d8a7b6c5d4e3 # v12.6.2
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uses: treosh/lighthouse-ci-action@v12
uses: treosh/lighthouse-ci-action@3e7e23f8e4a1e7f1c9a9c8b8f6c9d8a7b6c5d4e3 # v12.6.2
🧰 Tools
🪛 zizmor (1.26.1)

[error] 52-52: 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/lighthouse-scan.yml at line 52, The Lighthouse CI workflow
step currently uses the mutable treosh/lighthouse-ci-action@v12 tag, so update
the action reference in the lighthouse scan job to the v12.6.2 commit SHA
instead. Keep the same uses entry for treosh/lighthouse-ci-action, but replace
the version tag with the pinned commit so the workflow is reproducible and
immutable.

Source: Linters/SAST tools

Comment on lines +58 to +68
- name: format report summary
if: always() && steps.audit.outcome != 'skipped'
run: |
echo "### 🔦 Lighthouse Audit Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| URL | Performance | Accessibility | Best Practices | SEO |" >> $GITHUB_STEP_SUMMARY
echo "|-----|-------------|---------------|----------------|-----|" >> $GITHUB_STEP_SUMMARY
echo '${{ steps.audit.outputs.manifest }}' | jq -r '.[] | "| \(.url) | \(.summary.performance * 100 | round)% | \(.summary.accessibility * 100 | round)% | \(.summary["best-practices"] * 100 | round)% | \(.summary.seo * 100 | round)% |"' >> $GITHUB_STEP_SUMMARY || true
echo "" >> $GITHUB_STEP_SUMMARY
echo "Full reports: ${{ steps.audit.outputs.links }}" >> $GITHUB_STEP_SUMMARY
continue-on-error: true

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 & Privacy | 🟡 Minor | ⚡ Quick win

Move step outputs into env to avoid expansion into the script.

${{ steps.audit.outputs.manifest }} and ...links }} are expanded inline into the run script. While these originate from the action rather than the caller, binding them via env removes the injection surface flagged by static analysis and avoids quoting breakage if the values contain special characters.

🔒 Proposed fix
       - name: format report summary
         if: always() && steps.audit.outcome != 'skipped'
+        env:
+          MANIFEST: ${{ steps.audit.outputs.manifest }}
+          LINKS: ${{ steps.audit.outputs.links }}
         run: |
           echo "### 🔦 Lighthouse Audit Results" >> $GITHUB_STEP_SUMMARY
           echo "" >> $GITHUB_STEP_SUMMARY
           echo "| URL | Performance | Accessibility | Best Practices | SEO |" >> $GITHUB_STEP_SUMMARY
           echo "|-----|-------------|---------------|----------------|-----|" >> $GITHUB_STEP_SUMMARY
-          echo '${{ steps.audit.outputs.manifest }}' | jq -r '.[] | "| \(.url) | \(.summary.performance * 100 | round)% | \(.summary.accessibility * 100 | round)% | \(.summary["best-practices"] * 100 | round)% | \(.summary.seo * 100 | round)% |"' >> $GITHUB_STEP_SUMMARY || true
+          echo "$MANIFEST" | jq -r '.[] | "| \(.url) | \(.summary.performance * 100 | round)% | \(.summary.accessibility * 100 | round)% | \(.summary["best-practices"] * 100 | round)% | \(.summary.seo * 100 | round)% |"' >> $GITHUB_STEP_SUMMARY || true
           echo "" >> $GITHUB_STEP_SUMMARY
-          echo "Full reports: ${{ steps.audit.outputs.links }}" >> $GITHUB_STEP_SUMMARY
+          echo "Full reports: $LINKS" >> $GITHUB_STEP_SUMMARY
         continue-on-error: true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: format report summary
if: always() && steps.audit.outcome != 'skipped'
run: |
echo "### 🔦 Lighthouse Audit Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| URL | Performance | Accessibility | Best Practices | SEO |" >> $GITHUB_STEP_SUMMARY
echo "|-----|-------------|---------------|----------------|-----|" >> $GITHUB_STEP_SUMMARY
echo '${{ steps.audit.outputs.manifest }}' | jq -r '.[] | "| \(.url) | \(.summary.performance * 100 | round)% | \(.summary.accessibility * 100 | round)% | \(.summary["best-practices"] * 100 | round)% | \(.summary.seo * 100 | round)% |"' >> $GITHUB_STEP_SUMMARY || true
echo "" >> $GITHUB_STEP_SUMMARY
echo "Full reports: ${{ steps.audit.outputs.links }}" >> $GITHUB_STEP_SUMMARY
continue-on-error: true
- name: format report summary
if: always() && steps.audit.outcome != 'skipped'
env:
MANIFEST: ${{ steps.audit.outputs.manifest }}
LINKS: ${{ steps.audit.outputs.links }}
run: |
echo "### 🔦 Lighthouse Audit Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| URL | Performance | Accessibility | Best Practices | SEO |" >> $GITHUB_STEP_SUMMARY
echo "|-----|-------------|---------------|----------------|-----|" >> $GITHUB_STEP_SUMMARY
echo "$MANIFEST" | jq -r '.[] | "| \(.url) | \(.summary.performance * 100 | round)% | \(.summary.accessibility * 100 | round)% | \(.summary["best-practices"] * 100 | round)% | \(.summary.seo * 100 | round)%"' >> $GITHUB_STEP_SUMMARY || true
echo "" >> $GITHUB_STEP_SUMMARY
echo "Full reports: $LINKS" >> $GITHUB_STEP_SUMMARY
continue-on-error: true
🧰 Tools
🪛 zizmor (1.26.1)

[info] 65-65: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 67-67: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/lighthouse-scan.yml around lines 58 - 68, The Lighthouse
report summary step is expanding step outputs directly inside the shell script,
which can create quoting/injection issues. Update the “format report summary”
step to pass steps.audit.outputs.manifest and steps.audit.outputs.links through
env, then reference those env vars inside the run block instead of inline
interpolation. Keep the existing jq processing and summary formatting intact
while using the step name “format report summary” and steps.audit as the
location anchors.

Source: Linters/SAST tools

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