Skip to content

ci: add organization PR validation baseline - #8

Merged
Viktor Didkovskyi (viktor958) merged 3 commits into
mainfrom
viktor/hea-7249-org-actions-validation-baseline
Aug 12, 2026
Merged

ci: add organization PR validation baseline#8
Viktor Didkovskyi (viktor958) merged 3 commits into
mainfrom
viktor/hea-7249-org-actions-validation-baseline

Conversation

@viktor958

@viktor958 Viktor Didkovskyi (viktor958) commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

A reusable, read-only pull-request baseline that Healify repositories can adopt before it is enforced through an organization ruleset.

  • validate GitHub Actions YAML with checksum-pinned actionlint
  • scan the pull-request commit range with checksum-pinned gitleaks
  • reject merge gates that aggregate nothing and run nothing that can fail
  • annotate the workflow debt measured across the organization
  • pin checkout to an immutable SHA and disable persisted credentials

Two tiers, and why

A 2026-08-12 audit of all 216 workflow files across 26 repositories measured the current state:

Finding Count
External action references on mutable tags 561 / 657 (85%)
Workflows with no top-level permissions block 84
Jobs with no timeout-minutes 182 / 309
Steps interpolating a secret into a run script 10
Repositories running actionlint 1
Required CI Summary gates that run nothing 2

Failing on the first four would red-line nearly every repository the day this is adopted, and a baseline nobody can turn on protects nothing. So they annotate. A repository opts into enforcement with strict: true once it has cleaned up.

The blocking tier is limited to what the fleet already passes: syntax, committed secrets, and fake merge gates.

Gate detection

The previous revision matched the literal string echo "CI checks passed". That missed four trivial variants — a set -e line before the echo, any other phrase, exit 0, and true — and fired on a workflow whose gate was real but which mentioned the phrase in an unrelated job.

This revision parses the YAML and judges the gate job: a job is a fake gate when it neither aggregates other jobs through needs.<job>.result nor runs a command that can fail.

Verification

Run against the real corpus of all 216 workflow files, by extracting the audit script from this YAML so the tested code is the shipped code:

  • 2 blocking findings, both correct: the CI Summary gates in meditation-service and healify-org are literally run: echo "CI checks passed".
  • 0 false positives across the other 24 repositories, including the genuine aggregating gates in healify, healify-api, healify-agentcore, healify-web, dataroom, and testflight-automation.
  • All four previous bypasses and the false-positive case now resolve correctly (10/10 cases).
  • actionlint 1.7.12 clean on this file.
  • On this PR's own run: actionlint linted 2 files, gitleaks scanned a6a1f9e..adb558a (2 commits), and the advisory tier emitted 3 annotations against secret-scan-reusable.yml.

Rollout note

Adopting this in meditation-service and healify-org will fail until their CI Summary gates run real checks. That is the intended result, not a regression — with the enterprise ruleset making CI Summary the only required status, those two repositories currently have no effective gate on their default branch.

Not in this change

secret-scan-reusable.yml overlaps with the secret-scan step here and still floats on actions/checkout@v7. Retiring it or pointing it at this workflow is a rollout decision, kept out of this diff.

Linear: https://linear.app/lifecycle-innovations/issue/HEA-7249

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@viktor958, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a6077890-58f1-4994-bbb4-c6113bd5f83b

📥 Commits

Reviewing files that changed from the base of the PR and between adb558a and c9cb508.

📒 Files selected for processing (1)
  • .github/workflows/organization-pr-baseline.yml
📝 Walkthrough

Walkthrough

Added a reusable, read-only GitHub Actions workflow. It validates workflow files, audits permissions and security patterns, verifies pinned tool checksums, and scans pull-request or full commit history for secrets.

Changes

Organization PR baseline

