security: add |esc_url to all template URLs, register escape Twig filters (v1.1.3)
All checks were successful
Create Release Package / PHP Lint (push) Successful in 50s
Create Release Package / PHPUnit Tests (push) Successful in 44s
Create Release Package / Build Release (push) Successful in 2m17s

5th OWASP Top-10 pass: added |esc_url filter to all unescaped URL outputs
across 8 Twig template partials (headers, footers, search, comments).
Registered esc_html, esc_attr, esc_url as Twig filters with is_safe option.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 10:34:41 +01:00
parent 02689f687f
commit 6c8526d2a5
10 changed files with 33 additions and 24 deletions

View File

@@ -7,8 +7,7 @@
<div class="d-flex align-items-center gap-2 mb-1">
<strong class="small">
{% if comment.author_url %}
{# author_url is pre-escaped with esc_url() in ContextBuilder #}
<a href="{{ comment.author_url|raw }}" class="text-decoration-none text-body" rel="nofollow">
<a href="{{ comment.author_url|esc_url }}" class="text-decoration-none text-body" rel="nofollow">
{{ comment.author }}
</a>
{% else %}
@@ -19,7 +18,7 @@
{{ comment.date }}
</time>
{% if comment.edit_url %}
<a href="{{ comment.edit_url }}" class="text-body-secondary small">{{ __('Edit') }}</a>
<a href="{{ comment.edit_url|esc_url }}" class="text-body-secondary small">{{ __('Edit') }}</a>
{% endif %}
</div>
<div class="comment-content small">

View File

@@ -13,7 +13,7 @@
<ul class="list-unstyled">
{% for item in footer_menu %}
<li class="mb-1">
<a href="{{ item.url }}" class="text-body-secondary text-decoration-none">
<a href="{{ item.url|esc_url }}" class="text-body-secondary text-decoration-none">
{{ item.title }}
</a>
</li>

View File

@@ -11,7 +11,7 @@
<ul class="list-unstyled">
{% for item in footer_menu %}
<li>
<a href="{{ item.url }}" class="text-body-secondary text-decoration-none">
<a href="{{ item.url|esc_url }}" class="text-body-secondary text-decoration-none">
{{ item.title }}
</a>
</li>

View File

@@ -1,7 +1,7 @@
<header>
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
<div class="container flex-column">
<a class="navbar-brand fw-bold mb-2" href="{{ site.url }}">
<a class="navbar-brand fw-bold mb-2" href="{{ site.url|esc_url }}">
{{ site.name }}
</a>
{% if site.description %}
@@ -21,7 +21,7 @@
{% if item.children|length > 0 %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
href="{{ item.url }}" role="button"
href="{{ item.url|esc_url }}" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
{{ item.title }}
</a>
@@ -29,7 +29,7 @@
{% for child in item.children %}
<li>
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
href="{{ child.url }}"
href="{{ child.url|esc_url }}"
{% if child.active %}aria-current="page"{% endif %}
{% if child.target %}target="{{ child.target }}"{% endif %}>
{{ child.title }}
@@ -41,7 +41,7 @@
{% else %}
<li class="nav-item">
<a class="nav-link{{ item.active ? ' active' : '' }}"
href="{{ item.url }}"
href="{{ item.url|esc_url }}"
{% if item.active %}aria-current="page"{% endif %}
{% if item.target %}target="{{ item.target }}"{% endif %}>
{{ item.title }}

View File

@@ -1,7 +1,7 @@
<header>
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
<div class="container">
<a class="navbar-brand fw-bold" href="{{ site.url }}">
<a class="navbar-brand fw-bold" href="{{ site.url|esc_url }}">
{{ site.name }}
</a>
@@ -16,7 +16,7 @@
aria-labelledby="navbarOffcanvasLabel">
<div class="offcanvas-header">
{% if user.logged_in %}
<a href="{{ user.account_url }}" class="d-flex align-items-center text-decoration-none">
<a href="{{ user.account_url|esc_url }}" class="d-flex align-items-center text-decoration-none">
{{ user.avatar|raw }}
<span class="ms-2 fw-semibold">{{ user.display_name|esc_html }}</span>
</a>
@@ -32,7 +32,7 @@
{% if item.children|length > 0 %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
href="{{ item.url }}" role="button"
href="{{ item.url|esc_url }}" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
{{ item.title }}
</a>
@@ -40,7 +40,7 @@
{% for child in item.children %}
<li>
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
href="{{ child.url }}"
href="{{ child.url|esc_url }}"
{% if child.active %}aria-current="page"{% endif %}
{% if child.target %}target="{{ child.target }}"{% endif %}>
{{ child.title }}
@@ -52,7 +52,7 @@
{% else %}
<li class="nav-item">
<a class="nav-link{{ item.active ? ' active' : '' }}"
href="{{ item.url }}"
href="{{ item.url|esc_url }}"
{% if item.active %}aria-current="page"{% endif %}
{% if item.target %}target="{{ item.target }}"{% endif %}>
{{ item.title }}

View File

@@ -1,7 +1,7 @@
<header class="position-absolute w-100" style="z-index: 1030;">
<nav class="navbar navbar-expand-lg navbar-dark" aria-label="{{ __('Primary navigation') }}">
<div class="container">
<a class="navbar-brand fw-bold" href="{{ site.url }}">
<a class="navbar-brand fw-bold" href="{{ site.url|esc_url }}">
{{ site.name }}
</a>
@@ -18,7 +18,7 @@
{% if item.children|length > 0 %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
href="{{ item.url }}" role="button"
href="{{ item.url|esc_url }}" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
{{ item.title }}
</a>
@@ -26,7 +26,7 @@
{% for child in item.children %}
<li>
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
href="{{ child.url }}"
href="{{ child.url|esc_url }}"
{% if child.active %}aria-current="page"{% endif %}
{% if child.target %}target="{{ child.target }}"{% endif %}>
{{ child.title }}
@@ -38,7 +38,7 @@
{% else %}
<li class="nav-item">
<a class="nav-link{{ item.active ? ' active' : '' }}"
href="{{ item.url }}"
href="{{ item.url|esc_url }}"
{% if item.active %}aria-current="page"{% endif %}
{% if item.target %}target="{{ item.target }}"{% endif %}>
{{ item.title }}

View File

@@ -1,7 +1,7 @@
<header>
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
<div class="container">
<a class="navbar-brand fw-bold" href="{{ site.url }}">
<a class="navbar-brand fw-bold" href="{{ site.url|esc_url }}">
{{ site.name }}
</a>
@@ -18,7 +18,7 @@
{% if item.children|length > 0 %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
href="{{ item.url }}" role="button"
href="{{ item.url|esc_url }}" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
{{ item.title }}
</a>
@@ -26,7 +26,7 @@
{% for child in item.children %}
<li>
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
href="{{ child.url }}"
href="{{ child.url|esc_url }}"
{% if child.active %}aria-current="page"{% endif %}
{% if child.target %}target="{{ child.target }}"{% endif %}>
{{ child.title }}
@@ -38,7 +38,7 @@
{% else %}
<li class="nav-item">
<a class="nav-link{{ item.active ? ' active' : '' }}"
href="{{ item.url }}"
href="{{ item.url|esc_url }}"
{% if item.active %}aria-current="page"{% endif %}
{% if item.target %}target="{{ item.target }}"{% endif %}>
{{ item.title }}

View File

@@ -1,4 +1,4 @@
<form role="search" method="get" action="{{ site.url }}" class="mb-4">
<form role="search" method="get" action="{{ site.url|esc_url }}" class="mb-4">
<div class="input-group">
<input type="search" class="form-control" name="s"
placeholder="{{ __('Search...') }}"