diff --git modules/filter/filter.api.php modules/filter/filter.api.php
index 5e20ed2..d2070b9 100644
--- modules/filter/filter.api.php
+++ modules/filter/filter.api.php
@@ -40,6 +40,10 @@
  *     content before the actual filtering happens.
  *   - 'process callback': The name the function that performs the actual
  *     filtering.
+ * - When a text format is checked to determine if it is configured securely,
+ *   the following properties may be used:
+ *   - 'security callback': The name of a function that determines whether the
+ *     filter allows new exploits or prevents existing ones.
  *
  * Filtering is a two-step process. First, the content is 'prepared' by calling
  * the 'prepare callback' function for every filter. The purpose of the 'prepare
@@ -64,6 +68,14 @@
  * format is an entire filter setup: which filters to enable, in what order
  * and with what settings.
  *
+ * Different filters can introduce or remove security vulnerabilities in the
+ * text formats that contain them, depending on how they are configured.
+ * Filters whose presence in a text format can have implications for security
+ * should therefore use the 'security callback' function to provide information
+ * about whether they are configured securely. The filtering system will
+ * collect this information and use it to automatically determine whether the
+ * text format as a whole is secure.
+ *
  * Filters that require settings should provide the form controls to configure
  * the settings in a form builder function, specified in 'settings callback'.
  * The filter system stores the settings in the database per text format.
@@ -122,6 +134,86 @@
  * - $format: The format object of the text to be filtered.
  * - $long: Boolean whether to return long or short filter guidelines.
  *
+ * 'security callback' is invoked with the following parameter:
+ * - $filter: An object representing the filter within the text format that is
+ *   being checked. The most important property is $filter->settings, which
+ *   contains an array of settings for this filter as it is configured within
+ *   the current text format. The security callback function should inspect
+ *   these settings if they are relevant for determining when the filter is
+ *   configured securely.
+ *
+ *   - If the filter opens up a new exploit in a previously safe text format,
+ *     or
+ *   - If the filter could have specifically prevented an exploit if it had
+ *     been configured in a different way. 
+ *   The callback retuns an array keyed by the the categories in which
+ *   potential vulnerabilities exists, such as 'html' or 'php'. By far the most
+ *   common category is 'html', which occurs when the web browser processes
+ *   the content as HTML (for example, cross-site scripting); this includes
+ *   both HTML itself as well as other languages (such as JavaScript) that can
+ *   be included in HTML.  For example, a filter that processes a certain kind
+ *   of markup language and transforms safe text, such as "[script]", into
+ *   unsafe HTML tags, such as SCRIPT. A filter which needs to output a warning
+ *   about creation of a HTML-related vulnerability would therefore use return
+ *   an array like:
+ *   @code
+ *     return array('html' => array(
+ *       'type' => FILTER_EXPLOT_CREATES,
+ *       'warning' => t('A message explaining the vulnerability.')
+ *     ));
+ *   @endcode
+ *   Although HTML-related exploits are by far the most common category, others
+ *   are possible for certain types of unusual filters. For example, the PHP
+ *   module in Drupal core uses the 'php' category to indicate that the PHP
+ *   filter which it provides contains an inherent vulnerability since content
+ *   is executed as PHP code on the server; this vulnerability represents a 
+ *   separate risk that is independent from any HTML-related vulnerabilities,
+ *   and can not be undone by a later filter. In this case, the relevant
+ *   type is therefore always FILTER_EXPLOT_CREATES.
+ *   @code
+ *     return array('php' => array(
+ *       'type' => FILTER_EXPLOT_CREATES,
+ *       'warning' => t('A message explaining the vulnerability.')
+ *     ));
+ *   @endcode
+ *   Similarly, a hypothetical filter that took the content passed in to it and
+ *   executed it as Perl code by running a local program on the web server
+ *   would use the same format for its warning, with 'perl' as the key.
+ *
+ *   The constant FILTER_EXPLOIT_PREVENTS should be used if the filter prevents
+ *   security exploits by thoroughly sanitizing previously-unsafe HTML; thus, a
+ *   filter that escapes HTML would return:
+ *   @code
+ *     array('html' => array('type' => FILTER_EXPLOIT_PREVENTS)).
+ *   @endcode
+ *   The constant FILTER_EXPLOIT_ALLOWS should be used if the filter allows a
+ *   security exploit, but does not create one.  Typically used for a HTML
+ *   filter that allows some unsafe HTML tags to pass through and wants
+ *   to inform the user about the specifics of the risk:
+ *   @code
+ *     return array('html' => array(
+ *       'type' => FILTER_EXPLOT_ALLOWS,
+ *       'warning' => t('A message explaining the allowed tags.')
+ *     ));
+ *   @endcode
+ * If the security callback function does not include a particular category in
+ * the returned array, this is taken to mean that the filter does not affect 
+ * the security of this type of output one way or the other; unsanitized content 
+ * passed in to the filter remains unsanitized after the filter is applied, and 
+ * sanitized content passed in to the filter similarly remains sanitized.
+ *
+ * @code
+ *   function mymodule_filter_security($filter, &$warnings) {
+ *     if (empty($filter->settings['remove_unsafe_html'])) {
+ *       // The "remove unsafe HTML" configuration option was selected, so this
+ *       // filter prevents HTML exploits.
+ *       return array('html' => array('type' => FILTER_EXPLOIT_PREVENTS));
+ *     }
+ *   }
+ * @endcode
+ *
+ * @see filter_format_warnings()
+ *
  * For performance reasons content is only filtered once; the result is stored
  * in the cache table and retrieved from the cache the next time the same piece
  * of content is displayed. If a filter's output is dynamic, it can override the
