Skip to content

fix(ci): add missing actions:read perm to scan reusables (estate-wide SARIF + gitleaks unblock)#352

Merged
hyperpolymath merged 2 commits into
mainfrom
fix/reusable-workflow-permissions-actions-read
Jun 2, 2026
Merged

fix(ci): add missing actions:read perm to scan reusables (estate-wide SARIF + gitleaks unblock)#352
hyperpolymath merged 2 commits into
mainfrom
fix/reusable-workflow-permissions-actions-read

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

Two reusable workflows consume third-party actions that need GitHub-API permissions the reusables didn't grant. Reusable workflow permission blocks OVERRIDE the caller's, so adding perms at every consumer wrapper is a no-op — the fix must live here at source.

This unblocks SARIF upload + gitleaks PR scan across every estate consumer of these reusables.

Reproducing failures (before this PR)

From .git-private-farm#69 runs (verified Jun 02 18:34 UTC):

hypatia SARIF upload

hypatia.sarif written: 48 result(s).
Run github/codeql-action/upload-sarif@...
  sarif_file: hypatia.sarif
Validating hypatia.sarif
Adding fingerprints to SARIF file.
##[error]Resource not accessible by integration - GET /actions/runs/...

The scan + SARIF generation succeed. The upload-sarif step then calls GET /repos/{owner}/{repo}/actions/runs/{run_id} to attach the blob and fails because actions: read is not granted.

gitleaks ScanPullRequest

[hyperpolymath] is an individual user. No license key is required.
gitleaks version: 8.24.3
Gitleaks restored from cache
RequestError [HttpError]: Resource not accessible by integration
    at ... ScanPullRequest ...

The gitleaks binary scan succeeds. The post-scan ScanPullRequest step calls additional API endpoints (PR-files, workflow-run metadata, PR comment post) and fails because pull-requests: write + actions: read are not granted.

Changes

hypatia-scan-reusable.yml — workflow-level

permissions:
  contents: read
  security-events: write
  pull-requests: write
+ actions: read    # NEW — for upload-sarif's GET /actions/runs/{id}

Single-job workflow, so workflow-level is the right scope.

secret-scanner-reusable.yml — gitleaks job-level only

The other jobs (trufflehog, rust-secrets, shell-secrets) only need contents: read. Adding wider perms at workflow level would over-grant. Gitleaks gets a job-level block instead:

gitleaks:
+ permissions:
+   contents: read
+   pull-requests: write   # NEW — for PR comment post
+   actions: read          # NEW — for PR-files / workflow-run API calls
  steps: ...

Why this is safe

Both new grants are read-only (actions: read) or scoped to PRs only (pull-requests: write). They match GitHub's documented permission requirements for these specific third-party actions:

  • codeql-action/upload-sarif docs explicitly require actions: read when calling from a wrapper.
  • gitleaks-action v2 docs require pull-requests: write for PR commenting + actions: read for the new PR-files API.

