Respond (RS) · Approval gating
Approval gating at the server side layer
external · Outside the agent entirely
How do I stop the bleeding now?
What this cell does
Lock branch protection (revoke bypass perms, raise required reviewers), pause CI/CD, engage deployment freeze, lock GitHub environments.
Artifacts (3)
agent-approval-lockdown-serverview on GitHub#!/usr/bin/env bash
# ABOUTME: Server-side approval-gating lockdown runbook. Branch protection lock, workflows disabled, freeze engaged.
# ABOUTME: Pre-stage branch-protection-locked.json and environment-locked.json. gh CLI must be authenticated as break-glass.
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "Usage: agent-approval-lockdown-server <AGENT_NAME>" >&2
echo " REPO=org/repo agent-approval-lockdown-server <AGENT_NAME>" >&2
exit 64
fi
AGENT_NAME="$1"
REPO="${REPO:?REPO must be set to the GitHub owner/repo}"
INCIDENT_ID="$(uuidgen 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())')"
EMERGENCY_DIR="${EMERGENCY_DIR:-/etc/agents/emergency}"
for src in \
"$EMERGENCY_DIR/branch-protection-locked.json" \
"$EMERGENCY_DIR/environment-locked.json"; do
if [[ ! -r "$src" ]]; then
echo "REFUSING: pre-staged artifact missing: $src" >&2
exit 1
fi
done
# 1. Lock branch protection. Sets enforce_admins=true, lock_branch=true,
# required_approving_review_count=4, restrictions to incident-response team.
gh api -X PUT "repos/$REPO/branches/main/protection" \
--input "$EMERGENCY_DIR/branch-protection-locked.json" >/dev/null
# 2. Disable every workflow in the repo. This stops new pipeline runs but
# does NOT cancel in-flight runs; cancel them explicitly below.
mapfile -t WORKFLOW_IDS < <(
gh api "repos/$REPO/actions/workflows" --jq '.workflows[].id'
)
for wid in "${WORKFLOW_IDS[@]}"; do
gh api -X PUT "repos/$REPO/actions/workflows/$wid/disable" >/dev/null 2>&1 || true
done
# 3. Cancel in-flight runs.
mapfile -t RUN_IDS < <(
gh api "repos/$REPO/actions/runs?status=in_progress" --jq '.workflow_runs[].id' 2>/dev/null
)
for rid in "${RUN_IDS[@]}"; do
gh api -X POST "repos/$REPO/actions/runs/$rid/cancel" >/dev/null 2>&1 || true
done
# 4. Engage deployment freeze.
gh variable set DEPLOY_FREEZE -b true -R "$REPO" >/dev/null
# 5. Lock all GitHub environments.
mapfile -t ENV_NAMES < <(
gh api "repos/$REPO/environments" --jq '.environments[].name' 2>/dev/null
)
for env in "${ENV_NAMES[@]}"; do
gh api -X PUT "repos/$REPO/environments/$env" \
--input "$EMERGENCY_DIR/environment-locked.json" >/dev/null 2>&1 || true
done
# 6. Notify on-call.
if [[ -n "${PAGERDUTY_WEBHOOK:-}" ]]; then
curl -sS -X POST "$PAGERDUTY_WEBHOOK" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg agent "$AGENT_NAME" --arg incident "$INCIDENT_ID" \
'{incident:{title:("Agent approval lockdown: " + $agent), urgency:"high", incident_key:$incident}}')" \
>/dev/null || true
fi
logger -t agent-incident -p user.warning \
"$(jq -n \
--arg event "approval_locked_server" \
--arg agent "$AGENT_NAME" \
--arg repo "$REPO" \
--arg incident "$INCIDENT_ID" \
--arg actor "$(whoami)" \
--arg ts "$(date -Iseconds)" \
'{event:$event, agent:$agent, repo:$repo, incident:$incident, actor:$actor, ts:$ts}')"
echo "Server-side approval gating locked down for $AGENT_NAME on $REPO (incident $INCIDENT_ID)"
branch-protection-locked.jsonview on GitHub{
"required_status_checks": {
"strict": true,
"contexts": ["ci/build", "ci/test", "ci/security"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 4,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true,
"require_last_push_approval": true
},
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false,
"required_conversation_resolution": true,
"lock_branch": true,
"restrictions": {
"users": [],
"teams": ["incident-response"],
"apps": []
}
}
environment-locked.jsonview on GitHub{
"wait_timer": 0,
"deployment_branch_policy": null,
"reviewers": [
{
"type": "Team",
"id": 0
}
],
"_comment": "Substitute reviewers[].id with the GitHub team ID of incident-response. Use: gh api orgs/<org>/teams/incident-response --jq .id"
}
Cell notes
Interventions, Approval gating / Server-side
Trigger. Branch protection bypass detected, deployment freeze breach, CODEOWNERS bypass, force-push to a protected branch.
Authority. On-call plus security review (changes to approval surfaces themselves are sensitive).
Speed target. Under 60 seconds.
Tooling
- -
ghCLI authenticated as a break-glass identity withrepo_adminpermission. - - A PagerDuty webhook (or equivalent) configured for the
incident-responseteam.
Files in this directory
- -
agent-approval-lockdown-server, runbook script. Locks branch protection to require 4 reviewers,enforce_admins: true,lock_branch: true. Disables every workflow in the repo. SetsDEPLOY_FREEZE=true. Locks every GitHub environment to require theincident-responseteam. - -
branch-protection-locked.json, pre-staged branch-protection config applied during the lockdown. Pre-stage at/etc/agents/emergency/branch-protection-locked.json. - -
environment-locked.json, pre-staged GitHub Environment config restricting deployments to the incident-response team. Pre-stage at/etc/agents/emergency/environment-locked.json.
Verification
# 1. Branch protection locked
gh api repos/example-org/agent-config/branches/main/protection \
--jq '{enforce_admins: .enforce_admins.enabled, lock: .lock_branch.enabled, reviews: .required_pull_request_reviews.required_approving_review_count}'
# expected: enforce_admins: true, lock: true, reviews: 4
# 2. Workflows disabled
gh api repos/example-org/agent-config/actions/workflows --jq '.workflows[] | {name, state}'
# expected: all state: disabled_manually
# 3. Freeze active
gh variable list -R example-org/agent-config | grep DEPLOY_FREEZE
# expected: DEPLOY_FREEZE=true
Common mistakes
- -
enforce_adminsset to false in the locked config, the lockdown does not lock admins out. - - Workflow disable applied to wrong repo, emergency triage requires double-checking the
-Rflag. - - Freeze variable set but the apply job does not check it. Verify the workflow reads
vars.DEPLOY_FREEZE. - - Locking out the incident-response team itself by removing them from the restrictions list.
Citation
NIST CSF 2.0 RS.MI-01, RS.CO-02; GV.RR-02 (response dimension). EU AI Act Art. 14, Art. 26. NIST AI RMF GOVERN 4.1, MANAGE 4.1. OWASP ASI02, ASI09.
Primary failure modes
Documented, not hypothetical. A control whose bypass is undocumented is worse than no control, because somebody trusted it.
- enforce_admins false in locked config
- workflow disable does not cancel in-flight runs
- locking out incident-response team itself
Crosswalk
| NIST CSF 2 0 | RS.MI-01, RS.CO-02, GV.RR-02 |
|---|---|
| NIST AI RMF | GOVERN 4.1, MANAGE 4.1 |
| OWASP AGENTIC | ASI02, ASI09 |
| OTHER | EU AI Act Art. 14, Art. 26 |
Cite this cell:
https://agenticcovenants.com/respond/approval-gating/server-side/