Layer / File(s) Summary
Workflow contract and tool bootstrap
.github/workflows/organization-pr-baseline.yml
Adds pull-request and workflow_call triggers, configurable inputs, read-only permissions, concurrency cancellation, credential-free checkout, and checksum-pinned actionlint and gitleaks installation.
Workflow linting and structural audit
.github/workflows/organization-pr-baseline.yml
Discovers workflow files, runs actionlint, parses YAML with PyYAML, detects placeholder CI Summary gates, reports advisory findings, writes a step summary, and applies strict-mode failure rules.
Commit-range secret scanning
.github/workflows/organization-pr-baseline.yml
Runs gitleaks against the pull-request or push commit range and falls back to full-history scanning when the range is unavailable or invalid.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant Runner
  participant WorkflowAudit
  participant Gitleaks
  GitHubActions->>Runner: Start validation job
  Runner->>WorkflowAudit: Lint and audit workflow files
  WorkflowAudit-->>Runner: Return blocking and advisory findings
  Runner->>Gitleaks: Scan commit range or full history
  Gitleaks-->>Runner: Return secret findings
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding an organization-wide pull request validation baseline workflow.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch viktor/hea-7249-org-actions-validation-baseline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@viktor958
Viktor Didkovskyi (viktor958) marked this pull request as ready for review August 12, 2026 06:47

@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.

🧹 Nitpick comments (3)
.github/workflows/organization-pr-baseline.yml (3)

29-35: 🚀 Performance & Scalability | 🔵 Trivial

Consider the interaction between timeout-minutes: 10 and full-history scans.

Two settings combine here. fetch-depth: 0 clones all history. The gitleaks step falls back to a full-history scan whenever BASE is empty, which happens for every workflow_call invocation outside a pull-request event.

For a large monorepo, the clone plus the full scan can exceed 10 minutes. The job then fails on timeout, and repository owners see a red gate that has nothing to do with a leak. Measure the runtime on the largest repository in the trial cohort before you widen the rollout.

🤖 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/organization-pr-baseline.yml around lines 29 - 35,
Validate the combined runtime of the full-history checkout and gitleaks fallback
scan in the organization-pr-baseline workflow, especially for workflow_call
invocations with an empty BASE. Measure this on the largest trial repository and
adjust timeout-minutes or rollout scope based on the observed runtime so
legitimate scans do not fail due to the job timeout.

95-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This scan logic duplicates .github/workflows/secret-scan-reusable.yml.

The range selection, the zero-SHA guard, the git rev-parse --verify fallback, and the gitleaks git flags match lines 10-52 of .github/workflows/secret-scan-reusable.yml. The two copies will drift.

The new copy is the hardened one. It pins the download by checksum and disables credential persistence. The older file uses actions/checkout@v7 and an unverified download. After the staged rollout completes, retire the older reusable workflow or make it call this one.

The logic itself is correct. The fallback to a full-history scan when BASE is empty is the safe direction for workflow_call invocations that carry no pull-request context.

🤖 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/organization-pr-baseline.yml around lines 95 - 106, The
secret-scan logic in the pull-request workflow duplicates
secret-scan-reusable.yml and must have a single maintained implementation. After
the staged rollout, retire the older reusable workflow, or update it to call the
hardened scan implementation while preserving its range selection, zero-SHA
guard, commit verification fallback, and gitleaks flags.

69-93: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

The placeholder gate is both over-broad and easy to bypass.

Three concrete gaps exist in the current matcher:

  1. Scope. Both regexes run over the whole file. A workflow that contains a CI Summary job and, in an unrelated job, the string echo "CI checks passed" fails the gate. The two matches never need to belong to the same job.
  2. Phrase coupling. The gate only rejects the literal phrase CI checks passed. A placeholder such as echo ok, true, or exit 0 passes.
  3. Position coupling. The regex requires echo to be the first token after run:. A block scalar such as run: | followed by set -e and then echo "CI checks passed" is not matched.

Parse the YAML and inspect the steps of the CI Summary job instead. That removes all three gaps and makes the rule explainable to repository owners.

♻️ Proposed job-scoped implementation
       - name: Reject placeholder CI Summary gates
         shell: python
         run: |
           from pathlib import Path
-          import re
           import sys
+          import yaml
 
