This repository was archived by the owner on May 29, 2026. It is now read-only.
feat(resources): SVG sanitiser plugged into upload pipeline#51
Merged
Conversation
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 103/103 | |||
| npm | ✅ | ✅ 342/342 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-30 12:25 UTC
Download the full PDF report from the workflow artifacts.
Adds a server-side DOM-based whitelist sanitiser for uploaded SVG bytes and plugs it into the resource-uploads pipeline before the 5 MB size cap (so the cap is measured against the persisted, sanitised bytes). - lib/Service/SvgSanitiser.php (new): 24-element / 50-attribute whitelist (REQ-RES-010 / REQ-RES-011); strips ALL on* attributes defence-in-depth; rejects javascript: / data: hrefs (REQ-RES-012); removes style attributes containing expression() / javascript: / url(data:); parses with LIBXML_NONET | LIBXML_NOENT for XXE protection (REQ-RES-013); returns null on parse failure or fully-stripped result. - lib/Exception/InvalidSvgException.php (new): extends ResourceException with stable error code 'invalid_svg' and HTTP 400. ResourceController already maps any ResourceException through errorResponse() so no controller change is needed. - lib/Service/ResourceService.php: injects SvgSanitiser; for the SVG branch, sanitises BEFORE the size check; throws InvalidSvgException on null; persists the sanitised bytes (NOT the original). - l10n/en + nl: translation entry for the invalid_svg display message. - DEVELOPMENT.md: security-review checkbox documenting that adding to the whitelist is a deliberate code change. - tests: 16 unit tests for SvgSanitiser (clean / script / foreignObject / on* / javascript: / data: / expression() / url(data:) / safe https / geometry preserved / data-* stripped / external DTD bounded / billion-laughs bounded / unparseable / empty / non-svg root) + 6 integration tests for ResourceService that wire the real sanitiser end-to-end (malicious upload sanitised, garbage rejected, oversize-but-sanitises-down accepted, near-cap clean accepted, persisted bytes != original).
4784de7 to
b6a6fd7
Compare
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 103/103 | |||
| npm | ✅ | ✅ 342/342 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-30 12:37 UTC
Download the full PDF report from the workflow artifacts.
This was referenced May 1, 2026
rubenvdlinde
added a commit
that referenced
this pull request
May 3, 2026
#51) Adds a server-side DOM-based whitelist sanitiser for uploaded SVG bytes and plugs it into the resource-uploads pipeline before the 5 MB size cap (so the cap is measured against the persisted, sanitised bytes). - lib/Service/SvgSanitiser.php (new): 24-element / 50-attribute whitelist (REQ-RES-010 / REQ-RES-011); strips ALL on* attributes defence-in-depth; rejects javascript: / data: hrefs (REQ-RES-012); removes style attributes containing expression() / javascript: / url(data:); parses with LIBXML_NONET | LIBXML_NOENT for XXE protection (REQ-RES-013); returns null on parse failure or fully-stripped result. - lib/Exception/InvalidSvgException.php (new): extends ResourceException with stable error code 'invalid_svg' and HTTP 400. ResourceController already maps any ResourceException through errorResponse() so no controller change is needed. - lib/Service/ResourceService.php: injects SvgSanitiser; for the SVG branch, sanitises BEFORE the size check; throws InvalidSvgException on null; persists the sanitised bytes (NOT the original). - l10n/en + nl: translation entry for the invalid_svg display message. - DEVELOPMENT.md: security-review checkbox documenting that adding to the whitelist is a deliberate code change. - tests: 16 unit tests for SvgSanitiser (clean / script / foreignObject / on* / javascript: / data: / expression() / url(data:) / safe https / geometry preserved / data-* stripped / external DTD bounded / billion-laughs bounded / unparseable / empty / non-svg root) + 6 integration tests for ResourceService that wire the real sanitiser end-to-end (malicious upload sanitised, garbage rejected, oversize-but-sanitises-down accepted, near-cap clean accepted, persisted bytes != original).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements REQ-RES-009..013 from the
svg-sanitisationchange. Adds a server-side DOM-based whitelist sanitiser for uploaded SVG bytes and plugs it into theresource-uploadspipeline.Builds on top of #46 (now merged) — reuses the
ResourceExceptionbase class soResourceController::errorResponse()automatically renders the400 invalid_svgenvelope without any controller-level change.What changed
lib/Service/SvgSanitiser.php(new) — pure-PHP DOM-based sanitiser. No Nextcloud dependencies. Conservative whitelist of 24 element types (REQ-RES-010) and 50 attribute types (REQ-RES-011). Strips:on*attributes regardless of whitelist (defence in depth, in case a future whitelist edit accidentally adds one);href/xlink:hrefvalues starting withjavascript:ordata:(REQ-RES-012);stylevalues containingexpression(,javascript:, orurl(data:.lib/Exception/InvalidSvgException.php(new) — extends the existingResourceExceptionbase from PR feat(resources): admin-only resource upload endpoint #46 with stable error codeinvalid_svgand HTTP 400.lib/Service/ResourceService.php— injectsSvgSanitiser; for the SVG branch, sanitises before the size check; throwsInvalidSvgExceptiononnull; persists the sanitised bytes (NOT the original).l10n/en+l10n/nl— translation entry for theinvalid_svgdisplay message.DEVELOPMENT.md— security-review checkbox documenting that adding to the SVG whitelist is a deliberate code change, not an editorial one.Notes for future contributors
Size cap measured after sanitisationcovers this.<script>,<foreignObject>,<iframe>,<embed>,<object>, or any of the 60+ exotic SVG elements that carry executable surface. Adding any element / attribute is a security review checkbox perDEVELOPMENT.md(Security review checkboxes section).LIBXML_NONET | LIBXML_NOENTparse flags. TheLIBXML_NONETflag prevents the parser from fetching external DTDs / entities;LIBXML_NOENTsubstitutes entities so they do not amplify recursively. Modern libxml internal expansion limits also protect against billion-laughs (verified in tests).nullreturn → 400invalid_svgis reserved for unparseable XML or fully-stripped documents. Disallowed elements alone do not fail the upload — they are stripped silently and the request proceeds.Test plan
composer phpcspasses (42 / 42 files clean)composer lintpasses (no syntax errors)InvalidSvgExceptioncarries the stable error codeinvalid_svg+ HTTP 400 (asserted in integration test)Pre-existing issues observed (NOT touched by this PR)
lib/Service/UserAttributeResolver.php:59— PsalmUndefinedInterfaceMethodonIUser::getLanguage()lib/Service/ImageMimeValidator.php:109— PHPStan offset-on-array noticeThese predate this change and live in files this PR does not modify; left for a separate fix-in-passing pass on those files.
Spec
openspec/changes/svg-sanitisation/proposal.mdopenspec/changes/svg-sanitisation/tasks.mdopenspec/changes/svg-sanitisation/specs/resource-uploads/spec.md(REQ-RES-009..013)