Skip to content

ci(governance): harden pull_request_target governance workflows - #84

Merged
gswetha merged 3 commits into
mainfrom
ci/harden-governance-workflow
Oct 29, 2026
Merged

gswetha merged 3 commits into
mainfrom
ci/harden-governance-workflow

Conversation

@damaz91

@damaz91 damaz91 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

Hardens .github/workflows/reusable-governance.yml and .github/workflows/governance.yml for pull_request_target security best practices:

  • Remove unused untrusted PR code checkout: Drops the unused Check out PR code step from reusable-governance.yml so only trusted central governance scripts from Universal-Commerce-Protocol/.github (ref: main) are checked out into .github-central with persist-credentials: false.
  • Eliminate inline expression interpolation: Moves PR/SHA resolution, pr_validator.py execution, workflow summary/annotation publishing, and Governance / Approvals commit status posting into a dedicated, unit-tested Python stdlib helper (org-tools/governance/scripts/run_governance_gate.py) receiving all inputs strictly via env: variables.
  • Reliable fork PR lookup, TOCTOU head-SHA verification & runtime error surfacing: Resolves PR numbers on workflow_run via workflow_run.pull_requests[0].number (same-repo PRs) and paginated /repos/{repo}/pulls?state=open&sort=updated&direction=desc matching head.sha (fork and same-repo PRs with zero search index lag, falling back to /commits/{sha}/pulls and /search/issues preferring open PRs), and verifies pr.head.sha == commit_sha both before and after running pr_validator.py so superseded commits never receive a newer head's status. Also surfaces unexpected pr_validator.py crashes or runtime exceptions (exit code 2, missing/empty summary.txt, or ERROR:-prefixed summary) in $GITHUB_STEP_SUMMARY, commit status "error", and non-zero step exit.
  • Least-privilege permissions & protected branch filters: Sets workflow-level permissions: {}, restricts job permissions strictly to statuses: write with timeout-minutes: 5, disables setup-uv cache writes (enable-cache: "false", python-version: "3.12"), scopes pull_request_target to main and release/** base branches, and pins actions/checkout (v7.0.1) and astral-sh/setup-uv (v8.1.0) to full 40-character commit SHAs.

Category (Required)

Please select one or more categories that apply to this change.

  • Core Protocol: Changes to the base communication layer, global context, or breaking refactors. (Requires Technical Council approval)
  • Governance/Contributing: Updates to GOVERNANCE.md, CONTRIBUTING.md, or CODEOWNERS. (Requires Governance Council approval)
  • Capability: New schemas (Discovery, Cart, etc.) or extensions. (Requires Maintainer approval)
  • Documentation: Updates to README, or documentations regarding schema or capabilities. (Requires Maintainer approval)
  • Infrastructure: CI/CD, Linters, or build scripts. (Requires DevOps Maintainer approval)
  • Maintenance: Version bumps, lockfile updates, or minor bug fixes. (Requires DevOps Maintainer approval)
  • SDK: Language-specific SDK updates and releases. (Requires DevOps Maintainer approval)
  • Samples / Conformance: Maintaining samples and the conformance suite. (Requires Maintainer approval)
  • UCP Schema: Changes to the ucp-schema tool (resolver, linter, validator). (Requires Maintainer approval)
  • Community Health (.github): Updates to templates, workflows, or org-level configs. (Requires DevOps Maintainer approval)

Related Issues

N/A

Checklist

  • I have followed the Contributing Guide (including Conventional Commits title requirements and ! for breaking changes).
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • (For Core/Capability) I have included/updated the relevant JSON schemas.
  • I have regenerated Python Pydantic models by running generate_models.sh under python_sdk.

Screenshots / Logs (if applicable)

Verified locally with unittest discover -s org-tools/governance/tests (74/74 tests passing), pre-commit --all-files, check_copyright.py, and GitHub Actions zizmor scan.

@ptiper ptiper left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two small edge cases worth considering before merging (reviewed with Antigravity):

uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ github.repository_owner }}/.github

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To match the PR description (always checking out .github from main), should we add ref: main explicitly here?

actions/checkout only defaults to a repository's default branch when checking out a different repository. When called from .github itself, omitting ref makes it check out the triggering workflow's github.ref instead (for example a release branch, or untrusted PR code if any future workflow in .github ever calls this from a standard pull_request trigger). Setting ref: main keeps live main rule updates working across all repos without that same-repo quirk.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great catch, thank you! Added ref: main explicitly in a80bd05 so runs inside .github itself always check out main rather than falling back to github.ref.

)
return 0

if eval_exit_code != 0 and not summary_path.is_file():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Right now this crash handler only triggers when summary.txt is missing (for example if uv fails to start). When pr_validator.py hits a runtime exception (like an expired token, a GitHub API 5xx, or a missing rules file), its top-level exception handler still writes ERROR: ... into summary.txt and exits with code 1, so the gate treats it as normal missing approvals ("Pending required approvals" with a green workflow job) instead of posting an error status.

Could we handle that in this PR too (for example by exiting with code 2 on unexpected exceptions in pr_validator.py or checking for an ERROR: prefix here, while still writing the summary to the job summary so the failure reason stays easy to see)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — updated in a80bd05:

  • pr_validator.py now exits with code 2 on unexpected exceptions in main() (leaving exit code 1 strictly for unsatisfied governance rules).
  • run_governance_gate.py treats exit codes not in (0, 1), missing/empty summary.txt, or ERROR:-prefixed summaries as validator execution errors, appends the failure details to $GITHUB_STEP_SUMMARY, posts an "error" commit status, and fails the workflow job.

…me errors

- Explicitly set `ref: main` when checking out `.github-central` in
  `reusable-governance.yml` so same-repo runs inside `.github` evaluate
  against `main` rather than `github.ref`.
- Exit with code 2 on unexpected runtime exceptions in `pr_validator.py`
  and treat exit codes `!= 1` or `ERROR:`-prefixed summaries in
  `run_governance_gate.py` as execution errors, writing failure details
  to `GITHUB_STEP_SUMMARY` and posting an `error` commit status.
@gswetha

gswetha commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

LGTM thanks! 2 follow-up actions would be to update meeting-minutes/.github/workflows/governance.yml SHA pin and update the Actions Event policy to match.

@gswetha
gswetha merged commit 97682ca into main Oct 29, 2026
14 checks passed
carolinerg1 pushed a commit to Universal-Commerce-Protocol/ucp that referenced this pull request Oct 29, 2026
Hardens .github/workflows/governance.yml for pull_request_target security best practices and pins the central reusable governance workflow to the hardened revision merged in Universal-Commerce-Protocol/.github#84 (97682ca4ad40b4321d60bb58c869e12f19a8bcae):
carolinerg1 pushed a commit to Universal-Commerce-Protocol/ucp-schema that referenced this pull request Oct 29, 2026
Hardens .github/workflows/governance.yml for pull_request_target security best practices and pins the central reusable governance workflow to the hardened revision merged in Universal-Commerce-Protocol/.github#84 (97682ca4ad40b4321d60bb58c869e12f19a8bcae):
carolinerg1 pushed a commit to Universal-Commerce-Protocol/conformance that referenced this pull request Oct 29, 2026
Hardens .github/workflows/governance.yml for pull_request_target security best practices and pins the central reusable governance workflow to the hardened revision merged in Universal-Commerce-Protocol/.github#84 (97682ca4ad40b4321d60bb58c869e12f19a8bcae)
carolinerg1 pushed a commit to Universal-Commerce-Protocol/js-sdk that referenced this pull request Oct 29, 2026
Hardens .github/workflows/governance.yml for pull_request_target security best practices and pins the central reusable governance workflow to the hardened revision merged in Universal-Commerce-Protocol/.github#84 (97682ca4ad40b4321d60bb58c869e12f19a8bcae)
carolinerg1 pushed a commit to Universal-Commerce-Protocol/samples that referenced this pull request Oct 29, 2026
Hardens .github/workflows/governance.yml for pull_request_target security best practices and pins the central reusable governance workflow to the hardened revision merged in Universal-Commerce-Protocol/.github#84 (97682ca4ad40b4321d60bb58c869e12f19a8bcae)
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.

3 participants