diff --git a/bandit/blacklists/__init__.py b/bandit/blacklists/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py
new file mode 100644
index 00000000..8cfbe9de
--- /dev/null
+++ b/bandit/blacklists/calls.py
@@ -0,0 +1,225 @@
+# -*- coding:utf-8 -*-
+#
+# Copyright 2016 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from bandit.blacklists import utils
+
+
+def gen_blacklist():
+    """Generate a list of items to blacklist.
+
+    Methods of this type, "bandit.blacklist" plugins, are used to build a list
+    of items that bandit's built in blacklisting tests will use to trigger
+    issues. They replace the older blacklist* test plugins and allow
+    blacklisted items to have a unique bandit ID for filtering and profile
+    usage.
+
+    :return: a dictionary mapping node types to a list of blacklist data
+    """
+
+    sets = []
+    sets.append(utils.build_conf_dict(
+        'pickle', 'B301',
+        ['pickle.loads',
+         'pickle.load',
+         'pickle.Unpickler',
+         'cPickle.loads',
+         'cPickle.load',
+         'cPickle.Unpickler'],
+        'Pickle library appears to be in use, possible security issue.'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'marshal', 'B302', ['marshal.load', 'marshal.loads'],
+        'Deserialization with the marshal module is possibly dangerous.'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'md5', 'B303',
+        ['hashlib.md5',
+         'Crypto.Hash.MD2.new',
+         'Crypto.Hash.MD4.new',
+         'Crypto.Hash.MD5.new',
+         'cryptography.hazmat.primitives.hashes.MD5'],
+        'Use of insecure MD2, MD4, or MD5 hash function.'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'ciphers', 'B304',
+        ['Crypto.Cipher.ARC2.new',
+         'Crypto.Cipher.ARC4.new',
+         'Crypto.Cipher.Blowfish.new',
+         'Crypto.Cipher.DES.new',
+         'Crypto.Cipher.XOR.new',
+         'cryptography.hazmat.primitives.ciphers.algorithms.ARC4',
+         'cryptography.hazmat.primitives.ciphers.algorithms.Blowfish',
+         'cryptography.hazmat.primitives.ciphers.algorithms.IDEA'],
+        'Use of insecure cipher {name}. Replace with a known secure'
+        ' cipher such as AES.',
+        'HIGH'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'cipher_modes', 'B305',
+        ['cryptography.hazmat.primitives.ciphers.modes.ECB'],
+        'Use of insecure cipher mode {name}.'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'mktemp_q', 'B306', ['tempfile.mktemp'],
+        'Use of insecure and deprecated function (mktemp).'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'eval', 'B307', ['eval'],
+        'Use of possibly insecure function - consider using safer '
+        'ast.literal_eval.'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'mark_safe', 'B308', ['mark_safe'],
+        'Use of mark_safe() may expose cross-site scripting '
+        'vulnerabilities and should be reviewed.'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'httpsconnection', 'B309',
+        ['httplib.HTTPSConnection',
+         'http.client.HTTPSConnection',
+         'six.moves.http_client.HTTPSConnection'],
+        'Use of HTTPSConnection does not provide security, see '
+        'https://wiki.openstack.org/wiki/OSSN/OSSN-0033'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'urllib_urlopen', 'B310',
+        ['urllib.urlopen',
+         'urllib.request.urlopen',
+         'urllib.urlretrieve',
+         'urllib.request.urlretrieve',
+         'urllib.URLopener',
+         'urllib.request.URLopener',
+         'urllib.FancyURLopener',
+         'urllib.request.FancyURLopener',
+         'urllib2.urlopen',
+         'urllib2.Request',
+         'six.moves.urllib.request.urlopen',
+         'six.moves.urllib.request.urlretrieve',
+         'six.moves.urllib.request.URLopener',
+         'six.moves.urllib.request.FancyURLopener'],
+        'Audit url open for permitted schemes. Allowing use of file:/ or '
+        'custom schemes is often unexpected.'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'random', 'B311',
+        ['random.random',
+         'random.randrange',
+         'random.randint',
+         'random.choice',
+         'random.uniform',
+         'random.triangular'],
+        'Standard pseudo-random generators are not suitable for '
+        'security/cryptographic purposes.',
+        'LOW'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'telnetlib', 'B312', ['telnetlib.*'],
+        'Telnet-related funtions are being called. Telnet is considered '
+        'insecure. Use SSH or some other encrypted protocol.',
+        'HIGH'
+        ))
+
+    # Most of this is based off of Christian Heimes' work on defusedxml:
+    #   https://pypi.python.org/pypi/defusedxml/#defusedxml-sax
+
+    xml_msg = ('Using {name} to parse untrusted XML data is known to be '
+               'vulnerable to XML attacks. Replace {name} with its '
+               'defusedxml equivalent function.')
+
+    sets.append(utils.build_conf_dict(
+        'xml_bad_cElementTree', 'B313',
+        ['xml.etree.cElementTree.parse',
+         'xml.etree.cElementTree.iterparse',
+         'xml.etree.cElementTree.fromstring',
+         'xml.etree.cElementTree.XMLParser'],
+        xml_msg
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'xml_bad_ElementTree', 'B314',
+        ['xml.etree.ElementTree.parse',
+         'xml.etree.ElementTree.iterparse',
+         'xml.etree.ElementTree.fromstring',
+         'xml.etree.ElementTree.XMLParser'],
+        xml_msg
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'xml_bad_expatreader', 'B315', ['xml.sax.expatreader.create_parser'],
+        xml_msg
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'xml_bad_expatbuilder', 'B316',
+        ['xml.dom.expatbuilder.parse',
+         'xml.dom.expatbuilder.parseString'],
+        xml_msg
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'xml_bad_sax', 'B317',
+        ['xml.sax.parse',
+         'xml.sax.parseString',
+         'xml.sax.make_parser'],
+        xml_msg
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'xml_bad_minidom', 'B318',
+        ['xml.dom.minidom.parse',
+         'xml.dom.minidom.parseString'],
+        xml_msg
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'xml_bad_pulldom', 'B319',
+        ['xml.dom.pulldom.parse',
+         'xml.dom.pulldom.parseString'],
+        xml_msg
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'xml_bad_etree', 'B320',
+        ['lxml.etree.parse',
+         'lxml.etree.fromstring',
+         'lxml.etree.RestrictedElement',
+         'lxml.etree.GlobalParserTLS',
+         'lxml.etree.getDefaultParser',
+         'lxml.etree.check_docinfo'],
+        xml_msg
+        ))
+
+    # end of XML tests
+
+    sets.append(utils.build_conf_dict(
+        'ftplib', 'B321', ['ftplib.*'],
+        'FTP-related funtions are being called. FTP is considered '
+        'insecure. Use SSH/SFTP/SCP or some other encrypted protocol.',
+        'HIGH'
+        ))
+
+    return {'Call': sets}
diff --git a/bandit/blacklists/imports.py b/bandit/blacklists/imports.py
new file mode 100644
index 00000000..d0a5ae06
--- /dev/null
+++ b/bandit/blacklists/imports.py
@@ -0,0 +1,81 @@
+# -*- coding:utf-8 -*-
+#
+# Copyright 2016 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from bandit.blacklists import utils
+
+
+def gen_blacklist():
+    """Generate a list of items to blacklist.
+
+    Methods of this type, "bandit.blacklist" plugins, are used to build a list
+    of items that bandit's built in blacklisting tests will use to trigger
+    issues. They replace the older blacklist* test plugins and allow
+    blacklisted items to have a unique bandit ID for filtering and profile
+    usage.
+
+    :return: a dictionary mapping node types to a list of blacklist data
+    """
+
+    sets = []
+    sets.append(utils.build_conf_dict(
+        'telnet', 'B401', ['telnetlib'],
+        'A telnet-related module is being imported.  Telnet is '
+        'considered insecure. Use SSH or some other encrypted protocol.',
+        'HIGH'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'ftp', 'B402', ['ftplib'],
+        'A FTP-related module is being imported.  FTP is considered '
+        'insecure. Use SSH/SFTP/SCP or some other encrypted protocol.',
+        'HIGH'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'info_libs', 'B403', ['pickle', 'cPickle', 'subprocess', 'Crypto'],
+        'Consider possible security implications associated with '
+        '{name} module.', 'LOW'
+        ))
+
+    # Most of this is based off of Christian Heimes' work on defusedxml:
+    #   https://pypi.python.org/pypi/defusedxml/#defusedxml-sax
+
+    sets.append(utils.build_conf_dict(
+        'xml_libs', 'B404',
+        ['xml.etree.cElementTree',
+         'xml.etree.ElementTree',
+         'xml.sax.expatreader',
+         'xml.sax',
+         'xml.dom.expatbuilder',
+         'xml.dom.minidom',
+         'xml.dom.pulldom',
+         'lxml.etree',
+         'lxml'],
+        'Using {name} to parse untrusted XML data is known to be '
+        'vulnerable to XML attacks. Replace {name} with the equivalent '
+        'defusedxml package.', 'LOW'
+        ))
+
+    sets.append(utils.build_conf_dict(
+        'xml_libs_high', 'B405', ['xmlrpclib'],
+        'Using {name} to parse untrusted XML data is known to be '
+        'vulnerable to XML attacks. Use defused.xmlrpc.monkey_patch() '
+        'function to monkey-patch xmlrpclib and mitigate XML '
+        'vulnerabilities.', 'HIGH'
+        ))
+
+    return {'Import': sets, 'ImportFrom': sets, 'Call': sets}
diff --git a/bandit/blacklists/utils.py b/bandit/blacklists/utils.py
new file mode 100644
index 00000000..f14e4872
--- /dev/null
+++ b/bandit/blacklists/utils.py
@@ -0,0 +1,22 @@
+# -*- coding:utf-8 -*-
+#
+# Copyright 2016 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+def build_conf_dict(name, bid, qualnames, message, level='MEDIUM'):
+    """Build and return a blacklist configuration dict."""
+
+    return {'name': name, 'id': bid, 'message': message,
+            'qualnames': qualnames, 'level': level}
diff --git a/bandit/core/blacklisting.py b/bandit/core/blacklisting.py
new file mode 100644
index 00000000..973e7aed
--- /dev/null
+++ b/bandit/core/blacklisting.py
@@ -0,0 +1,61 @@
+# -*- coding:utf-8 -*-
+#
+# Copyright 2016 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import ast
+import fnmatch
+
+from bandit.core import extension_loader
+from bandit.core import issue
+
+
+def report_issue(check, name):
+    return issue.Issue(
+        severity=check['level'], confidence='HIGH',
+        text=check['message'].replace('{name}', name),
+        ident=name, test_id=check["id"])
+
+
+def blacklist(context):
+    blacklists = extension_loader.MANAGER.blacklist
+    node_type = context.node.__class__.__name__
+    if node_type not in blacklists:
+        return
+
+    if node_type == 'Call':
+        func = context.node.func
+        if isinstance(func, ast.Name) and func.id == '__import__':
+            if len(context.node.args):
+                name = context.node.args[0].s
+            else:
+                name = ""  # handle '__import__()'
+        else:
+            name = context.call_function_name_qual
+        for check in blacklists[node_type]:
+            for qn in check['qualnames']:
+                if fnmatch.fnmatch(name, qn):
+                    return report_issue(check, name)
+
+    if node_type.startswith('Import'):
+        prefix = ""
+        if node_type == "ImportFrom":
+            if context.node.module is not None:
+                prefix = context.node.module + "."
+
+        for check in blacklists[node_type]:
+            for name in context.node.names:
+                for qn in check['qualnames']:
+                    if (prefix + name.name).startswith(qn):
+                        return report_issue(check, name.name)
diff --git a/bandit/core/extension_loader.py b/bandit/core/extension_loader.py
index ac4c6036..aac79453 100644
--- a/bandit/core/extension_loader.py
+++ b/bandit/core/extension_loader.py
@@ -16,15 +16,18 @@ from __future__ import print_function
 
 import sys
 
