Agentic Covenants

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

  • - gh CLI authenticated as a break-glass identity with repo_admin permission.
  • - A PagerDuty webhook (or equivalent) configured for the incident-response team.

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. Sets DEPLOY_FREEZE=true. Locks every GitHub environment to require the incident-response team.
  • - 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_admins set 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 -R flag.
  • - 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 0RS.MI-01, RS.CO-02, GV.RR-02
NIST AI RMFGOVERN 4.1, MANAGE 4.1
OWASP AGENTICASI02, ASI09
OTHEREU AI Act Art. 14, Art. 26