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)}`);