-          placeholder = re.compile(
-              r"run:\s*(?:[>|][-+]?\s*\n\s*)?echo\s+[\"']?CI checks passed",
-              re.IGNORECASE,
-          )
+          # A gate is a placeholder when every step only echoes, exits 0, or is a no-op.
+          def is_placeholder(step):
+              run = (step.get("run") or "").strip()
+              if not run:
+                  return False
+              lines = [
+                  line.strip()
+                  for line in run.splitlines()
+                  if line.strip() and not line.strip().startswith("#")
+              ]
+              trivial = ("echo", "true", "exit 0", "set ", ":")
+              return all(line.startswith(trivial) for line in lines)
+
           failures = []
           for pattern in ("*.yml", "*.yaml"):
               for path in Path(".github/workflows").glob(pattern):
-                  text = path.read_text(encoding="utf-8")
-                  if re.search(r"name:\s*CI Summary\b", text) and placeholder.search(text):
-                      failures.append(path)
+                  try:
+                      doc = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
+                  except (yaml.YAMLError, UnicodeDecodeError) as exc:
+                      print(f"::error file={path}::Unable to parse workflow: {exc}")
+                      failures.append(path)
+                      continue
+                  for job in (doc.get("jobs") or {}).values():
+                      if not isinstance(job, dict):
+                          continue
+                      if job.get("name") != "CI Summary":
+                          continue
+                      steps = job.get("steps") or []
+                      if steps and all(is_placeholder(step) for step in steps):
+                          failures.append(path)
 
           if failures:
               for path in failures:

PyYAML is preinstalled on ubuntu-24.04 runners. Confirm this before you rely on it, or add an explicit pip install pyyaml step.

🤖 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/organization-pr-baseline.yml around lines 69 - 93, Replace
the file-wide regex checks in the “Reject placeholder CI Summary gates” step
with YAML parsing via PyYAML, confirming the runner dependency or installing it
explicitly. Locate the job whose name is “CI Summary”, inspect only its steps’
run commands, and reject commands that are unconditional placeholders such as
echo-only success, true, or exit 0 regardless of preceding setup commands.
Report the workflow path and fail the step when such a CI Summary step is found.
🤖 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.

Nitpick comments:
In @.github/workflows/organization-pr-baseline.yml:
- Around line 29-35: Validate the combined runtime of the full-history checkout
and gitleaks fallback scan in the organization-pr-baseline workflow, especially
for workflow_call invocations with an empty BASE. Measure this on the largest
trial repository and adjust timeout-minutes or rollout scope based on the
observed runtime so legitimate scans do not fail due to the job timeout.
- Around line 95-106: The secret-scan logic in the pull-request workflow
duplicates secret-scan-reusable.yml and must have a single maintained
implementation. After the staged rollout, retire the older reusable workflow, or
update it to call the hardened scan implementation while preserving its range
selection, zero-SHA guard, commit verification fallback, and gitleaks flags.
- Around line 69-93: Replace the file-wide regex checks in the “Reject
placeholder CI Summary gates” step with YAML parsing via PyYAML, confirming the
runner dependency or installing it explicitly. Locate the job whose name is “CI
Summary”, inspect only its steps’ run commands, and reject commands that are
unconditional placeholders such as echo-only success, true, or exit 0 regardless
of preceding setup commands. Report the workflow path and fail the step when
such a CI Summary step is found.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ee87c90-1bfc-461f-8062-a89822f0cd27

📥 Commits

Reviewing files that changed from the base of the PR and between a6a1f9e and 77a7178.

📒 Files selected for processing (1)
  • .github/workflows/organization-pr-baseline.yml

The placeholder-gate check was a literal string match on `echo "CI checks
passed"`. It missed four trivial variants (a `set -e` line before the echo,
any other phrase, `exit 0`, and `true`) and fired on a workflow whose real
gate was fine but which mentioned the phrase in an unrelated job.

Parse the YAML and judge the gate job instead: a job is a fake gate when it
neither aggregates other jobs through needs.<job>.result nor runs a command
that can fail. Checked against all 216 workflow files in the organization —
it flags the two known fake gates in meditation-service and healify-org, and
nothing else.

Add advisory checks for the debt the same audit measured: 561 of 657 external
action references sit on mutable tags, 84 workflows carry no permissions
block, 182 of 309 jobs have no timeout, and ten steps interpolate a secret
straight into a run script. These annotate rather than fail so the workflow
stays adoptable; a repository opts into enforcement with strict: true.

