| fe58309e | 16-Jul-2026 |
Andreas Gohr <gohr@cosmocode.de> |
extension: don't crash on malformed conflict/dependency ids
The conflicts and depends fields in the repository metadata are free form text entered by extension authors and may contain values that ar
extension: don't crash on malformed conflict/dependency ids
The conflicts and depends fields in the repository metadata are free form text entered by extension authors and may contain values that are not valid extension ids, such as "sprintdoc template". Since 9af82229f routed every Extension construction through the strict setBase() validation, such a value made Extension::createFromId() throw an uncaught RuntimeException while building the notices, taking down the whole extension manager.
Skip entries that cannot be parsed into an extension id when checking for missing dependencies and conflicts.
As an immeadiate fix this will also be fixed in the repository API to avoid sending invalid extension IDs to the extension manager.
Fixes #4691
show more ...
|
| 1ed5d398 | 08-Jul-2026 |
splitbrain <86426+splitbrain@users.noreply.github.com> |
Rector and PHPCS fixes |
| 2d05a06d | 08-Jul-2026 |
Andreas Gohr <gohr@cosmocode.de> |
fix(info): read parser modes from the registry, not the deprecated global
The info plugin's ~~INFO:syntaxtypes~~ read the $PARSER_MODES global directly. Since the mode taxonomy moved into ModeRegist
fix(info): read parser modes from the registry, not the deprecated global
The info plugin's ~~INFO:syntaxtypes~~ read the $PARSER_MODES global directly. Since the mode taxonomy moved into ModeRegistry (c8dd1b9d2), that global is only populated as a side effect of a parse, so on a warm instruction cache with no parse in the request it is unset and the syntax-types table renders empty. ~~INFO:syntaxmodes~~ used the deprecated p_get_parsermodes(), logging a deprecation on every render.
Both now build a ModeRegistry and read the categories and modes from it directly, independent of parse or cache state.
show more ...
|
| 18cdf102 | 07-Jul-2026 |
Andreas Gohr <gohr@cosmocode.de> |
fix(editor): give toolbar and picker buttons an explicit type
Toolbar and picker buttons are created as <button> elements with no type attribute, so they default to type="submit". When a plugin rend
fix(editor): give toolbar and picker buttons an explicit type
Toolbar and picker buttons are created as <button> elements with no type attribute, so they default to type="submit". When a plugin renders the toolbar into a container inside its own form, clicking any button (e.g. a smiley picker) submits that form. Set type="button" so the buttons never trigger a submit.
show more ...
|
| 6a8e48ed | 07-Jul-2026 |
Andreas Gohr <gohr@cosmocode.de> |
fix(extension): skip invalid directories when listing extensions
A leftover directory whose name is not a valid extension base (e.g. "myplugin (copy)") made Extension::setBase() throw, which propaga
fix(extension): skip invalid directories when listing extensions
A leftover directory whose name is not a valid extension base (e.g. "myplugin (copy)") made Extension::setBase() throw, which propagated out of the unguarded getPlugins()/getTemplates() calls and fataled the whole Extension Manager page and the extension list CLI.
Catch the exception per-directory in readExtensionsFromDirectory() so a single stray directory is skipped and logged instead of breaking every caller. The throw is kept for the install path.
show more ...
|
| c3a14f67 | 06-Jul-2026 |
Andreas Gohr <gohr@cosmocode.de> |
fix(media): don't upscale small images in the detail previews
The detail page, media manager and media diff requested a bounding-box resize at the full box size, so an image smaller than the box was
fix(media): don't upscale small images in the detail previews
The detail page, media manager and media diff requested a bounding-box resize at the full box size, so an image smaller than the box was enlarged and fetch.php generated and cached the upscaled copy.
slika 1.2 adds an $upscale option to resize/crop. getDisplayDimensions() now takes a $fit flag mirroring fetch.php one to one and predicts the matching no-upscale dimensions, media_resize_image()/media_mod_image() forward $upscale, and fetch.php disables upscaling for fit=1 requests. In-page image scaling is unchanged.
show more ...
|
| 10287e99 | 25-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Cache: key on resolved base URL instead of the raw Host header
The instruction (.i), render (.xhtml) and metadata cache keys embedded the client-supplied HTTP_HOST and SERVER_PORT, as did the JS and
Cache: key on resolved base URL instead of the raw Host header
The instruction (.i), render (.xhtml) and metadata cache keys embedded the client-supplied HTTP_HOST and SERVER_PORT, as did the JS and CSS dispatchers. A crawler varying the Host header - or a wiki reachable under several names - thus multiplied every cached artifact per host value, growing the cache directory without bound.
Instructions and metadata never embed resolved URLs, so they drop the host component entirely. Rendered output and the JS/CSS bundles key on DOKU_BASE (plus the SSL flag for JS) instead: fixed when baseurl is configured, and only varying for genuine multi-domain canonical setups. Feeds keep host/port keying since their links are always absolute.
This should address the obvious cases mentioned in #4574
show more ...
|
| 3e37eccf | 03-Jul-2026 |
Andreas Gohr <andi@splitbrain.org> |
Harden media manager select callback (defense-in-depth)
The media manager popup derives the select callback name and the target edit-field id from URL parameters (onselect, edid) and calls opener[cb
Harden media manager select callback (defense-in-depth)
The media manager popup derives the select callback name and the target edit-field id from URL parameters (onselect, edid) and calls opener[cb](edid, ...). Tighten this handling:
- sanitize edid to word characters and hyphens - strip all non-word characters from the callback name (global replace, not just the first run) - refuse to call anything that is not a page-script function, rejecting browser/JS built-ins such as eval, Function, setTimeout and open
This is a precaution, not a fix for an exploitable vulnerability. In core the popup is only ever opened by the trusted, same-origin editor, which builds the URL itself and never sets onselect; a directly loaded popup has no opener and a cross-origin opener is blocked by the same-origin policy, so an attacker cannot reach the sink. The change removes the reliance on that ambient browser protection and guards plugins that open the popup with their own callback.
show more ...
|
| aa331cf2 | 29-Jun-2026 |
Martin Růžička <martinr@post.cz> |
Translation update (cs) |
| 946266a3 | 26-Jun-2026 |
Schplurtz le Déboulonné <Schplurtz@laposte.net> |
Translation update (fr) |
| 2848662b | 25-Jun-2026 |
splitbrain <86426+splitbrain@users.noreply.github.com> |
Rector and PHPCS fixes |
| 9af82229 | 25-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
extension: validate base name to prevent path traversal on install
The extension base name flowed unsanitized from an uploaded archive's plugin.info.txt (or an extension id) into getInstallDir(), wh
extension: validate base name to prevent path traversal on install
The extension base name flowed unsanitized from an uploaded archive's plugin.info.txt (or an extension id) into getInstallDir(), where fullpath() collapses '..' segments. A crafted base such as '../../../../evil' therefore resolved outside lib/plugins/, letting dircopy() write the archive contents to an arbitrary web-writable path.
Route every base assignment through a new setBase() that rejects anything other than a bare extension name.
Note: this is not really a security concern since plugins can execute arbitrary code by design. The is mostly to make sure, plugin code is not installed accidentally at the wrong location.
show more ...
|
| 15751e18 | 25-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Preserve edid and onselect across media manager search (#4608)
The media manager popup search reloads the whole page, dropping the edid and onselect parameters that plugins (e.g. struct, prosemirror
Preserve edid and onselect across media manager search (#4608)
The media manager popup search reloads the whole page, dropping the edid and onselect parameters that plugins (e.g. struct, prosemirror) rely on to insert media and run their callback. Carry them over in the search form action URL so they survive the reload, and match them in media.js regardless of their position in the query string.
show more ...
|
| 195a3a7f | 25-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
make getUserData() fail safely on non-string input
When removing multiple users at once, plugins listening on the AUTH_USER_CHANGE event may pass the array of user names (params[0] for 'delete') to
make getUserData() fail safely on non-string input
When removing multiple users at once, plugins listening on the AUTH_USER_CHANGE event may pass the array of user names (params[0] for 'delete') to getUserData(), which crashed authplain with an "Array to string conversion" / illegal offset error.
Guard the bundled auth backends so a non-string user fails safely by returning false, and document that AUTH_USER_CHANGE params[0] is a string for create/modify but a string[] for delete.
Fixes #4567
show more ...
|
| 6436b0ab | 25-Jun-2026 |
Schplurtz le Déboulonné <Schplurtz@laposte.net> |
Translation update (fr) |
| 75364f13 | 25-Jun-2026 |
Marek Adamski <fevbew@wp.pl> |
Translation update (pl) |
| 5d71952d | 16-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Translation update (de) |
| 17acbd0f | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Cast the ACL permission to an integer when saving the rule table
The bulk update path pulled the permission map straight from input and wrote each value verbatim into a tab-separated ACL line, while
Cast the ACL permission to an integer when saving the rule table
The bulk update path pulled the permission map straight from input and wrote each value verbatim into a tab-separated ACL line, while the single-rule path already read it through an integer filter. A non-integer value could carry a newline or tab and inject extra rules into the file. Cast to int so the value can only ever be a single permission number.
This was not exploitable: the rule table is reachable only through the ACL admin plugin, which is superuser-only and CSRF-protected, and a superuser can already edit the ACL file directly. The cast is hardening for consistency with the single-rule path.
show more ...
|
| fa03edf3 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
(security) Enforce per-page edit permission in the revert plugin
The revert manager is accessible to managers, not just admins. Its reversion loop called saveWikiText() for every submitted page id w
(security) Enforce per-page edit permission in the revert plugin
The revert manager is accessible to managers, not just admins. Its reversion loop called saveWikiText() for every submitted page id without checking the per-page ACL, relying only on the manager role. A manager denied edit on a namespace could therefore revert those pages to an older revision or blank them entirely (low severity).
The page listing already hid unreadable pages, but offered every readable page for reversion, including ones the manager could not edit. A hand-crafted POST could also target arbitrary ids regardless of what the listing showed.
Each id is now cleaned and checked for edit permission before it is reverted; ids that fail the check are silently skipped. The listing additionally only offers pages the manager actually has edit permission on, so the form and the action agree.
show more ...
|
| 5d8c9d42 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
(security) Require a security token for the lock AJAX call
The lock AJAX call refreshes the edit lock and saves a draft, both of which change server state. It was gated only by the write ACL and, un
(security) Require a security token for the lock AJAX call
The lock AJAX call refreshes the edit lock and saves a draft, both of which change server state. It was gated only by the write ACL and, unlike the sibling draft-delete call, did not verify a security token (low severity).
A cross-site forged POST against a logged-in user could, within that user's own write permissions, take or hold an edit lock and store attacker-controlled draft content under their name.
The call now verifies the security token before taking the lock or saving the draft. Logged out users are unaffected, as no token is issued or checked for them. The edit lock timer now always sends the token with its refresh request, including when draft saving is disabled.
show more ...
|
| aabc4782 | 06-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
(security) Add CSRF protection and use Form class in popularity plugin
The plugin is accessible to managers, not just admins, and had two separate issues.
1. Missing CSRF token (low severity) Th
(security) Add CSRF protection and use Form class in popularity plugin
The plugin is accessible to managers, not just admins, and had two separate issues.
1. Missing CSRF token (low severity) The handler accepted the posted data and toggled autosubmit without checking a security token. A cross-site forged POST against a logged-in manager could enable autosubmit and trigger a submission of the wiki's data to the popularity server. 2. Reflected XSS (low severity) When a submission failed, the posted data was reflected back into a readonly textarea without escaping. A value closing the textarea early (eg. </textarea><script>...) could inject script into the manager's browser. To exploit this, not only a CSRF attack against an authenticated manager was needed, also the connection to the DokuWiki popularity server needed to fail.
The popularity plugin now verifies the security token before it sends data or toggles the autosubmit option.
The form is now built via the the Form API so the textarea value is escaped automatically. The security token is emitted automatically. The fallback browser-submission form posts to an external server and is built without the security token.
show more ...
|
| 3e427828 | 02-Jun-2026 |
Wizzard <wizzardsk@gmail.com> |
Translation update (sk) |
| 7e687fd8 | 29-May-2026 |
Andreas Gohr <andi@splitbrain.org> |
fix(auth): scope media ACL checks to the namespace
Media files have no per-file ACLs; permissions must be evaluated against the namespace they live in. Several call sites passed the raw media ID to
fix(auth): scope media ACL checks to the namespace
Media files have no per-file ACLs; permissions must be evaluated against the namespace they live in. Several call sites passed the raw media ID to auth_quickaclcheck(), so a page-intended exact-ID rule (e.g. on wiki:secret.png) could silently apply to a media file sharing that ID.
Introduce mediaAclPath() that builds the correct namespace wildcard path (handling root-namespace media) and route all media-related ACL checks through it. Also normalize the lone `:X` sentinel variant in fetch.functions.php to the standard `:*` form.
fixes: #4647
show more ...
|
| 1dbc46ac | 27-May-2026 |
Tzipirigu <tzipirigu.radu25683@gmail.com> |
Translation update (ro) |
| 1536bca2 | 24-May-2026 |
Kishore D Rajegowda <kishore328@gmail.com> |
fix(locktimer): reset lock timer on paste and other input events
The lock-timer handler was bound only to `keypress`, which fires for character key presses but not for paste via right-click menu, dr
fix(locktimer): reset lock timer on paste and other input events
The lock-timer handler was bound only to `keypress`, which fires for character key presses but not for paste via right-click menu, drag-and-drop, or other non-keyboard input methods. Users editing a page solely by pasting could therefore lose their page lock after the configured timeout despite active editing.
Switch the binding to the `input` event, which fires on every value change of the textarea regardless of source (typing, paste via any method, cut, drag-drop, undo/redo). This is a superset of the previous behaviour, so no editing action regresses.
Fixes #3714
show more ...
|