Add in-app HTML viewer with sandboxed JS execution#77
Merged
Conversation
Vault html/htm files now classify with viewer_kind "html" and open in
a dedicated React view that hosts the document in an iframe loaded
through a new "assets" scope on the existing neverwrite-file:
protocol. The new scope takes the raw relative path as the trailing
segments so that subresources (CSS, images, sibling .json) referenced
from the HTML resolve naturally; path validation still goes through
resolvePreviewFilePath so traversal is rejected.
Scripts execute (sandbox="allow-scripts allow-same-origin allow-forms
allow-modals"). To stop a malicious local document from exfiltrating
vault contents, the protocol handler attaches a Content-Security-Policy
response header that restricts connect-src and frame-src to
neverwrite-file: only, leaving local fetch("./data.json") working but
blocking arbitrary outbound network. Pages that need full network
access can still be opened in the system browser from the header.
The renderer CSP also adds neverwrite-file: to frame-src so the parent
can embed the iframe at all. Html file entries skip the text content
pre-fetch since the iframe loads them directly.
Owner
|
This is a really great idea, thank you so much. I didn’t realize how useful it could be! Also, thank you for taking care of the security aspects of this feature and thinking through the whole logic. I’m still stuck with an older PR and having a hard time with the Linux release pipeline, this will be merged as soon as possible. Thanks again! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a dedicated in-app viewer for
.html/.htmfiles so they render as a page (with JS) instead of falling through to the text/code viewer.End-to-end:
crates/vault/src/vault.rs—classify_vault_entry_pathnow returnsviewer_kind: "html"for.htmland.htm, ahead of the generictextfallback.apps/desktop/src-electron/main/ipc.ts—registerPreviewProtocolHandlergains anassetsscope:neverwrite-file://localhost/assets/<base64-vault>/<raw/relative/path>. The trailing segments stay un-base64 so that subresources referenced from the HTML (CSS, images,./data.json) resolve naturally via relative URLs. Path validation still goes throughresolvePreviewFilePath, so traversal is rejected at the main process.apps/desktop/src/features/editor/HtmlTabView.tsx— new React view. Hosts the document in an<iframe>withsandbox="allow-scripts allow-same-origin allow-forms allow-modals"andreferrerPolicy="no-referrer". Header reuses the local pattern fromFileTabViewfor Open Externally / Reveal in Finder.apps/desktop/src/features/editor/FileTabView.tsx— routesviewer === "html"toHtmlTabView.apps/desktop/src/app/store/editorTabs.ts—FileViewerModegains"html",inferFileViewermaps.html/.htm, andfileViewerNeedsTextContentreturnsfalsefor"html"so the file is not pre-fetched as text.apps/desktop/src/app/utils/vaultEntries.ts— adds a short-circuit alongside the existing image short-circuit so html entries open without a text read.apps/desktop/src/app/utils/filePreviewUrl.ts—buildVaultAssetUrlhelper.apps/desktop/config/desktop-security.json— renderer CSP addsneverwrite-file:toframe-srcso the parent can embed the iframe. CSP test updated.Security boundary
neverwrite-file://localhost(because ofallow-same-origin), different from the renderer's origin. Scripts in the iframe cannot script the parent app, read parent storage/cookies, or escape into the renderer process.neverwrite-file://localhost/...URLs (same-scheme/host). Every such request goes throughresolvePreviewFilePath, which validates the path against the active vault root, so a script cannot reach files outside the vault.Content-Security-Policyresponse header to HTML responses that pinsconnect-srcandframe-srcto'self' neverwrite-file:and setsform-action 'none'. Local self-contained pages (including ones doingfetch("./sibling.json")) keep working; arbitrary outbound network is blocked.Test plan
.htmlfile containing inline<script>into a vault; opening it from the file tree renders the page in-app and the script runs.fetch("https://example.com")is refused (CSP violation).fetch("./sibling.json")against a real sibling file succeeds.vitest run src/app/utils/desktopCsp.test.tspasses.cargo test -p neverwrite-vaultpasses.