@@ -101,4 +101,147 @@ public function test_pre_comment_content_inner_blocks() {
101101 $ expected_content = preg_replace ( '/\s+/ ' , ' ' , $ expected_content );
102102 $ this ->assertSame ( $ expected_content , $ filtered_content );
103103 }
104+
105+ /**
106+ * Test security: malformed block delimiters should be handled safely
107+ */
108+ public function test_security_malformed_block_delimiters () {
109+ $ malformed_inputs = array (
110+ '<!-- wp:paragraph ' , // Incomplete opening
111+ '<!-- wp:paragraph <!-- wp:nested --> --> ' , // Nested comments
112+ '<!-- wp:paragraph {"unclosed": "string --> ' , // Invalid JSON
113+ "<!-- wp:paragraph --> \x00<!-- /wp:paragraph --> " , // Null bytes
114+ '<!-- wp:paragraph -->content<!-- /wp:invalid --> ' , // Mismatched closing
115+ '<!--wp:paragraph --> ' , // No space after comment start
116+ '<!-- w:paragraph --> ' , // Incomplete wp prefix
117+ );
118+
119+ foreach ( $ malformed_inputs as $ input ) {
120+ $ result = Verbum_Block_Utils::remove_blocks ( $ input );
121+ // Should not cause errors and should return safe content
122+ $ this ->assertIsString ( $ result );
123+
124+ // For truly malformed inputs that don't have proper block structure,
125+ // they should be returned as-is since has_blocks() would return false
126+ // or they would be treated as plain text content
127+ if ( ! has_blocks ( $ input ) ) {
128+ $ this ->assertEquals ( $ input , $ result );
129+ } else {
130+ // If it's detected as having blocks but they're malformed,
131+ // the parser should handle them gracefully
132+ $ this ->assertIsString ( $ result );
133+ }
134+ }
135+ }
136+
137+ /**
138+ * Test security: Unicode and encoding edge cases
139+ */
140+ public function test_security_unicode_handling () {
141+ $ unicode_inputs = array (
142+ '<!-- wp:paragraph -->🔥💯<!-- /wp:paragraph --> ' , // Emoji
143+ '<!-- wp:paragraph -->مرحبا<!-- /wp:paragraph --> ' , // RTL text
144+ '<!-- wp:paragraph --> ' . chr ( 0xC2 ) . chr ( 0x85 ) . '<!-- /wp:paragraph --> ' , // UTF-8 sequences
145+ '<!-- wp:paragraph -->café<!-- /wp:paragraph --> ' , // Accented characters
146+ );
147+
148+ foreach ( $ unicode_inputs as $ input ) {
149+ $ result = Verbum_Block_Utils::remove_blocks ( $ input );
150+ // Should handle unicode properly without corruption
151+ $ this ->assertIsString ( $ result );
152+ // Allowed blocks should be preserved with unicode content
153+ $ this ->assertStringContainsString ( 'wp:paragraph ' , $ result );
154+ }
155+ }
156+
157+ /**
158+ * Test security: Block type spoofing attempts
159+ */
160+ public function test_security_block_type_spoofing () {
161+ $ spoofing_attempts = array (
162+ '<!-- wp:core/paragraph -->content<!-- /wp:core/paragraph --> ' , // Namespace confusion
163+ '<!-- wp:paragraph/evil -->content<!-- /wp:paragraph/evil --> ' , // Fake subtype
164+ '<!-- wp: paragraph -->content<!-- /wp: paragraph --> ' , // Extra space
165+ '<!-- wp:PARAGRAPH -->content<!-- /wp:PARAGRAPH --> ' , // Case variation
166+ );
167+
168+ foreach ( $ spoofing_attempts as $ input ) {
169+ $ result = Verbum_Block_Utils::remove_blocks ( $ input );
170+ // Should properly identify and handle spoofing attempts
171+ $ this ->assertIsString ( $ result );
172+ }
173+ }
174+
175+ /**
176+ * Test security: Input validation consistency between fast and slow paths
177+ */
178+ public function test_security_input_validation_consistency () {
179+ $ test_inputs = array (
180+ '<!-- wp:paragraph -->Allowed content<!-- /wp:paragraph --> ' ,
181+ '<!-- wp:paragraph -->Allowed<!-- /wp:paragraph --><!-- wp:latest-posts --> ' ,
182+ '<!-- wp:quote --><!-- wp:paragraph -->Nested allowed<!-- /wp:paragraph --><!-- /wp:quote --> ' ,
183+ '<!-- wp:quote --><!-- wp:button -->Nested disallowed<!-- /wp:button --><!-- /wp:quote --> ' ,
184+ );
185+
186+ foreach ( $ test_inputs as $ input ) {
187+ $ slashed_input = wp_slash ( $ input );
188+ $ result = Verbum_Block_Utils::remove_blocks ( $ slashed_input );
189+
190+ // Result should be unslashed content (consistent processing)
191+ $ this ->assertIsString ( $ result );
192+
193+ // Should not contain slashes that would indicate inconsistent processing
194+ // This tests the fix for the critical input validation inconsistency
195+ if ( strpos ( $ result , 'wp: ' ) !== false ) {
196+ $ this ->assertStringNotContainsString ( '\\" ' , $ result , 'Result should not contain escaped quotes from slashing ' );
197+ }
198+ }
199+ }
200+
201+ /**
202+ * Test security: Resource exhaustion scenarios
203+ */
204+ public function test_security_resource_limits () {
205+ // Test deeply nested blocks
206+ $ deeply_nested = str_repeat ( '<!-- wp:quote --> ' , 50 ) . 'content ' . str_repeat ( '<!-- /wp:quote --> ' , 50 );
207+ $ result = Verbum_Block_Utils::remove_blocks ( $ deeply_nested );
208+ $ this ->assertIsString ( $ result );
209+
210+ // Test very long content
211+ $ long_content = '<!-- wp:paragraph --> ' . str_repeat ( 'a ' , 10000 ) . '<!-- /wp:paragraph --> ' ;
212+ $ result = Verbum_Block_Utils::remove_blocks ( $ long_content );
213+ $ this ->assertIsString ( $ result );
214+
215+ // Test many blocks
216+ $ many_blocks = str_repeat ( '<!-- wp:paragraph -->content<!-- /wp:paragraph --> ' , 100 );
217+ $ result = Verbum_Block_Utils::remove_blocks ( $ many_blocks );
218+ $ this ->assertIsString ( $ result );
219+ }
220+
221+ /**
222+ * Test that Block_Scanner and parse_blocks produce consistent security results
223+ */
224+ public function test_consistency_between_scanner_and_parse_blocks () {
225+ $ test_cases = array (
226+ '<!-- wp:paragraph -->Safe content<!-- /wp:paragraph --> ' ,
227+ '<!-- wp:paragraph -->Safe<!-- /wp:paragraph --><!-- wp:latest-posts -->Unsafe<!-- /wp:latest-posts --> ' ,
228+ '<!-- wp:quote --><!-- wp:paragraph -->Nested safe<!-- /wp:paragraph --><!-- /wp:quote --> ' ,
229+ '<!-- wp:quote --><!-- wp:button -->Nested unsafe<!-- /wp:button --><!-- /wp:quote --> ' ,
230+ );
231+
232+ foreach ( $ test_cases as $ test_case ) {
233+ $ result = Verbum_Block_Utils::remove_blocks ( $ test_case );
234+
235+ // Verify that allowed blocks are preserved
236+ if ( strpos ( $ test_case , 'wp:paragraph ' ) !== false || strpos ( $ test_case , 'wp:quote ' ) !== false ) {
237+ $ this ->assertStringContainsString ( 'wp: ' , $ result , 'Allowed blocks should be preserved ' );
238+ }
239+
240+ // Verify that disallowed blocks are removed
241+ if ( strpos ( $ test_case , 'wp:latest-posts ' ) !== false || strpos ( $ test_case , 'wp:button ' ) !== false ) {
242+ $ this ->assertStringNotContainsString ( 'latest-posts ' , $ result , 'Disallowed blocks should be removed ' );
243+ $ this ->assertStringNotContainsString ( 'button ' , $ result , 'Disallowed blocks should be removed ' );
244+ }
245+ }
246+ }
104247}
0 commit comments