No new write capability beyond PR-comment posting, which already had to be granted somewhere (incorrectly assumed it'd inherit from the caller).

Blast radius

  • hypatia-scan-reusable.yml: ~estate-wide consumer of Hypatia security scan.
  • secret-scanner-reusable.yml: 281 estate deployments per the reusable's own header comment.

All consumers will see the fix on their next run via SHA-bump propagation (which is exactly the cascade the 3-system propagation track was designed for — .git-private-farm#66 + gitbot-fleet#249 + hypatia#419).

Test plan

  • After merge: rerun .git-private-farm#69 checks; both SARIF + gitleaks transition to GREEN.
  • Propagation cascade fires (or manual SHA bump if propagation not yet live), and other estate consumers green up too.

Related

🤖 Generated with Claude Code

The hypatia-scan + secret-scanner reusables consume third-party
actions (codeql-action/upload-sarif and gitleaks-action) that need
permissions the reusables didn't grant. Reusable workflow permission
blocks OVERRIDE the caller's, so adding perms at the wrapper is a no-op
— the fix must live here at source.

hypatia-scan-reusable.yml — workflow-level:
  + actions: read

  Lets `codeql-action/upload-sarif` call GET /actions/runs/{id} to attach
  the SARIF blob. Without it, the SARIF upload fails with "Resource not
  accessible by integration" AFTER the scan + conversion both succeed.
  Symptom reproduced across .git-private-farm and other consumers.

secret-scanner-reusable.yml — gitleaks job-level (NOT workflow-level,
keeps trufflehog / rust-secrets / shell-secrets at minimum perms):
  permissions:
    contents: read
    pull-requests: write
    actions: read

  gitleaks-action's `ScanPullRequest` posts a PR summary comment
  (needs pull-requests: write) and queries PR-files / workflow-run
  metadata (needs actions: read). Without both, the action throws
  RequestError [HttpError]: Resource not accessible by integration
  AFTER the gitleaks scan itself succeeds.

Verifying logs from .git-private-farm#69 / job 79144809024 (gitleaks)
+ job 79144809505 (hypatia SARIF) both show post-scan permission
errors that this PR resolves.

Net new perm grants are MINIMAL: `actions: read` is read-only and
matches GitHub's documented requirement for these specific actions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

🔍 Hypatia Security Scan

Findings: 201 issues detected

Severity Count
🔴 Critical 64
🟠 High 43
🟡 Medium 94

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action for the check script)\n        uses: actions/checkout@de0f needs attention",
    "type": "unpinned_action",
    "file": "governance-reusable.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action for the check script)\n        uses: actions/checkout@de0f needs attention",
    "type": "unpinned_action",
    "file": "governance-reusable.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in affinescript-verify.yml",
    "type": "missing_timeout_minutes",
    "file": "affinescript-verify.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in boj-build.yml",
    "type": "missing_timeout_minutes",
    "file": "boj-build.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in changelog-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "changelog-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in deno-ci-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "deno-ci-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath hyperpolymath merged commit 142dd7a into main Jun 2, 2026
9 of 18 checks passed
@hyperpolymath hyperpolymath deleted the fix/reusable-workflow-permissions-actions-read branch June 2, 2026 19:11
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

🔍 Hypatia Security Scan

Findings: 201 issues detected

Severity Count
🔴 Critical 64
🟠 High 43
🟡 Medium 94

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action for the check script)\n        uses: actions/checkout@de0f needs attention",
    "type": "unpinned_action",
    "file": "governance-reusable.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action for the check script)\n        uses: actions/checkout@de0f needs attention",
    "type": "unpinned_action",
    "file": "governance-reusable.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in affinescript-verify.yml",
    "type": "missing_timeout_minutes",
    "file": "affinescript-verify.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in boj-build.yml",
    "type": "missing_timeout_minutes",
    "file": "boj-build.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in changelog-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "changelog-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in deno-ci-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "deno-ci-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

hyperpolymath added a commit that referenced this pull request Jun 2, 2026
…abled (#353)

## Summary

The hypatia-scan reusable's \`Upload SARIF\` step hard-fails when the
consumer repo has code scanning disabled (private repo without Advanced
Security, or any repo with the feature admin-disabled). That's a
legitimate consumer-side config choice, not a regression.

This adds a \`cs-probe\` step that queries \`/code-scanning/alerts\`
(continue-on-error so the probe itself never blocks) and gates the
upload step on the probe's verdict.

## Reproducer

[.git-private-farm#69 run
26843343488](https://github.com/hyperpolymath/.git-private-farm/actions/runs/26843343488):

\`\`\`
hypatia.sarif written: 48 result(s).
Run github/codeql-action/upload-sarif@...
  sarif_file: hypatia.sarif
Validating hypatia.sarif
##[error]Code scanning is not enabled for this repository.
       Please enable code scanning in the repository settings.
\`\`\`

The scan + SARIF conversion both succeed; the \`actions: read\` perm I
added in #352 correctly grants the upload its scope; the upload only
fails because the **feature itself** is not enabled on the consumer
repo.

## Preserved guarantees

Per the existing design comment (post-#35):

- ✅ Fork PRs still skip (token read-only) — unchanged.
- ✅ Genuine permission regression still hard-fails loud (the probe
checks only whether the feature is enabled, not whether the upload would
otherwise succeed).
- ✅ Malformed SARIF / API outage still hard-fails loud — same reason.
- ✅ Hypatia findings still land as a build artifact even when SARIF
upload is skipped (the upload-artifact step is unaffected).

The probe is **fail-open**: if the API call errors for any reason (rate
limit, transient outage), \`enabled\` ends unset (no \`enabled=true\` in
the output), upload is skipped. Loud-red regressions remain loud — the
probe just adds a graceful skip for the specific case where the feature
is administratively unavailable.

## Test plan

- [ ] .git-private-farm#69 re-run picks up new reusable SHA → SARIF step
skips with notice instead of hard-fail.
- [ ] Public estate consumers (where code scanning IS enabled by
default) still publish SARIF normally.
- [ ] Fork PRs still skip via the unchanged second clause of the
\`if:\`.

## Blast radius

Same as #352 — all estate consumers of \`hypatia-scan-reusable.yml\`.
The change is fail-open and additive: existing green paths stay green;
previously-red paths that were red BECAUSE of disabled-feature now go
green.

## Related

- #352 — added the perms; this completes the resilience
- .git-private-farm#69 — first consumer hitting the disabled-feature
path

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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