{"record":{"id":"1ea582e10dda44b7","repo":"HeyPuter/puter","slug":"permission-denied","errorCode":null,"errorMessage":"Permission denied","messagePattern":"Permission denied","errorType":"http","errorClass":"HttpError","httpStatus":403,"severity":"error","filePath":"extensions/appTelemetry.ts","lineNumber":141,"sourceCode":"\n        const app = await this.stores.app.getByUid(app_uuid);\n        if (!app) throw new HttpError(404, 'App not found');\n\n        // The `apps-of-user:<uuid>:write` implicator keys on the owner's\n        // UUID, not the numeric id. Look up the owner explicitly — the raw\n        // app row only carries `owner_user_id`. (v1 got the owner for free\n        // because its entity-storage layer eager-joined the owner row.)\n        const ownerId = (app as { owner_user_id?: number }).owner_user_id;\n        if (!ownerId) throw new HttpError(404, 'App owner not found');\n        const owner = await this.stores.user.getById(ownerId);\n        if (!owner?.uuid) throw new HttpError(404, 'App owner not found');\n\n        const actor = Context.get('actor');\n        if (!actor) throw new HttpError(401, 'Authentication required');\n        const ownsApp = await this.services.permission\n            .check(actor as Actor, `apps-of-user:${owner.uuid}:write`)\n            .catch(() => false);\n        if (!ownsApp) throw new HttpError(403, 'Permission denied');\n\n        const appId = (app as { id: number }).id;\n\n        const users = (await this.clients.db.read(\n            `SELECT u.id, u.username, u.uuid, u.email FROM user_to_app_permissions p\n             INNER JOIN ${this.clients.db.quoteIdentifier('user')} u ON p.user_id = u.id\n             WHERE p.permission = 'flag:app-is-authenticated' AND p.app_id = ?\n             ORDER BY (p.dt IS NOT NULL), p.dt, p.user_id\n             LIMIT ? OFFSET ?`,\n            [appId, safeLimit, safeOffset],\n        )) as Array<{\n            id: number;\n            username: string;\n            uuid: string;\n            email: string | null;\n        }>;\n\n        // Only surface a user's email if *that user* granted this app the","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/extensions/appTelemetry.ts#L123-L159","documentation":"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.","triggerScenarios":"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.","commonSituations":"Submit button enabled before prompt entry; prompt pulled from a query param that is absent; default placeholder text passed through instead of real input.","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."],"exampleFix":"// before\nawait sora.generate({ prompt: form.get('prompt') });\n\n// after\nconst prompt = (form.get('prompt') ?? '').toString().trim();\nif (!prompt) return alert('Prompt is required');\nawait sora.generate({ prompt });","handlingStrategy":"validation","validationCode":"function cleanPrompt(p) {\n  if (typeof p !== 'string') throw new Error('prompt must be a non-empty string');\n  const trimmed = p.trim();\n  if (!trimmed) throw new Error('prompt must be a non-empty string');\n  return trimmed;\n}","typeGuard":"function isNonEmptyPrompt(p) { return typeof p === 'string' && p.trim().length > 0; }","tryCatchPattern":null,"preventionTips":["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."],"tags":["ai-video","openai","sora","validation","prompt"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}