Skip to content

Commit 2e8827d

Browse files
pattonwebzclaude
andcommitted
fix: render SVG code snippets in the Accessibility Analysis panel as images
The metabox's details panel previously injected an issue's raw SVG markup straight into the panel's HTML. Convert it to a data: URI and render it inside an <img> instead, the same technique already used for inline SVGs in the issue modal's image finder (IssueImage.js) - this keeps the metabox and the sidebar/modal consistent in how they display this content, and matches how untrusted markup should be handled here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent a9196c9 commit 2e8827d

3 files changed

Lines changed: 90 additions & 1 deletion

File tree

admin/class-ajax.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,15 @@ function ( $a, $b ) {
526526
)
527527
) . '" />';
528528
} elseif ( $object_svg ) {
529-
$html .= $object_svg;
529+
// Rendered as an <img> via a data URI, not injected as inline markup -
530+
// see edac_svg_markup_to_data_uri()'s docblock for why.
531+
$html .= '<img src="' . esc_url( edac_svg_markup_to_data_uri( $object_svg ), [ 'data', 'http', 'https' ] ) . '" alt="' . esc_attr(
532+
sprintf(
533+
/* translators: %d: issue ID number */
534+
__( 'image for issue %d', 'accessibility-checker' ),
535+
$id
536+
)
537+
) . '" />';
530538
}
531539

532540
$html .= '</div>';

includes/helper-functions.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,26 @@ function edac_parse_html_for_media( $html ) {
788788
];
789789
}
790790

791+
/**
792+
* Convert raw SVG markup into a data: URI safe for use as an <img> src.
793+
*
794+
* The source markup comes from scanned page code stored in the issues
795+
* table, which can contain arbitrary attributes/children (e.g. <script>,
796+
* on* handlers, <foreignObject>). Encoding it into a data URI and only
797+
* ever placing that URI in an <img> src means the browser treats it as an
798+
* image resource, not as markup to parse for scripting - the same
799+
* technique already used for untrusted SVGs in the issue modal's image
800+
* finder (see src/issueModal/components/IssueImage.js).
801+
*
802+
* @since x.x.x
803+
*
804+
* @param string $svg_markup Raw SVG markup.
805+
* @return string Data URI string (unescaped - callers must esc_url()/esc_attr() it before output).
806+
*/
807+
function edac_svg_markup_to_data_uri( $svg_markup ) {
808+
return 'data:image/svg+xml,' . rawurlencode( $svg_markup );
809+
}
810+
791811
/**
792812
* Remove corrected posts
793813
*
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
/**
3+
* Class SvgMarkupToDataUriTest
4+
*
5+
* @package Accessibility_Checker
6+
*/
7+
8+
/**
9+
* Test cases for edac_svg_markup_to_data_uri() function.
10+
*/
11+
class SvgMarkupToDataUriTest extends WP_UnitTestCase {
12+
13+
/**
14+
* Tests that the function returns a data URI with the markup percent-encoded.
15+
*/
16+
public function test_encodes_markup_as_data_uri() {
17+
$svg = '<svg width="100" height="100"><circle cx="50" cy="50" r="40" /></svg>';
18+
19+
$this->assertSame(
20+
'data:image/svg+xml,' . rawurlencode( $svg ),
21+
edac_svg_markup_to_data_uri( $svg )
22+
);
23+
}
24+
25+
/**
26+
* Tests that no raw markup characters survive encoding - the whole point
27+
* is that the string is inert HTML once placed in an <img src> attribute.
28+
*
29+
* @dataProvider malicious_svg_data
30+
*
31+
* @param string $svg The malicious SVG markup to encode.
32+
*/
33+
public function test_strips_no_bytes_but_leaves_no_raw_markup_characters( $svg ) {
34+
$data_uri = edac_svg_markup_to_data_uri( $svg );
35+
36+
$this->assertStringStartsWith( 'data:image/svg+xml,', $data_uri );
37+
$this->assertStringNotContainsString( '<', $data_uri );
38+
$this->assertStringNotContainsString( '>', $data_uri );
39+
$this->assertStringNotContainsString( '"', $data_uri );
40+
$this->assertStringNotContainsString( "'", $data_uri );
41+
42+
// The encoded payload still round-trips back to the original markup.
43+
$this->assertSame(
44+
$svg,
45+
rawurldecode( substr( $data_uri, strlen( 'data:image/svg+xml,' ) ) )
46+
);
47+
}
48+
49+
/**
50+
* Data provider of SVG markup containing common XSS vectors.
51+
*/
52+
public function malicious_svg_data() {
53+
return [
54+
'onload handler' => [ '<svg onload="alert(document.cookie)"></svg>' ],
55+
'script child' => [ '<svg><script>alert(1)</script></svg>' ],
56+
'foreignObject' => [ '<svg><foreignObject><img src=x onerror="alert(1)"></foreignObject></svg>' ],
57+
'javascript: xlink' => [ '<svg><a xlink:href="javascript:alert(1)"><text>click</text></a></svg>' ],
58+
'animate onbegin' => [ '<svg><animate onbegin="alert(1)" attributeName="x" /></svg>' ],
59+
];
60+
}
61+
}

0 commit comments

Comments
 (0)