sigil-markdown 0.9.4: stop publishing HTML comments
A <!-- ... --> block fell through to the paragraph case and rendered as escaped visible text. That published RELEASES.md's authoring notes onto usesigil.org, from a comment whose own first line read "this comment is not published".
Block-level comments, single and multi-line, are now consumed and emitted as nothing. Per CommonMark an unterminated comment runs to end of document; that is asserted by a test so it is deliberate rather than a surprise. Inline comments are still passed through - this parser has no inline raw-HTML handling at all.
32/32, up from 27. Sabotage-tested: neutering the detector fails exactly the three comment tests, and the two CONTROL tests (prose with angle brackets, a document with no comment) stay green, so they would catch a stripper that deleted too much.
CHANGELOG.md | 6 ++++++
package.sgl | 2 +-
src/sigil/markdown.sgl | 46 ++++++++++++++++++++++++++++++++++++++++++++++
test/test-markdown.sgl | 34 +++++++++++++++++++++++++++++++++-
4 files changed, 86 insertions(+), 2 deletions(-)CHANGELOG.mdmodified
The format is based on [Keep a Changelog](https://keepachangelog.com/),and this project adheres to [Semantic Versioning](https://semver.org/).## [0.9.4] - 2026-07-31### Fixed- HTML comments are no longer rendered. A `<!-- ... -->` block previously fell through to the paragraph case and appeared as escaped, visible text, so authoring notes intended for source readers were published. Both single-line and multi-line comments are now consumed and emitted as nothing. Per CommonMark, an unterminated comment runs to the end of the document; that behaviour is deliberate and covered by a test. Inline comments inside a paragraph are still passed through, since this parser has no inline raw-HTML handling.## [0.9.3] - 2026-07-30### Fixedpackage.sglmodified
(package name: "sigil-markdown" version: "0.9.3" version: "0.9.4" sigil: "^0.17" description: "Markdown and frontmatter parser" url: "https://codeberg.org/sigil/sigil-markdown"src/sigil/markdown.sglmodified
(loop start (+ i 1) lines)))))) ;; Check if line is a horizontal rule (---, ***, ___) ;; Does LINE open an HTML comment block? CommonMark HTML block type 2. ;; Leading spaces are allowed, as with the other block starts. (define (html-comment-start? line) (let ((trimmed (string-trim line))) (and (>= (string-length trimmed) 4) (string=? (substring trimmed 0 4) "<!--")))) ;; Does LINE contain the comment terminator? Per CommonMark the block ends ;; on the line containing `-->`, INCLUDING the opening line, so a one-line ;; `<!-- x -->` is a complete block. (define (html-comment-end? line) (let ((len (string-length line))) (let loop ((i 0)) (cond ((> (+ i 3) len) #f) ((string=? (substring line i (+ i 3)) "-->") #t) (else (loop (+ i 1))))))) (define (horizontal-rule? line) (let ((trimmed (string-trim line))) (and (>= (string-length trimmed) 3) ((string-blank? line) (loop rest blocks)) ;; HTML comment block - consumed and emitted as nothing. ;; ;; Must be tested BEFORE the paragraph fallthrough, which is ;; what previously rendered `<!-- ... -->` as escaped visible ;; text. That shipped authoring notes onto usesigil.org's ;; Releases page, from a comment whose own first line read ;; "this comment is not published". ;; ;; Per CommonMark, an UNTERMINATED comment runs to end of ;; document. That is the spec and other tools agree, so we ;; match it rather than inventing a safer-looking rule that ;; would render differently elsewhere. It does mean a stray ;; `<!--` silently eats the remainder; the alternative is ;; content that renders one way here and another way in every ;; other renderer, which is worse. ;; ;; Scope: BLOCK-level comments only. An inline `<!-- -->` in ;; the middle of a paragraph is still passed through as text, ;; because this parser has no inline raw-HTML handling at all. ((html-comment-start? line) (if (html-comment-end? line) (loop rest blocks) (let skip-loop ((rest rest)) (cond ((null? rest) (reverse blocks)) ((html-comment-end? (car rest)) (loop (cdr rest) blocks)) (else (skip-loop (cdr rest))))))) ;; ATX Header ((parse-atx-header line) => (lambda (header)test/test-markdown.sglmodified
(inner (car (filter pair? (cdr top)))) (deeper (car (filter pair? (cdr (car (sxml-content inner))))))) (assert-equal 'ul (sxml-tag inner)) (assert-equal 'ul (sxml-tag deeper))))) (assert-equal 'ul (sxml-tag deeper)))) ;; HTML comments. Before 0.9.4 these fell through to the paragraph case and ;; rendered as escaped VISIBLE TEXT — which published a RELEASES.md authoring ;; note onto usesigil.org, from a comment whose own first line read "this ;; comment is not published". ;; ;; The two CONTROL tests below are the point: it is easy to write a stripper ;; that also eats ordinary prose containing angle brackets, and a suite that ;; only checked "the comment is gone" would pass for a parser that deleted ;; far too much. (test "multi-line HTML comment is not rendered" (let ((doc (markdown->sxml "# T\n\n<!--\n hidden note\n-->\n\nVisible.\n"))) (assert-equal '(document (h1 "T") (p "Visible.")) doc))) (test "single-line HTML comment is not rendered" (let ((doc (markdown->sxml "# T\n\n<!-- hidden -->\n\nVisible.\n"))) (assert-equal '(document (h1 "T") (p "Visible.")) doc))) (test "CONTROL: prose containing angle brackets survives" (let ((doc (markdown->sxml "# T\n\na < b and c > d\n"))) (assert-equal '(document (h1 "T") (p "a < b and c > d")) doc))) (test "CONTROL: a document with no comment is unchanged" (let ((doc (markdown->sxml "# T\n\nVisible only.\n"))) (assert-equal '(document (h1 "T") (p "Visible only.")) doc))) ;; CommonMark: an unterminated comment runs to end of document. Asserted so ;; the behaviour is deliberate and discoverable rather than a surprise. (test "unterminated comment consumes the rest, per CommonMark" (let ((doc (markdown->sxml "# T\n\n<!-- oops\n\nSwallowed.\n"))) (assert-equal '(document (h1 "T")) doc))))