{"record":{"id":"fea83e1035353036","repo":"GraphiteEditor/Graphite","slug":"permission-denied","errorCode":null,"errorMessage":"Permission denied","messagePattern":"Permission denied","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/utility-functions/clipboard.ts","lineNumber":94,"sourceCode":"\t\trange.setStartAfter(textNode);\n\t\trange.collapse(true);\n\n\t\tselection.removeAllRanges();\n\t\tselection.addRange(range);\n\t}\n\n\telement.dispatchEvent(new Event(\"input\", { bubbles: true }));\n}\n\nexport async function triggerClipboardRead(editor: EditorWrapper) {\n\t// In the try block, attempt to read from the Clipboard API, which may not have permission and may not be supported in all browsers\n\t// In the catch block, explain to the user why the paste failed and how to fix or work around the problem\n\ttry {\n\t\t// Attempt to check if the clipboard permission is denied, and throw an error if that is the case\n\t\t// In Firefox, the `clipboard-read` permission isn't supported, so attempting to query it throws an error\n\t\t// In Safari, the entire Permissions API isn't supported, so the query never occurs and this block is skipped without an error and we assume we might have permission\n\t\tconst permission = await navigator.permissions?.query({ name: \"clipboard-read\" });\n\t\tif (permission?.state === \"denied\") throw new Error(\"Permission denied\");\n\n\t\t// Read the clipboard contents if the Clipboard API is available\n\t\tconst clipboardItems = await navigator.clipboard.read();\n\t\tif (!clipboardItems) throw new Error(\"Clipboard API unsupported\");\n\n\t\t// Read any layer data or images from the clipboard\n\t\tconst success = await Promise.any(\n\t\t\tArray.from(clipboardItems).map(async (item) => {\n\t\t\t\t// Read plain text and, if it is a layer, pass it to the editor\n\t\t\t\tif (item.types.includes(\"text/plain\")) {\n\t\t\t\t\tconst blob = await item.getType(\"text/plain\");\n\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\treader.onload = () => {\n\t\t\t\t\t\tif (typeof reader.result === \"string\") editor.pasteText(reader.result);\n\t\t\t\t\t};\n\t\t\t\t\treader.readAsText(blob);\n\t\t\t\t\treturn true;\n\t\t\t\t}","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/frontend/src/utility-functions/clipboard.ts#L76-L112","documentation":"Raised in triggerClipboardRead (the menu-driven paste flow) when navigator.permissions.query({ name: \"clipboard-read\" }) reports state \"denied\" — the user or browser policy has blocked clipboard reads for the origin. The surrounding catch maps this exact message to a dialog instructing the user to re-enable the permission in the browser's site settings.","triggerScenarios":"The user clicked \"Block\" on the clipboard permission prompt or disabled clipboard read in site permission settings; enterprise policy (e.g. Chrome's ClipboardReadWrite URL allowlist / DefaultClipboardSetting) denies read for the origin.","commonSituations":"Sites embedded in iframes missing allow=\"clipboard-read\"; permission revoked long ago and forgotten; managed/kiosk browsers where the origin is not allowlisted. Mostly a Chromium phenomenon — in Firefox the permissions query itself throws (routed to the \"unsupported\" dialog) and Safari skips the check entirely.","solutions":["Re-enable clipboard read via the browser's site settings (icon just left of the URL bar) — exactly what the error dialog instructs","For enterprise-managed browsers, have the admin add the site to the ClipboardReadWrite allowlist policy","Use Ctrl+V instead — the paste event does not depend on this permission","For iframe-embedded apps, add allow=\"clipboard-read\" to the host iframe"],"exampleFix":"// before\nconst permission = await navigator.permissions?.query({ name: \"clipboard-read\" });\nif (permission?.state === \"denied\") throw new Error(\"Permission denied\");\n\n// after: treat a throwing query (Firefox) as \"unknown\" instead of conflating it with denial\nlet state = \"prompt\";\ntry {\n\tstate = (await navigator.permissions?.query({ name: \"clipboard-read\" }))?.state ?? \"prompt\";\n} catch { /* Firefox throws on this query name; assume not denied */ }\nif (state === \"denied\") throw new Error(\"Permission denied\");","handlingStrategy":"validation","validationCode":"// Pre-flight: is clipboard read allowed before attempting the paste menu action?\nasync function clipboardReadAllowed(): Promise<boolean> {\n\ttry {\n\t\tconst p = await navigator.permissions?.query({ name: \"clipboard-read\" as PermissionName });\n\t\treturn p?.state !== \"denied\";\n\t} catch {\n\t\treturn true; // Firefox throws on this query name; unsupported does not mean denied\n\t}\n}","typeGuard":null,"tryCatchPattern":"// Keep the existing message-based mapping in the catch so any thrown variant still maps to the right dialog\nconst message = Object.entries(matchMessage).find(([key]) => String(err).includes(key))?.[1] || String(err);\neditor.errorDialog(\"Cannot access clipboard\", message);","preventionTips":["Only invoke clipboard reads from a real user gesture (menu click)","Treat a permissions.query() throw as \"unknown\", never as \"denied\" (Firefox behavior)","Keep the Ctrl+V paste-event path available as the permission-independent fallback"],"tags":["clipboard","permissions","browser","paste"],"backgroundTag":"clipboard-permission-denied","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}