What
DELETE /api/plugins/grants in server/src/plugins/routes.ts:803-812 checks if (!kind || !ref || !agentId).
Query params are always strings, and " " is truthy, so DELETE /grants?kind=mcp&ref=%20%20&agentId=%20%20 skips the 400, calls store.revoke with whitespace, deletes zero rows (exact-match delete), still writes a plugin_revoked audit row naming whitespace, and answers 200 {ok:true}.
The twin POST /grants on the same file (lines 778-784) already does typeof === "string" && .trim() → 400 with "A kind, a ref and a Bot are required.".
Repro
DELETE /api/plugins/grants?kind=mcp&ref=%20%20&agentId=bot-1 → 200 ok:true, zero rows deleted, junk audit row.
- Same with whitespace
agentId.
Expected: 400 with the same message as POST, no DB delete, no audit row.
Where it runs
Stateless request validation in the server process. Same 400 on every replica; the existing revoke path (exact-match delete + audit row) is unchanged for valid input.
Fix sketch
Trim-check ref/agentId on DELETE like POST does, pass trimmed values to enablementRefusal/revoke. Add route tests: whitespace ref/agentId → 400 + store never called; valid still 200.
What
DELETE /api/plugins/grantsinserver/src/plugins/routes.ts:803-812checksif (!kind || !ref || !agentId).Query params are always strings, and
" "is truthy, soDELETE /grants?kind=mcp&ref=%20%20&agentId=%20%20skips the 400, callsstore.revokewith whitespace, deletes zero rows (exact-match delete), still writes aplugin_revokedaudit row naming whitespace, and answers200 {ok:true}.The twin
POST /grantson the same file (lines 778-784) already doestypeof === "string" && .trim()→ 400 with"A kind, a ref and a Bot are required.".Repro
DELETE /api/plugins/grants?kind=mcp&ref=%20%20&agentId=bot-1→ 200 ok:true, zero rows deleted, junk audit row.agentId.Expected: 400 with the same message as POST, no DB delete, no audit row.
Where it runs
Stateless request validation in the server process. Same 400 on every replica; the existing revoke path (exact-match delete + audit row) is unchanged for valid input.
Fix sketch
Trim-check
ref/agentIdon DELETE like POST does, pass trimmed values toenablementRefusal/revoke. Add route tests: whitespace ref/agentId → 400 + store never called; valid still 200.