{"record":{"id":"f24520f37c410c0f","repo":"paperclipai/paperclip","slug":"cloud-control-assertion-has-already-been-used","errorCode":null,"errorMessage":"Cloud control assertion has already been used","messagePattern":"Cloud control assertion has already been used","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/cloud-runtime-identity.ts","lineNumber":576,"sourceCode":"    !payload.requestId\n    || payload.requestId.trim() !== payload.requestId\n    || payload.requestId.length > 256\n  ) {\n    throw new Error(\"Cloud control assertion request id is invalid\");\n  }\n  if (\n    payload.exp <= nowSeconds\n    || payload.iat > nowSeconds + MAX_CLOCK_SKEW_SECONDS\n    || payload.exp <= payload.iat\n    || payload.exp - payload.iat > CLOUD_CONTROL_MAX_LIFETIME_SECONDS\n  ) {\n    throw new Error(\"Cloud control assertion is expired or has an invalid lifetime\");\n  }\n  // Consumed LAST, only after every other check passed: a rejected\n  // assertion must not burn its request id, or an attacker could deny a\n  // legitimate call by replaying a mangled copy of it first.\n  if (!consumeControlRequestId(payload.requestId, payload.exp + MAX_CLOCK_SKEW_SECONDS, now.getTime())) {\n    throw new Error(\"Cloud control assertion has already been used\");\n  }\n  return payload as CloudControlClaims;\n}\n","sourceCodeStart":558,"sourceCodeEnd":580,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/cloud-runtime-identity.ts#L558-L580","documentation":"verifyCloudControlAssertion validates a signed cloud-control assertion (claims + lifetime + requestId). After all semantic checks pass, it calls consumeControlRequestId to atomically burn the assertion's request id with an expiry of payload.exp + MAX_CLOCK_SKEW_SECONDS. If that id was already consumed (replay), it throws 'Cloud control assertion has already been used' — a replay-protection guard so each assertion authorizes exactly one request.","triggerScenarios":"Reusing the same signed assertion for a second cloud-control API call; a client retrying a request with the identical assertion after a first (even successful or failed-late) attempt already consumed the requestId; two concurrent requests sharing one assertion racing on consumeControlRequestId.","commonSituations":"Client-side retry logic that replays the same assertion instead of minting a fresh one; load balancer/proxy retries duplicating a request; clocks far out of skew causing clients to believe an assertion is still valid; test harnesses caching a single assertion across multiple calls.","solutions":["Mint a fresh assertion with a new requestId for each cloud-control request","If retrying, request a new assertion from the control plane before re-sending","Synchronize clocks (NTP) and ensure assertion lifetimes exceed plausible retry windows only via new requestIds, not reuse","Check that no middleware/duplex layer double-invokes cloudControlMiddleware for one request"],"exampleFix":"// before\nawait fetch(url, { headers: { authorization: `Bearer ${cachedAssertion}` } });\nawait fetch(url, { headers: { authorization: `Bearer ${cachedAssertion}` } }); // replay\n// after\nawait fetch(url, { headers: { authorization: `Bearer ${await mintAssertion()}` } }); // fresh requestId per call","handlingStrategy":"try-catch","validationCode":"// client-side: track used requestIds you generated\nif (usedRequestIds.has(assertion.requestId)) {\n  assertion = await mintAssertion(); // mint fresh before sending\n}","typeGuard":"function isAssertionReplayError(e: unknown): e is Error {\n  return e instanceof Error && /assertion has already been used/.test(e.message);\n}","tryCatchPattern":"try {\n  await cloudControlCall(assertion);\n} catch (e) {\n  if (isAssertionReplayError(e)) {\n    const fresh = await mintAssertion();\n    return cloudControlCall(fresh); // do NOT retry the consumed one\n  }\n  throw e;\n}","preventionTips":["Always mint a new assertion (new requestId) per request; never cache one across calls","Configure retry layers (proxies, LBs) to re-request an assertion, not replay it","Keep clocks NTP-synced so exp/skew logic behaves predictably","Log requestId on failure to confirm whether a duplicate send happened"],"tags":["security","replay-protection","auth"],"backgroundTag":"jwt-token-expired","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}