{"record":{"id":"32a4ed64cf9fc2b9","repo":"HeyPuter/puter","slug":"forbidden-32a4ed","errorCode":"forbidden","errorMessage":"Forbidden","messagePattern":"Forbidden","errorType":"exception","errorClass":"HttpError","httpStatus":403,"severity":"warning","filePath":"src/backend/controllers/wisp/WispController.ts","lineNumber":150,"sourceCode":"        const isGuest = Boolean(decoded.guest);\n        let user: Record<string, unknown> | null = null;\n        if (!isGuest && decoded.user_uid) {\n            user = await this.stores.user.getByUuid(String(decoded.user_uid));\n        }\n\n        const event: Record<string, unknown> = {\n            allow: true,\n            policy: { allow: true },\n            guest: isGuest,\n            user,\n        };\n        // emitAndWait so async listeners can fetch policy data before\n        // mutating `event.allow` / `event.policy`; plain emit would return\n        // control before any awaited work completed.\n        await this.clients.event.emitAndWait('wisp.get-policy', event, {});\n\n        if (!event.allow) {\n            throw new HttpError(403, 'Forbidden', { legacyCode: 'forbidden' });\n        }\n\n        res.json(event.policy);\n    };\n\n    #wispConfig(): NonNullable<typeof this.config.wisp> {\n        return this.config.wisp ?? {};\n    }\n}\n","sourceCodeStart":132,"sourceCodeEnd":160,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/wisp/WispController.ts#L132-L160","documentation":"Thrown after a valid wisp token when an extension listener on the `wisp.get-policy` event set `event.allow` falsy. The token was fine; an installed extension's policy logic denied the relay. The deny happens via `emitAndWait`, so async listeners that fetch policy data fully resolve before this check.","triggerScenarios":"A backend extension registered with `extension.on('wisp.get-policy', ...)` mutates `event.allow` (or `event.policy.allow`) to false based on the guest/user/policy payload — e.g. guest access disabled, or the resolved user not permitted by an org policy.","commonSituations":"Self-hosted instance with a custom policy/allowlist extension; admin disabled guest wisp relay; an enterprise policy extension blocks certain users; a buggy listener accidentally setting allow=false.","solutions":["Inspect every listener registered on 'wisp.get-policy' and the config they read.","Confirm the calling user/guest is permitted by the active policy.","If the deny is unintended, fix or disable the offending extension.","Check extension logs for the policy decision rationale."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  policy = await verifyWispPolicy(token);\n} catch (e) {\n  if (e.code === 'forbidden') {\n    // token was valid but an extension policy denied access — surface to user/admin\n    showPolicyDenied(e);\n  } else throw e;\n}","preventionTips":["Document which 'wisp.get-policy' extensions are installed and their allow/deny rules.","Log the policy decision in the extension so denials are diagnosable.","Test policy extensions against guest vs. authenticated users before deploy."],"tags":["wisp","policy","extension","forbidden","rbac"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}