ktlint-gradle icon indicating copy to clipboard operation
ktlint-gradle copied to clipboard

XOR Testing

Open JLLeitschuh opened this issue 5 months ago • 5 comments

JLLeitschuh avatar Nov 14 '25 18:11 JLLeitschuh

@xor-hardener Audit workflow permissions; propose per-job least-privilege YAML patch.

JLLeitschuh avatar Nov 14 '25 18:11 JLLeitschuh

Your repository is not yet ready for this interaction. Please try again later.

xor-hardener[bot] avatar Nov 14 '25 18:11 xor-hardener[bot]

@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-all at 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

  1. Inventory and baseline

    • Enumerate all workflow YAMLs under the workflows directory and note existing permissions blocks (workflow and job level).
    • Set workflow-level default to permissions: read-all for 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)
  2. 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: write only 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), optionally packages: write (if publishing to GitHub Packages), and id-token: write if 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: write and, if committing changes, contents: write. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
      • Status/Checks publishers: add statuses: write and/or checks: write only if the job explicitly creates statuses or check runs. (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions)
    • Remove any blanket write-all or unnecessary top-level contents: write.
  3. 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)
  4. 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).
  5. 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)
  6. Optional guardrails

    • Add a CI check to lint for missing or overly broad permissions blocks on new workflows (e.g., a policy-as-code step) and document contribution guidelines.

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-all at 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[bot] avatar Nov 14 '25 18:11 xor-hardener[bot]

@xor-hardener approve

JLLeitschuh avatar Nov 14 '25 18:11 JLLeitschuh

@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

.github/workflows/build-and-check.yml

  • Current: Top-level permissions has contents: read (build-and-check.yml L10-L11). Jobs include:
  • 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.

xor-hardener[bot] avatar Nov 14 '25 19:11 xor-hardener[bot]