HeyPuter/puter · warning · HttpError

forbidden

forbidden

Error message

Forbidden

What it means

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.

Source

Thrown at src/backend/controllers/wisp/WispController.ts:150

        const isGuest = Boolean(decoded.guest);
        let user: Record<string, unknown> | null = null;
        if (!isGuest && decoded.user_uid) {
            user = await this.stores.user.getByUuid(String(decoded.user_uid));
        }

        const event: Record<string, unknown> = {
            allow: true,
            policy: { allow: true },
            guest: isGuest,
            user,
        };
        // emitAndWait so async listeners can fetch policy data before
        // mutating `event.allow` / `event.policy`; plain emit would return
        // control before any awaited work completed.
        await this.clients.event.emitAndWait('wisp.get-policy', event, {});

        if (!event.allow) {
            throw new HttpError(403, 'Forbidden', { legacyCode: 'forbidden' });
        }

        res.json(event.policy);
    };

    #wispConfig(): NonNullable<typeof this.config.wisp> {
        return this.config.wisp ?? {};
    }
}

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Inspect every listener registered on 'wisp.get-policy' and the config they read.
  2. Confirm the calling user/guest is permitted by the active policy.
  3. If the deny is unintended, fix or disable the offending extension.
  4. Check extension logs for the policy decision rationale.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  policy = await verifyWispPolicy(token);
} catch (e) {
  if (e.code === 'forbidden') {
    // token was valid but an extension policy denied access — surface to user/admin
    showPolicyDenied(e);
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/32a4ed64cf9fc2b9. Report an issue: GitHub.