Security hardening and performance improvements
- Add visibility check to attachment downloads (prevents unauthorized access) - Fix ticket ID collision with uniqueness verification loop - Harden CSP: replace unsafe-inline with nonce-based script execution - Add IP-based rate limiting (supplements session-based) - Add visibility checks to bulk operations - Validate internal visibility requires groups - Optimize user activity query (JOINs vs subqueries) - Update documentation with design decisions and security info Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
// This file contains the HTML template for creating a new ticket
|
||||
require_once __DIR__ . '/../middleware/SecurityHeadersMiddleware.php';
|
||||
require_once __DIR__ . '/../middleware/CsrfMiddleware.php';
|
||||
$nonce = SecurityHeadersMiddleware::getNonce();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -10,13 +13,10 @@
|
||||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260126c">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260124e">
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e"></script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
// CSRF Token for AJAX requests
|
||||
window.CSRF_TOKEN = '<?php
|
||||
require_once __DIR__ . '/../middleware/CsrfMiddleware.php';
|
||||
echo CsrfMiddleware::getToken();
|
||||
?>';
|
||||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -239,7 +239,7 @@
|
||||
</div>
|
||||
<!-- END OUTER FRAME -->
|
||||
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
// Duplicate detection with debounce
|
||||
let duplicateCheckTimeout = null;
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
// This file contains the HTML template for the dashboard
|
||||
// It receives $tickets, $totalTickets, $totalPages, $page, $status, $categories, $types variables from the controller
|
||||
require_once __DIR__ . '/../middleware/SecurityHeadersMiddleware.php';
|
||||
require_once __DIR__ . '/../middleware/CsrfMiddleware.php';
|
||||
$nonce = SecurityHeadersMiddleware::getNonce();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -10,16 +13,13 @@
|
||||
<title>Ticket Dashboard</title>
|
||||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260126c">
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ascii-banner.js"></script>
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260124e"></script>
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e"></script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ascii-banner.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260124e"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
// CSRF Token for AJAX requests
|
||||
window.CSRF_TOKEN = '<?php
|
||||
require_once __DIR__ . '/../middleware/CsrfMiddleware.php';
|
||||
echo CsrfMiddleware::getToken();
|
||||
?>';
|
||||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||||
// Timezone configuration (from server)
|
||||
window.APP_TIMEZONE = '<?php echo $GLOBALS['config']['TIMEZONE']; ?>';
|
||||
window.APP_TIMEZONE_OFFSET = <?php echo $GLOBALS['config']['TIMEZONE_OFFSET']; ?>; // minutes from UTC
|
||||
@@ -32,7 +32,7 @@
|
||||
<div id="boot-sequence" class="boot-overlay">
|
||||
<pre id="boot-text"></pre>
|
||||
</div>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
function showBootSequence() {
|
||||
const bootText = document.getElementById('boot-text');
|
||||
const bootOverlay = document.getElementById('boot-sequence');
|
||||
@@ -108,7 +108,7 @@
|
||||
</button>
|
||||
<div id="ascii-banner-container" class="banner-content"></div>
|
||||
</div>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
function toggleBanner() {
|
||||
const wrapper = document.querySelector('.ascii-banner-wrapper');
|
||||
const icon = document.querySelector('.toggle-icon');
|
||||
@@ -764,10 +764,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/settings.js"></script>
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/keyboard-shortcuts.js"></script>
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/advanced-search.js"></script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/settings.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/keyboard-shortcuts.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/advanced-search.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
// Admin dropdown toggle
|
||||
function toggleAdminMenu(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
@@ -38,6 +38,10 @@ function formatDetails($details, $actionType) {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
<?php
|
||||
require_once __DIR__ . '/../middleware/SecurityHeadersMiddleware.php';
|
||||
require_once __DIR__ . '/../middleware/CsrfMiddleware.php';
|
||||
$nonce = SecurityHeadersMiddleware::getNonce();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -48,22 +52,19 @@ function formatDetails($details, $actionType) {
|
||||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260126c">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260124e">
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260124e"></script>
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e"></script>
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ticket.js?v=20260124e"></script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260124e"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ticket.js?v=20260124e"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
// CSRF Token for AJAX requests
|
||||
window.CSRF_TOKEN = '<?php
|
||||
require_once __DIR__ . '/../middleware/CsrfMiddleware.php';
|
||||
echo CsrfMiddleware::getToken();
|
||||
?>';
|
||||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||||
// Timezone configuration (from server)
|
||||
window.APP_TIMEZONE = '<?php echo $GLOBALS['config']['TIMEZONE']; ?>';
|
||||
window.APP_TIMEZONE_OFFSET = <?php echo $GLOBALS['config']['TIMEZONE_OFFSET']; ?>; // minutes from UTC
|
||||
window.APP_TIMEZONE_ABBREV = '<?php echo $GLOBALS['config']['TIMEZONE_ABBREV']; ?>';
|
||||
</script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
// Store ticket data in a global variable (using json_encode for XSS safety)
|
||||
window.ticketData = {
|
||||
ticket_id: <?php echo json_encode($ticket['ticket_id']); ?>,
|
||||
@@ -445,7 +446,7 @@ function formatDetails($details, $actionType) {
|
||||
</div>
|
||||
</div>
|
||||
<!-- END OUTER FRAME -->
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
// Initialize the ticket view
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (typeof showTab === 'function') {
|
||||
@@ -455,7 +456,7 @@ function formatDetails($details, $actionType) {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
// Ticket data already initialized in head, add id alias for compatibility
|
||||
window.ticketData.id = window.ticketData.ticket_id;
|
||||
console.log('Ticket data loaded:', window.ticketData);
|
||||
@@ -597,6 +598,6 @@ function formatDetails($details, $actionType) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/settings.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/settings.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
// Admin view for managing API keys
|
||||
// Receives $apiKeys from controller
|
||||
require_once __DIR__ . '/../../middleware/SecurityHeadersMiddleware.php';
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
$nonce = SecurityHeadersMiddleware::getNonce();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -11,12 +14,9 @@
|
||||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css">
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script>
|
||||
window.CSRF_TOKEN = '<?php
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
echo CsrfMiddleware::getToken();
|
||||
?>';
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -161,7 +161,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
document.getElementById('generateKeyForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
// Admin view for managing custom fields
|
||||
// Receives $customFields from controller
|
||||
require_once __DIR__ . '/../../middleware/SecurityHeadersMiddleware.php';
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
$nonce = SecurityHeadersMiddleware::getNonce();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -11,11 +14,8 @@
|
||||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css">
|
||||
<script>
|
||||
window.CSRF_TOKEN = '<?php
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
echo CsrfMiddleware::getToken();
|
||||
?>';
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -154,8 +154,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
function showCreateModal() {
|
||||
document.getElementById('modalTitle').textContent = 'Create Custom Field';
|
||||
document.getElementById('fieldForm').reset();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
// Admin view for managing recurring tickets
|
||||
// Receives $recurringTickets from controller
|
||||
require_once __DIR__ . '/../../middleware/SecurityHeadersMiddleware.php';
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
$nonce = SecurityHeadersMiddleware::getNonce();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -11,11 +14,8 @@
|
||||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css">
|
||||
<script>
|
||||
window.CSRF_TOKEN = '<?php
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
echo CsrfMiddleware::getToken();
|
||||
?>';
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -189,8 +189,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
function showCreateModal() {
|
||||
document.getElementById('modalTitle').textContent = 'Create Recurring Ticket';
|
||||
document.getElementById('recurringForm').reset();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
// Admin view for managing ticket templates
|
||||
// Receives $templates from controller
|
||||
require_once __DIR__ . '/../../middleware/SecurityHeadersMiddleware.php';
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
$nonce = SecurityHeadersMiddleware::getNonce();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -11,11 +14,8 @@
|
||||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css">
|
||||
<script>
|
||||
window.CSRF_TOKEN = '<?php
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
echo CsrfMiddleware::getToken();
|
||||
?>';
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -160,8 +160,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
const templates = <?php echo json_encode($templates ?? []); ?>;
|
||||
|
||||
function showCreateModal() {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
// Admin view for workflow/status transitions designer
|
||||
// Receives $workflows from controller
|
||||
require_once __DIR__ . '/../../middleware/SecurityHeadersMiddleware.php';
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
$nonce = SecurityHeadersMiddleware::getNonce();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -11,11 +14,8 @@
|
||||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css">
|
||||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css">
|
||||
<script>
|
||||
window.CSRF_TOKEN = '<?php
|
||||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||||
echo CsrfMiddleware::getToken();
|
||||
?>';
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -177,8 +177,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script>
|
||||
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||||
<script nonce="<?php echo $nonce; ?>">
|
||||
const workflows = <?php echo json_encode($workflows ?? []); ?>;
|
||||
|
||||
function showCreateModal() {
|
||||
|
||||
Reference in New Issue
Block a user