Dashboard isolation
A published dashboard is arbitrary author-written HTML, so it is served in a sandbox that cannot reach your Dashies session. What that costs, precisely.
A Dashies dashboard is arbitrary HTML that somebody's AI wrote. It is supposed to run scripts: that is how the charts draw. So the interesting question is not whether script runs, but what it can reach.
The problem being solved
Published dashboards are served from the same domain as the Dashies app. Without a countermeasure, a script inside anyone's dashboard would share an origin with the application, and could read a viewer's stored session token, their cookies, and make authenticated requests as them. Opening a colleague's dashboard would be enough.
The countermeasure
Every dashboard is served with this response header:
Content-Security-Policy: sandbox allow-scripts
That single directive puts the document in an opaque origin. It gets a fresh,
unique origin that matches nothing, including the application's. Scripts run,
because allow-scripts is opted in. Nothing else is.
Concretely, and these are properties the dashboard runtime itself is written around:
- No storage of any kind.
localStorage,sessionStorage, cookies, and IndexedDB are all unavailable. An opaque origin has no storage to belong to. - No same-origin authenticated request. A request back to the Dashies application is cross-origin from an opaque origin and carries no session.
- No new windows or tabs. Opening an auxiliary browsing context is not
granted, so a link with
target="_blank"is silently swallowed. See the exception below.
Private dashboards additionally refuse to be framed at all, so an attacker's page cannot wrap one and harvest what a tricked viewer clicks.
The one place Dashies frames a dashboard itself, the version preview inside the app, keeps the sandbox exactly as it is and relaxes only who may frame it, to the Dashies app and nothing else. A previewed dashboard is no less isolated from the app than a served one.
What it does not do
It does not stop network calls. This is worth being blunt about, because it is the thing people assume and it is not true.
The header carries no connect-src, no default-src, and no script-src. A
dashboard can fetch from any origin it likes. Dashies relies on this: the
engine that powers large row-level dashboards is downloaded at view time from a
separate data domain, under exactly this header.
So the guarantee is isolation from your Dashies account, not confinement to the page. A dashboard's script cannot become you. It can talk to the internet, and the data in the page is data it can send.
The practical consequence: a published dashboard should be treated as code you are running, on the same footing as any other HTML somebody handed you. If you would not open an untrusted colleague's HTML file, do not open their dashboard either. This is why arbitrary script inside a dashboard is not itself treated as a vulnerability, while anything that lets that script reach the application, a viewer's credentials, or another tenant's data very much is.
The compatibility cost
Isolation is not free, and the cost lands on the author.
Anything relying on browser storage breaks. A dashboard that tries to
remember a viewer's preferred filter in localStorage, or set a cookie, will
throw rather than silently no-op. There is no way to opt out per dashboard.
This is why the runtime keeps active filters in the URL hash instead. A filtered view is a shareable link that survives a reload, which turns out to be the better design anyway, but it is a design forced by the sandbox rather than chosen freely.
Links that open a new tab do not open one. If your dashboard links out to a
source system, use an ordinary same-tab link and test it, rather than assuming
target="_blank" behaves as it would on a normal page.
One asymmetry between plans
A dashboard published by a free-tier account carries a small "Powered by Dashies" badge, and the badge is a link. So that the link actually works, the sandbox on those responses is relaxed just enough to permit opening a new tab.
The side effect: a target="_blank" link works on a free-plan dashboard and
stops working when the owner upgrades, because the badge and its relaxation go
away together. If a link in your dashboard mysteriously stopped opening after an
upgrade, this is why. Switch it to a same-tab link.
Anything expecting a normal origin should be tested, not assumed. The document only receives capabilities the header opts into, and the header opts into scripting. If you are reaching for a browser feature beyond drawing and interaction, publish a small test dashboard and confirm it works before building on it.
Where the data lives
For most dashboards the numbers are inline in the file itself, so isolating the document isolates the data with it.
When a dataset is too large to inline, it is served as a separate file from a different domain. That split is deliberate: it keeps the dashboard document cross-origin to the app, which is what preserves everything above.
A private dataset served this way is protected by a signed link with a four-hour life, minted only while serving the dashboard to somebody already authorized to see it. There is no endpoint that will sign a link on request, so there is no way to get one without already having the access it grants. Any bad or expired signature gets the same refusal, with nothing distinguishing which kind of bad it was, and private responses are never stored in a shared cache where they could be replayed.