XOR Testing
@xor-hardener Audit workflow permissions; propose per-job least-privilege YAML patch.
Your repository is not yet ready for this interaction. Please try again later.
@JLLeitschuh Please approve this plan and mention @xor-hardener for xor-hardener to handle patching the issue!
🧭 Issue Specification (Plan for Approval)
Title: Harden GitHub Actions: per-job least-privilege permissions
Summary: Reduce GITHUB_TOKEN exposure across all workflows by setting a restrictive default at the workflow level and granting only the minimum required permissions per job. This follows GitHub’s guidance to scope the token via the workflow/job-level permissions key. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions, https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
🎯 Objectives
- Goal: Enforce least-privilege permissions for GITHUB_TOKEN in every workflow job.
-
Outcome: Each workflow has
permissions: read-allat the top, and each job explicitly declares only the minimal permissions it needs. - Non-Goals: Redesigning CI logic; changing external publishing credentials; modifying non-Actions CI.
🧠 Proposed Plan
-
Inventory and baseline
- Enumerate all workflow YAMLs under the workflows directory and note existing
permissionsblocks (workflow and job level). - Set workflow-level default to
permissions: read-allfor each file to ensure a secure baseline. This reduces all scopes to read unless explicitly overridden per job. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
- Enumerate all workflow YAMLs under the workflows directory and note existing
-
Per-job least-privilege mapping and edits
- For each job, add a
permissions:block that grants only what’s necessary based on its steps:- Build/Test jobs: keep default read-only; if uploading artifacts, add
actions: writeonly as needed. Confirm by running and watching for permission errors. (https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token) - Release/Tag jobs: add
contents: write(for releases/tags), optionallypackages: write(if publishing to GitHub Packages), andid-token: writeif OIDC is required. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions, https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#requesting-an-id-token) - PR automation/commenting jobs: add
pull-requests: writeand, if committing changes,contents: write. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions) - Status/Checks publishers: add
statuses: writeand/orchecks: writeonly if the job explicitly creates statuses or check runs. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
- Build/Test jobs: keep default read-only; if uploading artifacts, add
- Remove any blanket
write-allor unnecessary top-levelcontents: write.
- For each job, add a
-
Example patches (patterns to apply per job)
- At top of each workflow file:
-
permissions: read-all(baseline)
-
- For a release job (when applicable):
-
permissions: contents: write id-token: write # only if OIDC is used packages: write # only if GH Packages is used
-
- For a build job that uploads artifacts:
-
permissions: actions: write
-
- For a PR automation job (e.g., creates/updates PRs):
-
permissions: contents: write pull-requests: write
-
- Notes: Job-level permissions override the top-level baseline for that job, keeping other scopes read-only. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
- At top of each workflow file:
-
Documentation and comments
- Annotate each job’s
permissions:with a one-line rationale referencing the step requiring it (e.g., artifact upload, release creation, PR comment).
- Annotate each job’s
-
Verification and iteration
- Open a draft PR with these changes; run workflows.
- If a job fails with “Resource not accessible by integration,” incrementally grant the minimally required scope for that job, re-run, and stop at the first successful minimal set. (https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
-
Optional guardrails
- Add a CI check to lint for missing or overly broad
permissionsblocks on new workflows (e.g., a policy-as-code step) and document contribution guidelines.
- Add a CI check to lint for missing or overly broad
Alternative Considerations: Instead of read-all, use a minimal explicit set (e.g., permissions: { contents: read }) where all jobs use only repo contents; however, read-all is simpler and reduces risk of missing read scopes required by actions. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
⚙️ Implementation Notes
-
Affected Components:
- /.github/workflows
- /.github/workflows/*.yml
- /.github/workflows/*.yaml
- Dependencies: None beyond GitHub Actions and secrets already in use.
- Risks: Under-provisioning may break steps; we will iterate minimally to restore necessary scopes. Over-provisioning risk is mitigated by the read-all baseline and per-job scoping.
🧪 Validation Plan
-
Testing:
- Create a PR with permission changes; ensure all workflows complete successfully.
- Where feasible, use local dry runs (e.g., nektos/act) for basic validation, acknowledging some permissions are cloud-only.
- Metrics: All CI workflows green on PR; no new warnings about insufficient permissions.
- Rollout: Single PR touching only workflow YAMLs; merge if green. Monitor subsequent runs and revert if needed.
📦 Deliverables
- [ ] Workflow YAMLs updated to set
permissions: read-allat the top and tighten job-level permissions - [ ] Brief inline comments describing why each job permission is required
- [ ] CI run(s) demonstrating successful execution under least privilege
- [ ] Contributor documentation note about permissions for new/updated workflows
✅ Review Checklist
- [ ] Goals clearly reduce token privileges
- [ ] Top-level read baseline established in every workflow
- [ ] Each job has only the permissions it actually needs
- [ ] Validation plan and rollback covered
Rollback Plan
- Revert the workflow changes with a single revert commit if any unexpected breakages occur.
⚠️ Please run Sourcegraph Search manually on the following files to verify workflow file inventory:
- /.github/workflows
- /.github/workflows/*.yml
- /.github/workflows/*.yaml
⚠️ Please run Repository Researcher manually on the following files to enumerate job/step details for permission scoping:
- /.github/workflows
- /.github/workflows/*.yml
- /.github/workflows/*.yaml
@xor-hardener approve
@JLLeitschuh ## XOR Analysis
Summary of attempted implementation (no patches applied due to tool unavailability)
We attempted to implement the approved plan to enforce per-job least-privilege GITHUB_TOKEN permissions across all workflows. Patching could not be completed in the automated environment; below is a precise set of changes to apply, with repository evidence and rationale.
Plan reference and rationale
- Set a restrictive default at the workflow level (permissions: read-all) and grant only the minimum required permissions per job, as recommended by GitHub. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
- Map permissions based on the actual operations performed by each job (artifact upload, release creation, PR operations, dependency submission, OIDC), per GitHub’s GITHUB_TOKEN guidance. (https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
- If OIDC is used, grant id-token: write only to those jobs. (https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#requesting-an-id-token)
Files and precise edits
.github/workflows/gradle-wrapper-validation.yml
- Current: Top-level permissions exists with contents: read (gradle-wrapper-validation.yml L3-L4).
- Change: Set workflow-level permissions: read-all. No job-level writes needed for Validation job (only checkout and wrapper validation steps, e.g., actions/checkout and gradle/wrapper-validation-action at gradle-wrapper-validation.yml L16-L17).
.github/workflows/build-and-check.yml
- Current: Top-level permissions has contents: read (build-and-check.yml L10-L11). Jobs include:
- test-jvm: uploads artifacts (build-and-check.yml L84-L93)
- build: uploads artifacts (build-and-check.yml L144-L147)
- check_samples: submits dependency graph (build-and-check.yml L224-L226)
- Changes:
- Set workflow-level permissions: read-all.
- Add job-level permissions for test-jvm and build: actions: write (needed for actions/upload-artifact).
- Add job-level permissions for check_samples: contents: write (needed by gradle/actions/dependency-submission).
- Leave build-jvm-matrix, check_bashisms, and check_release inheriting the read baseline.
.github/workflows/changelog.yml
- Current: Top-level permissions has contents: read (changelog.yml L6-L7). Job only checks files; no write ops.
- Change: Set workflow-level permissions: read-all. No job-level writes required.
.github/workflows/submit-dependency-graph.yml
- Current: Top-level permissions includes actions: read and contents: write (submit-dependency-graph.yml L7-L9). Job runs gradle/actions/dependency-submission (submit-dependency-graph.yml L19-L22).
- Changes:
- Set workflow-level permissions: read-all.
- Move write to job-level: Add permissions: contents: write for the submit-dependency-graph job with a one-line rationale.
.github/workflows/codeql.yml
- Current: Top-level permissions declares contents: read and security-events: write (codeql.yml L21-L23). The job also sets job-level permissions security-events: write and packages: read (codeql.yml L34-L38).
- Changes:
- Set workflow-level permissions: read-all (remove write from top-level).
- Keep job-level permissions security-events: write and packages: read as-is.
.github/workflows/trigger-release.yaml
- Current: No top-level permissions. The job uses actions/upload-artifact (trigger-release.yaml L38-L44) and performs commit + tag via stefanzweifel/git-auto-commit-action (trigger-release.yaml L54-L59).
- Changes:
- Add workflow-level permissions: read-all.
- Add job-level permissions for perform-release: actions: write (artifact upload) and contents: write (git-auto-commit-action pushes commit and tag).
.github/workflows/new-release.yml
- Current: No top-level permissions. Release job sets GITHUB_KEY to GITHUB_TOKEN and runs Gradle githubRelease (new-release.yml L26, new-release.yml L35, new-release.yml L51).
- Changes:
- Add workflow-level permissions: read-all.
- Add job-level permissions for make-new-release: contents: write (needed to create the GitHub release via API).
.github/workflows/scorecard.yml (for completeness)
- Already compliant with baseline permissions: read-all at workflow level (scorecard.yml L17), and job-level security-events: write and id-token: write as needed (scorecard.yml L23-L27). No changes proposed here.
Why these mappings
- Artifact uploads require actions: write. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
- Creating releases/tags or pushing commits requires contents: write when relying on GITHUB_TOKEN, as in new-release and git-auto-commit-action. (https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
- CodeQL uploads SARIF and needs security-events: write (CodeQL template guidance). (https://docs.github.com/en/code-security/code-scanning/integrations/uploading-a-sarif-file-to-github)
- Dependency submission requires contents: write (GitHub dependency submission API requires write access to repository metadata). (https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/exploring-the-dependencies-of-a-repository#about-the-dependency-graph)
Net effect (intended)
- Workflow-level: Establish a consistent read-only baseline across all workflows.
- Job-level: Grant write only where required by explicit steps, keeping all other scopes at read-only for that job.
- This follows GitHub guidance to scope GITHUB_TOKEN via the permissions key. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
Affected files (paths one per line) .github/workflows/gradle-wrapper-validation.yml .github/workflows/build-and-check.yml .github/workflows/changelog.yml .github/workflows/submit-dependency-graph.yml .github/workflows/codeql.yml .github/workflows/trigger-release.yaml .github/workflows/new-release.yml
Follow-up: how to validate
- Open a draft PR with the changes above and ensure all workflows complete successfully.
- If any job fails with “Resource not accessible by integration,” incrementally grant the minimally required scope for that job and re-run. (https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
Manual action required
- ⚠️ Please run CODER manually on the following files: .github/workflows/gradle-wrapper-validation.yml, .github/workflows/build-and-check.yml, .github/workflows/changelog.yml, .github/workflows/submit-dependency-graph.yml, .github/workflows/codeql.yml, .github/workflows/trigger-release.yaml, .github/workflows/new-release.yml.
- ⚠️ Please run REPOSITORY RESEARCHER manually on the following files: .github/workflows, .github/workflows/.yml, .github/workflows/.yaml.
- ⚠️ Please run ZIZMOR manually on the following files: .github/workflows/gradle-wrapper-validation.yml, .github/workflows/build-and-check.yml, .github/workflows/changelog.yml, .github/workflows/submit-dependency-graph.yml, .github/workflows/codeql.yml, .github/workflows/trigger-release.yaml, .github/workflows/new-release.yml, .github/workflows/scorecard.yml.
Note: No code changes could be committed automatically in this run due to tool environment limitations, but the above mapping and evidence provide exact edits to apply safely.
No code changes were generated for this issue.