+import six
 from stevedore import extension
 
 
 class Manager(object):
     def __init__(self, formatters_namespace='bandit.formatters',
-                 plugins_namespace='bandit.plugins'):
+                 plugins_namespace='bandit.plugins',
+                 blacklists_namespace='bandit.blacklists'):
         # Cache the extension managers, loaded extensions, and extension names
         self.load_formatters(formatters_namespace)
         self.load_plugins(plugins_namespace)
+        self.load_blacklists(blacklists_namespace)
 
     def load_formatters(self, formatters_namespace):
         self.formatters_mgr = extension.ExtensionManager(
@@ -63,6 +66,18 @@ class Manager(object):
     def get_plugin_id(self, plugin_name):
         return self.plugin_name_to_id.get(plugin_name)
 
+    def load_blacklists(self, blacklist_namespace):
+        self.blacklists_mgr = extension.ExtensionManager(
+            namespace=blacklist_namespace,
+            invoke_on_load=False,
+            verify_requirements=False,
+            )
+        self.blacklist = {}
+        blacklist = list(self.blacklists_mgr)
+        for item in blacklist:
+            for key, val in six.iteritems(item.plugin()):
+                self.blacklist.setdefault(key, []).extend(val)
+
 # Using entry-points and pkg_resources *can* be expensive. So let's load these
 # once, store them on the object, and have a module global object for
 # accessing them. After the first time this module is imported, it should save
diff --git a/bandit/core/issue.py b/bandit/core/issue.py
index 390a014a..ea3bde07 100644
--- a/bandit/core/issue.py
+++ b/bandit/core/issue.py
@@ -25,14 +25,14 @@ import linecache
 
 class Issue(object):
     def __init__(self, severity, confidence=constants.CONFIDENCE_DEFAULT,
-                 text="", ident=None, lineno=None):
+                 text="", ident=None, lineno=None, test_id=""):
         self.severity = severity
         self.confidence = confidence
         self.text = text
         self.ident = ident
         self.fname = ""
         self.test = ""
