You've already forked wp-bootstrap
security: add |esc_url to all template URLs, register escape Twig filters (v1.1.3)
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:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [1.1.3] - 2026-03-07
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- **Template output escaping**: Added `|esc_url` filter to all unescaped URL outputs across 8 Twig template partials — `header.html.twig`, `header-offcanvas.html.twig`, `header-transparent.html.twig`, `header-centered.html.twig`, `footer.html.twig`, `footer-columns.html.twig`, `search-form.html.twig`, `comment-item.html.twig`. Covers `site.url`, `item.url`, `child.url`, `user.account_url`, `comment.author_url`, and `comment.edit_url`.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Twig escape filters** (`TwigService.php`): Registered `esc_html`, `esc_attr`, and `esc_url` as Twig filters with `['is_safe' => ['html']]` to prevent double-encoding. Complements existing `wpautop` and `wp_kses_post` filters.
|
||||||
|
|
||||||
## [1.1.2] - 2026-03-01
|
## [1.1.2] - 2026-03-01
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Description: A modern WordPress Block Theme built from scratch with Bootstrap 5.
|
|||||||
Requires at least: 6.7
|
Requires at least: 6.7
|
||||||
Tested up to: 6.7
|
Tested up to: 6.7
|
||||||
Requires PHP: 8.3
|
Requires PHP: 8.3
|
||||||
Version: 1.1.2
|
Version: 1.1.3
|
||||||
License: GNU General Public License v2 or later
|
License: GNU General Public License v2 or later
|
||||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||||
Text Domain: wp-bootstrap
|
Text Domain: wp-bootstrap
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
<div class="d-flex align-items-center gap-2 mb-1">
|
<div class="d-flex align-items-center gap-2 mb-1">
|
||||||
<strong class="small">
|
<strong class="small">
|
||||||
{% if comment.author_url %}
|
{% if comment.author_url %}
|
||||||
{# author_url is pre-escaped with esc_url() in ContextBuilder #}
|
<a href="{{ comment.author_url|esc_url }}" class="text-decoration-none text-body" rel="nofollow">
|
||||||
<a href="{{ comment.author_url|raw }}" class="text-decoration-none text-body" rel="nofollow">
|
|
||||||
{{ comment.author }}
|
{{ comment.author }}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -19,7 +18,7 @@
|
|||||||
{{ comment.date }}
|
{{ comment.date }}
|
||||||
</time>
|
</time>
|
||||||
{% if comment.edit_url %}
|
{% 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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-content small">
|
<div class="comment-content small">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
{% for item in footer_menu %}
|
{% for item in footer_menu %}
|
||||||
<li class="mb-1">
|
<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 }}
|
{{ item.title }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
{% for item in footer_menu %}
|
{% for item in footer_menu %}
|
||||||
<li>
|
<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 }}
|
{{ item.title }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
|
||||||
<div class="container flex-column">
|
<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 }}
|
{{ site.name }}
|
||||||
</a>
|
</a>
|
||||||
{% if site.description %}
|
{% if site.description %}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
{% if item.children|length > 0 %}
|
{% if item.children|length > 0 %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
|
<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">
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</a>
|
</a>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
{% for child in item.children %}
|
{% for child in item.children %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
|
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
|
||||||
href="{{ child.url }}"
|
href="{{ child.url|esc_url }}"
|
||||||
{% if child.active %}aria-current="page"{% endif %}
|
{% if child.active %}aria-current="page"{% endif %}
|
||||||
{% if child.target %}target="{{ child.target }}"{% endif %}>
|
{% if child.target %}target="{{ child.target }}"{% endif %}>
|
||||||
{{ child.title }}
|
{{ child.title }}
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link{{ item.active ? ' active' : '' }}"
|
<a class="nav-link{{ item.active ? ' active' : '' }}"
|
||||||
href="{{ item.url }}"
|
href="{{ item.url|esc_url }}"
|
||||||
{% if item.active %}aria-current="page"{% endif %}
|
{% if item.active %}aria-current="page"{% endif %}
|
||||||
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
|
||||||
<div class="container">
|
<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 }}
|
{{ site.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
aria-labelledby="navbarOffcanvasLabel">
|
aria-labelledby="navbarOffcanvasLabel">
|
||||||
<div class="offcanvas-header">
|
<div class="offcanvas-header">
|
||||||
{% if user.logged_in %}
|
{% 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 }}
|
{{ user.avatar|raw }}
|
||||||
<span class="ms-2 fw-semibold">{{ user.display_name|esc_html }}</span>
|
<span class="ms-2 fw-semibold">{{ user.display_name|esc_html }}</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
{% if item.children|length > 0 %}
|
{% if item.children|length > 0 %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
|
<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">
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</a>
|
</a>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
{% for child in item.children %}
|
{% for child in item.children %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
|
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
|
||||||
href="{{ child.url }}"
|
href="{{ child.url|esc_url }}"
|
||||||
{% if child.active %}aria-current="page"{% endif %}
|
{% if child.active %}aria-current="page"{% endif %}
|
||||||
{% if child.target %}target="{{ child.target }}"{% endif %}>
|
{% if child.target %}target="{{ child.target }}"{% endif %}>
|
||||||
{{ child.title }}
|
{{ child.title }}
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link{{ item.active ? ' active' : '' }}"
|
<a class="nav-link{{ item.active ? ' active' : '' }}"
|
||||||
href="{{ item.url }}"
|
href="{{ item.url|esc_url }}"
|
||||||
{% if item.active %}aria-current="page"{% endif %}
|
{% if item.active %}aria-current="page"{% endif %}
|
||||||
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<header class="position-absolute w-100" style="z-index: 1030;">
|
<header class="position-absolute w-100" style="z-index: 1030;">
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark" aria-label="{{ __('Primary navigation') }}">
|
<nav class="navbar navbar-expand-lg navbar-dark" aria-label="{{ __('Primary navigation') }}">
|
||||||
<div class="container">
|
<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 }}
|
{{ site.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{% if item.children|length > 0 %}
|
{% if item.children|length > 0 %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
|
<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">
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</a>
|
</a>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{% for child in item.children %}
|
{% for child in item.children %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
|
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
|
||||||
href="{{ child.url }}"
|
href="{{ child.url|esc_url }}"
|
||||||
{% if child.active %}aria-current="page"{% endif %}
|
{% if child.active %}aria-current="page"{% endif %}
|
||||||
{% if child.target %}target="{{ child.target }}"{% endif %}>
|
{% if child.target %}target="{{ child.target }}"{% endif %}>
|
||||||
{{ child.title }}
|
{{ child.title }}
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link{{ item.active ? ' active' : '' }}"
|
<a class="nav-link{{ item.active ? ' active' : '' }}"
|
||||||
href="{{ item.url }}"
|
href="{{ item.url|esc_url }}"
|
||||||
{% if item.active %}aria-current="page"{% endif %}
|
{% if item.active %}aria-current="page"{% endif %}
|
||||||
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="{{ __('Primary navigation') }}">
|
||||||
<div class="container">
|
<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 }}
|
{{ site.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{% if item.children|length > 0 %}
|
{% if item.children|length > 0 %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle{{ item.active ? ' active' : '' }}"
|
<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">
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</a>
|
</a>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{% for child in item.children %}
|
{% for child in item.children %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
|
<a class="dropdown-item{{ child.active ? ' active' : '' }}"
|
||||||
href="{{ child.url }}"
|
href="{{ child.url|esc_url }}"
|
||||||
{% if child.active %}aria-current="page"{% endif %}
|
{% if child.active %}aria-current="page"{% endif %}
|
||||||
{% if child.target %}target="{{ child.target }}"{% endif %}>
|
{% if child.target %}target="{{ child.target }}"{% endif %}>
|
||||||
{{ child.title }}
|
{{ child.title }}
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link{{ item.active ? ' active' : '' }}"
|
<a class="nav-link{{ item.active ? ' active' : '' }}"
|
||||||
href="{{ item.url }}"
|
href="{{ item.url|esc_url }}"
|
||||||
{% if item.active %}aria-current="page"{% endif %}
|
{% if item.active %}aria-current="page"{% endif %}
|
||||||
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
|
|||||||
@@ -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">
|
<div class="input-group">
|
||||||
<input type="search" class="form-control" name="s"
|
<input type="search" class="form-control" name="s"
|
||||||
placeholder="{{ __('Search...') }}"
|
placeholder="{{ __('Search...') }}"
|
||||||
|
|||||||
Reference in New Issue
Block a user