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
- 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).
- Use a full user access token (not an app-scoped one) when calling gated service drivers, if the user is the owner.
- 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
- Grant 'service:<driver>:ii:<interface>' matching resolvedDriverName.
- Use a full user token for gated service drivers.
- Watch for alias mismatches between driver name and permission key.
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.