-        self.test_id = ""
+        self.test_id = test_id
         self.lineno = lineno
         self.linerange = []
 
diff --git a/bandit/core/test_set.py b/bandit/core/test_set.py
index 15e982df..de1831b9 100644
--- a/bandit/core/test_set.py
+++ b/bandit/core/test_set.py
@@ -22,6 +22,8 @@ import logging
 import sys
 import warnings
 
+from bandit.core import blacklisting
+from bandit.core import extension_loader
 from bandit.core import utils
 
 
@@ -37,6 +39,11 @@ class BanditTestSet():
         filter_list = self._filter_list_from_config(profile=profile)
         self.load_tests(filter=filter_list)
 
+        # load blacklists
+        for key in extension_loader.MANAGER.blacklist.keys():
+            value = self.tests.setdefault(key, {})
+            value["blacklist"] = blacklisting.blacklist
+
     def _filter_list_from_config(self, profile=None):
         # will create an (include,exclude) list tuple from a specified name
         # config section
diff --git a/bandit/core/tester.py b/bandit/core/tester.py
index 499b8929..ac63346c 100644
--- a/bandit/core/tester.py
+++ b/bandit/core/tester.py
@@ -74,7 +74,8 @@ class BanditTester():
                         result.lineno = temp_context['lineno']
                     result.linerange = temp_context['linerange']
                     result.test = test.__name__
-                    result.test_id = test._test_id
+                    if result.test_id == "":
+                        result.test_id = test._test_id
 
                     self.results.append(result)
 
