feat(guards): add GR-SEC-010 — output escaping enforcement in views

Add architecture test ViewLayerOutputEscapingContractTest that flags
raw <?php echo in .phtml files. Legitimate uses (pre-built ARIA attrs
from navActive(), hardcoded role values, pre-rendered HTML fragments)
must carry an inline // raw-html-ok: reason marker.

Annotated all 11 existing raw echo sites with justification markers.
Added guard to catalog, enforcement map (automated), CLAUDE.md security
section, and never-do-this list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 12:08:25 +02:00
parent 9a4d59eb4c
commit 576a0c51cd
12 changed files with 150 additions and 11 deletions

View File

@@ -23,7 +23,7 @@ $timeouts = [
<?php $type = $message['type'] ?? 'info'; ?>
<?php $timeout = $timeouts[$type] ?? 5000; ?>
<?php $isAssertive = $type === 'error' || $type === 'warning'; ?>
<div class="notice" data-variant="<?php e($type); ?>" data-flash-timeout="<?php e($timeout); ?>" role="<?php echo $isAssertive ? 'alert' : 'status'; ?>">
<div class="notice" data-variant="<?php e($type); ?>" data-flash-timeout="<?php e($timeout); ?>" role="<?php echo $isAssertive ? 'alert' : 'status'; // raw-html-ok: hardcoded ARIA role values ?>">
<span><?php e(t($message['message'] ?? '')); ?></span>
<form method="post" action="flash/dismiss/<?php e($message['id'] ?? ''); ?>">
<input type="hidden" name="return" value="<?php e($returnTarget); ?>">

View File

@@ -12,7 +12,7 @@ if (!in_array($listTitleTag, $allowedTitleTags, true)) {
<<?php e($listTitleTag); ?>><?php e($listTitle); ?></<?php e($listTitleTag); ?>>
<?php if (trim($listTitleActionsHtml) !== ''): ?>
<div class="app-list-titlebar-actions">
<?php echo $listTitleActionsHtml; ?>
<?php echo $listTitleActionsHtml; // raw-html-ok: pre-built HTML from titlebar action renderer ?>
</div>
<?php endif; ?>
</div>

View File

@@ -255,7 +255,7 @@ $renderAdminNavGroup = static function (array $group, string $tenantQueryParam):
$active = $item['active'] ?? ['class' => '', 'aria' => ''];
?>
<li>
<a href="<?php e($href); ?>" class="<?php e($active['class'] ?? ''); ?>" <?php echo $active['aria'] ?? ''; ?>>
<a href="<?php e($href); ?>" class="<?php e($active['class'] ?? ''); ?>" <?php echo $active['aria'] ?? ''; // raw-html-ok: pre-built ARIA attribute from navActive() ?>>
<?php e($item['label'] ?? ''); ?>
</a>
</li>
@@ -306,7 +306,7 @@ $modulePanelSlots = is_array($moduleSlots['aside.tab_panel'] ?? null) ? $moduleS
<ul>
<li>
<?php $home = navActive(['', 'admin'], false); ?>
<a href="<?php e(lurl('')); ?>" class="<?php e($home['class']); ?>" <?php echo $home['aria']; ?>>
<a href="<?php e(lurl('')); ?>" class="<?php e($home['class']); ?>" <?php echo $home['aria']; // raw-html-ok: pre-built ARIA attribute from navActive() ?>>
<?php e(t('Home')); ?>
</a>
</li>
@@ -325,7 +325,7 @@ $modulePanelSlots = is_array($moduleSlots['aside.tab_panel'] ?? null) ? $moduleS
?>
<li>
<a href="<?php e($explorerHref); ?>" class="<?php e($explorerActive['class'] ?? ''); ?>"
<?php echo $explorerActive['aria'] ?? ''; ?>>
<?php echo $explorerActive['aria'] ?? ''; // raw-html-ok: pre-built ARIA attribute from navActive() ?>>
<?php e(t($explorerLabel)); ?>
</a>
</li>

View File

@@ -33,7 +33,7 @@ $style = $styleParts ? implode(';', $styleParts) . ';' : '';
?>
<a
class="app-tile<?php echo $class ? ' ' . $class : ''; ?>"
class="app-tile<?php echo $class ? ' ' . $class : ''; // raw-html-ok: internal CSS class from tile config ?>"
href="<?php e($href); ?>"
<?php if ($style) : ?>style="<?php e($style); ?>"<?php endif; ?>
<?php if ($tooltip !== '') : ?>data-tooltip="<?php e($tooltip); ?>" data-tooltip-pos="<?php e($tooltipPos); ?>"<?php endif; ?>

View File

@@ -135,7 +135,7 @@ $moduleTopbarSlots = is_array($moduleSlots['topbar.right_item'] ?? null) ? $modu
data-theme-option
data-theme-value="<?php e($key); ?>"
class="<?php e($isActiveTheme ? 'active' : ''); ?>"
<?php echo $isActiveTheme ? 'aria-current="true"' : ''; ?>>
<?php echo $isActiveTheme ? 'aria-current="true"' : ''; // raw-html-ok: hardcoded ARIA attribute ?>>
<?php e(t($label)); ?>
</a>
</li>