Overview
The rollback endpoint allows administrators to close all pull requests created during a burst attack window. This feature is designed to clean up spam PRs created by bots or coordinated abuse campaigns.
This endpoint is protected by admin authentication and strict rate limiting (5 requests/minute).
Endpoint
Authentication
The endpoint accepts two forms of authentication:
Static password - The PANIC_PASSWORD environment variable configured during deployment
Single-use action token - Time-limited tokens (10 minutes TTL) generated for ntfy alert action buttons
Request Body
Admin password for rollback control. Must match the PANIC_PASSWORD environment variable.
Single-use action token generated by the system. Expires after 10 minutes.
You must provide either password or token, but not both.
Response
Number of PRs successfully closed.
Number of PRs that failed to close.
Array of PR URLs that were successfully closed.
Array of PR URLs that failed to close (with error details logged server-side).
Behavior
2-Hour Window
The rollback mechanism tracks PRs created during burst activity. PRs are registered for rollback only when:
A global burst alert is active (triggered when suspicious activity is detected)
The PR is less than 2 hours old (older entries are automatically pruned)
This ensures rollback only affects recent burst activity, not legitimate PRs.
Concurrent Processing
PRs are closed in parallel using up to 5 concurrent workers to minimize API rate limits and processing time.
Rate Limiting
The rollback endpoint has its own rate limit:
5 requests per minute (regardless of IP)
Exceeding this limit returns 429 Too Many Requests
Examples
Close Burst PRs
Response: Success (200 OK)
Response: No PRs to Close (200 OK)
Error: Invalid Credentials (401)
Error: Rate Limit Exceeded (429)
Error: Invalid Payload (400)
curl -X POST https://gitgost.leapcell.app/admin/rollback \
-H "Content-Type: application/json" \
-d '{"password":"<PANIC_PASSWORD>"}'
How PRs Are Tracked
From handlers.go:332-349, PRs are registered during push operations:
// Register PR URL for potential burst rollback only while a burst alert is active;
// prune entries older than TTL regardless.
if isGlobalBurstAlertActive () {
nowPR := time . Now ()
recentBurstPRsMu . Lock ()
cutoffPR := nowPR . Add ( - recentBurstPRsTTL )
newURLs := recentBurstPRs [: 0 ]
newAts := recentBurstPRsAt [: 0 ]
for i , at := range recentBurstPRsAt {
if at . After ( cutoffPR ) {
newURLs = append ( newURLs , recentBurstPRs [ i ])
newAts = append ( newAts , at )
}
}
newURLs = append ( newURLs , prURL )
newAts = append ( newAts , nowPR )
recentBurstPRs = newURLs
recentBurstPRsAt = newAts
recentBurstPRsMu . Unlock ()
}
Implementation Details
From handlers.go:826-909:
func RollbackBurstHandler ( c * gin . Context ) {
var req struct {
Password string `json:"password"`
Token string `json:"token"`
}
if err := c . ShouldBindJSON ( & req ); err != nil {
c . JSON ( http . StatusBadRequest , gin . H { "error" : "invalid payload" })
return
}
// Accept either the static password or a valid single-use action token
authorized := ( panicPassword != "" && req . Password == panicPassword ) ||
( req . Token != "" && consumeActionToken ( req . Token ))
if ! authorized {
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "invalid credentials" })
return
}
// Rate limit: max rollbackLimitMax calls per rollbackLimitWin
now := time . Now ()
rollbackLimitMu . Lock ()
valid := rollbackLimitTimes [: 0 ]
for _ , t := range rollbackLimitTimes {
if now . Sub ( t ) < rollbackLimitWin {
valid = append ( valid , t )
}
}
valid = append ( valid , now )
rollbackLimitTimes = valid
exceeded := len ( valid ) > rollbackLimitMax
rollbackLimitMu . Unlock ()
if exceeded {
c . JSON ( http . StatusTooManyRequests , gin . H { "error" : "rollback rate limit exceeded" })
return
}
recentBurstPRsMu . Lock ()
toClose := make ([] string , len ( recentBurstPRs ))
copy ( toClose , recentBurstPRs )
recentBurstPRs = recentBurstPRs [: 0 ]
recentBurstPRsAt = recentBurstPRsAt [: 0 ]
recentBurstPRsMu . Unlock ()
if len ( toClose ) == 0 {
c . JSON ( http . StatusOK , gin . H { "closed" : 0 , "message" : "no burst PRs to close" })
return
}
const maxCloseWorkers = 5
closeConcurrency := make ( chan struct {}, maxCloseWorkers )
var (
wg sync . WaitGroup
mu sync . Mutex
closed [] string
failed [] string
)
for _ , prURL := range toClose {
wg . Add ( 1 )
go func ( u string ) {
defer wg . Done ()
closeConcurrency <- struct {}{}
defer func () { <- closeConcurrency }()
if err := github . ClosePRByURL ( u ); err != nil {
utils . Log ( "rollback: failed to close %s : %v " , u , err )
mu . Lock ()
failed = append ( failed , u )
mu . Unlock ()
} else {
mu . Lock ()
closed = append ( closed , u )
mu . Unlock ()
}
}( prURL )
}
wg . Wait ()
utils . Log ( "rollback: closed %d PRs, failed %d " , len ( closed ), len ( failed ))
c . JSON ( http . StatusOK , gin . H {
"closed" : len ( closed ),
"failed" : len ( failed ),
"closed_urls" : closed ,
"failed_urls" : failed ,
})
}
When to Use Rollback
Use the rollback endpoint when:
You’ve received a burst alert notification from ntfy
You’ve identified a pattern of spam PRs in the recent activity logs
You’ve activated panic mode and want to clean up the damage
You need to close multiple abusive PRs quickly without manual GitHub UI interaction
Rollback only affects PRs created during the active burst window (up to 2 hours). Legitimate PRs created before the attack are unaffected.