diff --git a/ecrire/inc/auth.php b/ecrire/inc/auth.php
index e7e04b385ba46abbe18b76af8695d696edcfc6ce..9b60fd9245c4f075d35eaf8b1ae1b9897d9f2d96 100644
--- a/ecrire/inc/auth.php
+++ b/ecrire/inc/auth.php
@@ -111,7 +111,7 @@ function auth_echec($raison) {
 			_T('avis_erreur_connexion'),
 			_T('avis_erreur_visiteur')
 				// Lien vers le site public
-				. '<br /><a href="' . url_de_base() . '">' . _T('login_retour_public') . '</a>'
+				. '<br /><a href="' . attribut_url(url_de_base()) . '">' . _T('login_retour_public') . '</a>'
 				// Si la personne est connectée, lien de déconnexion ramenant vers la page de login
 				. ($est_connecte ? ' | <a href="' . generer_url_public('', 'action=logout&amp;logout=prive') . '">' . _T('icone_deconnecter') . '</a>' : '')
 		);
@@ -122,7 +122,7 @@ function auth_echec($raison) {
 			_T('avis_erreur_connexion'),
 			'<br /><br /><p>'
 			. _T('texte_inc_auth_1', ['auth_login' => $raison['login']])
-			. " <a href='$h'>"
+			. " <a href='" . attribut_url($h) . "'>"
 			. _T('texte_inc_auth_2')
 			. '</a>'
 			. _T('texte_inc_auth_3')
@@ -797,13 +797,13 @@ function ask_php_auth($pb, $raison, $retour = '', $url = '', $re = '', $lien = '
 	$public = generer_url_public();
 	$ecrire = generer_url_ecrire();
 	$retour = $retour ?: _T('icone_retour');
-	$corps .= "<p>$raison</p>[<a href='$public'>$retour</a>] ";
+	$corps .= "<p>$raison</p>[<a href='" . attribut_url($public) . "'>$retour</a>] ";
 	if ($url) {
-		$corps .= "[<a href='" . generer_url_action('cookie', "essai_auth_http=oui&$url") . "'>$re</a>]";
+		$corps .= "[<a href='" . attribut_url(generer_url_action('cookie', "essai_auth_http=oui&$url")) . "'>$re</a>]";
 	}
 
 	if ($lien) {
-		$corps .= " [<a href='$ecrire'>" . _T('login_espace_prive') . '</a>]';
+		$corps .= " [<a href='" . attribut_url($ecrire) . "'>" . _T('login_espace_prive') . '</a>]';
 	}
 	include_spip('inc/minipres');
 	echo minipres($pb, $corps);
diff --git a/ecrire/inc/documents.php b/ecrire/inc/documents.php
index 68f6982c9ac17294986471b4e752f1f94eb5f227..d11d57365d69ae46de4d5d2dfbf1198e6c63a8f8 100644
--- a/ecrire/inc/documents.php
+++ b/ecrire/inc/documents.php
@@ -327,7 +327,7 @@ function check_upload_error($error, $msg = '', $return = false) {
 	include_spip('inc/minipres');
 	echo minipres(
 		$msg,
-		"<div style='text-align: " . $GLOBALS['spip_lang_right'] . "'><a href='" . rawurldecode((string) $GLOBALS['redirect']) . "'><button type='button'>" . _T('ecrire:bouton_suivant') . '</button></a></div>'
+		"<div style='text-align: " . $GLOBALS['spip_lang_right'] . "'><a href='" . attribut_url(rawurldecode((string) $GLOBALS['redirect'])) . "'><button type='button'>" . _T('ecrire:bouton_suivant') . '</button></a></div>'
 	);
 	exit;
 }
diff --git a/ecrire/inc/filtres.php b/ecrire/inc/filtres.php
index 64ba887c23153e1bb9660bc75790af3fa611ab51..8205df49311f5e87dc3bdb9ceae5b50eb7e7db3b 100644
--- a/ecrire/inc/filtres.php
+++ b/ecrire/inc/filtres.php
@@ -2409,7 +2409,7 @@ function enclosure2microformat($e) {
 	$fichier = basename($url);
 
 	return '<a rel="enclosure"'
-	. ($url ? ' href="' . spip_htmlspecialchars($url) . '"' : '')
+	. ($url ? ' href="' . attribut_url($url) . '"' : '')
 	. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
 	. ($length ? ' title="' . spip_htmlspecialchars($length) . '"' : '')
 	. '>' . $fichier . '</a>';
@@ -4159,7 +4159,7 @@ function lien_ou_expose($url, $libelle = null, $on = false, $class = '', $title
 		$att .= 'class="' . ($class ? attribut_html($class) . ' ' : '') . (defined('_LIEN_OU_EXPOSE_CLASS_ON') ? _LIEN_OU_EXPOSE_CLASS_ON : 'on') . '"';
 	} else {
 		$bal = 'a';
-		$att = "href='$url'"
+		$att = "href='" . attribut_url($url) . "'"
 			. ($title ? " title='" . attribut_html($title) . "'" : '')
 			. ($class ? " class='" . attribut_html($class) . "'" : '')
 			. ($rel ? " rel='" . attribut_html($rel) . "'" : '')
@@ -4304,7 +4304,7 @@ function prepare_icone_base($type, $lien, $texte, $fond, $fonction = '', $class
 	// Markup final
 	if ($type == 'lien') {
 		return "<span class='icone $class_lien'>"
-		. "<a href='$lien'$title$ajax$javascript>"
+		. "<a href='" . attribut_url($lien) . "'$title$ajax$javascript>"
 		. $icone
 		. "<b>$texte</b>"
 		. "</a></span>\n";
@@ -4816,7 +4816,7 @@ function generer_objet_lien(int $id_objet, string $objet, int $longueur = 80, st
 	}
 	$url = generer_objet_url($id_objet, $objet, '', '', null, '', $connect);
 
-	return "<a href='$url' class='$objet'>" . couper($titre, $longueur) . '</a>';
+	return "<a href='" . attribut_url($url) . "' class='$objet'>" . couper($titre, $longueur) . '</a>';
 }
 
 /**
diff --git a/ecrire/inc/filtres_ecrire.php b/ecrire/inc/filtres_ecrire.php
index b5188ecb9139b31e5b60a4a465793d7eaa80dd8a..8197da854bc6931a80a7b325e49c9c33781f9684 100644
--- a/ecrire/inc/filtres_ecrire.php
+++ b/ecrire/inc/filtres_ecrire.php
@@ -462,7 +462,7 @@ function bouton_spip_rss($op, $args = [], $lang = '', $title = 'RSS') {
 	$clic = http_img_pack('rss-16.png', 'RSS', '', $title);
 
 	$url = generer_url_api_low_sec('transmettre', 'rss', $op, '', http_build_query($args), false, true);
-	return "<a style='float: " . $GLOBALS['spip_lang_right'] . ";' href='$url'>$clic</a>";
+	return "<a style='float: " . $GLOBALS['spip_lang_right'] . ";' href='" . attribut_url($url) . "'>$clic</a>";
 }
 
 
@@ -570,13 +570,13 @@ function filtre_afficher_enfant_rub_dist($id_rubrique) {
  */
 function afficher_plus_info($lien, $titre = '+', $titre_lien = '') {
 	$titre = attribut_html($titre);
-	$icone = "\n<a href='$lien' title='$titre' class='plus_info'>" .
+	$icone = "\n<a href='" . attribut_url($lien) . "' title='" . attribut_html($titre) . "' class='plus_info'>" .
 		http_img_pack('information-16.png', $titre) . '</a>';
 
 	if (!$titre_lien) {
 		return $icone;
 	} else {
-		return $icone . "\n<a href='$lien'>$titre_lien</a>";
+		return $icone . "\n<a href='" . attribut_url($lien) . "'>$titre_lien</a>";
 	}
 }
 
diff --git a/ecrire/inc/headers.php b/ecrire/inc/headers.php
index de92cc2210fcd891f228ea43646a18443ce426ac..4983e407e5d6e32b3de226774ebc863f37a8e005 100644
--- a/ecrire/inc/headers.php
+++ b/ecrire/inc/headers.php
@@ -53,6 +53,7 @@ function redirige_par_entete($url, $equiv = '', $status = 302) {
 	}
 
 	// ne pas laisser passer n'importe quoi dans l'url
+	include_spip('inc/filtres');
 	$url = str_replace(['<', '"'], ['&lt;', '&quot;'], (string) $url);
 	$url = str_replace(["\r", "\n", ' '], ['%0D', '%0A', '%20'], $url);
 	while (str_contains($url, '%0A')) {
@@ -93,7 +94,7 @@ function redirige_par_entete($url, $equiv = '', $status = 302) {
 		if (isset($GLOBALS['meta']['charset'])) {
 			@header('Content-Type: text/html; charset=' . $GLOBALS['meta']['charset']);
 		}
-		$equiv = "<meta http-equiv='Refresh' content='0; url=$url'>";
+		$equiv = "<meta http-equiv='Refresh' content='0; url=" . attribut_url($url) . "'>";
 	}
 	include_spip('inc/lang');
 	if ($status != 302) {
@@ -109,7 +110,7 @@ function redirige_par_entete($url, $equiv = '', $status = 302) {
 <body>
 <h1>HTTP ' . $status . '</h1>
 <a href="',
-	quote_amp($url),
+	attribut_url($url),
 	'">',
 	_T('navigateur_pas_redirige'),
 	'</a></body></html>';
@@ -128,13 +129,15 @@ function redirige_formulaire($url, $equiv = '', $format = 'message') {
 		redirige_par_entete(str_replace('&amp;', '&', (string) $url), $equiv);
 	} // si c'est une ancre, fixer simplement le window.location.hash
 	elseif ($format == 'ajaxform' && preg_match(',^#[0-9a-z\-_]+$,i', (string) $url)) {
+		include_spip('inc/filtres');
 		return [
 			// on renvoie un lien masque qui sera traite par ajaxCallback.js
-			"<a href='$url' name='ajax_ancre' style='display:none;'>anchor</a>",
+			"<a href='" . attribut_url($url) . "' name='ajax_ancre' style='display:none;'>anchor</a>",
 			// et rien dans le message ok
 			''
 		];
 	} else {
+		include_spip('inc/filtres');
 		// ne pas laisser passer n'importe quoi dans l'url
 		$url = str_replace(['<', '"'], ['&lt;', '&quot;'], (string) $url);
 
@@ -150,9 +153,9 @@ function redirige_formulaire($url, $equiv = '', $format = 'message') {
 		if ($format == 'ajaxform') {
 			return [
 				// on renvoie un lien masque qui sera traite par ajaxCallback.js
-				'<a href="' . quote_amp($url) . '" name="ajax_redirect"  style="display:none;">' . _T('navigateur_pas_redirige') . '</a>',
+				'<a href="' . attribut_url($url) . '" name="ajax_redirect"  style="display:none;">' . _T('navigateur_pas_redirige') . '</a>',
 				// et un message au cas ou
-				'<br /><a href="' . quote_amp($url) . '">' . _T('navigateur_pas_redirige') . '</a>'
+				'<br /><a href="' . attribut_url($url) . '">' . _T('navigateur_pas_redirige') . '</a>'
 			];
 		} else // format message texte, tout en js inline
 		{
@@ -161,7 +164,7 @@ function redirige_formulaire($url, $equiv = '', $format = 'message') {
 				"<script>if (parent.window){parent.window.document.location.replace(\"$url\");} else {document.location.replace(\"$url\");}</script>"
 				. http_img_pack('loader.svg', '', " class='loader'")
 				. '<br />'
-				. '<a href="' . quote_amp($url) . '">' . _T('navigateur_pas_redirige') . '</a>';
+				. '<a href="' . attribut_url($url) . '">' . _T('navigateur_pas_redirige') . '</a>';
 		}
 	}
 }
diff --git a/ecrire/inc/plugin.php b/ecrire/inc/plugin.php
index 3b9d32adc3321a495c65419b24609387515c9959..520832ac6296d0600c88ca66504fc662f715bb7e 100644
--- a/ecrire/inc/plugin.php
+++ b/ecrire/inc/plugin.php
@@ -758,7 +758,7 @@ function plugin_controler_lib($lib, $url) {
 		include_spip('inc/charger_plugin');
 		$url = '<br />'	. bouton_telechargement_plugin($url, 'lib');
 	}*/
-	return _T('plugin_necessite_lib', ['lib' => $lib]) . " <a href='$url'>$url</a>";
+	return _T('plugin_necessite_lib', ['lib' => $lib]) . " <a href='" . attribut_url($url) . "'>$url</a>";
 }
 
 
diff --git a/ecrire/plugins/afficher_plugin.php b/ecrire/plugins/afficher_plugin.php
index 9e173ae34c2763f4afc04babeb3c55e589c23014..beb4be0e7c55562b05baa2813837e9745226e144 100644
--- a/ecrire/plugins/afficher_plugin.php
+++ b/ecrire/plugins/afficher_plugin.php
@@ -195,7 +195,7 @@ function plugin_resume($info, $dir_plugins, $plug_file, $url_page) {
 	$i = "<div class='$icon_class'><a href='$url' rel='info'>$img</a></div>";
 
 	return "<div class='resume'>"
-	. "<h3><a href='$url' rel='info'>"
+	. "<h3><a href='" . attribut_url($url) . "' rel='info'>"
 	. $nom
 	. '</a></h3>'
 	. " <span class='version'>" . $info['version'] . '</span>'
@@ -218,7 +218,7 @@ function plugin_desintalle($plug_file, $nom, $dir_plugins = null) {
 	$file = basename($plug_file);
 
 	return "<div class='actions'>[" .
-	"<a href='$action'
+	"<a href='" . attribut_url($action) . "'
 		onclick='return confirm(\"$text $nom ?\\n$text2\")'>"
 	. $text
 	. '</a>]</div>';
@@ -280,7 +280,7 @@ function affiche_bloc_plugin($plug_file, $info, $dir_plugins = null) {
 	if (
 		isset($info['documentation']) && ($lien = $info['documentation'])
 	) {
-		$description .= "<p><em class='site'><a href='$lien' class='spip_out'>" . _T('en_savoir_plus') . '</a></em></p>';
+		$description .= "<p><em class='site'><a href='" . attribut_url($lien) . "' class='spip_out'>" . _T('en_savoir_plus') . '</a></em></p>';
 	}
 	$s .= "<dd class='desc'>" . $description . "</dd>\n";
 
@@ -368,7 +368,7 @@ function formater_credits($infos, $sep = ', ') {
 		$texte .=
 			(!is_array($_credit))
 				? PtoBR(propre($_credit))
-				: ($_credit['url'] ? '<a href="' . $_credit['url'] . '">' : '') .
+				: ($_credit['url'] ? '<a href="' . attribut_url($_credit['url']) . '">' : '') .
 				$_credit['nom'] .
 				($_credit['url'] ? '</a>' : '');
 	}
diff --git a/ecrire/public/aiguiller.php b/ecrire/public/aiguiller.php
index 3f7391fd6e46abeef25feb8fd2ac5d3aabaf65d1..ddcc3b90d8a68763294a893abf4a65a16bcb03a9 100644
--- a/ecrire/public/aiguiller.php
+++ b/ecrire/public/aiguiller.php
@@ -153,7 +153,7 @@ function traiter_appels_inclusions_ajax() {
 			if ($ancre = _request('var_ajax_ancre')) {
 				// pas n'importe quoi quand meme dans la variable !
 				$ancre = str_replace(['<', '"', "'"], ['&lt;', '&quot;', ''], $ancre);
-				$texte = "<a href='#$ancre' name='ajax_ancre' style='display:none;'>anchor</a>" . $texte;
+				$texte = "<a href='" . attribut_url("#$ancre") . "' name='ajax_ancre' style='display:none;'>anchor</a>" . $texte;
 			}
 		} else {
 			include_spip('inc/headers');
diff --git a/ecrire/public/assembler.php b/ecrire/public/assembler.php
index acb5cc79330b08fe45af633b31cebe74356d46b9..f963c361ced76e95d8883c888a4f294b4c415253 100644
--- a/ecrire/public/assembler.php
+++ b/ecrire/public/assembler.php
@@ -706,7 +706,7 @@ function inclure_modele($type, $id, $params, $lien, string $connect = '', $env =
 		);
 	} else {
 		if ($lien) {
-			$retour = '<a href="' . $lien['href'] . '" class="' . $lien['class'] . '">' . $retour . '</a>';
+			$retour = '<a href="' . attribut_url($lien['href']) . '" class="' . attribut_html($lien['class']) . '">' . $retour . '</a>';
 		}
 	}
 
diff --git a/ecrire/src/Afficher/Minipage/AbstractPage.php b/ecrire/src/Afficher/Minipage/AbstractPage.php
index a9040fa9a948769273bde71e4f7bf95e62c7fb0c..32c5c025c5b98899c185e6e6b68d396fc5fe6dd4 100644
--- a/ecrire/src/Afficher/Minipage/AbstractPage.php
+++ b/ecrire/src/Afficher/Minipage/AbstractPage.php
@@ -128,7 +128,7 @@ abstract class AbstractPage {
 			$css = "<style type='text/css'>$inline</style>";
 			foreach ($files as $name) {
 				$file = timestamp(direction_css($name));
-				$css .= "<link rel='stylesheet' href='" . attribut_html($file) . "' type='text/css'>\n";
+				$css .= "<link rel='stylesheet' href='" . attribut_url($file) . "' type='text/css'>\n";
 			}
 			if (!empty($options['css'])) {
 				$css .= "<style type='text/css'>" . $options['css'] . '</style>';
@@ -157,7 +157,7 @@ abstract class AbstractPage {
 	protected function ouvreCorps($options = []) {
 		$url_site = url_de_base();
 		$header = "<header>\n" .
-			'<h1><a href="' . attribut_html($url_site) . '">' . interdire_scripts($GLOBALS['meta']['nom_site'] ?? '') . "</a></h1>\n";
+			'<h1><a href="' . attribut_url($url_site) . '">' . interdire_scripts($GLOBALS['meta']['nom_site'] ?? '') . "</a></h1>\n";
 
 		$titre = ($options['titre'] ?? '');
 		if ($titre) {
@@ -179,7 +179,7 @@ abstract class AbstractPage {
 		if (isset($options['footer'])) {
 			$footer = $options['footer'];
 		} else {
-			$footer = '<a href="' . attribut_html($url_site) . '">' . _T('retour') . "</a>\n";
+			$footer = '<a href="' . attribut_url($url_site) . '">' . _T('retour') . "</a>\n";
 		}
 		if (!empty($footer)) {
 			$footer = "<footer>\n{$footer}</footer>";
diff --git a/prive/modeles/pagination.html b/prive/modeles/pagination.html
index d460dd8f74a510481e1294e5f6b7de51079776ec..4b146a983f8829415ebef523ae8140172fe0f69b 100644
--- a/prive/modeles/pagination.html
+++ b/prive/modeles/pagination.html
@@ -18,7 +18,7 @@
 				)</li>]
 		]
 		[<li class="pagination-item"><a
-	      href="[(#ENV{url}|parametre_url{#ENV{debut},''}|ancre_url{#ENV{ancre}})]"
+	      href="[(#ENV{url}|parametre_url{#ENV{debut},''}|ancre_url{#ENV{ancre}}|attribut_url)]"
 				class="pagination-item-label lien_pagination" aria-label="<:lien_aller_a_la_premiere_page|attribut_html:>"
 				rel="nofollow">(#GET{premiere}|>{1}|?{#GET{type}|pagination_affiche_texte_lien_page{1,0}})</a></li>[(#GET{premiere}|>{2}|oui)<li
 				class="pagination-item tbc disabled"><span class="pagination-item-label">…</span></li>]]
@@ -36,7 +36,7 @@
 		#SET{item, #ENV{nombre_pages}|moins{1}|mult{#ENV{pas}}}
 		[[(#GET{derniere}|<{#ENV{nombre_pages}|moins{1}}|oui)<li class="pagination-item tbc disabled"><span class="pagination-item-label">…</span></li>]
 		<li class="pagination-item"><a
-		  href="[(#ENV{url}|parametre_url{#ENV{debut},#GET{item}}|ancre_url{#ENV{ancre}})]"
+		  href="[(#ENV{url}|parametre_url{#ENV{debut},#GET{item}}|ancre_url{#ENV{ancre}}|attribut_url)]"
 		  class="pagination-item-label lien_pagination" aria-label="<:lien_aller_a_la_derniere_page|attribut_html:>"
 		  rel="nofollow">(#GET{derniere}|<{#ENV{nombre_pages}}|?{#GET{type}|pagination_affiche_texte_lien_page{#ENV{nombre_pages},#GET{item}}})</a></li>]
 
diff --git a/prive/modeles/pagination_prive.html b/prive/modeles/pagination_prive.html
index 50026ae5cd4deacaf2604a32e96d0ca4d18f58e5..04967afb2025e3094e3c520cc0e4ba604187a2e1 100644
--- a/prive/modeles/pagination_prive.html
+++ b/prive/modeles/pagination_prive.html
@@ -19,7 +19,7 @@
 				)</li>]
 		]
 		[<li class="pagination-item"><a
-	      href="[(#ENV{url}|parametre_url{#ENV{debut},''}|ancre_url{#ENV{ancre}})]"
+	      href="[(#ENV{url}|parametre_url{#ENV{debut},''}|ancre_url{#ENV{ancre}}|attribut_url)]"
 				class="pagination-item-label lien_pagination" aria-label="<:lien_aller_a_la_premiere_page|attribut_html:>"
 				rel="nofollow">(#GET{premiere}|>{1}|?{#GET{type}|pagination_affiche_texte_lien_page{1,0}})</a></li>[(#GET{premiere}|>{2}|oui)<li
 				class="pagination-item tbc disabled"><span class="pagination-item-label">…</span></li>]]
@@ -37,7 +37,7 @@
 		[[(#GET{derniere}|<{#ENV{nombre_pages}|moins{1}}|oui)<li class="pagination-item tbc disabled"><span class="pagination-item-label">…</span></li>]
 		#SET{item, #ENV{nombre_pages}|moins{1}|mult{#ENV{pas}}}
 		<li class="pagination-item"><a
-		  href="[(#ENV{url}|parametre_url{#ENV{debut},#GET{item}}|ancre_url{#ENV{ancre}})]"
+		  href="[(#ENV{url}|parametre_url{#ENV{debut},#GET{item}}|ancre_url{#ENV{ancre}}|attribut_url)]"
 		  class="pagination-item-label lien_pagination" aria-label="<:lien_aller_a_la_derniere_page|attribut_html:>"
 		  rel="nofollow">(#GET{derniere}|<{#ENV{nombre_pages}}|?{#GET{type}|pagination_affiche_texte_lien_page{#ENV{nombre_pages},#GET{item}}})</a></li>]