Appearance
Permissions and Security
Typora's extension model starts from one rule: opening a Markdown file never runs code. Fence bodies, inline text and front matter are data that Typora hands to extensions you installed; nothing in a document executes.
On top of that, every extension runs with least privilege:
- L0 contributions are data that Typora interprets. They can't run code.
- L1 pages run in sandboxed frames on an opaque origin, with
connect-src 'none'and scripts only from the extension's own files. - L2 scripts run in one Worker per extension with their own Content Security Policy (no network, scripts only from the extension) and an allowlist of globals.
- Network, storage, clipboard and document access go through Typora, which checks every call against the extension's declared and granted permissions.
- Extensions never see Typora's internals — the editor DOM, its state, or other extensions. There is nothing to patch.
Declaring permissions
json
"permissions": {
"network": ["https://kroki.io", "https://*.example.com"],
"document": "read",
"workspace": "read",
"clipboard": true,
"ai": true,
"process": ["git"]
}| Permission | Grants | Used by |
|---|---|---|
network: [origins] | HTTP(S) requests to those origins | typora.net.fetch, global fetch in Workers, view.fetch in pages, Kroki renderers |
document: "read" | Read the active document, and the local images it refers to | editor.getSelection, editor.getMarkdown, document.getBlocks, document.readImage; document blocks sent to detail pages; view.reveal |
document: "write" | Read and change the active document (implies read) | editor.insertMarkdown, editor.replaceRange, editor.replaceDocument |
workspace: "read" | List and read files in the folder open in the window | typora.workspace.* |
clipboard: true | Read and write clipboard text | typora.clipboard.* |
ai: true | Send text to the model the user set up in Settings ▸ Intelligence, and get its reply | typora.ai |
process: "*" or process: ["git", …] | Run programs as the user: anything (including the user's shell), or these commands; always the package's own binaries | typora.process, view.process |
Network origins are https://host or http://host, with an optional port and an optional *. prefix for subdomains. They are shown to the user exactly as written. List exact hosts whenever you can.
When the server is the user's own — their blog, a self-hosted Kroki — list "{config.<key>}" instead, naming a string setting. Requests may go to the origin of whatever the user entered there (https://blog.example.com/wp allows https://blog.example.com; without a scheme, https is assumed), and nowhere while it's empty. The setting's default must be empty or one of your listed origins, so only the user can point it somewhere new; the consent dialog says Connect to the server you enter in “Site URL”.
ai uses the user's own model, with the key and endpoint they configured; your extension never sees either. Requests fail while AI features are off or no model is set up, and every request is listed in your extension's log.
process is different from the others: the sandbox stops at the program. A program can do anything the user can. The list form (["git"]) covers those commands by name, looked up on the user's PATH, and the same programs at a full path the user picked in your options (["java"] allows /opt/homebrew/opt/openjdk/bin/java or C:\Program Files\Java\bin\java.exe, never a program with another name). It tells users and reviewers what you run, but it is not a boundary — git runs hooks, and many programs run other programs. Ask for "*" only for a terminal or something like it.
What extension code itself can never do: read or write arbitrary paths, read other extensions' storage, or change Typora settings outside its own.
What users see
- An extension with no permissions (pure L0 data, pages without network) installs and runs without a prompt.
- An extension that declares any permission is installed but doesn't run until the user allows it. Settings ▸ Extensions shows Needs your permission and a consent dialog that lists the permissions in plain language — for example Connect to https://kroki.io, Read and change the open document, Read files in the open folder.
- For Kroki renderers the dialog adds that diagram source is sent to those servers.
- For
aithe dialog says Send text to your AI model (Settings ▸ Intelligence). - An extension that can both read the document and use the network gets an extra warning: it could send your text elsewhere.
- Typora remembers what the user accepted. If an update widens the permission set, the extension stops running until the user reviews and allows the new set.
- An extension with
processalso needs Settings ▸ Extensions ▸ Allow extensions to run programs, which is on by default. While the user has it off, the extension is marked Needs “Allow extensions to run programs” and doesn't run. Its consent dialog says that its programs act as the user.
Ask for the least you need. Users — and store reviewers — are more likely to trust an extension with a short permission list.
Untrusted input
Treat everything from a document or a file as untrusted:
- Parse files inside your page, not in
main: a broken parser in a sandboxed page has no network or file access. - Don't
evalornew Functionanything from a document or the network (the page and Worker policies don't allowevalanyway). - Typora sanitizes SVG and HTML returned by static fence renderers and Kroki: scripts, event handlers, embedded frames and external references are removed before the output enters the document.
- Inline-rule regexes run in a background Worker with a time budget. A rule that blows the budget or hangs is switched off, and the Worker running it is stopped.
Store review
Extensions in the store are reviewed with depth that follows their permissions, and built by Typora's CI from the reviewed source. With process, reviewers read every place a program starts. Store extensions run programs the user already has; prebuilt binaries are not accepted, and CI builds of them from source are not available yet — publish an extension with binaries yourself for now. Extensions installed from a file or folder run in the same sandbox with the same permission prompts.