Flags direct pushes to main that did not come from a merged PR. Free-plan workaround since branch protection requires GitHub Pro.
63 lines
2.5 KiB
YAML
63 lines
2.5 KiB
YAML
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)}`);
|