Skip to content

Commit 8a221f6

Browse files
committed
Content Filtering: Covered new config options and filters with tests
1 parent 035be66 commit 8a221f6

File tree

5 files changed

+96
-5
lines changed

5 files changed

+96
-5
lines changed

app/Config/app.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,17 @@
4242
// Even when overridden the WYSIWYG editor may still escape script content.
4343
'allow_content_scripts' => env('ALLOW_CONTENT_SCRIPTS', false),
4444

45-
// Control the behaviour of page content filtering.
45+
// Control the behaviour of content filtering, primarily used for page content.
4646
// This setting is a collection of characters which represent different available filters:
47-
// - j - Filter out JavaScript based content
48-
// - h - Filter out unexpected, potentially dangerous, HTML elements
47+
// - j - Filter out JavaScript and unknown binary data based content
48+
// - h - Filter out unexpected, and potentially dangerous, HTML elements
4949
// - f - Filter out unexpected form elements
5050
// - a - Run content through a more complex allow-list filter
5151
// This defaults to using all filters, unless ALLOW_CONTENT_SCRIPTS is set to true in which case no filters are used.
5252
// Note: These filters are a best attempt, and may not be 100% effective. They are typically a layer used in addition to other security measures.
5353
// TODO - Add to example env
5454
// TODO - Remove allow_content_scripts option above
55-
'content_filtering' => env('CONTENT_FILTERING', env('ALLOW_CONTENT_SCRIPTS', false) === true ? '' : 'jfha'),
55+
'content_filtering' => env('APP_CONTENT_FILTERING', env('ALLOW_CONTENT_SCRIPTS', false) === true ? '' : 'jhfa'),
5656

5757
// Allow server-side fetches to be performed to potentially unknown
5858
// and user-provided locations. Primarily used in exports when loading

app/Entities/Tools/PageContent.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ protected function getContentCacheKey(string $html): string
341341
$contentId = $this->page->id;
342342
$contentTime = $this->page->updated_at?->timestamp ?? time();
343343
$appVersion = AppVersion::get();
344-
return "page-content-cache::{$appVersion}::{$contentId}::{$contentTime}::{$contentHash}";
344+
$filterConfig = config('app.content_filtering') ?? '';
345+
return "page-content-cache::{$filterConfig}::{$appVersion}::{$contentId}::{$contentTime}::{$contentHash}";
345346
}
346347

