From 619ccb20f039d75611be48d90ae79ac898c8efde Mon Sep 17 00:00:00 2001 From: peptiderepo <273788462+peptiderepo@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:44:56 +0700 Subject: [PATCH] ci: add main-push audit tripwire Flags direct pushes to main that did not come from a merged PR. Free-plan workaround since branch protection requires GitHub Pro. --- .github/workflows/main-push-audit.yml | 62 +++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/main-push-audit.yml diff --git a/.github/workflows/main-push-audit.yml b/.github/workflows/main-push-audit.yml new file mode 100644 index 0000000..0c5bec3 --- /dev/null +++ b/.github/workflows/main-push-audit.yml @@ -0,0 +1,62 @@ +name: Audit — Direct push to main + +# Tripwire: fires on every push to main and flags pushes that did not come +# from a merged PR. We run GitHub on the free plan, which means we can't use +# branch protection on private repos. This is the soft-enforcement layer. +# +# Behavior: +# - If the pushed commit is associated with a merged PR, exit quietly. +# - If not, open a GitHub issue tagging the pusher so it's visible. +# +# This never blocks the push. It's an audit, not a gate. + +on: + push: + branches: [main] + +permissions: + contents: read + issues: write + pull-requests: read + +jobs: + audit: + name: Check commit came from PR + runs-on: ubuntu-latest + steps: + - name: Check for associated PR + uses: actions/github-script@v7 + with: + script: | + const sha = context.sha; + const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: sha, + }); + const merged = prs.find(p => p.merged_at); + if (merged) { + core.info(`OK: commit ${sha.substring(0,7)} came from PR #${merged.number}`); + return; + } + const pusher = context.payload.pusher?.name || context.actor; + const msg = context.payload.head_commit?.message?.split('\n')[0] || '(no message)'; + const body = [ + `**Direct push to \`main\` detected** — commit was not associated with a merged PR.`, + ``, + `- Commit: \`${sha.substring(0,7)}\` — ${msg}`, + `- Pusher: @${pusher}`, + `- Workflow run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + ``, + `The project policy is that all changes to \`main\` go through a pull request so they can be reviewed before deploying to production. If this push was intentional (hotfix, infra change) close this issue with a comment explaining why.`, + ``, + `_This issue was opened automatically by \`.github/workflows/main-push-audit.yml\`._`, + ].join('\n'); + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `Direct push to main: ${sha.substring(0,7)}`, + body, + labels: ['policy-violation', 'audit'], + }); + core.warning(`Opened audit issue for direct push ${sha.substring(0,7)}`);