{"record":{"id":"4b52352bef1134cf","repo":"toeverything/AFFiNE","slug":"access-denied","errorCode":"access_denied","errorMessage":"Invalid internal request","messagePattern":"Invalid internal request","errorType":"exception","errorClass":"AccessDenied","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/auth/guard.ts","lineNumber":99,"sourceCode":"          const method = req.method.toUpperCase();\n          const path = req.path;\n\n          const timestampInRange =\n            payload.ts <= now + INTERNAL_ACCESS_TOKEN_CLOCK_SKEW_MS &&\n            now - payload.ts <= INTERNAL_ACCESS_TOKEN_TTL_MS;\n\n          if (timestampInRange && payload.m === method && payload.p === path) {\n            const nonceKey = `rpc:nonce:${payload.nonce}`;\n            const ok = await this.cache.setnx(nonceKey, 1, {\n              ttl: INTERNAL_ACCESS_TOKEN_TTL_MS,\n            });\n            if (ok) {\n              return true;\n            }\n          }\n        }\n      }\n      throw new AccessDenied('Invalid internal request');\n    }\n\n    // api is public\n    const isPublic = this.reflector.getAllAndOverride<boolean>(\n      PUBLIC_ENTRYPOINT_SYMBOL,\n      [clazz, handler]\n    );\n\n    const authedUser = await this.signIn(req, res, isPublic);\n\n    if (isPublic) {\n      return true;\n    }\n\n    if (!authedUser) {\n      throw new AuthenticationRequired();\n    }\n","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/auth/guard.ts#L81-L117","documentation":"Thrown by the `AuthGuard` for an `@Internal()` (RPC) endpoint when the `x-access-token` is absent, fails to parse, is expired/out of the 5-minute + 30s skew window, the method/path don't match the token payload, or the nonce was already used (replay). HTTP 403 (no_permission).","triggerScenarios":"An inter-service RPC call missing the internal access token, a token whose timestamp drifted beyond the skew/TTL, a replayed token (nonce already seen), or a token minted for a different method/path than the current request.","commonSituations":"Service-to-service caller forgot to attach `x-access-token`, clock skew between signer and verifier >30s, the internal-token signing secret rotated and the caller still uses the old one, or a load balancer retries an idempotent-but-nonce-guarded request.","solutions":["Mint a fresh internal access token per request via `CryptoHelper.parseInternalAccessToken`'s counterpart, including current timestamp, method, path, and a unique nonce.","Sync clocks (NTP) on all services to stay within the 30s skew window.","Do not retry a request with the same token/nonce — generate a new one each time.","Confirm the internal signing secret is consistent across services."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await rpcCall();\n} catch (e) {\n  if (e.code === 'access_denied') {\n    // regenerate token with fresh nonce + timestamp and retry once\n    const token = mintInternalAccessToken({ method, path, nonce: randomUUID() });\n    await rpcCall(token);\n  } else throw e;\n}","preventionTips":["Mint a fresh internal access token per request with current timestamp, exact method/path, and a unique nonce.","Keep service clocks NTP-synced to stay within the 30s skew window.","Never retry with the same token/nonce — generate a new one each call.","Keep the internal signing secret consistent across services."],"tags":["internal","rpc","access-token","forbidden","replay-protection"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}