PoC — esecuzione arbitraria di JavaScript guidata dal frontmatter in Note Toolbar per Obsidian (GHSA-q8cw-3m8c-5pf2, CVE-2026-87002, CVSS 7.0).
{{prop_NAME}} → {{js:}}Stato CVE: richiesto, in attesa di assegnazione. Questa segnalazione è pubblicata come GHSA-q8cw-3m8c-5pf2. All'assegnazione del CVE questo repository viene rinominato
CVE-YYYY-NNNNN-obsidian-note-toolbar-PoCe questo banner viene sostituito con il link al CVE.
| Ricercatore | Dostxodjayev Abdullox (@squeeze440) |
| Advisory | GHSA-q8cw-3m8c-5pf2 |
| CVSS 3.1 | 7.0 (Alto) |
| Debolezza | CWE-94, CWE-1336 |
{{prop_NAME}} → {{js:}}Una neutralizzazione impropria dell'output nel motore di sostituzione delle variabili di Note Toolbar (plugin per Obsidian) consente a un attaccante che controlla il frontmatter YAML di una nota (ad esempio un collaboratore su un vault sincronizzato/condiviso) di ottenere l'esecuzione di codice arbitrario sulla macchina della vittima quando questa si limita ad aprire la nota, iniettando un payload {{js: ...}} in una proprietà del frontmatter a cui fa riferimento l'etichetta/tooltip/link di un elemento della toolbar preesistente e non-script tramite {{prop_NAME}}.
Note Toolbar (note-toolbar) — plugin della community di Obsidian
Repository: https://github.com/chrisgurney/obsidian-note-toolbar
v1.34.12 (commit 520271c3027eb6da38bab10a686b21e14c664c13, 2026-07-29), su Obsidian desktop 1.13.4 su Linux.
7.0 (Alto) — CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
AV:L — il componente vulnerabile (il valutatore JS del plugin, in esecuzione all'interno del renderer Electron di Obsidian) non è esposto alla rete; il payload arriva come contenuto locale del vault/file sincronizzato con qualunque mezzo utilizzi la vittima (Git, Syncthing, Obsidian Sync, cartella condivisa), coerentemente con precedenti segnalazioni di questa famiglia (ad es. obsidian-syncthing-integration, obsidian-codescript-toolkit).AC:H — lo sfruttamento dipende da condizioni fuori dal controllo dell'attaccante che devono già esistere sull'installazione della vittima: (1) l'impostazione "Scripting" del plugin deve essere già abilitata (disattivata per impostazione predefinita), e (2) la vittima deve aver già configurato un elemento della toolbar la cui etichetta, tooltip o link sia un riferimento {{prop_NAME}} nudo a una qualche chiave del frontmatter. Entrambe sono modalità d'uso realistiche e documentate, ma nessuna delle due è quella predefinita.PR:N — l'attaccante non necessita di alcun accesso al sistema della vittima, solo della capacità di far giungere una nota nel vault della vittima con un frontmatter controllato dall'attaccante.UI:R — la vittima deve aprire/visualizzare la nota specifica; non è richiesto alcun clic sull'elemento della toolbar, la toolbar viene renderizzata automaticamente.S:U, C:H/I:H/A:H — il codice viene eseguito con pieno accesso a Node.js (require('child_process'), filesystem, ecc.) come account utente locale che esegue Obsidian.Note Toolbar supporta una variabile documentata {{prop_NAME}} che sostituisce il valore del frontmatter di una nota nell'etichetta, nel tooltip o nel link di un elemento della toolbar (skills/note-toolbar-variables/SKILL.md, wiki Variables.md). Supporta inoltre una variabile {{js: <expr>}} che valuta l'espressione come JavaScript live quando l'impostazione "Scripting" del plugin è abilitata.
Il bug risiede nell'ordine delle operazioni in src/Toolbar/VariableResolver.ts::replaceVars():
{{prop_KEY}} vengono sostituiti con il valore grezzo di frontmatter[KEY] dalla nota corrente — incondizionatamente, indipendentemente dall'impostazione scriptingEnabled, e indipendentemente dalla fonte di quel valore del frontmatter.if (this.ntb.settings.scriptingEnabled)): la funzione verifica poi s.trim().startsWith('{{js:') sulla stringa s già sostituita, e se vera rimuove il wrapper {{js:/}} e passa il resto a JavaScriptAdapter.use() per la valutazione.Poiché il passo 1 viene eseguito prima del passo 2 e riscrive s sul posto, un valore del frontmatter che inizia esso stesso con {{js: e termina con }} viene promosso da "testo visualizzato" a "script eseguito" — anche se l'elemento della toolbar non è mai stato creato come elemento script. Il proprietario dell'elemento della toolbar ha digitato solo {{prop_status}}; il payload eseguibile proviene interamente dal frontmatter della nota stessa.
JavaScriptAdapter.evaluate() (src/Adapters/JavaScriptAdapter.ts:143-205) esegue l'espressione con un vero AsyncFunction non sandboxato:
src/Adapters/Adapter.ts:11
protected static readonly AsyncFunction = (Object.getPrototypeOf(async function(){}) as { constructor: typeof Function }).constructor;
src/Adapters/JavaScriptAdapter.ts:164
const func = new JavaScriptAdapter.AsyncFunction("input", expression);
...
result = await Promise.resolve((func as (...args: unknown[]) => unknown)(args));
Questo è il costruttore AsyncFunction autentico del motore JS, non un interprete/sandbox — all'interno del renderer Electron di Obsidian ha pieno accesso a require()/Node.js, confermato nel PoC sottostante tramite require('child_process').execSync(...).
Fondamentalmente, l'adapter JS è integrato e non richiede alcun plugin complementare (src/Adapters/AdapterManager.ts:59: adapter = this.js; // built-in, doesn't rely on plugin), a differenza dei prefissi di variabile Dataview/Templater/JS-Engine che richiedono l'installazione di quei plugin. Solo l'interruttore "Scripting" del plugin stesso (scriptingEnabled, predefinito false, src/Settings/NoteToolbarSettings.ts:300) lo controlla.
Infine, il percorso di rendering che attiva la sostituzione è automatico, non vincolato a un clic: ToolbarRenderer.ts::renderLItems() chiama this.ntb.vars.resolveText(toolbar, file) (src/Toolbar/ToolbarRenderer.ts:418) ogni volta che una toolbar viene renderizzata per una nota — cioè all'apertura/visualizzazione della nota — il che risolve l'etichetta e il tooltip di ogni elemento tramite replaceVars(). Non è richiesto alcun clic sull'elemento della toolbar interessato.
L'assegnazione toolbar-nota è essa stessa guidata dal frontmatter (impostazione toolbarProp, chiave predefinita notetoolbar, src/Settings/NoteToolbarSettings.ts:317), quindi una nota sincronizzata può selezionare quale toolbar viene renderizzata su di essa — ma ciò non è necessario per lo sfruttamento se la vittima utilizza già una toolbar predefinita o una mappatura di cartella che include l'elemento vulnerabile.
Questo è distinto dal rischio documentato nel SECURITY.md dello stesso manutentore ("User scripts... executes JavaScript provided by the user... intentional and by design") — quello descrive un utente che crea consapevolmente un elemento della toolbar {{js:}}, o che importa consapevolmente una configurazione di toolbar condivisa che ne contiene uno. Qui, l'elemento della toolbar non è affatto un elemento script dal punto di vista di chi lo configura (è una semplice etichetta di visualizzazione di proprietà), e il payload eseguibile arriva tramite il normale contenuto/frontmatter della nota — lo stesso modello di minaccia di contenuto di vault non attendibile di altre segnalazioni di esecuzione di codice da sync/config di Obsidian (obsidian-syncthing-integration, obsidian-codescript-toolkit).