Also stop cancelling in-progress runs on branch pushes, so a rapid series of
commits cannot leave one unscanned behind a cancelled run; raise the default
timeout to 20 minutes because the fallback secret scan walks all history;
retry the tool downloads; and install PyYAML explicitly rather than assuming
the runner image provides it.

@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: 3

🧹 Nitpick comments (1)
.github/workflows/organization-pr-baseline.yml (1)

236-250: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Confirm the fallback scan stays inside the job timeout.

The fallback at Line 249 walks the full history. The default timeout is 20 minutes. On a repository with a long history, gitleaks can exceed that budget, and the job fails with a timeout instead of a scan verdict. The input description at Line 28 documents the risk, but callers must set the value before the first run.

Consider printing the commit count before the fallback scan, so a timeout is attributable in the log.

🤖 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/organization-pr-baseline.yml around lines 236 - 250,
Update the fallback branch in the “Scan the pull-request range for committed
secrets” step to print the repository’s commit count immediately before running
the full-history gitleaks scan. Keep the existing fallback scan and timeout
behavior unchanged, and make the log clearly identify the count as the scope of
the fallback scan.
🤖 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/organization-pr-baseline.yml:
- Around line 17-20: Update the pull_request trigger in the workflow
configuration to remove the paths filter so organization-pr-baseline runs for
every pull request, ensuring the existing gitleaks step executes regardless of
which files changed.
- Around line 123-144: Update real_commands to analyze each
shell-operator-separated command rather than matching INERT against the entire
line prefix. Treat a line as inert only when every command part is an explicitly
inert command, ensuring keywords such as echo, true, shift, and : match complete
command names and chained commands like echo ... && make verify are classified
as real.
- Around line 92-97: Update the workflow’s PyYAML setup and audit execution to
use an isolated Python environment, such as a virtual environment created with
the configured Python interpreter. Install PyYAML through that environment and
invoke the audit with the same interpreter, replacing the bare pip install and
system python3 usage in the “Ensure the YAML parser is present” step.

---

Nitpick comments:
In @.github/workflows/organization-pr-baseline.yml:
- Around line 236-250: Update the fallback branch in the “Scan the pull-request
range for committed secrets” step to print the repository’s commit count
immediately before running the full-history gitleaks scan. Keep the existing
fallback scan and timeout behavior unchanged, and make the log clearly identify
the count as the scope of the fallback scan.
🪄 Autofix

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 Plus

Run ID: 5f023f1f-06e1-42ed-a374-94843302de91

📥 Commits

Reviewing files that changed from the base of the PR and between 77a7178 and adb558a.

📒 Files selected for processing (1)
  • .github/workflows/organization-pr-baseline.yml

Comment thread .github/workflows/organization-pr-baseline.yml Outdated
Comment thread .github/workflows/organization-pr-baseline.yml Outdated
Comment thread .github/workflows/organization-pr-baseline.yml Outdated
Three fixes from review.

The paths filter meant the secret scan only ran when a pull request touched
a workflow file, so a commit that put a credential in application code was
never scanned. Drop the filter; the audit is fast and the repository is small.

The inert-command check matched on line prefix, so `echo "verifying" && make
verify` looked inert and a compliant gate could be reported as fake — a false
positive in the blocking tier, which is the one thing that tier must not do.
Split each line on shell operators and require every segment to be inert, and
match command words whole so `truncate_logs` is no longer read as `true`.
`exit 0` stays inert; any other exit can fail and counts as real.

Installing PyYAML with a bare pip would have failed on Ubuntu 24.04, whose
system Python is PEP 668 externally-managed. That path never ran because the
image already ships PyYAML, which is precisely why it needed fixing rather
than leaving as untested fallback. Use actions/setup-python, pinned by SHA.

Regression cases now cover the compound-command false positive, whole-word
matching, and the still-fake variants. Re-checked against all 216 workflow
files in the organization: the same two fake gates, nothing else.
@viktor958
Viktor Didkovskyi (viktor958) merged commit 0d88258 into main Aug 12, 2026
3 checks passed
@viktor958
Viktor Didkovskyi (viktor958) deleted the viktor/hea-7249-org-actions-validation-baseline branch August 12, 2026 09:23
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