347348
/**

phpunit.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<server name="AUTH_AUTO_INITIATE" value="false"/>
3535
<server name="DISABLE_EXTERNAL_SERVICES" value="true"/>
3636
<server name="ALLOW_UNTRUSTED_SERVER_FETCHING" value="false"/>
37+
<server name="CONTENT_FILTERING" value="jhfa"/>
3738
<server name="ALLOW_CONTENT_SCRIPTS" value="false"/>
3839
<server name="AVATAR_URL" value=""/>
3940
<server name="LDAP_START_TLS" value="false"/>

tests/Entity/PageContentFilteringTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public function test_page_content_scripts_removed_by_default()
2222

2323
public function test_more_complex_content_script_escaping_scenarios()
2424
{
25+
config()->set('app.content_filtering', 'j');
26+
2527
$checks = [
2628
"<p>Some script</p><script>alert('cat')</script>",
2729
"<div><div><div><div><p>Some script</p><script>alert('cat')</script></div></div></div></div>",
@@ -47,6 +49,8 @@ public function test_more_complex_content_script_escaping_scenarios()
4749

4850
public function test_js_and_base64_src_urls_are_removed()
4951
{
52+
config()->set('app.content_filtering', 'j');
53+
5054
$checks = [
5155
'<iframe src="javascript:alert(document.cookie)"></iframe>',
5256
'<iframe src="JavAScRipT:alert(document.cookie)"></iframe>',
@@ -89,6 +93,8 @@ public function test_js_and_base64_src_urls_are_removed()
8993

9094
public function test_javascript_uri_links_are_removed()
9195
{
96+
config()->set('app.content_filtering', 'j');
97+
9298
$checks = [
9399
'<a id="xss" href="javascript:alert(document.cookie)>Click me</a>',
94100
'<a id="xss" href="javascript: alert(document.cookie)>Click me</a>',
@@ -110,8 +116,23 @@ public function test_javascript_uri_links_are_removed()
110116
}
111117
}
112118

119+
public function test_form_filtering_is_controlled_by_config()
120+
{
121+
config()->set('app.content_filtering', '');
122+
$page = $this->entities->page();
123+
$page->html = '<form><input type="text" id="dont-see-this" value="test"></form>';
124+
$page->save();
125+
126+
$this->asEditor()->get($page->getUrl())->assertSee('dont-see-this', false);
127+
128+
config()->set('app.content_filtering', 'f');
129+
$this->get($page->getUrl())->assertDontSee('dont-see-this', false);
130+
}
131+
113132
public function test_form_actions_with_javascript_are_removed()
114133
{
134+
config()->set('app.content_filtering', 'j');
135+
115136
$checks = [
116137
'<customform><custominput id="xss" type=submit formaction=javascript:alert(document.domain) value=Submit><custominput></customform>',
117138
'<customform ><custombutton id="xss" formaction="JaVaScRiPt:alert(document.domain)">Click me</custombutton></customform>',
@@ -139,6 +160,8 @@ public function test_form_actions_with_javascript_are_removed()
139160

140161
public function test_form_elements_are_removed()
141162
{
163+
config()->set('app.content_filtering', 'f');
164+
142165
$checks = [
143166
'<p>thisisacattofind</p><form>thisdogshouldnotbefound</form>',
144167
'<p>thisisacattofind</p><input type="text" value="thisdogshouldnotbefound">',
@@ -182,6 +205,8 @@ public function test_form_elements_are_removed()
182205

183206
public function test_form_attributes_are_removed()
184207
{
208+
config()->set('app.content_filtering', 'f');
209+
185210
$withinSvgSample = <<<'TESTCASE'
186211
<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg">
187212
<foreignObject width="100%" height="100%">
@@ -229,6 +254,8 @@ public function test_form_attributes_are_removed()
229254

230255
public function test_metadata_redirects_are_removed()
231256
{
257+
config()->set('app.content_filtering', 'h');
258+
232259
$checks = [
233260
'<meta http-equiv="refresh" content="0; url=//external_url">',
234261
'<meta http-equiv="refresh" ConTeNt="0; url=//external_url">',
@@ -253,6 +280,8 @@ public function test_metadata_redirects_are_removed()
253280

254281
public function test_page_inline_on_attributes_removed_by_default()
255282
{
283+
config()->set('app.content_filtering', 'j');
284+
256285
$this->asEditor();
257286
$page = $this->entities->page();
258287
$script = '<p onmouseenter="console.log(\'test\')">Hello</p>';
@@ -267,6 +296,8 @@ public function test_page_inline_on_attributes_removed_by_default()
267296

268297
public function test_more_complex_inline_on_attributes_escaping_scenarios()
269298
{
299+
config()->set('app.content_filtering', 'j');
300+
270301
$checks = [
271302
'<p onclick="console.log(\'test\')">Hello</p>',
272303
'<p OnCliCk="console.log(\'test\')">Hello</p>',
@@ -308,6 +339,8 @@ public function test_page_content_scripts_show_with_filters_disabled()
308339

309340
public function test_svg_script_usage_is_removed()
310341
{
342+
config()->set('app.content_filtering', 'j');
343+
311344
$checks = [
312345
'<svg id="test" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><a xlink:href="javascript:alert(document.domain)"><rect x="0" y="0" width="100" height="100" /></a></svg>',
313346
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><use xlink:href="data:application/xml;base64 ,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KPGRlZnM+CjxjaXJjbGUgaWQ9InRlc3QiIHI9IjAiIGN4PSIwIiBjeT0iMCIgc3R5bGU9ImZpbGw6ICNGMDAiPgo8c2V0IGF0dHJpYnV0ZU5hbWU9ImZpbGwiIGF0dHJpYnV0ZVR5cGU9IkNTUyIgb25iZWdpbj0nYWxlcnQoZG9jdW1lbnQuZG9tYWluKScKb25lbmQ9J2FsZXJ0KCJvbmVuZCIpJyB0bz0iIzAwRiIgYmVnaW49IjBzIiBkdXI9Ijk5OXMiIC8+CjwvY2lyY2xlPgo8L2RlZnM+Cjx1c2UgeGxpbms6aHJlZj0iI3Rlc3QiLz4KPC9zdmc+#test"/></svg>',
@@ -350,4 +383,46 @@ public function test_page_inline_on_attributes_show_with_filters_disabled()
350383
$pageView->assertSee($script, false);
351384
$pageView->assertDontSee('<p>Hello</p>', false);
352385
}
386+
387+
public function test_non_content_filtering_is_controlled_by_config()
388+
{
389+
config()->set('app.content_filtering', 'h');
390+
$page = $this->entities->page();
391+
$html = <<<'HTML'
392+
<style>superbeans!</style>
393+
<p>inbetweenpsection</p>
394+
<link rel="stylesheet" href="https://example.com/superbeans.css">
395+
<meta name="description" content="superbeans!">
396+
<title>superbeans!</title>
397+
<template id="template">superbeans!</template>
398+
HTML;
399+
400+
$page->html = $html;
401+
$page->save();
402+
403+
$resp = $this->asEditor()->get($page->getUrl());
404+
$resp->assertDontSee('superbeans', false);
405+
$resp->assertSee('inbetweenpsection', false);
406+
}
407+
408+
public function test_non_content_filtering()
409+
{
410+
config()->set('app.content_filtering', 'h');
411+
}
412+
413+
public function test_allow_list_filtering_is_controlled_by_config()
414+
{
415+
config()->set('app.content_filtering', '');
416+
$page = $this->entities->page();
417+
$page->html = '<div style="position: absolute; left: 0;color:#00FFEE;">Hello!</div>';
418+
$page->save();
419+
420+
$resp = $this->asEditor()->get($page->getUrl());
421+
$resp->assertSee('style="position: absolute; left: 0;color:#00FFEE;"', false);
422+
423+
config()->set('app.content_filtering', 'a');
424+
$resp = $this->get($page->getUrl());
425+
$resp->assertDontSee('style="position: absolute; left: 0;color:#00FFEE;"', false);
426+
$resp->assertSee('style="color:#00FFEE;"', false);
427+
}
353428
}

tests/Unit/ConfigTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ public function test_mysql_host_parsed_as_expected()
170170
}
171171
}
172172

173+
public function test_content_filtering_defaults_to_enabled()
174+
{
175+
$this->runWithEnv(['APP_CONTENT_FILTERING' => null, 'ALLOW_CONTENT_SCRIPTS' => null], function () {
176+
$this->assertEquals('jhfa', config('app.content_filtering'));
177+
});
178+
}
179+
180+
public function test_allow_content_scripts_disables_content_filtering()
181+
{
182+
$this->runWithEnv(['APP_CONTENT_FILTERING' => null, 'ALLOW_CONTENT_SCRIPTS' => 'true'], function () {
183+
$this->assertEquals('', config('app.content_filtering'));
184+
});
185+
}
186+
173187
/**
174188
* Set an environment variable of the given name and value
175189
* then check the given config key to see if it matches the given result.

0 commit comments

Comments
 (0)