HeyPuter/puter · error · HttpError

forbidden

forbidden

Error message

Permission denied for ${ifaceName}:${method}

What it means

Thrown by POST /drivers/call (403 forbidden) when permService.check(actor, 'service:<resolvedDriverName>:ii:<ifaceName>') returns false. The driver/method exist and are callable, but the actor lacks the per-driver service permission. This is the main authorization gate for driver RPC.

Source

Thrown at src/backend/controllers/drivers/DriverController.ts:317

            const permService = this.services.permission as unknown as
                PermissionService | undefined;
            if (permService) {
                // Build via PermissionUtil.join so any `:` in a driver or
                // interface name is escaped — raw interpolation would let a
                // crafted name shift permission-segment boundaries and match
                // a broader/narrower parent than intended in the scan logic.
                const permKey = PermissionUtil.join(
                    'service',
                    String(resolvedDriverName),
                    'ii',
                    ifaceName,
                );
                const hasPermission = await permService.check(
                    req.actor,
                    permKey,
                );
                if (!hasPermission) {
                    throw new HttpError(
                        403,
                        `Permission denied for ${ifaceName}:${method}`,
                        {
                            legacyCode: 'forbidden',
                        },
                    );
                }
            }
        }

        // Per-method rate-limit and concurrent specs both live on the
        // driver's resolved meta (set by `@Driver({ rateLimit, concurrent })`
        // or imperative fields). Rate-limit is single-shot; concurrent
        // acquires a slot that must be released when the response is done
        // — we hook `res.finish` / `res.close` for that so streamed
        // responses hold their slot until the stream drains, and aborted
        // requests still give the slot back.
        const rateLimitSpec = resolveDriverMethodRateLimit(

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Grant the permission 'service:<driver>:ii:<interface>' to the actor/user, matching the driver's resolved driverName (instance field, then prototype meta, then requested name).
  2. Use a full user access token (not an app-scoped one) when calling gated service drivers, if the user is the owner.
  3. Confirm the resolvedDriverName used in the key matches what the driver declares — aliases cause silent key mismatches.

Example fix

// no client code fix — this is a permission grant
await permService.grant(user, 'service:puter-chat-completion:ii:puter-chat-completion');
Defensive patterns

Strategy: validation

Validate before calling

// server-side: confirm permission key matches resolvedDriverName
const has = await permService.check(actor, `service:${driverName}:ii:${ifaceName}`);
if (!has) throw new ForbiddenError('needs service permission');

Prevention

When it happens

Trigger: A user/token calling a driver method without having been granted the matching 'service:<driver>:ii:<interface>' permission; calling a gated AI/external driver via a limited-scope token; a permission record that was revoked or never provisioned.

Common situations: App token missing the service permission for an AI driver; new driver deployed without granting the permission to existing users; permission key built with a different driver name than resolvedDriverName (e.g. alias mismatch).

Related errors


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