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 the

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Trim and assert non-empty prompt before calling generate().
  2. Gate the UI submit on a non-empty prompt field.
  3. Coerce input via String(value).trim() at the form boundary.
  4. 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

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


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