{"record":{"id":"84bf00b9b9bc7931","repo":"paperclipai/paperclip","slug":"cloud-runtime-identity-assertion-is-expired-or-has","errorCode":null,"errorMessage":"Cloud runtime identity assertion is expired or has an invalid lifetime","messagePattern":"Cloud runtime identity assertion is expired or has an invalid lifetime","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/cloud-runtime-identity.ts","lineNumber":310,"sourceCode":"    || typeof payload.sub !== \"string\"\n    || typeof payload.claimId !== \"string\"\n    || typeof payload.previousOrigin !== \"string\"\n    || typeof payload.canonicalOrigin !== \"string\"\n    || typeof payload.stackSlug !== \"string\"\n    || typeof payload.iat !== \"number\"\n    || !Number.isInteger(payload.iat)\n    || typeof payload.exp !== \"number\"\n    || !Number.isInteger(payload.exp)\n  ) {\n    throw new Error(\"Cloud runtime identity claims are incomplete\");\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 > MAX_ASSERTION_LIFETIME_SECONDS\n  ) {\n    throw new Error(\"Cloud runtime identity assertion is expired or has an invalid lifetime\");\n  }\n  return payload as RuntimeIdentityClaims;\n}\n\n/** Verify that an assertion is signed for this exact, still-unclaimed instance. */\nexport function verifyCloudRuntimeIdentityAssertion(input: {\n  compactJws: string;\n  env?: NodeJS.ProcessEnv;\n  now?: Date;\n  expectedPreviousOrigin: string | null;\n}): RuntimeIdentityClaims {\n  const env = input.env ?? process.env;\n  const claims = verifyClaims({ compactJws: input.compactJws, env, now: input.now ?? new Date() });\n  const configuredStackId = nonEmpty(env.PAPERCLIP_CLOUD_STACK_ID);\n  if (!configuredStackId || claims.sub !== configuredStackId) {\n    throw new Error(\"Cloud runtime identity stack does not match this instance\");\n  }\n  const previousOrigin = exactHttpsOrigin(claims.previousOrigin);","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/cloud-runtime-identity.ts#L292-L328","documentation":"verifyClaims in cloud-runtime-identity.ts validates the lifetime window of a signed runtime identity JWS assertion before accepting the runtime identity claims. It throws when the assertion is expired, stamped in the future beyond allowed clock skew, or has an exp/iat span that is empty or exceeds MAX_ASSERTION_LIFETIME_SECONDS. This prevents replay of stale or over-long-lived identity assertions when claiming a cloud runtime instance.","triggerScenarios":"Calling verifyClaims (directly or via verifyCloudRuntimeIdentityAssertion/applyCloudRuntimeIdentityAssertion) with a compactJws whose payload.exp <= now, whose iat is more than MAX_CLOCK_SKEW_SECONDS in the future, whose exp <= iat, or whose lifetime exceeds MAX_ASSERTION_LIFETIME_SECONDS.","commonSituations":"Minter and verifier clocks drifted apart; an assertion was cached and reused after expiry; a minting service issued assertions with a too-long or inverted lifetime (iat after exp); an old assertion snapshot was retried after a delay.","solutions":["Re-mint a fresh runtime identity assertion and retry the claim immediately","Synchronize clock (NTP) on both the minting service and the instance","Fix the minter to set iat < exp within MAX_ASSERTION_LIFETIME_SECONDS","Verify no stale assertion is being persisted/replayed across restarts"],"exampleFix":"// before\nconst jws = mintAssertion({ iat: now - 3600, exp: now + 3600 });\n// after\nconst iat = Math.floor(Date.now() / 1000);\nconst jws = mintAssertion({ iat, exp: iat + 300 });","handlingStrategy":"validation","validationCode":"const payload = decodeJwtPayload(jws);\nconst now = Math.floor(Date.now()/1000);\nconst ok = payload.exp > now && payload.iat <= now + 60 && payload.exp > payload.iat && (payload.exp - payload.iat) <= 600;\nif (!ok) throw new Error('assertion lifetime invalid; re-mint before applying');","typeGuard":"function hasValidLifetime(p: {iat:number;exp:number}, nowSec:number): boolean {\n  return p.exp > nowSec && p.iat <= nowSec + 60 && p.exp > p.iat && p.exp - p.iat <= 600;\n}","tryCatchPattern":"try {\n  await applyCloudRuntimeIdentityAssertion({ db, compactJws: jws });\n} catch (e) {\n  if (e.message.includes('invalid lifetime')) jws = reMintAssertion(); // re-mint and retry once\n  else throw e;\n}","preventionTips":["Mint assertions with short lifetimes well under MAX_ASSERTION_LIFETIME_SECONDS and use immediately","Run NTP on all minting and verifying hosts","Never cache or persist assertions across their exp","Unit-test the minter's iat/exp ordering"],"tags":["auth","jwt","clock-skew"],"backgroundTag":"jwt-token-expired","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}