mirror of
https://github.com/THU-MAIC/OpenMAIC.git
synced 2026-10-29 00:46:40 +08:00
* fix(math): disable formula-defined macros in every KaTeX and Temml render Add safeKatexOptions to @openmaic/dsl, the one package every renderer, importer, editor and the app already depend on. It merges hardened settings into each caller's options: macro-defining commands (\def, \gdef, \edef, \xdef, \global, \long, \let, \futurelet, \newcommand, \renewcommand, \providecommand) become inert macros that consume the definition, trust is forced off, maxExpand is pinned, and a fresh macros object is built per call so nothing persists between renders. Caller options and caller-supplied macros are kept. Route every render through it: whiteboard and slide latex elements, quiz math, standalone HTML inline math, PPTX export via Temml, the editor's latex dialog and inline math node, generated latex elements, and the importer's formula paths. Keep the importer's node-globals rollup plugin off the dsl build, whose old parser cannot read `??`. Tests cover each render path with nested-definition inputs, a corpus of legitimate formulas that must render byte-identically, \gdef isolation, and a guard that fails if a new KaTeX/Temml call skips the helper. Bump @openmaic/dsl 0.11.3, editor 0.0.10, generation 0.3.16, importer 0.3.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(math): harden workbench chat math and newcommand parsing Workbench chat Markdown rendered math through @streamdown/math, which only forwards errorColor to rehype-katex. Add createSafeMathPlugin, which keeps that plugin's remark side and rehype-katex instance but replaces the rehype options with safeKatexOptions, and use it in the chat text block. Rework the inert \newcommand family: peek at unconsumed tokens so a body written as {[} stays a body, accept the starred form, skip [n] and [default] groups with brace-aware scanning, then consume exactly one replacement body. Document that caller-supplied macros are trusted code and must not be derived from content. Replace the call-text coverage check with an import-aware guard built on the TypeScript parser. It finds every runtime import of a math engine (default, aliased, named, namespace, side-effect, import = require, re-export, dynamic import, require) plus injected auto-render script text, and requires each file to be on a reviewed allowlist. Files that import KaTeX or Temml directly must use them only as render calls with safeKatexOptions(...). The interactive post-processor is allowlisted: its page already runs model-written scripts in a sandboxed iframe. Fixtures cover each bypass shape. Add KaTeX and Temml regression tests for the newcommand edge cases, and the workbench chat path to the expansion-input suite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(math): fresh macros per chat formula and stricter render guard KaTeX writes built-in state into the macros object while rendering (an align environment sets \@eqnsw), and rehype-katex spreads one options object into every render. The workbench chat plugin shared one macros object, so that state leaked between formulas and between messages. Make macros an enumerable getter that returns a fresh hardened object on every read, so each render and each error retry gets its own. Add cross-formula and cross-message regression tests. Tighten the guard for files that import KaTeX or Temml directly: any engine reach other than a static default or namespace import (require, dynamic import, re-export, import = require, side-effect import) is rejected; the options argument is checked at each method's own position with nothing after it; and it must be a call to safeKatexOptions imported from @openmaic/dsl, not a local look-alike. Document the guard's scope: module imports only; global access, engine code inside other HTML strings and require.resolve need review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(math): keep built-in equation numbering with macro definitions disabled KaTeX and Temml implement \tag, \tag*, \nonumber and \notag as built-in macros that expand to \gdef\df@tag{\text{...}} and \gdef\@eqnsw{0}. Swallowing every \gdef therefore dropped equation tags and stopped \nonumber / \notag from suppressing align/gather/equation numbers. The inert \gdef now accepts exactly those two built-in shapes and stores them the way the engines' own \gdef does; every other \gdef, and every other definition command, stays inert. '@' is a letter in both lexers, so formulas can name these macros directly; the allowed shapes add nothing \tag* and \nonumber do not already provide: - \@eqnsw may only be set to 0; - \df@tag may only be one \text{...} group that never mentions \df@tag, so the tag is expanded exactly once (a self-reference would recurse until the call stack or the expansion budget runs out, as it does in the stock engines on \tag{\df@tag}). Tests: tagged and \notag/\nonumber formulas render identically to stock KaTeX (html, mathml, htmlAndMathml) and Temml; the slide element, quiz, editor dialog and Workbench chat renderers show tags and suppress numbers; definitions inside tags, self-referencing tags, nested tags and non-built-in \df@tag/\@eqnsw shapes stay inert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(math): expand a tag in full only once per accepted definition A formula can name \df@tag itself, so a long \tag*{...} (or an accepted \gdef\df@tag{\text{...}}) followed by many \df@tag mentions re-expanded the whole tag on every mention, up to the expansion budget: about 12 KB of input produced several MB of output. An accepted \df@tag is now stored as a macro that yields its body on the first expansion and an empty \text{} afterwards. The engines expand \df@tag once per formula, or once per align row before clearing it, so tags render unchanged (stock-equality tests, including an align with a tag on several rows, stay green in KaTeX and Temml). The empty \text{} rather than nothing avoids a Temml TypeError on an empty tag. Also document, and test through the PPTX export, that Temml throws a TypeError on a script left bare by an inert definition (x^\notag); the only Temml consumer catches it and returns null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(generation): bump to 0.3.17 after main took 0.3.16 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>