Skip to content

Fix unguarded .content[1].t access in Lua filters#14587

Merged
cderv merged 7 commits into
mainfrom
feature/audit-unguarded-content-1-t-pa
Jun 10, 2026
Merged

Fix unguarded .content[1].t access in Lua filters#14587
cderv merged 7 commits into
mainfrom
feature/audit-unguarded-content-1-t-pa

Conversation

@cderv

@cderv cderv commented Jun 10, 2026

Copy link
Copy Markdown
Member

Lua filters access .content[1].t in ~35 callsites across src/resources/filters/. About half have proper nil/empty guards; the rest crash when content is empty or structurally unexpected. Same class of bug as #14585 (empty blockquote crash).

Changes

Four filter files fixed, one dead code path removed:

  • latexdiv.luaquarto.utils.match("[1]/Para") guards the first-element check; empty divs with latex attribute fall through to the RawBlock branch
  • parsefiguredivs.lua:489andor logic bug in Figure bail-out condition; with and, empty content crashes and single non-Plain doesn't bail
  • pandoc3_figure.lua — HTML path migrated to quarto.utils.match("[1]/Plain/[1]/Link") with not link guard (match() returns false, not nil, on no-match); Typst path gets empty-content early return
  • parsefiguredivs.lua:309 — migrated to quarto.utils.match("[1]/Para/[1]/Image") for the nested type check
  • crossref/theorems.lua — removed 90 lines of dead proof-rendering code in the Div handler. Since e644af3a4 (Dec 2023), parseblockreftargets converts proof divs to Proof custom nodes before crossref_theorems() runs; the Div handler was never reached

Test Plan

  • empty-latex-div.qmd — empty div with latex attribute renders to PDF
  • empty-figure-content.qmd — Figure with content stripped by pre-ast filter renders to PDF
  • empty-pandoc3-figure.qmd — non-crossref Figure with empty content renders to HTML
  • empty-para-inlines.qmd — fig-div with empty Para inlines renders to HTML
  • proof-rendering.qmd / proof-rendering-html.qmd — proof, remark, named proof, empty proof still render correctly after dead code removal
  • Feature-format-matrix image/alt-text tests pass (regression from match() return value)

cderv added 6 commits June 10, 2026 17:29
`divEl.content[1].t` crashes when a div with `latex` attribute has
no content blocks. Add `#divEl.content > 0` guard so empty divs fall
through to the RawBlock branch, producing valid \begin{env}\end{env}.

quarto.utils.match() not used here — the first-and-last element check
with content mutation doesn't map to its path-traversal pattern.
`#fig.content ~= 1 and fig.content[1].t ~= "Plain"` uses `and` but
the intent is "bail unless content is exactly one Plain block." With
`and`, empty content crashes (indexes nil) and single non-Plain doesn't
bail. Change to `or` — short-circuits on empty, bails on non-Plain.

Test uses a pre-ast Lua filter to strip Figure content, confirming
the crash triggers at the normalize stage.
HTML path: replace manual `figure.content[1].t == "Plain"` then
`plain.content[1].t == "Link"` chain with single
`quarto.utils.match("[1]/Plain/[1]/Link")(figure)` call.
match() returns false on nil/empty content — no crash possible.

Typst path: add `#figure.content == 0` guard before accessing
figure.content[1] (match() doesn't fit here — the value is passed
to make_typst_figure, not used as a type check).

Test uses pre-ast Lua filter to empty non-crossref Figure content.
Replace manual `content[1].content[1].t == "Image"` with
`quarto.utils.match("[1]/Para/[1]/Image")(content)` which returns
false on nil/empty content — no crash when Para has no inlines.

Test uses pre-ast Lua filter to create a fig-div with an empty Para
and external fig-cap attribute, confirming the crash at line 309.
The Div handler in crossref_theorems() that rendered proof-type divs
has been dead code since e644af3 (Dec 2023, "crossreferenceable
remarks"). That commit introduced customnodes/proof.lua with a Proof
custom node and renderer. The normalize stage (parseblockreftargets
in astpipeline.lua) converts all proof-type divs to Proof custom nodes
before crossref_theorems() runs — the Div handler never receives them.

The theoremType branch already acknowledged this with internal_error().
Remove the entire Div handler; proof rendering is handled by proof.lua.

New regression tests verify proof rendering (PDF and HTML) still works
with content, named proofs, empty proofs, and remarks.
quarto.utils.match() returns false (not nil) on no-match. The == nil
check let the boolean false pass through to pandoc.Para({link}),
crashing with "Inline-ish expected, got boolean" on figures without
linked images (e.g. alt-text feature-format-matrix tests).
@posit-snyk-bot

posit-snyk-bot commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

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

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

- empty-latex-div: use distinct env (flushright) for empty div so regex
  uniquely verifies it instead of being satisfied by the non-empty case
- proof-rendering: assert named proof title \begin{proof}[Of the main theorem]
@cderv cderv merged commit 10da1f7 into main Jun 10, 2026
51 checks passed
@cderv cderv deleted the feature/audit-unguarded-content-1-t-pa branch June 10, 2026 17:19
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