diff --git a/VERSIONS_WIP/87205 b/VERSIONS_WIP/87205
new file mode 100644
index 0000000000000000000000000000000000000000..d802abe82350839fa3aee0fc3620b8678ca95608
--- /dev/null
+++ b/VERSIONS_WIP/87205
@@ -0,0 +1 @@
+ - ticket #87205 : Moteur de recherche : corrections de vulnérabilités
\ No newline at end of file
diff --git a/application/modules/opac/controllers/RechercheController.php b/application/modules/opac/controllers/RechercheController.php
index a3f3eebdf500f9f49891aa6ed749b2285a693716..5732f13aa0023d0143c1453ec8bd06646c610025 100644
--- a/application/modules/opac/controllers/RechercheController.php
+++ b/application/modules/opac/controllers/RechercheController.php
@@ -492,13 +492,7 @@ class RechercheController extends ZendAfi_Controller_Action {
   protected function addHistoRecherche($criteres_recherche) {
     $criteres_recherche->setTime(microtime(true));
     $criteres_histo = clone $criteres_recherche;
-    $histo_session = new Zend_Session_Namespace('historiqueRecherche');
-
-    (null == ($criteres = $histo_session->criteres))
-      ? ($criteres = [serialize($criteres_histo)])
-      : ($criteres[] = serialize($criteres_histo));
-
-    return $histo_session->criteres = array_unique($criteres);
+    return (new Class_SearchHistory())->add($criteres_histo);
   }
 
 
diff --git a/library/Class/CodifSection.php b/library/Class/CodifSection.php
index 455b21229d81e84bd8946a5fc5f5e0247fddb68a..03cbc5c21dc9705f6732bbf843a32dd2a9966794 100644
--- a/library/Class/CodifSection.php
+++ b/library/Class/CodifSection.php
@@ -29,7 +29,7 @@ class Class_CodifSectionLoader extends Storm_Model_Loader {
 
 
   public function getMultiOptions() {
-    $datas = Class_CodifSection::findAllBy(['invisible' => 0, 'order' => 'libelle']);
+    $datas = Class_CodifSection::findAllVisibleOrderedByLabel();
     $items  = ['' => $this->_('toutes')];
 
     $controle = '';
@@ -42,6 +42,11 @@ class Class_CodifSectionLoader extends Storm_Model_Loader {
 
     return $items;
   }
+
+
+  public function findAllVisibleOrderedByLabel() {
+    return Class_CodifSection::findAllBy(['invisible' => 0, 'order' => 'libelle']);
+  }
 }
 
 
diff --git a/library/Class/CriteresRecherche.php b/library/Class/CriteresRecherche.php
index 5a23c81901d8cdb4dc6ef8e9c4a5620e4e732833..db21eb84a0736cf3a4a3777ebe9d447e44d71ecc 100644
--- a/library/Class/CriteresRecherche.php
+++ b/library/Class/CriteresRecherche.php
@@ -39,7 +39,8 @@ class Class_CriteresRecherche {
     $_validate_facette,
     $_default_page_size = 20,
     $_profil,
-    $_time;
+    $_time,
+    $_validator;
 
   public static $criteres = ['expressionRecherche' => 'Expression',
                              'rech_titres' => 'Titre',
@@ -250,8 +251,8 @@ class Class_CriteresRecherche {
   }
 
 
-  public function getGeoZone(){
-    return $this->getParam('geo_zone',false);
+  public function getGeoZone() {
+    return $this->getParam('geo_zone', false);
   }
 
 
@@ -290,8 +291,9 @@ class Class_CriteresRecherche {
                                                     },
                                                     $bibs);
 
-    if ($geo_zone = $this->getGeoZone()) {
-      $all_bibs = Class_Zone::find($geo_zone)->getBibs();
+    if (($geo_zone = $this->getGeoZone())
+        && ($geo_zone = Class_Zone::find($geo_zone))) {
+      $all_bibs = $geo_zone->getBibs();
       $filtres[Class_Bib::CODE_FACETTE] = array_map(function($bib)
                                                     {
                                                       return Class_Bib::CODE_FACETTE . $bib->getId();
@@ -685,17 +687,10 @@ class Class_CriteresRecherche {
 
     $params = $this->replaceEmptyRechercheByStar($params);
 
-    foreach($valid_parameters as $key) {
-      if (array_isset($key, $params)) {
-        if (preg_match('/^operateur_/', $key)
-            && (!array_key_exists(str_replace('operateur_','rech_',$key),$params)
-                || $params[str_replace('operateur_','rech_',$key)]=='')) {
-          continue;
-        }
-
+    foreach($valid_parameters as $key)
+      if (array_isset($key, $params)
+          && $this->_getValidator()->isValid($key, $params))
         $filtered_params[$key] = $params[$key];
-      }
-    }
 
     if (isset($params['page_size']) && (static::MAX_PAGE_SIZE < (int)$params['page_size']))
       $filtered_params['page_size']  = static::MAX_PAGE_SIZE;
@@ -704,6 +699,13 @@ class Class_CriteresRecherche {
   }
 
 
+  protected function _getValidator() {
+    return $this->_validator
+      ? $this->_validator
+      : $this->_validator = new Class_CriteresRecherche_Validator();
+  }
+
+
   public function getCriteres() {
     return $this->_params;
   }
diff --git a/library/Class/CriteresRecherche/Validator.php b/library/Class/CriteresRecherche/Validator.php
new file mode 100644
index 0000000000000000000000000000000000000000..086cec4f50045c7f508a7a6d59b04d8c06ee0e3b
--- /dev/null
+++ b/library/Class/CriteresRecherche/Validator.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_CriteresRecherche_Validator {
+  const
+    PATTERN_YEAR = '/^[0-9]{4}$/',
+    PATTERN_FACET = '/^[a-zA-Z0-9]+$/',
+    PATTERN_MULTIFACET = '/^[a-zA-Z0-9-]+$/',
+    PATTERN_MULTI_IDS = '/^[0-9,]+$/',
+    PATTERN_ALPHAMAJ = '/^[A-Z0-9-]+$/';
+
+  public function isValid($key, $params) {
+    return $this->_validatorFor($key)->isValid($key, $params);
+  }
+
+
+  protected function _validatorFor($name) {
+    if ($validator = $this->_regexValidatorFor($name))
+      return $validator;
+
+    if ('operateur_' == substr($name, 0, 10))
+      return new Class_CriteresRecherche_ValidatorOperator();
+
+    if ('section' == $name) {
+      $ids = (new Storm_Model_Collection(Class_CodifSection::findAllVisibleOrderedByLabel()))
+        ->collect('id')
+        ->getArrayCopy();
+
+      return new Class_CriteresRecherche_ValidatorInArray($ids);
+    }
+
+    return new Class_CriteresRecherche_ValidatorValid();
+  }
+
+
+  protected function _regexValidatorFor($name) {
+    $map = [static::PATTERN_YEAR       => ['annee_debut', 'annee_fin'],
+            static::PATTERN_FACET      => ['rubrique', 'code_rebond'],
+            static::PATTERN_MULTIFACET => ['multifacets'],
+            static::PATTERN_MULTI_IDS  => ['bib_select'],
+            static::PATTERN_ALPHAMAJ   => ['serie']
+    ];
+
+    foreach($map as $pattern => $names)
+      if (in_array($name, $names))
+        return new Class_CriteresRecherche_ValidatorRegex($pattern);
+  }
+}
+
+
+
+
+class Class_CriteresRecherche_ValidatorValid {
+  public function isValid($key, $params) {
+    return true;
+  }
+}
+
+
+
+class Class_CriteresRecherche_ValidatorRegex {
+  protected $_pattern;
+
+  public function __construct($pattern) {
+    $this->_pattern = $pattern;
+  }
+
+
+  public function isValid($key, $params) {
+    return preg_match($this->_pattern, $params[$key]);
+  }
+}
+
+
+
+class Class_CriteresRecherche_ValidatorInArray {
+  protected $_possibles = [];
+
+
+  public function __construct($possibles) {
+    $this->_possibles = $possibles;
+  }
+
+
+  public function isValid($key, $params) {
+    return in_array($params[$key], $this->_possibles);
+  }
+}
+
+
+
+class Class_CriteresRecherche_ValidatorOperator {
+  public function isValid($key, $params) {
+    $matching_rech = str_replace('operateur_', 'rech_', $key);
+
+    return array_key_exists($matching_rech, $params)
+      && $params[$matching_rech]
+      && in_array($params[$key], ['or', 'and', 'and not']);
+  }
+}
diff --git a/library/Class/SearchHistory.php b/library/Class/SearchHistory.php
new file mode 100644
index 0000000000000000000000000000000000000000..73206259022715ecf8a5b3a29052edad6240d51b
--- /dev/null
+++ b/library/Class/SearchHistory.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_SearchHistory {
+  const SESSION_NAMESPACE = 'historiqueRecherche';
+
+  protected $_session;
+
+
+  /** @param $new Class_CriteresRecherche */
+  public function add($new) {
+    $session = $this->_getSession();
+    (null == ($history = $session->criteres))
+      ? ($history = [serialize($new)])
+      : ($history[] = serialize($new));
+
+    return $session->criteres = array_unique($history);
+  }
+
+
+  public function getHistory() {
+    return (null == ($history = $this->_getSession()->criteres))
+      ? []
+      : $history;
+  }
+
+
+  public function clear() {
+    $session = $this->_getSession();
+    unset($session->criteres);
+  }
+
+
+  protected function _getSession() {
+    return $this->_session
+      ? $this->_session
+      : $this->_session = new Zend_Session_Namespace(static::SESSION_NAMESPACE);
+  }
+}
diff --git a/library/Class/Systeme/Sql.php b/library/Class/Systeme/Sql.php
index 154fbd64e182a3640a0527851a79531325294dc8..90a49f4c21bc30af5047b2aaf8c251607669f013 100644
--- a/library/Class/Systeme/Sql.php
+++ b/library/Class/Systeme/Sql.php
@@ -19,10 +19,12 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_Systeme_Sql {
-  private $adapter;                             // Handle de connexion
-  private $ignore_erreurs=false;              // Pour l'intégration les erreurs sont ignorées
-  private $log;                               // Instance classe de log
-  private $filtre_write;                      // Filtre pour les ecritures
+  protected static $_throw_errors = false;
+
+  private $adapter;
+  private $ignore_erreurs = false;
+  private $log;
+  private $filtre_write;
 
 
   public function __construct() {
@@ -44,10 +46,25 @@ class Class_Systeme_Sql {
   }
 
 
+  /** @category testing */
+  public static function shouldThrowErrors() {
+    static::$_throw_errors = true;
+  }
+
+
+  /** @category testing */
+  public static function shouldNotThrowErrors() {
+    static::$_throw_errors = false;
+  }
+
+
   protected function run($closure, $req='') {
     try {
       return $closure();
     } catch(Exception $e) {
+      if (static::$_throw_errors)
+        throw $e;
+
       $this->traiteErreur($req, $e);
       return false;
     }
@@ -182,5 +199,3 @@ class Class_Systeme_Sql {
     exit;
   }
 }
-
-?>
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Manager/BookmarkedSearches.php b/library/ZendAfi/Controller/Plugin/Manager/BookmarkedSearches.php
index bca75c3c449b788962db98552c7cbdb04ce72ba4..2da94758d30df04aa827204850310e5da934e2fe 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/BookmarkedSearches.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/BookmarkedSearches.php
@@ -39,10 +39,7 @@ class ZendAfi_Controller_Plugin_Manager_BookmarkedSearches extends ZendAfi_Contr
 
 
   protected function _getCriterias() {
-    if (!$namespace = new Zend_Session_Namespace('historiqueRecherche'))
-      return null;
-
-    if (!$searches = $namespace->criteres)
+    if (!$searches = (new Class_SearchHistory())->getHistory())
       return null;
 
     return array_pop($searches);
diff --git a/library/ZendAfi/View/Helper/HistoriqueRecherche.php b/library/ZendAfi/View/Helper/HistoriqueRecherche.php
index da70094826911938ea9a7197f17286a292444e25..6d1777e1c961a0ca8f1faacf0ded0860e30d7cbb 100644
--- a/library/ZendAfi/View/Helper/HistoriqueRecherche.php
+++ b/library/ZendAfi/View/Helper/HistoriqueRecherche.php
@@ -16,23 +16,21 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
+
 class ZendAfi_View_Helper_HistoriqueRecherche extends ZendAfi_View_Helper_BaseHelper {
   public function historiqueRecherche() {
-    $html='';
+    if (!$history = (new Class_SearchHistory())->getHistory())
+      return '';
 
-    $this->_session = new Zend_Session_Namespace('historiqueRecherche');
-    if (!$this->_session->criteres)
-      return $html;
-    
-    $criteres_recherche = array_reverse($this->_session->criteres);
-    
-    foreach($criteres_recherche as $critere)
-      $html.=$this->view->tagHistoriqueRecherche(unserialize($critere))."";
+    $history = array_reverse($history);
+    $html = '';
+    foreach($history as $critere)
+      $html .= $this->view->tagHistoriqueRecherche(unserialize($critere));
 
-    return "<div class='criteres_recherche'>"
-      ."<ul>".$html."</ul>"
-      ."</div>";
+    return $this->_tag('div',
+                       $this->_tag('ul', $html),
+                       ['class' => 'criteres_recherche']);
   }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagCriteresRecherche.php b/library/ZendAfi/View/Helper/TagCriteresRecherche.php
index bad61c909e0f0531f7f822a28ba2f82da75be4e3..9b30ff62f349862b13d14845a0348e575efe3fd8 100644
--- a/library/ZendAfi/View/Helper/TagCriteresRecherche.php
+++ b/library/ZendAfi/View/Helper/TagCriteresRecherche.php
@@ -133,11 +133,11 @@ class ZendAfi_View_Helper_TagCriteresRecherche extends ZendAfi_View_Helper_BaseH
   }
 
 
-  public function getSuppressionImgUrlForLibelle($libelle,$url) {
+  public function getSuppressionImgUrlForLibelle($libelle, $url) {
     unset($url['page']);
     $title = $this->view->_('Retirer le critère: %s', $libelle);
     return $this->view->tagAnchor($this->view->url($url, null, true),
-                                  $libelle .
+                                  $this->view->escape($libelle) .
                                   $this->view->tagImg(URL_IMG . 'suppression.gif'),
                                   ['title' => $title]);
   }
diff --git a/library/ZendAfi/View/Helper/TagHistoriqueRecherche.php b/library/ZendAfi/View/Helper/TagHistoriqueRecherche.php
index c65aff8c66cb54a1e9cc3f0c7f4d33ce6614b268..b7ae528371c1bc66960b2835bf2a125cdc43d961 100644
--- a/library/ZendAfi/View/Helper/TagHistoriqueRecherche.php
+++ b/library/ZendAfi/View/Helper/TagHistoriqueRecherche.php
@@ -18,6 +18,7 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
+
 class ZendAfi_View_Helper_TagHistoriqueRecherche extends ZendAfi_View_Helper_TagCriteresRecherche {
   protected
     $_codification,
@@ -25,46 +26,49 @@ class ZendAfi_View_Helper_TagHistoriqueRecherche extends ZendAfi_View_Helper_Tag
     $_expression;
 
   public function tagHistoriqueRecherche($criteres_recherche) {
+    if (!$criteres_recherche)
+      return '';
+
     $this->_html='';
     $this->_affichage_criteres = '';
-    $this->_expression = $this->view->_('Tous les documents');
+    $this->_expression = $this->_('Tous les documents');
     $this->_codification = Class_Codification::getInstance();
 
     parent::visitCriteresRecherche($criteres_recherche);
     parent::_injectMultiFacets();
     $this->_insertTime($criteres_recherche);
+
     $url = $this->view->url($this->_criteres_recherche->getUrlCriteresWithFacettes(),null,true);
+    $icon = Class_Admin_Skin::current()
+      ->renderActionIconOn('loupe', $this->view,
+                           ['alt' => $this->_('Relancer cette Recherche')]);
 
-    $this->_html =
-      '<a href="'.$url.'" title="'.$this->view->_('Relancer cette recherche').'">'
-      . Class_Admin_Skin::current()->renderActionIconOn('loupe', $this->view,
-                                                        ['alt' => $this->view->_('Relancer cette Recherche')])
-      . $this->_expression . '</a>';
+    $this->_html = $this->view
+      ->tagAnchor($url, $icon . $this->view->escape($this->_expression),
+                  ['title' => $this->_('Relancer cette recherche')]);
 
     if ($this->_affichage_criteres)
-      $this->_html .='<ul>'.$this->_affichage_criteres.'</ul>';
+      $this->_html .= $this->_tag('ul', $this->_affichage_criteres);
 
-    return '<li>'.$this->_html.'</li>';
+    return $this->_tag('li', $this->_html);
   }
 
 
-  public function visitRubrique($rubrique,$fil) {
+  public function visitRubrique($rubrique, $fil) {
     $libelle = $this->_codification->getLibelleFacette($rubrique);
-    $this->htmlAppend($this->view->_('Rubrique: ').$libelle);
+    $this->htmlAppend($this->_('Rubrique: ') . $libelle);
   }
 
 
   public function visitTextInput($name, $operateur, $type_recherche, $value) {
-    if (!$operateur)
-      $libelle_operateur = " et ";
-    else
-      $libelle_operateur = $this->_libelles_operateur[$operateur];
-
-    $this->htmlAppend(
-      $libelle_operateur.
-      $this->_libelles_criteres[$name].
-      ': '.
-      $value);
+    $libelle_operateur = ($operateur && array_key_exists($operateur, $this->_libelles_operateur))
+      ? $this->_libelles_operateur[$operateur]
+      : $this->_(' et ');
+
+    $this->htmlAppend($libelle_operateur
+                      . $this->_libelles_criteres[$name]
+                      . ': '
+                      . $value);
   }
 
 
@@ -73,28 +77,29 @@ class ZendAfi_View_Helper_TagHistoriqueRecherche extends ZendAfi_View_Helper_Tag
       return;
 
     $libelle = $libelle_facette . ': ' . $this->_codification->getLibelleFacette($facette);
-    $this->htmlAppend('Facette: '.$libelle);
+    $this->htmlAppend($this->_('Facette: ') . $libelle);
   }
 
 
   public function visitPage($page) {
-    $this->htmlAppend($this->view->_('Page: ').$page);
+    $this->htmlAppend($this->_('Page: ') . $page);
   }
 
 
   public function visitTri($tri, $libelle) {
-    $this->htmlAppend($this->view->_('Trié par: ').$libelle);
+    $this->htmlAppend($this->_('Trié par: ') . $libelle);
   }
 
 
-  public function getSuppressionImgUrlForLibelle($libelle,$url) {
+  public function getSuppressionImgUrlForLibelle($libelle, $url) {
     return $libelle;
   }
 
 
   public function visitTypeDoc($type_docs) {
     $type_doc = array_shift($type_docs);
-    $this->htmlAppend('Type de document: ' . $this->_codification->getLibelleFacette('T' . $type_doc));
+    $this->htmlAppend($this->_('Type de document: ')
+                      . $this->_codification->getLibelleFacette('T' . $type_doc));
   }
 
 
@@ -105,25 +110,27 @@ class ZendAfi_View_Helper_TagHistoriqueRecherche extends ZendAfi_View_Helper_Tag
 
 
   public function visitNouveaute($nouveaute) {
-    $url=$this->_criteres_recherche->getUrlCriteresWithoutElement('nouveaute');
+    $url = $this->_criteres_recherche->getUrlCriteresWithoutElement('nouveaute');
     $libelle = ($nouveaute == 0)
-      ? $this->view->_('Nouveautés')
-      : $this->view->_('Nouveautés de moins de: ').$nouveaute.' mois';
+      ? $this->_('Nouveautés')
+      : $this->_('Nouveautés de moins de: %s mois', $nouveaute);
+
     $this->htmlAppend($libelle);
   }
 
 
   public function visitAnneeDebutFin($annee_debut, $annee_fin) {
-    $texte = $this->view->_("Documents parus ");
+    $texte = $this->_('Documents parus ');
     $texte .= ($annee_debut == $annee_fin)
-      ? "en " . $annee_debut
-      : $this->view->_("entre %s et %s", $annee_debut, $annee_fin);
+      ? $this->_('en %s', $annee_debut)
+      : $this->_('entre %s et %s', $annee_debut, $annee_fin);
+
     $this->htmlAppend($texte);
   }
 
 
   public function htmlAppend($text, $attribs=[]) {
-    $this->_affichage_criteres .= $this->_tag('li', $text);
+    $this->_affichage_criteres .= $this->_tag('li', $this->view->escape($text));
     return $this;
   }
 
@@ -132,7 +139,7 @@ class ZendAfi_View_Helper_TagHistoriqueRecherche extends ZendAfi_View_Helper_Tag
     if(!$criteres_recherche->getTime())
       return;
 
-    $this->htmlAppend($this->_('Date : %s', date('d/m/Y H:i:s',$criteres_recherche->getTime())));
+    $this->htmlAppend($this->_('Date : %s', date('d/m/Y H:i:s', $criteres_recherche->getTime())));
     return $this;
   }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagNombreDePages.php b/library/ZendAfi/View/Helper/TagNombreDePages.php
index ca07f98ef8bc7efbae920f3c53ff53019376ad9d..faca0599ae47a4c3dc90abbae59e7280e1e7252f 100644
--- a/library/ZendAfi/View/Helper/TagNombreDePages.php
+++ b/library/ZendAfi/View/Helper/TagNombreDePages.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
  *
  * BOKEH is free software; you can redistribute it and/or modify
  * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
@@ -16,13 +16,17 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-class ZendAfi_View_Helper_TagNombreDePages extends Zend_View_Helper_HtmlElement {
 
-  public function tagNombreDePages($page){
-    if(!intval($page)) $page=1;
-    return $html='<div class="nb-pages-recherche"><span>'.$this->view->_('page ').$page.'</span></div>';
+class ZendAfi_View_Helper_TagNombreDePages extends ZendAfi_View_Helper_BaseHelper {
+  public function tagNombreDePages($page) {
+    $page = (int)$page;
+
+    return $this
+      ->_tag('div',
+             $this->_tag('span',
+                         $this->_('page ') . ($page ? $page : 1)),
+             ['class' => 'nb-pages-recherche']);
   }
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/RechercheControllerHistoriqueRechercheTest.php b/tests/application/modules/opac/controllers/RechercheControllerHistoriqueRechercheTest.php
index 1d4a33c43ec5fbe625478cd78e166a59b20eae43..082309b46c5af6909265367dcdfb99f940b8ad57 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerHistoriqueRechercheTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerHistoriqueRechercheTest.php
@@ -34,8 +34,9 @@ class RechercheControllerHistoriqueRechercheTest extends AbstractControllerTestC
       ->setParams(['rech_auteurs' => 'Miles Davis',
                    'annee_fin' => '1970']);
 
-    $session = new Zend_Session_Namespace('historiqueRecherche');
-    $session->criteres = [serialize($criteres_potter), serialize($criteres_davis)];
+    $history = new Class_SearchHistory();
+    $history->add($criteres_potter);
+    $history->add($criteres_davis);
 
     $this->dispatch('/opac/recherche/saisie', true);
   }
@@ -58,5 +59,3 @@ class RechercheControllerHistoriqueRechercheTest extends AbstractControllerTestC
     $this->assertXPath('//div[@class="configuration_module"]//a[contains(@href, "action1/saisie")]');
   }
 }
-
-?>
diff --git a/tests/application/modules/opac/controllers/RechercheControllerSimpleSerieTest.php b/tests/application/modules/opac/controllers/RechercheControllerSimpleSerieTest.php
index ed91d803eedbcaebcf4e608cb2c9bfd1f4c424d1..479081fb1848f972807da0f1c3fb637d26396a30 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerSimpleSerieTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerSimpleSerieTest.php
@@ -20,8 +20,7 @@
  */
 
 
-class RechercheControllerRechercheSimpleSerieTest
-  extends AbstractControllerTestCase {
+class RechercheControllerRechercheSimpleSerieTest extends AbstractControllerTestCase {
   protected
     $_storm_default_to_volatile = true,
     $_search;
diff --git a/tests/library/Class/MoteurRechercheTest.php b/tests/library/Class/MoteurRechercheTest.php
index c210f1fec9325f48493c6412377a3c1e479f4a5a..243f2e09136d6e109fe85db794a67320a4037050 100644
--- a/tests/library/Class/MoteurRechercheTest.php
+++ b/tests/library/Class/MoteurRechercheTest.php
@@ -39,6 +39,10 @@ abstract class MoteurRechercheTestCase extends ModelTestCase {
     $this->fixture('Class_Profil',
                    ['id' => 1, 'libelle' => 'Portail'])
          ->beCurrentProfil();
+
+    $this->fixture('Class_CodifSection',
+                   ['id' => 1,
+                    'libelle' => 'Super section']);
   }
 
 
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/HistoriqueRecherchesTest.php b/tests/library/ZendAfi/View/Helper/Accueil/HistoriqueRecherchesTest.php
index 9a1f9488378d78009e48f247ad8cc9ceb2fca218..fd8c69c90e310ee73959cf9311d263ce03d71154 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/HistoriqueRecherchesTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/HistoriqueRecherchesTest.php
@@ -26,11 +26,13 @@ class ZendAfi_View_Helper_Accueil_HistoriqueRecherchesTest extends ViewHelperTes
   public function setUp() {
     parent::setUp();
     Class_AdminVar::newInstanceWithId('MULTIMEDIA_KEY',['valeur'=>'81b3ab7b0b9a621afb6044a9c2f48ed2']);
+
     $this->_helper = new ZendAfi_View_Helper_Accueil_HistoriqueRecherches(2, [
       'type_module'=>'HISTORIQUE_RECHERCHES',
       'division' => '1',
       'preferences' => ['titre' => 'Historique Recherches']]);
-    $this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
+
+    $this->_helper->setView($this->view);
 
     $criteres_potter = (new Class_CriteresRecherche)
       ->setParams(['expressionRecherche' => 'Harry Potter',
@@ -40,9 +42,10 @@ class ZendAfi_View_Helper_Accueil_HistoriqueRecherchesTest extends ViewHelperTes
       ->setParams(['rech_auteurs' => 'Miles Davis',
                    'annee_fin' => '1970']);
 
-    $session = new Zend_Session_Namespace('historiqueRecherche');
-    $session->criteres = [serialize($criteres_potter), serialize($criteres_davis)];
-    
+    $history = new Class_SearchHistory();
+    $history->add($criteres_potter);
+    $history->add($criteres_davis);
+
     $this->html = $this->_helper->getBoite();
   }
 
@@ -56,18 +59,17 @@ class ZendAfi_View_Helper_Accueil_HistoriqueRecherchesTest extends ViewHelperTes
 
 
   /** @test */
-  public function URLRechercheHarryPotterShouldBeDisplay(){
-    $this->assertXPath($this->html, 
+  public function urlRechercheHarryPotterShouldBeDisplayWithoutPageNumber(){
+    $this->assertXPath($this->html,
                        '//a[contains(@href, "/recherche/simple/expressionRecherche/Harry+Potter")]',
                        $this->html);
   }
 
+
   /** @test */
-  public function URLRechercheMilesDavisShouldBeDisplay(){
-    $this->assertXPath($this->html, 
+  public function urlRechercheMilesDavisShouldBeDisplayWithEndYear(){
+    $this->assertXPath($this->html,
                        '//a[contains(@href, "/recherche/simple/rech_auteurs/Miles+Davis/annee_fin/1970")]',
                        $this->html);
   }
 }
-
-?>
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/HistoriqueRechercheTest.php b/tests/library/ZendAfi/View/Helper/HistoriqueRechercheTest.php
index fbd00a384f5b5f649b5b163b7b7f3e7054e8d6b1..ac7d74ab51c7188c5e322c8194fed04096dec7d5 100644
--- a/tests/library/ZendAfi/View/Helper/HistoriqueRechercheTest.php
+++ b/tests/library/ZendAfi/View/Helper/HistoriqueRechercheTest.php
@@ -16,56 +16,57 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 class ZendAfi_View_Helper_HistoriqueRechercheWith2CriteresTest extends ViewHelperTestCase {
 
   public function setUp() {
     parent::setUp();
-    $this->_session = new Zend_Session_Namespace('historiqueRecherche');
+
     $criteres =  new Class_CriteresRecherche();
     $criteres->setParams(['expressionRecherche' => 'Millenium']);
+
     $criteres_auteur = new Class_CriteresRecherche();
     $criteres_auteur->setParams(['rech_auteurs' => 'Larsson',
-                                   'operateur_auteurs'=>'and',
-                                  'rech_titres' => 'Les hommes qui n\'aimaient pas les femmes']);
-    
-    $this->_session->criteres=[serialize($criteres),serialize($criteres_auteur)];
-    $view = new ZendAfi_Controller_Action_Helper_View();
+                                 'operateur_auteurs'=>'and',
+                                 'rech_titres' => 'Les hommes qui n\'aimaient pas les femmes']);
+
+    $history = new Class_SearchHistory();
+    $history->add($criteres);
+    $history->add($criteres_auteur);
+
     $this->_helper = new ZendAfi_View_Helper_HistoriqueRecherche();
-    $this->_helper->setView($view);
-  
+    $this->_helper->setView($this->view);
   }
 
 
-
   /** @test */
   public function milleniumShouldBeDisplayed() {
-    $this->assertXPathContentContains($this->_helper->historiqueRecherche(), 
+    $this->assertXPathContentContains($this->_helper->historiqueRecherche(),
                                       '//div',
                                       'Millenium');
   }
 
+
   /** @test */
   public function milleniumAndLarssonShouldBeDisplayed() {
-    $this->assertXPathContentContains($this->_helper->historiqueRecherche(), 
+    $this->assertXPathContentContains($this->_helper->historiqueRecherche(),
                                       '//div',
                                       'Larsson');
   }
 
+
   /** @test */
   public function URLRechercheSimpleShouldBeDisplay(){
-    $this->assertXPath($this->_helper->historiqueRecherche(), 
+    $this->assertXPath($this->_helper->historiqueRecherche(),
                        '//a[contains(@href, "/recherche/simple/expressionRecherche/Millenium")]');
   }
 
+
   /** @test */
   public function URLRechercheAvanceShouldBeDisplay(){
-    $this->assertXPath($this->_helper->historiqueRecherche(), 
+    $this->assertXPath($this->_helper->historiqueRecherche(),
                        '//a[contains(@href, "/recherche/simple/rech_titres/Les+hommes+qui+n%27aimaient+pas+les+femmes/rech_auteurs/Larsson")]',$this->_helper->historiqueRecherche());
   }
-  
 }
-
-
diff --git a/tests/scenarios/Security/SearchTest.php b/tests/scenarios/Security/SearchTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4bf7cdcbbc0813d6ede9ccb41e469d13dc6568ea
--- /dev/null
+++ b/tests/scenarios/Security/SearchTest.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Security_SearchTest extends AbstractControllerTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_default_params;
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->_default_params = ['operateur_titres' => 'and',
+                              'rech_titres' => '1',
+                              'operateur_auteurs' => 'and',
+                              'rech_auteurs' => '1',
+                              'operateur_matieres' => 'and',
+                              'rech_matieres' => '1',
+                              'operateur_dewey' => 'and',
+                              'type_recherche' => 'fulltext',
+                              'rech_dewey' => '1',
+                              'operateur_editeur' => 'and',
+                              'tri' => '*',
+                              'rech_editeur' => '1',
+                              'operateur_collection' => 'and',
+                              'annee_debut' => '1',
+                              'rech_collection' => '1',
+                              'annee_fin' => '1',
+                              'nouveaute' => '1',
+                              'type_doc ' => '1',
+                              'annexe' => '1',
+                              'section' => '1',
+                              'genre' => ''];
+
+    $this->onLoaderOfModel('Class_TypeDoc')
+         ->whenCalled('findUsedTypeDocIds')
+         ->answers([]);
+
+    Class_Systeme_Sql::shouldThrowErrors();
+  }
+
+
+  public function tearDown() {
+    Class_Systeme_Sql::shouldNotThrowErrors();
+    (new Class_SearchHistory())->clear();
+
+    parent::tearDown();
+  }
+
+
+  public function validParameters() {
+    return array_map(function($item) { return [$item]; },
+                     (new Class_CriteresRecherche())->getValidParameters());
+  }
+
+
+  /**
+   * @test
+   * @dataProvider validParameters
+   */
+  public function paramShouldNotMakeSqlCrash($param) {
+    $this->_default_params[$param] = '%22\'%3E%3Cqss%20%60%3b!--%3%26%7b()%7d%3E';
+    $this->dispatch('/recherche/simple?' . http_build_query($this->_default_params));
+  }
+
+
+  /**
+   * @test
+   * @dataProvider validParameters
+   */
+  public function paramShouldNotBeInjectedInHtml($param) {
+    $this->_default_params[$param] = '1"\'><qss>';
+    $this->dispatch('/recherche/simple?' . http_build_query($this->_default_params));
+    $this->assertNotContains('1"\'><qss>', $this->_response->getBody());
+  }
+
+
+
+  /**
+   * @test
+   * @dataProvider validParameters
+   */
+  public function paramShouldNotBeInjectedInHistory($param) {
+    $history = new Class_SearchHistory();
+    $history->clear();
+    $history->add((new Class_CriteresRecherche)
+                  ->setParams([$param => '1 <script>_q_q=random()</script>']));
+
+    $this->dispatch('/recherche/avancee');
+
+    $this->assertNotContains('1 <script>_q_q=random()</script>', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function expressionRechercheShouldNotBeXssable() {
+    $this->dispatch('/recherche/simple?' . http_build_query(['expressionRecherche' => '1 <script>_q_q=random()</script>']));
+    $this->assertNotContains('1 <script>_q_q=random()</script>', $this->_response->getBody());
+  }
+}
diff --git a/tests/scenarios/bookmarks/SearchTest.php b/tests/scenarios/bookmarks/SearchTest.php
index f35445656f4fec090513f5b8c4acb7245bd96a6c..edcb3f03cc16c071c744a3eaa074d66ae93cc854 100644
--- a/tests/scenarios/bookmarks/SearchTest.php
+++ b/tests/scenarios/bookmarks/SearchTest.php
@@ -63,14 +63,15 @@ abstract class Bookmarks_SearchWithSessionAbstract extends Admin_AbstractControl
                    'annee_fin' => '1970',
                    'page' => 2]);
 
-    $session = new Zend_Session_Namespace('historiqueRecherche');
-    $session->criteres = [serialize($criteres_potter), serialize($criteres_davis)];
+    $history = new Class_SearchHistory();
+    $history->add($criteres_potter);
+    $history->add($criteres_davis);
   }
 }
 
 
 
-class Bookmarks_SearchAbonneBookmarkSearchDispatchTest  extends Bookmarks_SearchWithSessionAbstract {
+class Bookmarks_SearchAbonneBookmarkSearchDispatchTest extends Bookmarks_SearchWithSessionAbstract {
   public function setUp() {
     parent::setUp();
     Class_AdminVar::set('ENABLE_BOOKMARKABLE_SEARCHES_NOTIFY', 1);
@@ -98,7 +99,7 @@ class Bookmarks_SearchAbonneBookmarkSearchDispatchTest  extends Bookmarks_Search
 
 
 
-class Bookmarks_SearchAbonneBookmarkSearchPostTest  extends Bookmarks_SearchWithSessionAbstract {
+class Bookmarks_SearchAbonneBookmarkSearchPostTest extends Bookmarks_SearchWithSessionAbstract {
   public function setUp() {
     parent::setUp();
     Class_User_BookmarkedSearch::setTimeSource(new TimeSourceForTest('2018-01-17 01:01:01'));