164 lines
6.6 KiB
YAML
164 lines
6.6 KiB
YAML
# Warden security clearance.
|
|
#
|
|
# Grants or revokes a "security clearance" review on PRs based on blocking
|
|
# findings from the Warden skills: all security findings plus high desktop↔den
|
|
# sync findings. Advisory sync findings never withhold clearance. Humans still
|
|
# review and merge; the clearance only satisfies the required-review gate in
|
|
# the branch ruleset for clean diffs.
|
|
#
|
|
# Why workflow_run: this file always executes from the default branch, so a
|
|
# PR cannot tamper with the clearance logic. The verdict step additionally
|
|
# refuses to self-clear any PR that touches review machinery
|
|
# (.github/, warden.toml, .warden/, .agents/skills/, .claude/skills/).
|
|
#
|
|
# Identity: a dedicated GitHub App (WARDEN_APP_ID / WARDEN_PRIVATE_KEY in the
|
|
# `warden-clearance` environment). The app never pushes commits, so
|
|
# require_last_push_approval always holds (clearance identity != pusher !=
|
|
# author). Until the app secrets are configured this workflow no-ops.
|
|
|
|
name: Warden Clearance
|
|
|
|
on:
|
|
workflow_run:
|
|
workflows: [Warden]
|
|
types: [completed]
|
|
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
pull-requests: read
|
|
|
|
concurrency:
|
|
group: warden-clearance-${{ github.event.workflow_run.head_branch }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
clearance:
|
|
if: >-
|
|
github.event.workflow_run.event == 'pull_request' &&
|
|
github.event.workflow_run.conclusion == 'success' &&
|
|
github.event.workflow_run.head_repository.full_name == github.repository
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
environment: warden-clearance
|
|
steps:
|
|
- name: Check clearance identity is configured
|
|
id: identity
|
|
env:
|
|
APP_ID: ${{ secrets.WARDEN_APP_ID }}
|
|
run: |
|
|
if [ -z "$APP_ID" ]; then
|
|
echo "WARDEN_APP_ID is not configured; clearance disabled."
|
|
echo "configured=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "configured=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Download clearance summary
|
|
if: steps.identity.outputs.configured == 'true'
|
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
with:
|
|
name: warden-summary
|
|
run-id: ${{ github.event.workflow_run.id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Evaluate verdict
|
|
if: steps.identity.outputs.configured == 'true'
|
|
id: verdict
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPO: ${{ github.repository }}
|
|
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
verdict() { echo "verdict=$1" >> "$GITHUB_OUTPUT"; exit 0; }
|
|
|
|
summary_sha="$(jq -r '.head_sha' warden-summary.json)"
|
|
blocking="$(jq -r '.blocking_count // .findings_count' warden-summary.json)"
|
|
advisory="$(jq -r '.sync_advisory_count // 0' warden-summary.json)"
|
|
echo "advisory=$advisory" >> "$GITHUB_OUTPUT"
|
|
|
|
if [ "$summary_sha" != "$HEAD_SHA" ]; then
|
|
echo "Summary SHA ($summary_sha) does not match analyzed SHA ($HEAD_SHA); skipping."
|
|
verdict skip
|
|
fi
|
|
|
|
pr="$(gh api "repos/$REPO/commits/$HEAD_SHA/pulls" --jq '.[0].number // empty')"
|
|
if [ -z "$pr" ]; then
|
|
echo "No PR associated with $HEAD_SHA; skipping."
|
|
verdict skip
|
|
fi
|
|
echo "pr=$pr" >> "$GITHUB_OUTPUT"
|
|
|
|
current_head="$(gh api "repos/$REPO/pulls/$pr" --jq '.head.sha')"
|
|
if [ "$current_head" != "$HEAD_SHA" ]; then
|
|
echo "PR #$pr head moved to $current_head; skipping stale run."
|
|
verdict skip
|
|
fi
|
|
|
|
# Never self-clear a PR that touches the review machinery.
|
|
guarded="$(gh api --paginate "repos/$REPO/pulls/$pr/files" --jq '.[].filename' \
|
|
| grep -E '^(\.github/|warden\.toml$|\.warden/|\.agents/skills/|\.claude/skills/)' || true)"
|
|
if [ -n "$guarded" ]; then
|
|
echo "PR #$pr touches review machinery; human review required:"
|
|
echo "$guarded"
|
|
verdict guarded
|
|
fi
|
|
|
|
if [ "$blocking" = "0" ]; then
|
|
echo "No blocking findings for $HEAD_SHA."
|
|
verdict clear
|
|
else
|
|
echo "$blocking blocking finding(s) on $HEAD_SHA."
|
|
verdict flagged
|
|
fi
|
|
|
|
- name: Mint clearance token
|
|
if: steps.verdict.outputs.verdict == 'clear' || steps.verdict.outputs.verdict == 'flagged'
|
|
id: app
|
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
|
|
with:
|
|
app-id: ${{ secrets.WARDEN_APP_ID }}
|
|
private-key: ${{ secrets.WARDEN_PRIVATE_KEY }}
|
|
|
|
- name: Grant clearance
|
|
if: steps.verdict.outputs.verdict == 'clear'
|
|
env:
|
|
GH_TOKEN: ${{ steps.app.outputs.token }}
|
|
REPO: ${{ github.repository }}
|
|
PR: ${{ steps.verdict.outputs.pr }}
|
|
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
|
RUN_URL: ${{ github.event.workflow_run.html_url }}
|
|
ADVISORY: ${{ steps.verdict.outputs.advisory }}
|
|
run: |
|
|
set -euo pipefail
|
|
BODY="**Warden security clearance: clear.** No blocking findings (security or desktop↔den sync) in this diff (\`$HEAD_SHA\`)."
|
|
if [[ "$ADVISORY" =~ ^[0-9]+$ ]] && [ "$ADVISORY" -gt 0 ]; then
|
|
BODY="$BODY $ADVISORY advisory desktop↔den sync note(s) were posted as review comments; they do not affect clearance."
|
|
fi
|
|
BODY="$BODY Automated clearance satisfies the required-review gate only — a human still reviews and merges. [Analysis run]($RUN_URL)"
|
|
gh api "repos/$REPO/pulls/$PR/reviews" \
|
|
-f event=APPROVE \
|
|
-f commit_id="$HEAD_SHA" \
|
|
-f body="$BODY"
|
|
|
|
- name: Revoke stale clearance
|
|
if: steps.verdict.outputs.verdict == 'flagged'
|
|
env:
|
|
GH_TOKEN: ${{ steps.app.outputs.token }}
|
|
REPO: ${{ github.repository }}
|
|
PR: ${{ steps.verdict.outputs.pr }}
|
|
APP_SLUG: ${{ steps.app.outputs.app-slug }}
|
|
run: |
|
|
set -euo pipefail
|
|
rid="$(gh api "repos/$REPO/pulls/$PR/reviews" \
|
|
--jq "[.[] | select(.user.login == \"${APP_SLUG}[bot]\" and .state == \"APPROVED\")] | last | .id // empty")"
|
|
if [ -n "$rid" ]; then
|
|
gh api -X PUT "repos/$REPO/pulls/$PR/reviews/$rid/dismissals" \
|
|
-f message="Warden found new blocking findings (security or desktop↔den sync) on the latest diff; clearance revoked." \
|
|
|| echo "Could not dismiss review $rid (may already be dismissed)."
|
|
else
|
|
echo "No active clearance to revoke."
|
|
fi
|