Skip to content

0.9.30: security (config/parser RCE sandbox) + heading-ID and @import fixes#443

Merged
shd101wyy merged 7 commits into
developfrom
bug-fixes
Jun 6, 2026
Merged

0.9.30: security (config/parser RCE sandbox) + heading-ID and @import fixes#443
shd101wyy merged 7 commits into
developfrom
bug-fixes

Conversation

@shd101wyy

Copy link
Copy Markdown
Owner

Summary

Release 0.9.30. Bundles one security fix and two rendering/resolution bug fixes.

🔒 Security — RCE in .crossnote/config.js / .crossnote/parser.js (GHSA-427h-jhpr-8jch)

These workspace files were evaluated with vm.runInNewContext() (desktop) and sval (web). Neither is a security boundary — both share the host realm's object prototypes, so untrusted code could climb ({}).constructor.constructor to reach the host Function constructor and, from there, process / child_process, achieving arbitrary command execution just by opening a markdown file in a malicious repository (no further interaction).

Fix: both files are now evaluated inside a QuickJS engine compiled to WebAssembly (src/lib/js-sandbox.ts). The guest has its own realm/intrinsics and heap in WASM memory; the host process/Function do not exist there, so the prototype-chain escape has nothing to reach. Only plain data (config.js) and strings (parser.js hooks) cross the boundary. A memory limit and an execution-time deadline guard against DoS. The same sandbox runs in both Node and the browser/VS Code web extension (singlefile variant, WASM embedded as base64 — no dynamic import or asset fetch).

Additionally, security-sensitive keys (enableScriptExecution, chromePath, pandocPath, imageMagickPath, markdownYoBinaryPath) are stripped from an untrusted config.js result so a repo cannot grant itself trust or point an executable path at an arbitrary binary — these are only honoured from trusted editor settings.

  • Removes the vm/sval-based interpretJS and the obsolete sanitizeParserConfig; swaps the sval dependency for quickjs-emscripten-core + the singlefile variant.
  • Behaviour note: functions can no longer cross the sandbox boundary, so config.js must return plain data (stray functions in its result are dropped). parser.js hooks are unaffected (string→string).

Thanks to @ritikchaddha for reporting the issue.

🐛 Heading auto-ID for underscore emphasis (vscode-mpe#2319)

Headings using underscore emphasis (# _Toy Story_, # __Bold Title__) produced IDs that retained the underscores, which markdown-it then re-interpreted as emphasis — splitting the internal {#id data-source-line} block and leaking it into the output. IDs now strip emphasis markers per CommonMark rules (matching GitHub's anchors), and IDs embedded in the {#id} block are backslash-escaped so rendered IDs always match TOC anchors.

🐛 @import / ![[wikilink]] resolution with # in the path (vscode-mpe#2317)

When a directory name contains # (e.g. [#11111111]), the # was mistaken for a heading-anchor fragment separator, breaking file resolution. The fragment is now extracted from the original import syntax before path resolution. Also fixes line-level ![[note^block-id]] embeds (bare block ref without #).

Testing

  • All 559 tests pass (with pandoc installed locally; the only failures otherwise are environmental spawn pandoc ENOENT).
  • New regression tests: QuickJS sandbox (RCE blocked at eval & in hooks, no host globals, DoS timeout, sensitive-key stripping, normal hooks/config still work), heading-emphasis rendering + TOC consistency, #-in-path resolution, bare block-ref embed.
  • tsc --noEmit clean, eslint clean.
  • Build verified for both Node and browser: full build.js exits 0 (CJS + neutral ESM + webview); ESM browser-safety guard passes; quickjs deps externalized; Node runtime smoke test works; the sandbox bundles cleanly for platform: browser with node builtins externalized and WASM base64-inlined (no fetch).

🤖 Generated with Claude Code

shd101wyy and others added 7 commits June 5, 2026 22:49
…embeds

- Strip underscore emphasis in heading IDs per CommonMark rules
  (punctuation boundaries, adjacent runs, em+strong) so generated
  anchors match GitHub's; intraword underscores are kept.
- Backslash-escape `_`/`*` in the internal `{#id ...}` attribute block
  (markdown-it / markdown_yo) so IDs containing emphasis markers
  survive inline parsing intact and rendered heading IDs always match
  TOC anchors. Pandoc is excluded as it parses attributes natively.
- Fix line-level `![[note^block-id]]` embeds: prepend the `#` that
  downstream block-transclusion handling expects.
- Dedupe @import/image hash extraction; minor cleanups.
- Tests: GitHub-parity ID cases, end-to-end heading-emphasis rendering
  (no attr-block leak, TOC/anchor consistency, markdown_yo), and a
  line-level bare block-ref embed test.

Refs:
- shd101wyy/vscode-markdown-preview-enhanced#2319
- shd101wyy/vscode-markdown-preview-enhanced#2317

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`.crossnote/config.js` and `.crossnote/parser.js` were evaluated with
`vm.runInNewContext()` (desktop) and `sval` (web), neither of which is a
security boundary: both share the host realm's object prototypes, so
untrusted workspace code could climb `({}).constructor.constructor` to the
host Function constructor and reach `process` / `child_process`, achieving
RCE just by opening a markdown file in a malicious repo (GHSA-427h-jhpr-8jch).

- Evaluate both files inside a QuickJS engine compiled to WebAssembly
  (src/lib/js-sandbox.ts). The guest has its own realm/intrinsics and heap in
  WASM memory; the host process/Function do not exist there, so the
  prototype-chain escape has nothing to reach. Only plain data (config.js) and
  strings (parser.js hooks) cross the boundary. A memory limit and an
  execution-time deadline guard against DoS. Same sandbox runs in Node and the
  browser/VS Code web extension (singlefile variant, WASM embedded as base64).
- Strip security-sensitive keys (enableScriptExecution, chromePath, pandocPath,
  imageMagickPath, markdownYoBinaryPath) from an untrusted config.js result so
  it cannot grant itself trust or point an executable path at an arbitrary
  binary — these are only honoured from trusted editor settings.
- Remove the vm/sval-based interpretJS and the obsolete sanitizeParserConfig;
  swap the `sval` dependency for quickjs-emscripten-core + singlefile variant.
- Add regression tests: RCE blocked at eval and in hooks, no host globals, DoS
  timeout, sensitive-key stripping, and that normal hooks/config still work.

Thanks to @ritikchaddha for reporting the issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pnpm/action-setup@v4 runs on the deprecated Node.js 20 actions runtime;
v6 runs on Node.js 24. The action is invoked with no `version` input and
resolves pnpm from the package.json `packageManager` field, which v6
supports identically — no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shd101wyy shd101wyy merged commit e4890d6 into develop Jun 6, 2026
2 checks passed
@shd101wyy shd101wyy deleted the bug-fixes branch June 6, 2026 07:03
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.

1 participant