@@ -145,6 +237,11 @@
  *     of the filtering.
  *   - 'process callback': (required) The callback function to call in the
  *     'process' step of the filtering.
+ *   - 'security callback': A callback function that determines whether the
+ *     filter is configured to allow new security exploits or prevent existing
+ *     ones. Many filters do not need to implement this callback at all; it
+ *     should only be used by filters whose presence in a text format can have
+ *     security implications.
  *   - 'settings callback': A callback function that provides form controls
  *     for the filter's settings. Each filter should apply either the default
  *     settings or the configured settings contained in $filter->settings. The
@@ -166,6 +263,7 @@ function hook_filter_info() {
     'title' => t('Limit allowed HTML tags'),
     'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
     'process callback' => '_filter_html',
+    'security callback' => '_filter_html_security',
     'settings callback' => '_filter_html_settings',
     'default settings' => array(
       'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
diff --git modules/filter/filter.module modules/filter/filter.module
index eda4a03..c630872 100644
--- modules/filter/filter.module
+++ modules/filter/filter.module
@@ -7,6 +7,29 @@
  */
 
 /**
+ * Status which indicates that a filter prevents security exploits.
+ *
+ * This should be used by filters that sanitize previously-unsafe content.
+ */
+define('FILTER_EXPLOIT_PREVENTS', 1);
+
+/**
+ * Status which indicates that a filter creates a new security exploit.
+ *
+ * This should be used by filters that would output unsafe content even if the
+ * content passed in to the filter was previously sanitized, or filters that
+ * result in code execution.
+ */
+define('FILTER_EXPLOIT_CREATES', 2);
+
+/**
+ * Status which indicates that a filter allows a prior security exploit through.
+ *
+ * Typically for a filter that allow though some html tags and adds a warning.
+ */
+define('FILTER_EXPLOIT_ALLOWS', 3);
+
+/**
  * Implement hook_help().
  */
 function filter_help($path, $arg) {
@@ -200,8 +223,14 @@ function filter_format_save(&$format) {
   }
   foreach ($format->filters as $name => $filter) {
     $fields = array();
-    // Add new filters to the bottom.
-    $fields['weight'] = isset($current[$name]->weight) ? $current[$name]->weight : 10;
+    // If a specific weight was requested, use that.
+    if (isset($filter['weight'])) {
+      $fields['weight'] = $filter['weight'];
+    }
+    // Otherwise add new filters to the bottom.
+    else {
+      $fields['weight'] = isset($current[$name]->weight) ? $current[$name]->weight : 10;
+    }
     $fields['status'] = $filter['status'];
     $fields['module'] = $filter_info[$name]['module'];
     $format->filters[$name]['module'] = $filter_info[$name]['module'];
@@ -276,17 +305,21 @@ function filter_permission() {
     'description' => t('Manage text formats and filters, and use any of them, without restriction, when entering or editing content. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
   );
 
-  // Generate permissions for each text format. Warn the administrator that any
-  // of them are potentially unsafe.
+  // Generate permissions for each text format. Warn the administrator about
+  // text formats that are unsafe.
   foreach (filter_formats() as $format) {
     $permission = filter_permission_name($format);
     if (!empty($permission)) {
-      // Only link to the text format configuration page if the user who is
-      // viewing this will have access to that page.
-      $format_name_replacement = user_access('administer filters') ? l($format->name, 'admin/config/content/formats/' . $format->format) : theme('placeholder', array('text' => $format->name));
+      $warnings = filter_format_warnings($format->format);
+      $replacements = array(
+        // Only link to the text format configuration page if the user who is
+        // viewing this will have access to that page.
+        '!text_format' => user_access('administer filters') ? l($format->name, 'admin/config/content/formats/' . $format->format) : theme('placeholder', array('text' => $format->name)),
+        '%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'),
+      );
       $perms[$permission] = array(
         'title' => t("Use the %text_format text format", array('%text_format' => $format->name)),
-        'description' => t('Use !text_format in forms when entering or editing content. %warning', array('!text_format' => $format_name_replacement, '%warning' => t('Warning: This permission may have security implications depending on how the text format is configured.'))),
+        'description' =>  $warnings ? t('Use !text_format in forms when entering or editing content. %warning', $replacements) : t('Use !text_format in forms when entering or editing content.', $replacements),
       );
     }
   }
@@ -544,6 +577,64 @@ function filter_list_format($format_id) {
 }
 
 /**
+ * Determines if a text format is configured securely.
+ *
+ * @param $format_id
+ *   The ID of the format to check.
+ * @return
+ *   If the text format is insecure, an array of  warning messages
+ *   explaining the reasons why the text format is insecure.
+ *   In the case of a text format that is configured securely, the returned
+ *   array will be empty.
+ */
+function filter_format_warnings($format_id) {
+  $warnings = array();
+
+  // All text formats start off as insecure, because unfiltered HTML is by
+  // definition not safe.
+  $format_security['html'][] = array('type' => FILTER_EXPLOIT_CREATES, 'warning' => t('No filter has been added to sanitize raw HTML.'));
+
+  // Go through each filter and execute its security callback function,
+  // modifying the overall state of the text format as appropriate.
+  $filters = filter_list_format($format_id);
+  $filter_info = filter_get_filters();
+  foreach ($filters as $name => $filter) {
+    if ($filter->status && isset($filter_info[$name]['security callback']) && function_exists($filter_info[$name]['security callback'])) {
+      // Check if the callback function returns any data..
+      $callback_function = $filter_info[$name]['security callback'];
+      $filter_security = $callback_function($filter);
+      foreach ($filter_security as $category => $data) {
+        $format_security[$category][] = $data; 
+      }
+    }
+  }
+  // Start from the last filter of each category and find all warnings until
+  // reaching a filter that sanitizes it.  We retain warnings for any
+  // vulnerabilities created after a sanitizing filter.
+  foreach ($format_security as $category_security) {
+    $temp_warnings = array();
+    $status = FILTER_EXPLOIT_ALLOWS;
+    do {
+      $security = array_pop($category_security);
+      if (!empty($security['warning'])) {
+        $temp_warnings[] = $security['warning'];
+        if ($security['type'] == FILTER_EXPLOIT_CREATES) {
+          $status = FILTER_EXPLOIT_CREATES;
+        }
+      }
+    } while (!empty($category_security) && $security['type'] != FILTER_EXPLOIT_PREVENTS);
+    // The status will only stay at FILTER_EXPLOIT_ALLOWS if we find a
+    // FILTER_EXPLOIT_PREVENTS or if there is no FILTER_EXPLOIT_CREATES.
+    if ($status != FILTER_EXPLOIT_ALLOWS) {
+      // Combine with existing warnings.
+      $warnings = array_merge($warnings, $temp_warnings);
+    }
+  }
+
+  return $warnings;
+}
+
+/**
  * Run all the enabled filters on a piece of text.
  *
  * Note: Because filters can inject JavaScript or execute PHP code, security is
@@ -786,6 +877,38 @@ function filter_dom_serialize($dom_document) {
 }
 
 /**
+ * Splits a string containing HTML tags into an array of tag names.
+ *
+ * @param $tag_string
+ *   A string containing HTML tags; for example, '<a> <em> <strong>'.
+ * @return
+ *   An array of tag names; for example, array('a', 'em', 'strong').
+ */
+function _filter_split_tags($tag_string) {
+  return preg_split('/\s+|<|>/', $tag_string, -1, PREG_SPLIT_NO_EMPTY);
+}
+
+/**
+ * Returns an array of unsafe HTML tags.
+ *
+ * Tags in this list represent a security risk if untrusted users are allowed
+ * to use them when entering content.
+ *
+ * @return
+ *   An array of unsafe HTML tag names.
+ */
+function filter_html_unsafe_tags() {
+  // @todo: Expand this list and make it complete.
+  return array(
+    'embed',
+    'iframe',
+    'object',
+    'script',
+    'style',
+  );
+}
+
+/**
  * Format a link to the more extensive filter tips.
  *
  * @ingroup themeable
@@ -824,6 +947,7 @@ function filter_filter_info() {
     'title' => t('Limit allowed HTML tags'),
     'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
     'process callback' => '_filter_html',
+    'security callback' => '_filter_html_security',
     'settings callback' => '_filter_html_settings',
     'default settings' => array(
       'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
@@ -857,6 +981,7 @@ function filter_filter_info() {
     'title' => t('Escape all HTML'),
     'description' => t('Escapes all HTML tags, so they will be visible instead of being effective.'),
     'process callback' => '_filter_html_escape',
+    'security callback' => '_filter_html_escape_security',
     'tips callback' => '_filter_html_escape_tips',
   );
   return $filters;
@@ -893,7 +1018,7 @@ function _filter_html_settings($form, &$form_state, $filter, $defaults) {
  * HTML filter. Provides filtering of input into accepted HTML.
  */
 function _filter_html($text, $filter) {
-  $allowed_tags = preg_split('/\s+|<|>/', $filter->settings['allowed_html'], -1, PREG_SPLIT_NO_EMPTY);
+  $allowed_tags = _filter_split_tags($filter->settings['allowed_html']);
   $text = filter_xss($text, $allowed_tags);
 
   if ($filter->settings['filter_html_nofollow']) {
@@ -909,6 +1034,25 @@ function _filter_html($text, $filter) {
 }
 
 /**
+ * Security callback for the HTML filter.
+ */
+function _filter_html_security($filter) {
+  $allowed_tags = _filter_split_tags($filter->settings['allowed_html']);
+  $allowed_unsafe_tags = array_intersect(filter_html_unsafe_tags(), $allowed_tags);
+  if (empty($allowed_unsafe_tags)) {
+    // If the filter does not allow any unsafe tags, it is properly configured
+    // to prevent HTML exploits.
+    return array('html' => array('type' => FILTER_EXPLOIT_PREVENTS));
+  }
+  else {
+    // Otherwise, generate a useful warning for the case where this filter is
+    // being used in an already insecure text format, to explain how it could
+    // have been made safe.
+    return array('html' => array('type' => FILTER_EXPLOIT_ALLOWS, 'warning' => t('The HTML filter is configured to allow the following unsafe tags: %tags', array('%tags' => implode(', ', $allowed_unsafe_tags)))));
+  }
+}
+
+/**
  * Filter tips callback for HTML filter.
  */
 function _filter_html_tips($filter, $format, $long = FALSE) {
@@ -1175,6 +1319,15 @@ function _filter_html_escape($text) {
 }
 
 /**
+ * Security callback for the HTML escaping filter.
+ */
+function _filter_html_escape_security($filter) {
+  // Because this filter escapes all HTML, it prevents any HTML-related
+  // exploits from occurring.
+  return array('html' => array('type' => FILTER_EXPLOIT_PREVENTS));
+}
+
+/**
  * Filter tips callback for HTML escaping filter.
  */
 function _filter_html_escape_tips($filter, $format, $long = FALSE) {
diff --git modules/filter/filter.test modules/filter/filter.test
index 0d9f8c8..5113585 100644
--- modules/filter/filter.test
+++ modules/filter/filter.test
@@ -137,7 +137,40 @@ class FilterCRUDTestCase extends DrupalWebTestCase {
   }
 }
 
-class FilterAdminTestCase extends DrupalWebTestCase {
+class FilterTestCase extends DrupalWebTestCase {
+  /**
+   * Helper method for creating a new text format.
+   *
+   * @param $filters
+   *   An ordered array of filters to enable for the text format.
+   * @param $filter_settings
+   *   An array of optional filter settings, keyed by the filter they will be
+   *   applied to. 
+   * @return
+   *   The new text format object.
+   */
+  function createTextFormat($filters = array(), $filter_settings = array()) {
+    $format = new stdClass();
+    $format->name = $this->randomName();
+    $format->filters = array();
+    $weight = 0;
+    foreach ($filters as $filter) {
+      // Enable each filter, set its weight, and add any requested settings.
+      $format->filters[$filter] = array(
+        'status' => 1,
+        'weight' => $weight,
+      );
+      if (isset($filter_settings[$filter])) {
+        $format->filters[$filter]['settings'] = $filter_settings[$filter];
+      }
+      $weight++;
+    }
+    filter_format_save($format);
+    return $format;
+  }
+}
+
+class FilterAdminTestCase extends FilterTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Filter administration functionality',
@@ -359,7 +392,7 @@ class FilterAdminTestCase extends DrupalWebTestCase {
   }
 }
 
-class FilterAccessTestCase extends DrupalWebTestCase {
+class FilterAccessTestCase extends FilterTestCase {
   protected $admin_user;
   protected $web_user;
   protected $allowed_format;
@@ -459,7 +492,7 @@ class FilterAccessTestCase extends DrupalWebTestCase {
   }
 }
 
-class FilterDefaultFormatTestCase extends DrupalWebTestCase {
+class FilterDefaultFormatTestCase extends FilterTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Default text format functionality',
@@ -517,7 +550,7 @@ class FilterDefaultFormatTestCase extends DrupalWebTestCase {
   }
 }
 
-class FilterNoFormatTestCase extends DrupalWebTestCase {
+class FilterNoFormatTestCase extends FilterTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Unassigned text format functionality',
@@ -540,7 +573,7 @@ class FilterNoFormatTestCase extends DrupalWebTestCase {
 /**
  * Unit tests for core filters.
  */
-class FilterUnitTestCase extends DrupalUnitTestCase {
+class FilterUnitTestCase extends FilterTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Core filters',
@@ -1118,9 +1151,113 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
 }
 
 /**
+ * Tests for the text format security API.
+ */
+class FilterFormatSecurityTestCase extends FilterTestCase {
+  protected $filter_html_unsafe_settings;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Text format security',
+      'description' => 'Test the API for determining whether text formats are secure.',
+      'group' => 'Filter',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('filter_test');
+    $this->filter_html_unsafe_settings = array('allowed_html' => '<a> <em> <script> <strong>');
+  }
+
+  /**
+   * Test a text format with no filters; it should be labeled insecure.
+   */
+  function testNoFilters() {
+    $format = $this->createTextFormat();
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 1, t('A text format with no filters has an associated warning message about insecure HTML.'));
+  }
+
+  /**
+   * Use the HTML filter with default settings; the text format should be
+   * labeled secure.
+   */
+  function testHTMLFilterDefaultSettings() {
+    $format = $this->createTextFormat(array('filter_html'));
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 0, t('A text format with the default HTML filter has no associated warning messages.'));
+  }
+
+  /**
+   * Configure the HTML filter to allow an unsafe tag; the text format should
+   * be labeled insecure.
+   */
+  function testHTMLFilterUnsafeTag() {
+    $format = $this->createTextFormat(array('filter_html'), array('filter_html' => $this->filter_html_unsafe_settings));
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 2, t('A text format that allows script tags has an associated warning message about insecure HTML.'));
+  }
+
+  /**
+   * Configure the HTML filter to allow an unsafe tag, but sanitize the input
+   * beforehand by escaping all HTML; the text format should be labeled secure.
+   */
+  function testHTMLFilterUnsafeTagSanitizeBefore() {
+    $format = $this->createTextFormat(array('filter_html_escape', 'filter_html'), array('filter_html' => $this->filter_html_unsafe_settings));
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 0, t('A text format that escapes HTML before incompletely filtering it has no associated warning messages.'));
+  }
+
+  /**
+   * Configure the HTML filter to allow an unsafe tag, but sanitize the input
+   * afterwards by escaping all HTML; the text format should be labeled secure.
+   */
+  function testHTMLFilterUnsafeTagSanitizeAfter() {
+    $format = $this->createTextFormat(array('filter_html', 'filter_html_escape'), array('filter_html' => $this->filter_html_unsafe_settings));
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 0, t('A text format that escapes HTML after incompletely filtering it has no associated warning messages.'));
+  }
+
+  /**
+   * Escape all HTML, but after doing so, apply a filter that creates new
+   * HTML-related security vulnerabilities; the text format should be labeled
+   * insecure.
+   */
+  function testNewHTMLVulnerability() {
+    $format = $this->createTextFormat(array('filter_html_escape', 'filter_test_new_html_exploit'));
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 1, t('A text format that escapes HTML but then allows a new HTML-related security exploit afterwards has an associated warning message about insecure HTML.'));
+  }
+
+  /**
+   * Escape all HTML, apply a filter that creates new HTML-related security
+   * vulnerabilities, and then apply the HTML filter configured to allow an
+   * unsafe tag; the text format should be labeled insecure.
+   */
+  function testNewHTMLVulnerabilityAndUnsafeTag() {
+    $format = $this->createTextFormat(array('filter_html_escape', 'filter_test_new_html_exploit', 'filter_html'), array('filter_html' => $this->filter_html_unsafe_settings));
+    $warnings = filter_format_warnings($format->format);
+    // Warning messages from the last two filters should both be present, since
+    // neither security vulnerability has been removed.
+    $this->assertEqual(count($warnings), 2, t('A text format that incompletely filters HTML after allowing a new HTML-related security exploit has two associated warning messages about insecure HTML.'));
+  }
+
+  /**
+   * Escape all HTML, apply a filter that creates new HTML-related security
+   * vulnerabilities, and then apply the HTML filter with the default settings;
+   * the text format should be labeled secure.
+   */
+  function testNewHTMLVulnerabilitySanitizeAfter() {
+    $format = $this->createTextFormat(array('filter_html_escape', 'filter_test_new_html_exploit', 'filter_html'));
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 0, t('A text format that correctly filters HTML after allowing a new HTML-related security exploit has no associated warning messages.'));
+  }
+}
+
+/**
  * Tests for filter hook invocation.
  */
-class FilterHooksTestCase extends DrupalWebTestCase {
+class FilterHooksTestCase extends FilterTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Filter format hooks',
diff --git modules/php/php.module modules/php/php.module
index 196892d..93d5224 100644
--- modules/php/php.module
+++ modules/php/php.module
@@ -129,9 +129,17 @@ function php_filter_info() {
     'title' => t('PHP evaluator'),
     'description' => t('Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!'),
     'process callback' => 'php_eval',
+    'security callback' => '_php_filter_security',
     'tips callback' => '_php_filter_tips',
     'cache' => FALSE,
   );
   return $filters;
 }
 
+/**
+ * Security callback for the PHP filter.
+ */
+function _php_filter_security($filter) {
+  // Any format using this filter is insecure.
+  return array('php' => array('type' => FILTER_EXPLOIT_CREATES, 'warning' => t('The PHP filter allows users to execute arbitrary PHP code on your site.')));
+}
diff --git modules/php/php.test modules/php/php.test
index b7fb2a2..a337f17 100644
--- modules/php/php.test
+++ modules/php/php.test
@@ -4,7 +4,7 @@
 /**
  * Base PHP test case class.
  */
-class PHPTestCase extends DrupalWebTestCase {
+class PHPTestCase extends FilterTestCase {
   protected $php_code_format;
 
   function setUp() {
@@ -105,3 +105,37 @@ class PHPAccessTestCase extends PHPTestCase {
     $this->assertNoRaw('<option value="' . $this->php_code_format . '">', t('PHP code format not available.'));
   }
 }
+
+/**
+ * Test that text formats with the PHP filter are always labeled as insecure.
+ */
+class PHPFilterSecurityTestCase extends PHPTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'PHP filter security',
+      'description' => 'Test that text formats containing the PHP filter are always labeled as insecure.',
+      'group' => 'PHP',
+    );
+  }
+
+  /**
+   * Test a text format that only applies the PHP filter; it should be labeled
+   * insecure for both HTML and PHP exploits.
+   */
+  function testPHPFilter() {
+    $format = $this->createTextFormat(array('php_code'));
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 2, t('A text format with the PHP filter has an associated warning message about PHP code execution and insecure HTML.'));
+  }
+
+  /**
+   * Test a text format that applies the PHP filter and escapes all HTML
+   * afterwards; it should be labeled insecure for PHP exploits, but not for
+   * HTML exploits.
+   */
+  function testPHPFilterEscapeHTML() {
+    $format = $this->createTextFormat(array('php_code', 'filter_html_escape'));
+    $warnings = filter_format_warnings($format->format);
+    $this->assertEqual(count($warnings), 1, t('A text format with the PHP filter that also escapes HTML has an associated warning message about PHP code execution.'));
+  }
+}
diff --git modules/simpletest/tests/filter_test.module modules/simpletest/tests/filter_test.module
index 7fdd2f4..a980e79 100644
--- modules/simpletest/tests/filter_test.module
+++ modules/simpletest/tests/filter_test.module
@@ -7,6 +7,39 @@
  */
 
 /**
+ * Implement hook_filter_info().
+ */
+function filter_test_filter_info() {
+  $filters['filter_test_new_html_exploit'] = array(
+    'title' => t('Create a new HTML exploit'),
+    'description' => t('This filter intentionally creates a new HTML-related security vulnerability, for the purpose of testing the text format security API.'),
+    'process callback' => '_filter_test_new_html_exploit',
+    'security callback' => '_filter_test_new_html_exploit_security',
+  );
+  return $filters;
+}
+
+/**
+ * Filters text in such a way that a new HTML exploit is intentionally created.
+ *
+ * This filter is used for testing the text format security API, and it is an
+ * example of a filter that creates a new HTML-related security exploit by
+ * allowing safe text, such as "[script]", to be converted to an unsafe
+ * "<script>" HTML tag.
+ */
+function _filter_test_new_html_exploit($text) {
+  return str_replace(array('[', ']'), array('<', '>'), $text);
+}
+
+/**
+ * Security callback for the test HTML exploit filter.
+ */
+function _filter_test_new_html_exploit_security($filter) {
+  $warning = t('The HTML exploit filter deliberately creates a new HTML-related security vulnerability, even if the content passed in to the filter was previously sanitized.');
+  return array('html' => array('type' => FILTER_EXPLOIT_CREATES, 'warning' => $warning));
+}
+
+/**
  * Implement hook_filter_format_insert().
  */
 function filter_test_filter_format_insert($format) {
