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.
- GitHub → Settings → Developer settings → GitHub Apps → workwingman-ci-runner → General → Private keys.
- 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.
- On the affected PC, stop it from thrashing:
pwsh ./skill/scripts/Runner-Stop.ps1 - Wipe the DPAPI blob on that PC:
Remove-Item (Import-PowerShellDataFile ./config.psd1).KeyRef -Force - The other 3 PCs keep serving with their own keys — no action needed.
- 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¶
- GitHub → App → Generate a private key → download new
.pemfor that PC. - Add it to the KeePass vault (replace the old entry).
- 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
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.