diff --git a/include/class.csrf.php b/include/class.csrf.php
index 94f103cb8277013ef4131035968cb0775487b6eb..a1c3aed21392d5932b6b1edb31108cc6d3bedf8a 100644
--- a/include/class.csrf.php
+++ b/include/class.csrf.php
@@ -53,12 +53,15 @@ Class CSRF {
         return $this->name;
     }
 
-    function getToken() {
+    function rotate() {
+        $this->csrf['token'] = sha1(session_id().Crypto::random(16).SECRET_SALT);
+        $this->csrf['time'] = time();
+    }
 
-        if(!$this->csrf['token'] || $this->isExpired()) {
+    function getToken() {
 
-            $this->csrf['token'] = sha1(session_id().Crypto::random(16).SECRET_SALT);
-            $this->csrf['time'] = time();
+        if (!$this->csrf['token'] || $this->isExpired()) {
+            $this->rotate();
         } else {
             //Reset the timer
             $this->csrf['time'] = time();
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index e620669c9339a1dcfe1f5f31e56b530f14c0909a..833eb318b97b581896b30744742d706a4d3bb089 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -1295,10 +1295,17 @@ class SelectionField extends FormField {
         return $this->getList()->getForm();
     }
     function getSubFields() {
+        $fields = new ListObject(array(
+            new TextboxField(array(
+                // XXX: i18n: Change to a better word when the UI changes
+                'label' => '['.__('Abbrev').']',
+                'id' => 'abb',
+            ))
+        ));
         $form = $this->getList()->getForm();
-        if ($form)
-            return $form->getFields();
-        return array();
+        if ($form && ($F = $form->getFields()))
+            $fields->extend($F);
+        return $fields;
     }
 
     function toString($items) {
@@ -1405,9 +1412,21 @@ class SelectionField extends FormField {
     }
 
     function getFilterData() {
+        // Start with the filter data for the list item as the [0] index
         $data = array(parent::getFilterData());
-        if (($v = $this->getClean()) instanceof DynamicListItem) {
-            $data = array_merge($data, $v->getFilterData());
+        if (($v = $this->getClean())) {
+            // Add in the properties for all selected list items in sub
+            // labeled by their field id
+            foreach ($v as $id=>$L) {
+                if (!($li = DynamicListItem::lookup($id)))
+                    continue;
+                foreach ($li->getFilterData() as $prop=>$value) {
+                    if (!isset($data[$prop]))
+                        $data[$prop] = $value;
+                    else
+                        $data[$prop] .= " $value";
+                }
+            }
         }
         return $data;
     }
@@ -1474,9 +1493,9 @@ class TypeaheadSelectionWidget extends ChoicesWidget {
         foreach ($this->field->getList()->getItems() as $i)
             $source[] = array(
                 'value' => $i->getValue(), 'id' => $i->getId(),
-                'info' => sprintf('%s %s',
+                'info' => sprintf('%s%s',
                     $i->getValue(),
-                    (($extra= $i->getAbbrev()) ? "-- $extra" : '')),
+                    (($extra= $i->getAbbrev()) ? " — $extra" : '')),
             );
         ?>
         <span style="display:inline-block">
@@ -1497,6 +1516,7 @@ class TypeaheadSelectionWidget extends ChoicesWidget {
                     $('input#<?php echo $this->name; ?>_id')
                       .attr('name', '<?php echo $this->name; ?>[' + item['id'] + ']')
                       .val(item['value']);
+                    return false;
                 }
             });
         });
@@ -1515,8 +1535,12 @@ class TypeaheadSelectionWidget extends ChoicesWidget {
     function getEnteredValue() {
         // Used to verify typeahead fields
         $data = $this->field->getSource();
-        if (isset($data[$this->name.'_name']))
-            return trim($data[$this->name.'_name']);
+        if (isset($data[$this->name.'_name'])) {
+            // Drop the extra part, if any
+            $v = $data[$this->name.'_name'];
+            $v = substr($v, 0, strrpos($v, ' — '));
+            return trim($v);
+        }
         return parent::getValue();
     }
 }
diff --git a/include/class.forms.php b/include/class.forms.php
index 5122ddd933bacf16c1bf3a91e46ca377067f48bd..3345669da755517199ec6a29d75ac76d8aa0147d 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -761,17 +761,6 @@ class FormField {
         return null;
     }
 
-    /**
-     * Indicates if the field provides for searching for something other
-     * than keywords. For instance, textbox fields can have hits by keyword
-     * searches alone, but selection fields should provide the option to
-     * match a specific value or set of values and therefore need to
-     * participate on any search builder.
-     */
-    function hasSpecialSearch() {
-        return true;
-    }
-
     function getConfigurationForm($source=null) {
         if (!$this->_cform) {
             $type = static::getFieldType($this->get('type'));
@@ -880,10 +869,6 @@ class TextboxField extends FormField {
         );
     }
 
-    function hasSpecialSearch() {
-        return false;
-    }
-
     function validateEntry($value) {
         parent::validateEntry($value);
         $config = $this->getConfiguration();
@@ -955,10 +940,6 @@ class TextareaField extends FormField {
         );
     }
 
-    function hasSpecialSearch() {
-        return false;
-    }
-
     function display($value) {
         $config = $this->getConfiguration();
         if ($config['html'])
@@ -1011,10 +992,6 @@ class PhoneField extends FormField {
         );
     }
 
-    function hasSpecialSearch() {
-        return false;
-    }
-
     function validateEntry($value) {
         parent::validateEntry($value);
         $config = $this->getConfiguration();
@@ -1447,9 +1424,6 @@ class ThreadEntryField extends FormField {
     function isPresentationOnly() {
         return true;
     }
-    function hasSpecialSearch() {
-        return false;
-    }
 
     function getConfigurationOptions() {
         global $cfg;
@@ -1904,10 +1878,6 @@ class FileUploadField extends FormField {
         );
     }
 
-    function hasSpecialSearch() {
-        return false;
-    }
-
     /**
      * Called from the ajax handler for async uploads via web clients.
      */
diff --git a/include/class.list.php b/include/class.list.php
index df431b9efe231fbeaa36a8a7f93630c97e32cf62..be6baec233279867eb46081bf852ff2db180c8a0 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -596,6 +596,15 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
         }
     }
 
+    function getFilterData() {
+        $data = array();
+        foreach ($this->getConfigurationForm()->getFields() as $F) {
+            $data['.'.$F->get('id')] = $F->toString($F->value);
+        }
+        $data['.abb'] = (string) $this->get('extra');
+        return $data;
+    }
+
     function getTranslateTag($subtag) {
         return _H(sprintf('listitem.%s.%s', $subtag, $this->id));
     }
diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php
index b6afe4f38a37c1020426adc62c7da892b049a5c7..dd9945a1eaf27fe2d48024df9ea5c722b2c83f29 100644
--- a/include/staff/header.inc.php
+++ b/include/staff/header.inc.php
@@ -65,7 +65,7 @@ if (($lang = Internationalization::getCurrentLanguage())
         echo sprintf('<div id="notice_bar">%s</div>', $ost->getNotice());
     ?>
     <div id="header">
-        <p id="info" class="pull-right"><?php echo sprintf(__('Welcome, %s.'), '<strong>'.$thisstaff->getFirstName().'</strong>'); ?>
+        <p id="info" class="pull-right no-pjax"><?php echo sprintf(__('Welcome, %s.'), '<strong>'.$thisstaff->getFirstName().'</strong>'); ?>
            <?php
             if($thisstaff->isAdmin() && !defined('ADMINPAGE')) { ?>
             | <a href="admin.php" class="no-pjax"><?php echo __('Admin Panel'); ?></a>
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 0db3e30eb6ae95b9673f62de2041b1ff9cec8e6c..68f58c5ebd07db09e5e22e9d52f6f4a4c63a1c17 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-d9e311ad50bfe981f4356500c1d584ce
+9143a511719555e8f8f09b49523bd022
diff --git a/include/upgrader/streams/core/2d590ffa-d9e311ad.patch.sql b/include/upgrader/streams/core/2d590ffa-9143a511.patch.sql
similarity index 94%
rename from include/upgrader/streams/core/2d590ffa-d9e311ad.patch.sql
rename to include/upgrader/streams/core/2d590ffa-9143a511.patch.sql
index 4bf8eb68df8b75e0a17d87b39ae0760c8cf94461..f52e0bb25639e765aa47b617c3726e689dce1c2c 100644
--- a/include/upgrader/streams/core/2d590ffa-d9e311ad.patch.sql
+++ b/include/upgrader/streams/core/2d590ffa-9143a511.patch.sql
@@ -1,5 +1,5 @@
 /*
- * @signature d9e311ad50bfe981f4356500c1d584ce
+ * @signature 9143a511719555e8f8f09b49523bd022
  * @version v1.9.6
  * @title All collaborators have threads
  *
@@ -47,5 +47,5 @@ DROP TABLE `%TABLE_PREFIX%_orig_msg_ids`;
 
 -- Finished with patch
 UPDATE `%TABLE_PREFIX%config`
-    SET `value` = 'd9e311ad50bfe981f4356500c1d584ce'
+    SET `value` = '9143a511719555e8f8f09b49523bd022'
     WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/login.php b/login.php
index 201f840c5b5cf6f4b31bf7e2922eb22d4263db78..0d6f9e3d4c30a8df3f6e8aa83334b0582563022e 100644
--- a/login.php
+++ b/login.php
@@ -31,6 +31,20 @@ else
     $inc = 'login.inc.php';
 
 $suggest_pwreset = false;
+
+// Check the CSRF token, and ensure that future requests will have to use a
+// different CSRF token. This will help ward off both parallel and serial
+// brute force attacks, because new tokens will have to be requested for
+// each attempt.
+if ($_POST) {
+    // Check CSRF token
+    if (!$ost->checkCSRFToken())
+        Http::response(400, __('Valid CSRF Token Required'));
+
+    // Rotate the CSRF token (original cannot be reused)
+    $ost->getCSRF()->rotate();
+}
+
 if ($_POST && isset($_POST['luser'])) {
     if (!$_POST['luser'])
         $errors['err'] = __('Valid username or email address is required');
diff --git a/scp/login.php b/scp/login.php
index 609a0c5eca29df70b768233f29c9eb38e4e5fe1c..6655239c2be175d57a10a49744bb45019b928032 100644
--- a/scp/login.php
+++ b/scp/login.php
@@ -31,6 +31,16 @@ $msg = $msg ?: ($content ? $content->getLocalName() : __('Authentication Require
 $dest=($dest && (!strstr($dest,'login.php') && !strstr($dest,'ajax.php')))?$dest:'index.php';
 $show_reset = false;
 if($_POST) {
+    // Check the CSRF token, and ensure that future requests will have to
+    // use a different CSRF token. This will help ward off both parallel and
+    // serial brute force attacks, because new tokens will have to be
+    // requested for each attempt.
+    if (!$ost->checkCSRFToken())
+        Http::response(400, __('Valid CSRF Token Required'));
+
+    // Rotate the CSRF token (original cannot be reused)
+    $ost->getCSRF()->rotate();
+
     // Lookup support backends for this staff
     $username = trim($_POST['userid']);
     if ($user = StaffAuthenticationBackend::process($username,
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index ca83ca1230f5d082adb988908ff378a333fb5cba..26f129681118b2c67eea32dc5f4b74f78d30a656 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -709,7 +709,6 @@ CREATE TABLE `%TABLE_PREFIX%lock` (
   `code` varchar(20),
   `created` datetime NOT NULL,
   PRIMARY KEY  (`lock_id`),
-  UNIQUE KEY `ticket_id` (`ticket_id`),
   KEY `staff_id` (`staff_id`)
 ) DEFAULT CHARSET=utf8;