Skip to content

fix: review findings of 2026-10-07 (Markdown rendering, type lock, deleted topics, nosniff) - #1

Merged
eluhr merged 5 commits into
masterfrom
fix/review-findings
Oct 7, 2026
Merged

eluhr merged 5 commits into
masterfrom
fix/review-findings

Conversation

@eluhr

@eluhr eluhr commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Review of the package for flaws and bugs, with scenario probes against the test suite (706 tests before, 707 after; all green on PHP 8.4).

Fixed

  • Markdown rendering (bug + stored XSS). Html::encode() before parsing turned > into >, so blockquotes became paragraphs, link titles broke, autolinks stopped working and >/& inside code blocks were encoded twice. Links and images also passed javascript: URLs through to readers of the frontend. New SafeGithubMarkdown shows HTML as text, the output runs through HtmlPurifier (safe URL schemes only).
  • Types: has_validity_period is locked once items of the type have versions. The flag decides which version of an item is valid (date range vs. highest number); switching it silently changed what readers see. The form disables the checkbox with a hint, the optional i18n migration m261007_120000 adds the German text.
  • Drafts: a topic deleted after it was picked in a draft made publishing and submitting fail with "Topics is invalid." on a field the editor cannot see. Existence is checked in the details step only; publication applies the topics that still exist.
  • Files: downloads carry X-Content-Type-Options: nosniff; knowledge_get_file reads through FileService::readStream() and honours the storage named by the file row like the downloads.
  • Docs: ItemState precedence, wizard button submit-for-review, new behaviour in the README.

Checked and found correct

Correction and withdrawal flows (extension of the previous version, correction of historical versions, types without validity period), four-eyes checks in the model, route permission prefix resolution (knowledge does not grant knowledge-library_*), upload hardening (base name, MIME sniffing, size), output encoding of the views, MCP protocol handling.

Known limitation (not changed)

Withdrawing the version in force while an upcoming version exists leaves a gap that no new version can fill: a new version must start after the upcoming one, and withdrawn versions cannot be corrected. The upcoming version has to be withdrawn as well.

🤖 Generated with Claude Code

eluhr and others added 5 commits October 7, 2026 21:23
… drop javascript: URLs [*]

Html::encode() before parsing turned `>` into `>`, so blockquotes became
paragraphs, link titles with quotes broke, autolinks stopped working and
`>` or `&` inside code blocks were encoded twice. Links and images also
passed any URL through, including `javascript:`, which readers of the
frontend would execute.

SafeGithubMarkdown shows HTML written into the text as text (block HTML as
escaped paragraph, inline tags escaped in place, entities kept), and the
rendered HTML runs through HtmlPurifier, which keeps only safe URL schemes.
The purifier cache lives in `@runtime/html-purifier`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ions [*]

Which version of an item is valid depends on `has_validity_period` of its
type (date range or highest number). The flag could be switched at any
time, silently changing what readers see and leaving versions with dates
the type no longer expects, while the type of an item is locked for exactly
this reason. The type now rejects the change once any of its items has a
version, the form disables the checkbox with a hint, and the optional i18n
migration adds the German text.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e service [*]

Downloads carry `X-Content-Type-Options: nosniff`, so browsers do not guess
another type than the one detected on upload. `knowledge_get_file` read
the stored file from the module filesystem directly and ignored the
storage named by the file row; it now uses FileService::readStream() like
the downloads, which also logs read errors in one place.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…w behaviour [*]

ItemState described in review before a valid version, the code resolves a
valid version first. The README named the wizard button `submit`, which
is `submit-for-review`. Documents the validity period lock of types, the
URL scheme filter of the Markdown rendering, the nosniff header and the new
i18n migration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cation [*]

A topic referenced only by a draft (`draft_topic_ids`) can be deleted, as
the delete lock of topics checks the item assignments only. Publishing or
submitting the draft then failed with "Topics is invalid.", an error on a
field the editor cannot see or correct in the wizard: the select lists
existing topics only. The existence of the topics is now checked when the
editor picks them (step details), and the publication applies the topics
that still exist.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@eluhr
eluhr merged commit a2954c7 into master Oct 7, 2026
4 checks passed
@eluhr
eluhr deleted the fix/review-findings branch October 7, 2026 22: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