diff --git a/bandit/plugins/blacklist_calls.py b/bandit/plugins/blacklist_calls.py
deleted file mode 100644
index ef1ff160..00000000
--- a/bandit/plugins/blacklist_calls.py
+++ /dev/null
@@ -1,379 +0,0 @@
-# -*- coding:utf-8 -*-
-#
-# Copyright 2014 Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-r"""
-=================================
-B301: Test for black listed calls
-=================================
-
-A number of Python methods and functions are known to have potential security
-implications. The blacklist calls plugin test is designed to detect the use of
-these methods by scanning code for method calls and checking for their presence
-in a configurable blacklist. The scanned calls are fully qualified and
-de-aliased prior to checking. To illustrate this, imagine a check for
-"evil.thing()" running on the following example code:
-
-.. code-block:: python
-
-    import evil as good
-
-    good.thing()
-    thing()
-
-This would generate a warning about calling `evil.thing()` despite the module
-being aliased as `good`. It would also not generate a warning on the call to
-`thing()` in the local module, as it's fully qualified name will not match.
-
-Each of the provided blacklisted calls can be grouped such that they generate
-appropriate warnings (message, severity) and a token `{func}` may be used
-in the provided output message, to be replaced with the actual method name.
-
-Due to the nature of the test, confidence is always reported as HIGH
-
-**Config Options:**
-
-.. code-block:: yaml
-
-    blacklist_calls:
-        bad_name_sets:
-            - pickle:
-                qualnames:
-                    - pickle.loads
-                    - pickle.load
-                    - pickle.Unpickler
-                    - cPickle.loads
-                    - cPickle.load
-                    - cPickle.Unpickler
-                message: >
-                    Pickle library appears to be in use, possible security
-                    issue.
-            - marshal:
-                qualnames: [marshal.load, marshal.loads]
-                message: >
-                    Deserialization with the {func} is possibly dangerous.
-                level: LOW
-
-:Example:
-
-.. code-block:: none
-
-      >> Issue: Pickle library appears to be in use, possible security issue.
-
-        Severity: Medium   Confidence: High
-        Location: ./examples/pickle_deserialize.py:20
-      19  serialized = cPickle.dumps({(): []})
-      20  print(cPickle.loads(serialized))
-      21
-
-.. seealso::
-
- - https://security.openstack.org
-
-.. versionadded:: 0.9.0
-
-"""
-
-import fnmatch
-
-import bandit
-from bandit.core import test_properties as test
-
-
-_cached_blacklist_checks = []
-_cached_blacklist_config = None  # FIXME(tkelsey): there is no point in this ..
-
-
-def _build_conf_dict(name, qualnames, message, level='MEDIUM'):
-    return {name: {'message': message, 'qualnames': qualnames, 'level': level}}
-
-
-def gen_config(name):
-    if 'blacklist_calls' == name:
-        sets = []
-
-        sets.append(_build_conf_dict(
-            'pickle',
-            ['pickle.loads',
-             'pickle.load',
-             'pickle.Unpickler',
-             'cPickle.loads',
-             'cPickle.load',
-             'cPickle.Unpickler'],
-            'Pickle library appears to be in use, possible security issue.'
-            ))
-
-        sets.append(_build_conf_dict(
-            'marshal', ['marshal.load', 'marshal.loads'],
-            'Deserialization with the marshal module is possibly dangerous.'
-            ))
-
-        sets.append(_build_conf_dict(
-            'md5',
-            ['hashlib.md5',
-             'Crypto.Hash.MD2.new',
-             'Crypto.Hash.MD4.new',
-             'Crypto.Hash.MD5.new',
-             'cryptography.hazmat.primitives.hashes.MD5'],
-            'Use of insecure MD2, MD4, or MD5 hash function.'
-            ))
-
-        sets.append(_build_conf_dict(
-            'ciphers',
-            ['Crypto.Cipher.ARC2.new',
-             'Crypto.Cipher.ARC4.new',
-             'Crypto.Cipher.Blowfish.new',
-             'Crypto.Cipher.DES.new',
-             'Crypto.Cipher.XOR.new',
-             'cryptography.hazmat.primitives.ciphers.algorithms.ARC4',
-             'cryptography.hazmat.primitives.ciphers.algorithms.Blowfish',
-             'cryptography.hazmat.primitives.ciphers.algorithms.IDEA'],
-            'Use of insecure cipher {func}. Replace with a known secure'
-            ' cipher such as AES.',
-            'HIGH'
-            ))
-
-        sets.append(_build_conf_dict(
-            'cipher_modes',
-            ['cryptography.hazmat.primitives.ciphers.modes.ECB'],
-            'Use of insecure cipher mode {func}.'
-            ))
-
-        sets.append(_build_conf_dict(
-            'mktemp_q', ['tempfile.mktemp'],
-            'Use of insecure and deprecated function (mktemp).'
-            ))
-
-        sets.append(_build_conf_dict(
-            'eval', ['eval'],
-            'Use of possibly insecure function - consider using safer '
-            'ast.literal_eval.'
-            ))
-
-        sets.append(_build_conf_dict(
-            'mark_safe', ['mark_safe'],
-            'Use of mark_safe() may expose cross-site scripting '
-            'vulnerabilities and should be reviewed.'
-            ))
-
-        sets.append(_build_conf_dict(
-            'httpsconnection',
-            ['httplib.HTTPSConnection',
-             'http.client.HTTPSConnection',
-             'six.moves.http_client.HTTPSConnection'],
-            'Use of HTTPSConnection does not provide security, see '
-            'https://wiki.openstack.org/wiki/OSSN/OSSN-0033'
-            ))
-
-        sets.append(_build_conf_dict(
-            'urllib_urlopen',
-            ['urllib.urlopen',
-             'urllib.request.urlopen',
-             'urllib.urlretrieve',
-             'urllib.request.urlretrieve',
-             'urllib.URLopener',
-             'urllib.request.URLopener',
-             'urllib.FancyURLopener',
-             'urllib.request.FancyURLopener',
-             'urllib2.urlopen',
-             'urllib2.Request',
-             'six.moves.urllib.request.urlopen',
-             'six.moves.urllib.request.urlretrieve',
-             'six.moves.urllib.request.URLopener',
-             'six.moves.urllib.request.FancyURLopener'],
-            'Audit url open for permitted schemes. Allowing use of file:/ or '
-            'custom schemes is often unexpected.'
-            ))
-
-        sets.append(_build_conf_dict(
-            'random',
-            ['random.random',
-             'random.randrange',
-             'random.randint',
-             'random.choice',
-             'random.uniform',
-             'random.triangular'],
-            'Standard pseudo-random generators are not suitable for '
-            'security/cryptographic purposes.',
-            'LOW'
-            ))
-
-        sets.append(_build_conf_dict(
-            'telnetlib', ['telnetlib.*'],
-            'Telnet-related funtions are being called. Telnet is considered '
-            'insecure. Use SSH or some other encrypted protocol.',
-            'HIGH'
-            ))
-
-        sets.append(_build_conf_dict(
-            'ftplib', ['ftplib.*'],
-            'FTP-related funtions are being called. FTP is considered '
-            'insecure. Use SSH/SFTP/SCP or some other encrypted protocol.',
-            'HIGH'
-            ))
-
-        # Most of this is based off of Christian Heimes' work on defusedxml:
-        #   https://pypi.python.org/pypi/defusedxml/#defusedxml-sax
-
-        xml_msg = ('Using {func} to parse untrusted XML data is known to be '
-                   'vulnerable to XML attacks. Replace {func} with its '
-                   'defusedxml equivalent function.')
-
-        sets.append(_build_conf_dict(
-            'xml_bad_cElementTree',
-            ['xml.etree.cElementTree.parse',
-             'xml.etree.cElementTree.iterparse',
-             'xml.etree.cElementTree.fromstring',
-             'xml.etree.cElementTree.XMLParser'],
-            xml_msg
-            ))
-
-        sets.append(_build_conf_dict(
-            'xml_bad_ElementTree',
-            ['xml.etree.ElementTree.parse',
-             'xml.etree.ElementTree.iterparse',
-             'xml.etree.ElementTree.fromstring',
-             'xml.etree.ElementTree.XMLParser'],
-            xml_msg
-            ))
-
-        sets.append(_build_conf_dict(
-            'xml_bad_expatreader', ['xml.sax.expatreader.create_parser'],
-            xml_msg
-            ))
-
-        sets.append(_build_conf_dict(
-            'xml_bad_expatbuilder',
-            ['xml.dom.expatbuilder.parse',
-             'xml.dom.expatbuilder.parseString'],
-            xml_msg
-            ))
-
-        sets.append(_build_conf_dict(
-            'xml_bad_sax',
-            ['xml.sax.parse',
-             'xml.sax.parseString',
-             'xml.sax.make_parser'],
-            xml_msg
-            ))
-
-        sets.append(_build_conf_dict(
-            'xml_bad_minidom',
-            ['xml.dom.minidom.parse',
-             'xml.dom.minidom.parseString'],
-            xml_msg
-            ))
-
-        sets.append(_build_conf_dict(
-            'xml_bad_pulldom',
-            ['xml.dom.pulldom.parse',
-             'xml.dom.pulldom.parseString'],
-            xml_msg
-            ))
-
-        sets.append(_build_conf_dict(
-            'xml_bad_etree',
-            ['lxml.etree.parse',
-             'lxml.etree.fromstring',
-             'lxml.etree.RestrictedElement',
-             'lxml.etree.GlobalParserTLS',
-             'lxml.etree.getDefaultParser',
-             'lxml.etree.check_docinfo'],
-            xml_msg
-            ))
-
-        return {'bad_name_sets': sets}
-
-
-@test.takes_config
-@test.checks('Call')
-@test.test_id('B301')
-def blacklist_calls(context, config):
-    _ensure_cache(config)
-    checks = _cached_blacklist_checks
-
-    # for each check, go through and see if it matches all qualifications
-    for qualnames, names, message_tpl, level, params in checks:
-        confidence = 'HIGH'
-        does_match = True
-        # item 0=qualnames, 1=names, 2=message, 3=level, 4=params
-        if does_match and qualnames:
-            # match the qualname - respect wildcards if present
-            does_match = any(
-                fnmatch.fnmatch(context.call_function_name_qual, qn)
-                for qn in qualnames)
-
-        if does_match and names:
-            does_match = any(context.call_function_name == n for n in names)
-
-        if does_match and params:
-            matched_p = False
-            for p in params:
-                for arg_num in range(0, context.call_args_count - 1):
-                    if p == context.get_call_arg_at_position(arg_num):
-                        matched_p = True
-            if not matched_p:
-                does_match = False
-
-        if does_match:
-            message = message_tpl.replace("{func}",
-                                          context.call_function_name_qual)
-
-            return bandit.Issue(
-                severity=level, confidence=confidence,
-                text=message,
-                ident=context.call_function_name_qual
-            )
-
-
-def _ensure_cache(config):
-    global _cached_blacklist_config
-    if _cached_blacklist_checks and config is _cached_blacklist_config:
-        return
-
-    _cached_blacklist_config = config
-    if config is not None and 'bad_name_sets' in config:
-        sets = config['bad_name_sets']
-    else:
-        sets = []
-
-    # load all the checks from the config file
-    for cur_item in sets:
-        for blacklist_item in cur_item:
-            blacklist_object = cur_item[blacklist_item]
-            cur_check = _get_tuple_for_item(blacklist_object)
-            # skip bogus checks
-            if cur_check:
-                _cached_blacklist_checks.append(cur_check)
-
-
-def _get_tuple_for_item(blacklist_object):
-    level_map = {'LOW': bandit.LOW, 'MEDIUM': bandit.MEDIUM,
-                 'HIGH': bandit.HIGH}
-
-    # if the item we got passed isn't a dictionary, do nothing with this object
-    if not isinstance(blacklist_object, dict):
-        return None
-
-    # not all of the fields will be set, so all have default fallbacks
-    qualnames = blacklist_object.get('qualnames')
-    names = blacklist_object.get('names')
-    message = blacklist_object.get('message', '')
-    params = blacklist_object.get('params')
-
-    level_name = blacklist_object.get('level', 'MEDIUM').upper()
-    level = level_map.get(level_name, 'MEDIUM')
-
-    return (qualnames, names, message, level, params)
diff --git a/bandit/plugins/blacklist_imports.py b/bandit/plugins/blacklist_imports.py
deleted file mode 100644
index 8bed9356..00000000
--- a/bandit/plugins/blacklist_imports.py
+++ /dev/null
@@ -1,296 +0,0 @@
-# -*- coding:utf-8 -*-
-#
-# Copyright 2014 Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import bandit
-from bandit.core import test_properties as test
-
-
-def _build_conf_dict(name, imports, message, level='MEDIUM'):
-    return {name: {'message': message, 'imports': imports, 'level': level}}
-
-
-def gen_config(name):
-    if 'blacklist_imports' == name:
-        sets = []
-
-        sets.append(_build_conf_dict(
-            'telnet', ['telnetlib'],
-            'A telnet-related module is being imported.  Telnet is '
-            'considered insecure. Use SSH or some other encrypted protocol.',
-            'HIGH'
-            ))
-
-        sets.append(_build_conf_dict(
-            'ftp', ['ftplib'],
-            'A FTP-related module is being imported.  FTP is considered '
-            'insecure. Use SSH/SFTP/SCP or some other encrypted protocol.',
-            'HIGH'
-            ))
-
-        sets.append(_build_conf_dict(
-            'info_libs', ['pickle', 'cPickle', 'subprocess', 'Crypto'],
-            'Consider possible security implications associated with '
-            '{module} module.', 'LOW'
-            ))
-
-        # Most of this is based off of Christian Heimes' work on defusedxml:
-        #   https://pypi.python.org/pypi/defusedxml/#defusedxml-sax
-
-        sets.append(_build_conf_dict(
-            'xml_libs',
-            ['xml.etree.cElementTree',
-             'xml.etree.ElementTree',
-             'xml.sax.expatreader',
-             'xml.sax',
-             'xml.dom.expatbuilder',
-             'xml.dom.minidom',
-             'xml.dom.pulldom',
-             'lxml.etree',
-             'lxml'],
-            'Using {module} to parse untrusted XML data is known to be '
-            'vulnerable to XML attacks. Replace {module} with the equivalent '
-            'defusedxml package.', 'LOW'
-            ))
-
-        sets.append(_build_conf_dict(
-            'xml_libs_high', ['xmlrpclib'],
-            'Using {module} to parse untrusted XML data is known to be '
-            'vulnerable to XML attacks. Use defused.xmlrpc.monkey_patch() '
-            'function to monkey-patch xmlrpclib and mitigate XML '
-            'vulnerabilities.', 'HIGH'
-            ))
-
-        return {'bad_import_sets': sets}
-
-
-@test.takes_config
-@test.checks('Import', 'ImportFrom')
-@test.test_id('B401')
-def blacklist_imports(context, config):
-    """**B401: Test for blacklisted imports**
-
-    A number of Python modules are known to provide collections of
-    functionality with potential security implications. The blacklist imports
-    plugin test is designed to detect the use of these modules by scanning code
-    for `import` statements and checking for the imported modules presence in a
-    configurable blacklist. The imported modules are fully qualified and
-    de-aliased prior to checking. To illustrate this, imagine a check for
-    "module.evil" running on the following example code:
-
-    .. code-block:: python
-
-        import module                    # no warning
-        import module.evil               # warning
-        from module import evil          # warning
-        from module import evil as good  # warning
-
-    This would generate a warning about importing `module.evil` in each of the
-    last three cases, despite the module being aliased as `good` in one of
-    them. It would also not generate a warning on the first import
-    (of `module`) as it's fully qualified name will not match.
-
-    Each of the provided blacklisted modules can be grouped such that they
-    generate appropriate warnings (message, severity) and a token `{module}`
-    may be used in the provided output message, to be replaced with the actual
-    module name.
-
-    Due to the nature of the test, confidence is always reported as HIGH
-
-    **Config Options:**
-
-    .. code-block:: yaml
-
-        blacklist_imports:
-            bad_import_sets:
-                - xml_libs:
-                    imports:
-                        - xml.etree.cElementTree
-                        - xml.etree.ElementTree
-                        - xml.sax.expatreader
-                        - xml.sax
-                        - xml.dom.expatbuilder
-                        - xml.dom.minidom
-                        - xml.dom.pulldom
-                        - lxml.etree
-                        - lxml
-                    message: >
-                        Using {module} to parse untrusted XML data is known to
-                        be vulnerable to XML attacks. Replace {module} with the
-                        equivalent defusedxml package.
-                    level: LOW
-
-
-    :Example:
-
-    .. code-block:: none
-
-        >> Issue: Using xml.sax to parse untrusted XML data is known to be
-        vulnerable to XML attacks. Replace xml.sax with the equivalent
-        defusedxml package.
-
-           Severity: Low   Confidence: High
-           Location: ./examples/xml_sax.py:1
-        1 import xml.sax
-        2 from xml import sax
-
-        >> Issue: Using xml.sax.parseString to parse untrusted XML data is
-        known to be vulnerable to XML attacks. Replace xml.sax.parseString with
-        its defusedxml equivalent function.
-
-           Severity: Medium   Confidence: High
-           Location: ./examples/xml_sax.py:21
-        20  # bad
-        21  xml.sax.parseString(xmlString, ExampleContentHandler())
-        22  xml.sax.parse('notaxmlfilethatexists.xml', ExampleContentHandler())
-
-    .. seealso::
-
-     - https://security.openstack.org
-
-    .. versionadded:: 0.9.0
-    """
-
-    checks = _load_checks(config)
-
-    # for each check, go through and see if it matches all qualifications
-    for check in checks:
-        # item 0=import, 1=message, 2=level
-        if check[0]:
-            for im in check[0]:
-                if context.is_module_being_imported(im):
-                    return _get_result(check, im)
-
-
-@test.takes_config('blacklist_imports')
-@test.checks('Call')
-@test.test_id('B402')
-def blacklist_import_func(context, config):
-    """**B402: Test for blacklisted import functions**
-
-    This test is in all ways identical blacklist_imports. However, it
-    is designed to catch modules that have been imported using Python's special
-    builtin import function, `__import__()`. For example, running a test on the
-    following code for `module.evil` would warn as shown:
-
-    .. code-block:: python
-
-        __import__('module')                    # no warning
-        __import__('module.evil')               # warning
-
-    This test shares the configuration provided for the standard
-    blacklist_imports test.
-
-    :Example:
-
-    .. code-block:: none
-
-        >> Issue: Using xml.sax to parse untrusted XML data is known to be
-        vulnerable to XML attacks. Replace xml.sax with the equivalent
-        defusedxml package.
-
-           Severity: Low   Confidence: High
-           Location: ./examples/xml_sax.py:1
-        1 import xml.sax
-        2 from xml import sax
-
-        >> Issue: Using xml.sax.parseString to parse untrusted XML data is
-        known to be vulnerable to XML attacks. Replace xml.sax.parseString with
-        its defusedxml equivalent function.
-
-           Severity: Medium   Confidence: High
-           Location: ./examples/xml_sax.py:21
-        20  # bad
-        21  xml.sax.parseString(xmlString, ExampleContentHandler())
-        22  xml.sax.parse('notaxmlfilethatexists.xml', ExampleContentHandler())
-
-
-    .. seealso::
-
-     - https://security.openstack.org
-
-    .. versionadded:: 0.9.0
-    """
-    checks = _load_checks(config)
-    if context.call_function_name_qual == '__import__':
-        for check in checks:
-            # item 0=import, 1=message, 2=level
-            if check[0]:
-                for im in check[0]:
-                    if len(context.call_args) and im == context.call_args[0]:
-                        return _get_result(check, im)
-
-
-def _load_checks(config):
-    # load all the checks from the config file
-    if config is not None and 'bad_import_sets' in config:
-        sets = config['bad_import_sets']
-    else:
-        sets = []
-
-    checks = []
-    for cur_item in sets:
-        for blacklist_item in cur_item:
-            blacklist_object = cur_item[blacklist_item]
-            cur_check = _get_tuple_for_item(blacklist_object)
-            # skip bogus checks
-            if cur_check:
-                checks.append(cur_check)
-    return checks
-
-
-def _get_tuple_for_item(blacklist_object):
-    # default values
-    imports = None
-    message = ""
-    level = 'MEDIUM'
-
-    # if the item we got passed isn't a dictionary, do nothing with the object;
-    # if the item we got passed doesn't have an imports field, we can't do
-    # anything with this.  Return None
-    if (not isinstance(blacklist_object, dict) or
-            'imports' not in blacklist_object):
-        return None
-
-    imports = blacklist_object['imports']
-
-    if 'message' in blacklist_object:
-        message = blacklist_object['message']
-
-    if 'level' in blacklist_object:
-        if blacklist_object['level'] == 'HIGH':
-            level = 'HIGH'
-        elif blacklist_object['level'] == 'MEDIUM':
-            level = 'MEDIUM'
-        elif blacklist_object['level'] == 'LOW':
-            level = 'LOW'
-
-    return_tuple = (imports, message, level)
-    return return_tuple
-
-
-def _get_result(check, im):
-    # substitute '{module}' for the imported module name
-    message = check[1].replace('{module}', im)
-
-    level = None
-    if check[2] == 'HIGH':
-        level = bandit.HIGH
-    elif check[2] == 'MEDIUM':
-        level = bandit.MEDIUM
-    elif check[2] == 'LOW':
-        level = bandit.LOW
-
-    return bandit.Issue(severity=level, confidence=bandit.HIGH, text=message)
diff --git a/setup.cfg b/setup.cfg
index fc4d0b27..336d2402 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,6 +26,9 @@ console_scripts =
     bandit = bandit.cli.main:main
     bandit-config-generator = bandit.cli.config_generator:main
     bandit-baseline = bandit.cli.baseline:main
+bandit.blacklists =
+    calls = bandit.blacklists.calls:gen_blacklist
+    imports = bandit.blacklists.imports:gen_blacklist
 bandit.formatters =
     csv = bandit.formatters.csv:report
     json = bandit.formatters.json:report
@@ -40,13 +43,6 @@ bandit.plugins =
     # bandit/plugins/asserts.py
     assert_used = bandit.plugins.asserts:assert_used
 
-    # bandit/plugins/blacklist_calls.py
-    blacklist_calls = bandit.plugins.blacklist_calls:blacklist_calls
-
-    # bandit/plugins/blacklist_imports.py
-    blacklist_imports = bandit.plugins.blacklist_imports:blacklist_imports
-    blacklist_import_func = bandit.plugins.blacklist_imports:blacklist_import_func
-
     # bandit/plugins/crypto_request_no_cert_validation.py
     request_with_no_cert_validation = bandit.plugins.crypto_request_no_cert_validation:request_with_no_cert_validation
 
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index f6e6961b..2ee534de 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -116,12 +116,14 @@ class FunctionalTests(testtools.TestCase):
 
     def test_crypto_md5(self):
         '''Test the `hashlib.md5` example.'''
-        expect = {'SEVERITY': {'MEDIUM': 8}, 'CONFIDENCE': {'HIGH': 8}}
+        expect = {'SEVERITY': {'MEDIUM': 8, 'LOW': 3},
+                  'CONFIDENCE': {'HIGH': 11}}
         self.check_example('crypto-md5.py', expect)
 
     def test_ciphers(self):
         '''Test the `Crypto.Cipher` example.'''
-        expect = {'SEVERITY': {'LOW': 1, 'HIGH': 8}, 'CONFIDENCE': {'HIGH': 9}}
+        expect = {'SEVERITY': {'LOW': 8, 'HIGH': 8},
+                  'CONFIDENCE': {'HIGH': 16}}
         self.check_example('ciphers.py', expect)
 
     def test_cipher_modes(self):
@@ -384,8 +386,8 @@ class FunctionalTests(testtools.TestCase):
                   'CONFIDENCE': {'HIGH': 2, 'MEDIUM': 2}}
         self.check_example('xml_minidom.py', expect)
 
-        expect = {'SEVERITY': {'LOW': 1, 'HIGH': 6},
-                  'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 6}}
+        expect = {'SEVERITY': {'LOW': 2, 'HIGH': 6},
+                  'CONFIDENCE': {'HIGH': 2, 'MEDIUM': 6}}
         self.check_example('xml_sax.py', expect)
 
     def test_asserts(self):
@@ -436,8 +438,8 @@ class FunctionalTests(testtools.TestCase):
     def test_weak_cryptographic_key(self):
         '''Test for weak key sizes.'''
         expect = {
-            'SEVERITY': {'MEDIUM': 5, 'HIGH': 4},
-            'CONFIDENCE': {'HIGH': 9}
+            'SEVERITY': {'LOW': 2, 'MEDIUM': 5, 'HIGH': 4},
+            'CONFIDENCE': {'HIGH': 11}
         }
         self.check_example('weak_cryptographic_key_sizes.py', expect)
 
diff --git a/tests/functional/test_runtime.py b/tests/functional/test_runtime.py
index 57b45b7f..c545ecec 100644
--- a/tests/functional/test_runtime.py
+++ b/tests/functional/test_runtime.py
@@ -96,7 +96,7 @@ class RuntimeTests(testtools.TestCase):
         self.assertIn("Low: 2", output)
         self.assertIn("High: 2", output)
         self.assertIn("Files skipped (0):", output)
-        self.assertIn("Issue: [B401:blacklist_imports] Consider possible",
+        self.assertIn("Issue: [B403:blacklist] Consider possible",
                       output)
         self.assertIn("imports.py:2", output)
         self.assertIn("imports.py:4", output)
diff --git a/tests/unit/core/test_manager.py b/tests/unit/core/test_manager.py
index 2ba9cb69..dbe0fc95 100644
--- a/tests/unit/core/test_manager.py
+++ b/tests/unit/core/test_manager.py
@@ -131,7 +131,6 @@ class ManagerTests(testtools.TestCase):
         self.assertEqual(m.debug, False)
         self.assertEqual(m.verbose, False)
         self.assertEqual(m.agg_type, 'file')
-        self.assertFalse(m.has_tests)
 
     def test_matches_globlist(self):
         self.assertTrue(manager._matches_glob_list('test', ['*tes*']))