Skip to content

Fast-revocation runbook

Use when a PC's App private key is suspected compromised, a runner is misbehaving, or you need to pull the whole pool offline fast. Ordered fastest-blast-radius first.

TL;DR decision

Situation Action
One PC's key possibly leaked §A revoke that PC's key + §C reprovision
The whole App may be compromised §B nuke the App (kills all 4 PCs at once)
Just want runners off, no compromise §D graceful stop
A rogue runner is registered §E force-deregister by id

§A — Revoke ONE PC's private key (blast radius: that PC only)

Because each PC has its own private key, you can revoke one without touching the others. This is the main reason for per-PC keys.

  1. GitHub → Settings → Developer settings → GitHub Apps → workwingman-ci-runner → General → Private keys.
  2. Find the key for the affected PC and click Delete. Any JWT signed with it is now useless; the watchdog on that PC can no longer mint tokens.
  3. On the affected PC, stop it from thrashing:
    pwsh ./skill/scripts/Runner-Stop.ps1
    
  4. Wipe the DPAPI blob on that PC:
    Remove-Item (Import-PowerShellDataFile ./config.psd1).KeyRef -Force
    
  5. The other 3 PCs keep serving with their own keys — no action needed.
  6. When ready, reissue a fresh key for that PC (§C).

Effect on in-flight tokens: deleting the key stops new JWTs. Registration tokens already minted (≤1 h) remain valid until they expire — so also run §E if you need to kill an already-registered rogue runner immediately.


§B — Nuke the whole App (blast radius: ALL 4 PCs)

Use only if the App itself (not just one key) is compromised.

Option B1 — suspend the installation (reversible, fastest): - GitHub → Settings → Installations → the WorkWingman install → Suspend. All token exchanges (installation + registration) immediately fail for every PC. Reversible via Unsuspend.

Option B2 — delete the App (irreversible): - App → Advanced → Delete GitHub App. Every PC loses auth at once. You will re-do §1 of SETUP.md to rebuild.

After either, run §D on each reachable PC to stop watchdogs, and §E to sweep any runners registered with still-valid (≤1 h) tokens.


§C — Reprovision a fresh key on a PC

  1. GitHub → App → Generate a private key → download new .pem for that PC.
  2. Add it to the KeePass vault (replace the old entry).
  3. On the PC:
    pwsh ./skill/scripts/Provision-Key.ps1 -PemPath C:\temp\ww-app-<pc>-new.pem
    pwsh ./skill/scripts/Runner-Online.ps1 -Once   # sanity check
    

§D — Graceful stop (no compromise, just offline)

pwsh ./skill/scripts/Runner-Stop.ps1
Stops + disables the scheduled tasks, kills local container/process, and force-deregisters this PC's runners. Re-enable with:
Enable-ScheduledTask -TaskName WorkWingmanRunner-Linux-<pc>
Enable-ScheduledTask -TaskName WorkWingmanRunner-Windows-<pc>
Start-ScheduledTask   -TaskName WorkWingmanRunner-Linux-<pc>
Start-ScheduledTask   -TaskName WorkWingmanRunner-Windows-<pc>


§E — Force-deregister a specific rogue runner

Registration tokens live ~1 h, so a leaked one could register an attacker runner. Kill it directly by id:

# List runners to find the offending id:
pwsh ./skill/scripts/Runner-Status.ps1
# Then delete it (Runner-Stop does this for THIS pc's runners automatically):
# Manual, any id:
Import-Module ./watchdog/GitHubApp.psm1
$cfg = Import-PowerShellDataFile ./config.psd1
$rsa = Get-WWAppPrivateKey -KeyRef $cfg.KeyRef -KeySource $cfg.KeySource
$jwt = New-WWAppJwt -AppId $cfg.AppId -Rsa $rsa
$inst = Get-WWInstallationToken -Jwt $jwt -InstallationId $cfg.InstallationId
Invoke-RestMethod -Method DELETE `
  -Uri "https://api.github.com/repos/$($cfg.Repo)/actions/runners/<RUNNER_ID>" `
  -Headers @{ Authorization="token $inst"; Accept='application/vnd.github+json';
              'X-GitHub-Api-Version'='2022-11-28'; 'User-Agent'='ww-runner' }
$rsa.Dispose()

Also GitHub UI: repo → Settings → Actions → Runners → the runner → Remove.


Post-incident

  • Rotate the affected PC's key (§C) even if you only suspended (§B1).
  • Because runners are --ephemeral, each only ever ran one job — check that job's logs for anything the token could have touched (Administration:write is repo-scoped; the token cannot reach other repos or org settings).
  • The App key never entered any container, so container compromise alone does not leak the key — only the ≤1 h registration token in that container.