Skip to content

Add CSP script-src directive and remove inline event handlers (CO-2720) #67

Merged
merged 8 commits into from
Jul 21, 2025
23 changes: 23 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at https://editorconfig.org

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = false
trim_trailing_whitespace = false

[*.bat]
end_of_line = crlf

[*.yml]
indent_size = 2

[*.twig]
insert_final_newline = false

[Makefile]
indent_style = tab
3 changes: 3 additions & 0 deletions app/src/Controller/AppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ public function beforeRender(EventInterface $event) {
$this->set('vv_menu_permissions',
$this->Authorization->menuPermissions($this->request->getSession()->read('Auth.User.username'), $mgid));
}

// Generate a nonce for use in JavaScript tags with the Content-Security-Policy script-src directive
$this->set('vv_js_nonce', base64_encode(random_bytes(16)));
}

/**
Expand Down
15 changes: 9 additions & 6 deletions app/templates/MatchgridSettings/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

use \App\Lib\Enum\ReferenceIdEnum;
?>
<script type="text/javascript">
<script nonce="<?= $vv_js_nonce ?>">
// JS specific to these fields

function fields_update_gadgets() {
Expand All @@ -43,17 +43,20 @@ use \App\Lib\Enum\ReferenceIdEnum;
$("#referenceid-start").closest('li').hide();
}
}
function js_local_onload() {

$(function() {
fields_update_gadgets();
}

$('#referenceid-method').change(function() {
fields_update_gadgets();
});
});
</script>
<?php
// This view does not support read-only
if($action == 'edit') {
print $this->Field->control('referenceid_method',
['empty' => true,
'onChange' => 'fields_update_gadgets();']);
['empty' => true]);

print $this->Field->control('referenceid_start',
['default' => 1001]);
Expand Down
8 changes: 4 additions & 4 deletions app/templates/Matchgrids/reconcile.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,12 @@
</table>
</div>

<script>
function js_local_onload() {
<script nonce="<?= $vv_js_nonce ?>">
$(function() {
// Handle display-mode switching
$('.view-controls input').click(function () {
// Remove existing view-mode classes and add the new class equal to the view-controls switch ID
$('#reconcile-table').removeClass('view-mode-diff view-mode-match view-mode-both').addClass($(this).attr('id'));
});
}
</script>
});
</script>
20 changes: 11 additions & 9 deletions app/templates/Permissions/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

use \App\Lib\Enum\PermissionEnum;
?>
<script type="text/javascript">
<script nonce="<?= $vv_js_nonce ?>">
// JS specific to these fields

function fields_update_gadgets() {
Expand All @@ -43,19 +43,21 @@ use \App\Lib\Enum\PermissionEnum;
$("#matchgrid-id").closest('li').show();
}
}
function js_local_onload() {

$(function() {
fields_update_gadgets();
}

$('#permission').change(function() {
fields_update_gadgets();
});
});
</script>
<?php
// This view does not support read-only
if($action == 'add' || $action == 'edit') {
print $this->Field->control('username');

print $this->Field->control('permission',
['empty' => true,
'onChange' => 'fields_update_gadgets();']);


print $this->Field->control('permission', ['empty' => true]);

print $this->Field->control('matchgrid_id', ['empty' => true]);
}
16 changes: 10 additions & 6 deletions app/templates/RuleAttributes/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
use \App\Lib\Enum\ConfidenceModeEnum;
use \App\Lib\Enum\SearchTypeEnum;
?>
<script type="text/javascript">
<script nonce="<?= $vv_js_nonce ?>">
// JS specific to these fields

function fields_update_gadgets() {
Expand All @@ -44,17 +44,21 @@ use \App\Lib\Enum\SearchTypeEnum;
$("#match-empty").closest('li').show();
}
}
function js_local_onload() {

$(function() {
fields_update_gadgets();
}

$('#required').change(function() {
fields_update_gadgets();
});
});
</script>
<?php
<?php
// This view does not support read-only
if($action == 'add' || $action == 'edit') {
print $this->Field->control('attribute_id', ['empty' => true]);
print $this->Field->control('crosscheck_attribute_id', ['empty' => true], false, __('match.fd.RuleAttributes.crosscheck_attribute_id'));
print $this->Field->control('search_type', ['empty' => true]);
print $this->Field->control('required', ['onChange'=>'fields_update_gadgets();'], false);
print $this->Field->control('required', [], false);
print $this->Field->control('match_empty', [], false);
}
17 changes: 10 additions & 7 deletions app/templates/SystemsOfRecord/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
use \App\Lib\Enum\ResolutionModeEnum;
use \App\Lib\Enum\TrustModeEnum;
?>
<script type="text/javascript">
<script nonce="<?= $vv_js_nonce ?>">
// JS specific to these fields

function fields_update_gadgets() {
Expand All @@ -43,9 +43,13 @@ use \App\Lib\Enum\TrustModeEnum;
}
}

function js_local_onload() {
$(function() {
fields_update_gadgets();
}

$('#resolution-mode').change(function() {
fields_update_gadgets();
});
});
</script>
<?php
// This view does not support read-only
Expand All @@ -56,9 +60,8 @@ if($action == 'add' || $action == 'edit') {
['empty' => true,
'default' => TrustModeEnum::Standard ]);

print $this->Field->control('resolution_mode',
['empty' => true,
'onChange' => 'fields_update_gadgets();']);

print $this->Field->control('resolution_mode',
['empty' => true]);

print $this->Field->control('notification_email', [], false);
}
2 changes: 1 addition & 1 deletion app/templates/element/dialog.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="dialog-title"><?= __('match.op.confirm'); ?></h2>
<button type="button" class="btn-close nospin" data-bs-dismiss="modal" aria-label="Close"/>
<button type="button" class="btn-close nospin" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="dialog-text" class="modal-body">
</div>
Expand Down
9 changes: 6 additions & 3 deletions app/templates/element/httpHeaders.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

// As a general rule, all Match pages are post-login and so shouldn't be cached
// As a general rule, all Match pages are post-login and so shouldn't be cached
header("Expires: Thursday, 10-Jan-69 00:00:00 GMT");
header("Cache-Control: no-store, no-cache, max-age=0, must-revalidate");
header("Pragma: no-cache");

// CakePHP adds inline event handlers ("oninput" and "oninvalid") to fields as part of FormHelper.
// So as not to throw CSP errors, we must include "script-src-attr 'unsafe-inline'".
header("Content-Security-Policy: object-src 'none'; base-uri 'none'; frame-ancestors 'self'; script-src 'self' 'nonce-$vv_js_nonce'; script-src-attr 'unsafe-inline';");

header("Content-Security-Policy: object-src 'none'; base-uri 'none'; frame-ancestors 'self'");
header("X-Content-Type-Options: nosniff");
header("Permissions-Policy: accelerometer=(),autoplay=(),camera=(),cross-origin-isolated=(),display-capture=(),encrypted-media=(),fullscreen=(),geolocation=(),gyroscope=(),keyboard-map=(),magnetometer=(),microphone=(),midi=(),payment=(),picture-in-picture=(),publickey-credentials-get=(),screen-wake-lock=(),sync-xhr=(self),usb=(),web-share=(),xr-spatial-tracking=(),gamepad=(),hid=(),idle-detection=(),interest-cohort=(),serial=()");
header("Cross-Origin-Opener-Policy: same-origin");
header("X-Permitted-Cross-Domain-Policies: none");

// Add X-UA-Compatible header for IE
// Add X-UA-Compatible header for IE
if (isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false)) {
header('X-UA-Compatible: IE=edge,chrome=1');
}
Loading