HeyPuter/puter · error · HttpError
Permission denied
Error message
Permission denied
What it means
OpenAIVideoProvider.generate (Sora) validates that `prompt` is a non-empty string before selecting the model or honoring test_mode. Identical guard to the other providers — non-string or whitespace-only prompt is rejected with 400 bad_request as the very first check.
Source
Thrown at extensions/appTelemetry.ts:141
const app = await this.stores.app.getByUid(app_uuid);
if (!app) throw new HttpError(404, 'App not found');
// The `apps-of-user:<uuid>:write` implicator keys on the owner's
// UUID, not the numeric id. Look up the owner explicitly — the raw
// app row only carries `owner_user_id`. (v1 got the owner for free
// because its entity-storage layer eager-joined the owner row.)
const ownerId = (app as { owner_user_id?: number }).owner_user_id;
if (!ownerId) throw new HttpError(404, 'App owner not found');
const owner = await this.stores.user.getById(ownerId);
if (!owner?.uuid) throw new HttpError(404, 'App owner not found');
const actor = Context.get('actor');
if (!actor) throw new HttpError(401, 'Authentication required');
const ownsApp = await this.services.permission
.check(actor as Actor, `apps-of-user:${owner.uuid}:write`)
.catch(() => false);
if (!ownsApp) throw new HttpError(403, 'Permission denied');
const appId = (app as { id: number }).id;
const users = (await this.clients.db.read(
`SELECT u.id, u.username, u.uuid, u.email FROM user_to_app_permissions p
INNER JOIN ${this.clients.db.quoteIdentifier('user')} u ON p.user_id = u.id
WHERE p.permission = 'flag:app-is-authenticated' AND p.app_id = ?
ORDER BY (p.dt IS NOT NULL), p.dt, p.user_id
LIMIT ? OFFSET ?`,
[appId, safeLimit, safeOffset],
)) as Array<{
id: number;
username: string;
uuid: string;
email: string | null;
}>;
// Only surface a user's email if *that user* granted this app theView on GitHub (pinned to 908ec23eda)
Solutions
- Trim and assert non-empty prompt before calling generate().
- Gate the UI submit on a non-empty prompt field.
- Coerce input via String(value).trim() at the form boundary.
- Verify prompt is the `prompt` key on the params object, not a positional arg.
Example fix
// before
await sora.generate({ prompt: form.get('prompt') });
// after
const prompt = (form.get('prompt') ?? '').toString().trim();
if (!prompt) return alert('Prompt is required');
await sora.generate({ prompt }); Defensive patterns
Strategy: validation
Validate before calling
function cleanPrompt(p) {
if (typeof p !== 'string') throw new Error('prompt must be a non-empty string');
const trimmed = p.trim();
if (!trimmed) throw new Error('prompt must be a non-empty string');
return trimmed;
} Type guard
function isNonEmptyPrompt(p) { return typeof p === 'string' && p.trim().length > 0; } Prevention
- Gate submit on a non-empty prompt field.
- Trim and validate at the form boundary.
- Pass prompt as the `prompt` key, not a positional arg.
When it happens
Trigger: Calling the Sora generate path with prompt omitted, null, undefined, non-string, or empty/whitespace. Typically a missing form value or a prompt assembled from an unset variable.
Common situations: Submit button enabled before prompt entry; prompt pulled from a query param that is absent; default placeholder text passed through instead of real input.
Related errors
- App owner not found
- Authentication required
- ${key} must be an integer between ${min} and ${max}
- bad_request
- bad_request
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/1ea582e10dda44b7.
Report an issue: GitHub.