Skip to content

feat(web-app-html-editor): add an HTML editor with live preview#13895

Open
dj4oC wants to merge 8 commits into
masterfrom
feat/web-app-html-editor
Open

feat(web-app-html-editor): add an HTML editor with live preview#13895
dj4oC wants to merge 8 commits into
masterfrom
feat/web-app-html-editor

Conversation

@dj4oC

@dj4oC dj4oC commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new core app, web-app-html-editor, that opens .html / .htm / .xhtml
files in a CodeMirror 6 source editor with a live, sandboxed preview. No WOPI and no
extra server: the file is loaded and saved over WebDAV by the standard AppWrapper,
the source is edited in CodeMirror, and the rendered result is shown in a strictly
sandboxed <iframe srcdoc>.

Approach

The app is intentionally thin and idiomatic. It mirrors web-app-text-editor:
defineWebApplication registers it by file extension and routes through
AppWrapperRoute. By declaring a currentContent prop and emitting
update:currentContent, it inherits the framework's WebDAV load/save, dirty
tracking, Ctrl+S, autosave, the unsaved-changes guard and error notifications, so
none of that is reimplemented. The app itself only composes:

  • HtmlEditorPane.vue - a CodeMirror 6 wrapper (HTML mode, line numbers, theme
    follows the active ownCloud theme). CodeMirror 6 is already in the workspace via
    md-editor-v3, so the individual @codemirror/* packages are added at the
    versions already in the lockfile.
  • HtmlPreviewPane.vue - a sandboxed srcdoc iframe.
  • HtmlToolbar.vue - an Editor | Split | Preview view-mode toggle (CSS grid; the
    filename, Save and action menu come from AppTopBar).

The app is enabled by default via the apps array in config/config.json.dist and
config/config.json.sample-ocis.

Security

The preview renders attacker-influenceable HTML (a user may open a file shared to
them), so the preview is treated as untrusted:

  • The iframe uses sandbox="allow-scripts" with no allow-same-origin (opaque
    origin), so the preview cannot read the shell's cookies, storage or OIDC token,
    cannot call the oCIS API as the user, and cannot script the parent. allow-forms
    and allow-popups are deliberately omitted.
  • A strict CSP (default-src 'none'; form-action 'none'; base-uri 'none'; inline
    script/style and data:/blob: only) is injected into the srcdoc, so the
    preview is network-isolated and does not depend on the deployment proxy CSP.
  • The live preview is paused for very large files until the user opts in, so a huge
    or hostile document cannot freeze the tab on open.
  • A regression test pins the exact sandbox contract (token set + srcdoc-not-src).

Trade-off: because the preview is network-isolated, documents that reference
external stylesheets/scripts/images will not load them. This is intentional for
a first version. The package ships ARCHAEOLOGY.md, DECISIONS.md and
SECURITY-REVIEW.md documenting the design rationale and the threat model.

Testing

  • Unit tests (vitest): app wiring, the CodeMirror pane, the preview pane (sandbox
    contract, srcdoc), the toolbar, the CSP-injection helper, and the large-file
    preview pause. pnpm --filter html-editor test:unit is green (27 tests).
  • vue-tsc --noEmit, eslint and prettier are clean.
  • Verified manually against a running oCIS 8.0.4: opening a .html file (the app is
    picked up as the default opener), live edit updating the preview, and saving back
    over WebDAV.

Notes

  • Registration is by file extension (oCIS associates apps to files by extension, not
    MIME type), matching web-app-text-editor.
  • No drag-to-resize split handle in this version (kept simple).

dj4oC and others added 5 commits June 19, 2026 15:20
Document how an oCIS editor app actually works before writing code: apps
register by file extension (not MIME type), and AppWrapper already provides
the WebDAV load/save, dirty tracking, Ctrl+S, unsaved-changes guard and error
toasts. Record the editor-library, preview-sandboxing, layout and registration
decisions, each citing the Phase 1 file it relies on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
Mirror the text-editor package layout (package.json, vitest config, l10n) and
add CodeMirror 6 (already resolved in the lockfile via web-pkg) as direct deps.
Enable the app by adding "html-editor" to the default and sample-ocis configs;
the build auto-discovers it by its web-app-* directory name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
…ew modes

Register for the html, htm and xhtml extensions and route through AppWrapper,
so WebDAV load/save, dirty state, Ctrl+S and the unsaved-changes guard are
inherited. App.vue is a thin shell that binds currentContent and emits
update:currentContent; it composes a CodeMirror 6 source editor (HTML mode,
themed via ODS tokens), a sandboxed srcdoc iframe preview (no allow-same-origin),
and a view-mode toolbar (Editor | Split | Preview) laid out with CSS grid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
…olbar

Cover the app wiring (re-emits edits, switches view mode, debounces the
preview), the CodeMirror pane (renders, emits on change, applies external
content), the preview pane (srcdoc, sandbox without allow-same-origin, no
referrer) and the toolbar (active mode, emits changeMode).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
…view

An adversarial security review (SECURITY-REVIEW.md) confirmed the design is sound
against the primary threat (a victim opening an attacker-controlled HTML file): the
opaque-origin iframe plus bearer-token-in-JS auth prevent token theft, parent-origin
XSS and acting as the victim. This tightens the residual low/info findings:

- Reduce the iframe sandbox to "allow-scripts" only. allow-forms and allow-popups
  added no value for a preview but enabled zero-click phishing / external form-POST
  beaconing from the opaque-origin frame; dropping them removes the vector.
- Inject a strict, self-contained CSP into the preview srcdoc (default-src 'none';
  form-action 'none'; base-uri 'none'; inline script/style and data:/blob: only) so
  the preview is network-isolated and not reliant on the deployment proxy CSP.
- Pause the live preview for large files until the user opts in, so a huge or
  hostile document cannot freeze the tab on open.
- Pin the full sandbox contract (exact token set + srcdoc-not-src) in tests so a
  future change cannot silently loosen the one control everything depends on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
@update-docs

update-docs Bot commented Jun 19, 2026

Copy link
Copy Markdown

Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a changelog item based on your changes.

@kw-security

kw-security commented Jun 19, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

#13895

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
@dj4oC dj4oC requested a review from LukasHirt June 19, 2026 14:12
dj4oC and others added 2 commits June 19, 2026 20:52
SonarCloud flagged the appInfo.extensions map and the app-switcher menu-item
block in index.ts as duplicated against web-app-text-editor (new-code duplication
over the 3% gate). Assign the already-typed fileExtensions directly instead of
re-mapping them, and drop the app-switcher launcher entry: it was copied
boilerplate and is not needed for v1. Users can still create a new HTML file from
the Files "New" menu via the extension's newFileMenu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
… test

The shared unit-test run (vitest projects) went red on web-pkg's @vitest/web-worker
tests with timeouts only after this package was added, and the symptom (worker
callbacks never firing) matches fake timers being active during real-timer async.
Rewrite the debounce assertion to use real timers and a short wait so this test
project cannot influence timer state in the shared run. No change to behavior under
test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants