Blob Blame History Raw
From ac2145ab7d52b2e7676b8cdfdafd4a3e13c5ec35 Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Thu, 2 Mar 2017 05:10:13 +0000
Subject: [PATCH 15/43] Updated from global requirements

Change-Id: Ia0dcd4aa507f4babc64b503419bc5198ed6064eb
---
 requirements.txt      | 2 +-
 test-requirements.txt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index 0030220..af36648 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,4 @@
 GitPython>=1.0.1 # BSD License (3 clause)
 PyYAML>=3.10.0 # MIT
 six>=1.9.0 # MIT
-stevedore>=1.17.1 # Apache-2.0
+stevedore>=1.20.0 # Apache-2.0
diff --git a/test-requirements.txt b/test-requirements.txt
index 9e613c6..9e1a700 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,7 +3,7 @@
 # process, which may cause wedges in the gate later.
 coverage>=4.0 # Apache-2.0
 fixtures>=3.0.0 # Apache-2.0/BSD
-hacking<0.10,>=0.9.2
+hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0
 mock>=2.0 # BSD
 python-subunit>=0.0.18 # Apache-2.0/BSD
 testrepository>=0.0.18 # Apache-2.0/BSD
-- 
2.13.6

From fc44e0e31aacc76cc9a6b95c607e6d0be9ffcb02 Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Thu, 2 Mar 2017 11:42:52 +0000
Subject: [PATCH 16/43] Updated from global requirements

Change-Id: I85156d90b94995ee81bc75e0c78a8a1804dd7a0f
---
 setup.py              | 2 +-
 test-requirements.txt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/setup.py b/setup.py
index 782bb21..566d844 100644
--- a/setup.py
+++ b/setup.py
@@ -25,5 +25,5 @@ except ImportError:
     pass
 
 setuptools.setup(
-    setup_requires=['pbr>=1.8'],
+    setup_requires=['pbr>=2.0.0'],
     pbr=True)
diff --git a/test-requirements.txt b/test-requirements.txt
index 9e1a700..f0b3542 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,7 +3,7 @@
 # process, which may cause wedges in the gate later.
 coverage>=4.0 # Apache-2.0
 fixtures>=3.0.0 # Apache-2.0/BSD
-hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0
+hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
 mock>=2.0 # BSD
 python-subunit>=0.0.18 # Apache-2.0/BSD
 testrepository>=0.0.18 # Apache-2.0/BSD
-- 
2.13.6

From c2af2c8f5a4f109bec98d2e0d6f2e21d45180336 Mon Sep 17 00:00:00 2001
From: Jeremy Liu <liujiong@gohighsec.com>
Date: Mon, 6 Mar 2017 17:41:14 +0800
Subject: [PATCH 17/43] Enable coverage report in console output

This will output coverage rate of every module in console.

Change-Id: Iffa984bd404d7f197786029d5f50ee3b0a2e3e49
---
 tox.ini | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/tox.ini b/tox.ini
index 1ac373f..3d5f89d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -50,7 +50,9 @@ commands = bandit-baseline -r bandit -ll -ii
 [testenv:cover]
 deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
-commands = python setup.py testr --coverage --testr-args='{posargs}'
+commands =
+    python setup.py testr --coverage --testr-args='{posargs}'
+    coverage report
 
 [testenv:openstack_coverage]
 deps = PyYAML>=3.1.0
-- 
2.13.6

From 085c789490575c5a98154957b0bddf9061dcc765 Mon Sep 17 00:00:00 2001
From: Eric Brown <browne@vmware.com>
Date: Thu, 9 Mar 2017 14:33:19 -0800
Subject: [PATCH 18/43] Correct the yaml doc example to be actually yaml

The current doc for the yaml formatter shows an example of CSV
output, not yaml.

Change-Id: I75b01cab5455559738d89b0803eb64261c445967
---
 bandit/formatters/yaml.py | 44 +++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 39 insertions(+), 5 deletions(-)

diff --git a/bandit/formatters/yaml.py b/bandit/formatters/yaml.py
index 69aeedb..2f26b20 100644
--- a/bandit/formatters/yaml.py
+++ b/bandit/formatters/yaml.py
@@ -23,11 +23,45 @@ This formatter outputs the issues in a yaml format.
 
 .. code-block:: none
 
-    filename,test_name,test_id,issue_severity,issue_confidence,issue_text,
-    line_number,line_range
-    examples/yaml_load.py,blacklist_calls,B301,MEDIUM,HIGH,"Use of unsafe yaml
-    load. Allows instantiation of arbitrary objects. Consider yaml.safe_load().
-    ",5,[5]
+    errors: []
+    generated_at: '2017-03-09T22:29:30Z'
+    metrics:
+      _totals:
+        CONFIDENCE.HIGH: 1
+        CONFIDENCE.LOW: 0
+        CONFIDENCE.MEDIUM: 0
+        CONFIDENCE.UNDEFINED: 0
+        SEVERITY.HIGH: 0
+        SEVERITY.LOW: 0
+        SEVERITY.MEDIUM: 1
+        SEVERITY.UNDEFINED: 0
+        loc: 9
+        nosec: 0
+      examples/yaml_load.py:
+        CONFIDENCE.HIGH: 1
+        CONFIDENCE.LOW: 0
+        CONFIDENCE.MEDIUM: 0
+        CONFIDENCE.UNDEFINED: 0
+        SEVERITY.HIGH: 0
+        SEVERITY.LOW: 0
+        SEVERITY.MEDIUM: 1
+        SEVERITY.UNDEFINED: 0
+        loc: 9
+        nosec: 0
+    results:
+    - code: '5     ystr = yaml.dump({''a'' : 1, ''b'' : 2, ''c'' : 3})\n
+             6     y = yaml.load(ystr)\n7     yaml.dump(y)\n'
+      filename: examples/yaml_load.py
+      issue_confidence: HIGH
+      issue_severity: MEDIUM
+      issue_text: Use of unsafe yaml load. Allows instantiation of arbitrary
+                  objects.
+        Consider yaml.safe_load().
+      line_number: 6
+      line_range:
+      - 6
+      test_id: B506
+      test_name: yaml_load
 
 .. versionadded:: 1.4.1
 
-- 
2.13.6

From e40af23ff67fc89f024c5a73f93c8d9a0b43caa3 Mon Sep 17 00:00:00 2001
From: Eric Brown <browne@vmware.com>
Date: Mon, 20 Feb 2017 12:06:19 -0800
Subject: [PATCH 19/43] Blacklist call of ssl._create_unverified_context

The ssl._create_unverified_context creates a context for use with
such classes as HTTPSConnection which will do no certificate or
hostname verification. This should be flagged.

Change-Id: I326316e20ee11034c0a794f41c1bd8ae75720142
---
 README.rst                          |  1 +
 bandit/blacklists/calls.py          | 23 +++++++++++++++++++++++
 examples/unverified_context.py      |  7 +++++++
 tests/functional/test_functional.py |  8 ++++++++
 4 files changed, 39 insertions(+)
 create mode 100644 examples/unverified_context.py

diff --git a/README.rst b/README.rst
index b7f4ba9..d3e4ba0 100644
--- a/README.rst
+++ b/README.rst
@@ -173,6 +173,7 @@ Usage::
       B320  xml_bad_etree
       B321  ftplib
       B322  input
+      B323  unverified_context
       B401  import_telnetlib
       B402  import_ftplib
       B403  import_pickle
diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py
index 47858ca..075bdce 100644
--- a/bandit/blacklists/calls.py
+++ b/bandit/blacklists/calls.py
@@ -278,6 +278,20 @@ is safe in Python 3.
 | B322 | input               | - input                            | High      |
 +------+---------------------+------------------------------------+-----------+
 
+B323: unverified_context
+------------------------
+
+By default, Python will create a secure, verified ssl context for use in such
+classes as HTTPSConnection. However, it still allows using an insecure
+context via the _create_unverified_context that reverts to the previous
+behavior that does not validate certificates or perform hostname checks.
+
++------+---------------------+------------------------------------+-----------+
+| ID   |  Name               |  Calls                             |  Severity |
++======+=====================+====================================+===========+
+| B322 | unverified_context  | - ssl._create_unverified_context   | Medium    |
++------+---------------------+------------------------------------+-----------+
+
 """
 
 from bandit.blacklists import utils
@@ -509,4 +523,13 @@ def gen_blacklist():
         'HIGH'
         ))
 
+    sets.append(utils.build_conf_dict(
+        'unverified_context', 'B323', ['ssl._create_unverified_context'],
+        'By default, Python will create a secure, verified ssl context for '
+        'use in such classes as HTTPSConnection. However, it still allows '
+        'using an insecure context via the _create_unverified_context that '
+        'reverts to the previous behavior that does not validate certificates '
+        'or perform hostname checks.'
+        ))
+
     return {'Call': sets}
diff --git a/examples/unverified_context.py b/examples/unverified_context.py
new file mode 100644
index 0000000..0f45439
--- /dev/null
+++ b/examples/unverified_context.py
@@ -0,0 +1,7 @@
+import ssl
+
+# Correct
+context = ssl.create_default_context()
+
+# Incorrect: unverified context
+context = ssl._create_unverified_context()
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index 5667001..4522481 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -689,3 +689,11 @@ class FunctionalTests(testtools.TestCase):
             'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
         }
         self.check_example('input.py', expect)
+
+    def test_unverified_context(self):
+        '''Test for `ssl._create_unverified_context`.'''
+        expect = {
+            'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0},
+            'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
+        }
+        self.check_example('unverified_context.py', expect)
-- 
2.13.6

From 693f57faaecbaeab338e173adbecc8fd566fcc79 Mon Sep 17 00:00:00 2001
From: M V P Nitesh <m.nitesh@nectechnologies.in>
Date: Tue, 4 Apr 2017 15:31:33 +0530
Subject: [PATCH 20/43] Replace six.iteritems() with .items()

1.As mentioned in [1], we should avoid using six.iteritems to achieve
iterators. We can use dict.items instead, as it will return iterators
in PY3 as well. And dict.items/keys will more readable.
2.In py2, the performance about list should be negligible, see the
link [2].
[1] https://wiki.openstack.org/wiki/Python3
[2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html

Change-Id: I5340fa3d71b6fba76e8fcf75f9f30432329023d2
---
 bandit/cli/config_generator.py    | 5 ++---
 bandit/cli/main.py                | 5 ++---
 bandit/core/config.py             | 9 ++++-----
 bandit/core/test_set.py           | 5 ++---
 tests/unit/formatters/test_xml.py | 7 +++----
 5 files changed, 13 insertions(+), 18 deletions(-)

diff --git a/bandit/cli/config_generator.py b/bandit/cli/config_generator.py
index a48c518..5d532d0 100644
--- a/bandit/cli/config_generator.py
+++ b/bandit/cli/config_generator.py
@@ -19,7 +19,6 @@ import logging
 import os
 import sys
 
-import six
 import yaml
 
 from bandit.core import extension_loader
@@ -158,8 +157,8 @@ def main():
                 test_list = [tpl.format(t.plugin._test_id, t.name)
                              for t in extension_loader.MANAGER.plugins]
 
-                others = [tpl.format(k, v['name']) for k, v in six.iteritems(
-                    extension_loader.MANAGER.blacklist_by_id)]
+                others = [tpl.format(k, v['name']) for k, v in (
+                    extension_loader.MANAGER.blacklist_by_id.items())]
                 test_list.extend(others)
                 test_list.sort()
 
diff --git a/bandit/cli/main.py b/bandit/cli/main.py
index 8bb61c1..423e95c 100644
--- a/bandit/cli/main.py
+++ b/bandit/cli/main.py
@@ -19,7 +19,6 @@ import logging
 import os
 import sys
 
-import six
 
 import bandit
 from bandit.core import config as b_config
@@ -247,9 +246,9 @@ def main():
     parser.set_defaults(ignore_nosec=False)
 
     plugin_info = ["%s\t%s" % (a[0], a[1].name) for a in
-                   six.iteritems(extension_mgr.plugins_by_id)]
+                   extension_mgr.plugins_by_id.items()]
     blacklist_info = []
-    for a in six.iteritems(extension_mgr.blacklist):
+    for a in extension_mgr.blacklist.items():
         for b in a[1]:
             blacklist_info.append('%s\t%s' % (b['id'], b['name']))
 
diff --git a/bandit/core/config.py b/bandit/core/config.py
index c148933..acde78a 100644
--- a/bandit/core/config.py
+++ b/bandit/core/config.py
@@ -16,7 +16,6 @@
 
 import logging
 
-import six
 import yaml
 
 from bandit.core import constants
@@ -132,7 +131,7 @@ class BanditConfig(object):
         extman = extension_loader.MANAGER
 
         updated_profiles = {}
-        for name, profile in six.iteritems(self.get_option('profiles') or {}):
+        for name, profile in (self.get_option('profiles') or {}).items():
             # NOTE(tkelsey): can't use default of get() because value is
             # sometimes explicity 'None', for example when the list if given in
             # yaml but not populated with any values.
@@ -151,7 +150,7 @@ class BanditConfig(object):
         bad_calls = self.get_option('blacklist_calls') or {}
         bad_calls = bad_calls.get('bad_name_sets', {})
         for item in bad_calls:
-            for key, val in six.iteritems(item):
+            for key, val in item.items():
                 val['name'] = key
                 val['message'] = val['message'].replace('{func}', '{name}')
                 bad_calls_list.append(val)
@@ -159,7 +158,7 @@ class BanditConfig(object):
         bad_imports = self.get_option('blacklist_imports') or {}
         bad_imports = bad_imports.get('bad_import_sets', {})
         for item in bad_imports:
-            for key, val in six.iteritems(item):
+            for key, val in item.items():
                 val['name'] = key
                 val['message'] = val['message'].replace('{module}', '{name}')
                 val['qualnames'] = val['imports']
@@ -179,7 +178,7 @@ class BanditConfig(object):
                 data.remove(name)
                 data.add('B001')
 
-        for name, profile in six.iteritems(profiles):
+        for name, profile in profiles.items():
             blacklist = {}
             include = profile['include']
             exclude = profile['exclude']
diff --git a/bandit/core/test_set.py b/bandit/core/test_set.py
index 2429aeb..b2213e8 100644
--- a/bandit/core/test_set.py
+++ b/bandit/core/test_set.py
@@ -18,7 +18,6 @@
 import importlib
 import logging
 
-import six
 
 from bandit.core import blacklisting
 from bandit.core import extension_loader
@@ -46,7 +45,7 @@ class BanditTestSet(object):
         exc = set(profile.get('exclude', []))
 
         all_blacklist_tests = set()
-        for _node, tests in six.iteritems(extman.blacklist):
+        for _node, tests in extman.blacklist.items():
             all_blacklist_tests.update(t['id'] for t in tests)
 
         # this block is purely for backwards compatibility, the rules are as
@@ -83,7 +82,7 @@ class BanditTestSet(object):
         blacklist = profile.get('blacklist')
         if not blacklist:  # not overridden by legacy data
             blacklist = {}
-            for node, tests in six.iteritems(extman.blacklist):
+            for node, tests in extman.blacklist.items():
                 values = [t for t in tests if t['id'] in filtering]
                 if values:
                     blacklist[node] = values
diff --git a/tests/unit/formatters/test_xml.py b/tests/unit/formatters/test_xml.py
index a16319c..39ed009 100644
--- a/tests/unit/formatters/test_xml.py
+++ b/tests/unit/formatters/test_xml.py
@@ -16,7 +16,6 @@ import collections
 import tempfile
 from xml.etree import cElementTree as ET
 
-import six
 import testtools
 
 import bandit
@@ -54,12 +53,12 @@ class XmlFormatterTests(testtools.TestCase):
         if children:
             dd = collections.defaultdict(list)
             for dc in map(self._xml_to_dict, children):
-                for k, v in six.iteritems(dc):
+                for k, v in dc.items():
                     dd[k].append(v)
             d = {t.tag: {k: v[0] if len(v) == 1 else v
-                         for k, v in six.iteritems(dd)}}
+                         for k, v in dd.items()}}
         if t.attrib:
-            d[t.tag].update(('@' + k, v) for k, v in six.iteritems(t.attrib))
+            d[t.tag].update(('@' + k, v) for k, v in t.attrib.items())
         if t.text:
             text = t.text.strip()
             if children or t.attrib:
-- 
2.13.6

From 88a7f256d1680ac57984a218be108ebc8a6dbab0 Mon Sep 17 00:00:00 2001
From: loooosy <syluo5695@fiberhome.com>
Date: Sun, 9 Apr 2017 18:00:18 +0800
Subject: [PATCH 21/43] Optimize the link address

Use https instead of http to ensure the safety

Change-Id: I4df36b1f0a2b22fd7c9971b973cf1470400f8a4d
---
 README.rst | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/README.rst b/README.rst
index d3e4ba0..41aede1 100644
--- a/README.rst
+++ b/README.rst
@@ -1,8 +1,8 @@
 Bandit
 ======
 
-.. image:: http://governance.openstack.org/badges/bandit.svg
-    :target: http://governance.openstack.org/reference/tags/index.html
+.. image:: https://governance.openstack.org/badges/bandit.svg
+    :target: https://governance.openstack.org/reference/tags/index.html
     :alt: Bandit team and repository tags
 
 .. image:: https://img.shields.io/pypi/v/bandit.svg
@@ -388,8 +388,8 @@ Bandit wiki: https://wiki.openstack.org/wiki/Security/Projects/Bandit
 Python AST module documentation: https://docs.python.org/2/library/ast.html
 
 Green Tree Snakes - the missing Python AST docs:
-http://greentreesnakes.readthedocs.org/en/latest/
+https://greentreesnakes.readthedocs.org/en/latest/
 
 Documentation of the various types of AST nodes that Bandit currently covers
 or could be extended to cover:
-http://greentreesnakes.readthedocs.org/en/latest/nodes.html
+https://greentreesnakes.readthedocs.org/en/latest/nodes.html
-- 
2.13.6

From 28d5607630622b4085f44e3b9802d8960a48499c Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Mon, 15 May 2017 00:41:53 +0000
Subject: [PATCH 22/43] Updated from global requirements

Change-Id: I9c87d81fcd2a3612121b693051f2b2eb4f7d2384
---
 test-requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test-requirements.txt b/test-requirements.txt
index f0b3542..a1f94ef 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,7 @@
 # The order of packages is significant, because pip processes them in the order
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
-coverage>=4.0 # Apache-2.0
+coverage!=4.4,>=4.0 # Apache-2.0
 fixtures>=3.0.0 # Apache-2.0/BSD
 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
 mock>=2.0 # BSD
-- 
2.13.6

From 8783d47cb100cecc5ad634201d4aace40b16425b Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Wed, 17 May 2017 03:46:04 +0000
Subject: [PATCH 23/43] Updated from global requirements

Change-Id: I9b953a312a525ac693092e2e6a02f00eec64db4d
---
 test-requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test-requirements.txt b/test-requirements.txt
index a1f94ef..e87060b 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -11,7 +11,7 @@ testscenarios>=0.4 # Apache-2.0/BSD
 testtools>=1.4.0 # MIT
 oslotest>=1.10.0 # Apache-2.0
 
-sphinx>=1.5.1 # BSD
+sphinx!=1.6.1,>=1.5.1 # BSD
 oslosphinx>=4.7.0 # Apache-2.0
 beautifulsoup4 # MIT
 reno>=1.8.0 # Apache-2.0
-- 
2.13.6

From 02f52a49641eeaa75bd63ae05e179f72dce2d73b Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Fri, 2 Jun 2017 21:53:29 +0000
Subject: [PATCH 24/43] Updated from global requirements

Change-Id: I670c906f634f38b121c9708e85fd6dccb451f484
---
 test-requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test-requirements.txt b/test-requirements.txt
index e87060b..ffb532d 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -14,6 +14,6 @@ oslotest>=1.10.0 # Apache-2.0
 sphinx!=1.6.1,>=1.5.1 # BSD
 oslosphinx>=4.7.0 # Apache-2.0
 beautifulsoup4 # MIT
-reno>=1.8.0 # Apache-2.0
+reno!=2.3.1,>=1.8.0 # Apache-2.0
 
 pylint==1.4.5 # GPLv2
-- 
2.13.6

From f10fd4f5d45a2349071cc3415d12bb60e5d5dd6c Mon Sep 17 00:00:00 2001
From: lioplhp <hpliu5898@fiberhome.com>
Date: Fri, 23 Jun 2017 15:45:02 +0800
Subject: [PATCH 25/43] Enable some off-by-default checks

Some of the available checks are diskabled by default, like:
[H106] Don't put vim configuration in source files;
[H203] Use assertIs(Not)None to check for None.

Change-Id: Ib822b3b4cb9ae1176a8d69bbc0ab45126adc1bab
---
 tox.ini | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tox.ini b/tox.ini
index 3d5f89d..780c7d4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -69,8 +69,11 @@ commands=
     python setup.py build_sphinx
 
 [flake8]
+# [H106] Don't put vim configuration in source files.
+# [H203] Use assertIs(Not)None to check for None.
 show-source = True
 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,releasenotes
+enable-extensions = H106,H203
 
 [testenv:releasenotes]
 commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
-- 
2.13.6

From d59d37d51f495ba0909b6895e531c78719d9c45d Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Tue, 27 Jun 2017 12:08:06 +0000
Subject: [PATCH 26/43] Updated from global requirements

Change-Id: Ief6d4ed8ca25eb201de73df4c5ccdc67ffdffa0c
---
 test-requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test-requirements.txt b/test-requirements.txt
index ffb532d..1b7e3f0 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -11,7 +11,7 @@ testscenarios>=0.4 # Apache-2.0/BSD
 testtools>=1.4.0 # MIT
 oslotest>=1.10.0 # Apache-2.0
 
-sphinx!=1.6.1,>=1.5.1 # BSD
+sphinx>=1.6.2 # BSD
 oslosphinx>=4.7.0 # Apache-2.0
 beautifulsoup4 # MIT
 reno!=2.3.1,>=1.8.0 # Apache-2.0
-- 
2.13.6

From 715f2fd5370b45e69563b758ebb8194a85d0926a Mon Sep 17 00:00:00 2001
From: lioplhp <hpliu5898@fiberhome.com>
Date: Fri, 7 Jul 2017 16:37:52 +0800
Subject: [PATCH 27/43] Add Apache License Content in index.rst

Add Apache License 2.0 Content which is necessary
for ./releasenotes/source/index.rst.

Change-Id: I93055a0e9d2a39c8b70ed020c5c772137db38055
---
 releasenotes/source/index.rst | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index b1cf4ff..7b443aa 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -1,3 +1,16 @@
+..
+      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
+
+          https://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.
+
 ====================
 Bandit Release Notes
 ====================
-- 
2.13.6

From d46ac805efc13835f64343060696f23431cce0a2 Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Fri, 14 Jul 2017 04:56:23 +0000
Subject: [PATCH 28/43] Updated from global requirements

Change-Id: I7f9863699b97514957152cf727b5af74399dc701
---
 test-requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test-requirements.txt b/test-requirements.txt
index 1b7e3f0..7a4378b 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -13,7 +13,7 @@ oslotest>=1.10.0 # Apache-2.0
 
 sphinx>=1.6.2 # BSD
 oslosphinx>=4.7.0 # Apache-2.0
-beautifulsoup4 # MIT
+beautifulsoup4>=4.6.0 # MIT
 reno!=2.3.1,>=1.8.0 # Apache-2.0
 
 pylint==1.4.5 # GPLv2
-- 
2.13.6

From 2455f9564046f3fb93e45c4704528d4836879a00 Mon Sep 17 00:00:00 2001
From: sudhir_agarwal <sudhir.agarwal@nectechnologies.in>
Date: Fri, 14 Jul 2017 11:00:31 +0530
Subject: [PATCH 29/43] Fixed order of arguments in assertEqual

Some tests used incorrect order of arguments in
assertEqual(observed, expected). The correct order expected
by testtool is assertEqual(expected, observed).

Change-Id: I64138c2b08c44a970e7fdd96a634e8a0acd2bfa4
---
 tests/unit/core/test_manager.py  | 32 ++++++++++++------------
 tests/unit/core/test_test_set.py | 54 ++++++++++++++++++++--------------------
 2 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/tests/unit/core/test_manager.py b/tests/unit/core/test_manager.py
index 4097194..161b55c 100644
--- a/tests/unit/core/test_manager.py
+++ b/tests/unit/core/test_manager.py
@@ -51,9 +51,9 @@ class ManagerTests(testtools.TestCase):
 
     def test_create_manager(self):
         # make sure we can create a manager
-        self.assertEqual(self.manager.debug, False)
-        self.assertEqual(self.manager.verbose, False)
-        self.assertEqual(self.manager.agg_type, 'file')
+        self.assertEqual(False, self.manager.debug)
+        self.assertEqual(False, self.manager.verbose)
+        self.assertEqual('file', self.manager.agg_type)
 
     def test_create_manager_with_profile(self):
         # make sure we can create a manager
@@ -61,9 +61,9 @@ class ManagerTests(testtools.TestCase):
                                   debug=False, verbose=False,
                                   profile=self.profile)
 
-        self.assertEqual(m.debug, False)
-        self.assertEqual(m.verbose, False)
-        self.assertEqual(m.agg_type, 'file')
+        self.assertEqual(False, m.debug)
+        self.assertEqual(False, m.verbose)
+        self.assertEqual('file', m.agg_type)
 
     def test_matches_globlist(self):
         self.assertTrue(manager._matches_glob_list('test', ['*tes*']))
@@ -101,8 +101,8 @@ class ManagerTests(testtools.TestCase):
                                                included_globs=['*.py'],
                                                excluded_path_strings=None)
 
-        self.assertEqual(exc, set(['/a/c.ww']))
-        self.assertEqual(inc, set(['/a/a.py', '/a/b.py']))
+        self.assertEqual(set(['/a/c.ww']), exc)
+        self.assertEqual(set(['/a/a.py', '/a/b.py']), inc)
 
     def test_populate_baseline_success(self):
         # Test populate_baseline with valid JSON
@@ -183,8 +183,8 @@ class ManagerTests(testtools.TestCase):
     def test_discover_files_recurse_skip(self, isdir):
         isdir.return_value = True
         self.manager.discover_files(['thing'], False)
-        self.assertEqual(self.manager.files_list, [])
-        self.assertEqual(self.manager.excluded_files, [])
+        self.assertEqual([], self.manager.files_list)
+        self.assertEqual([], self.manager.excluded_files)
 
     @mock.patch('os.path.isdir')
     def test_discover_files_recurse_files(self, isdir):
@@ -192,8 +192,8 @@ class ManagerTests(testtools.TestCase):
         with mock.patch.object(manager, '_get_files_from_dir') as m:
             m.return_value = (set(['files']), set(['excluded']))
             self.manager.discover_files(['thing'], True)
-            self.assertEqual(self.manager.files_list, ['files'])
-            self.assertEqual(self.manager.excluded_files, ['excluded'])
+            self.assertEqual(['files'], self.manager.files_list)
+            self.assertEqual(['excluded'], self.manager.excluded_files)
 
     @mock.patch('os.path.isdir')
     def test_discover_files_exclude(self, isdir):
@@ -201,8 +201,8 @@ class ManagerTests(testtools.TestCase):
         with mock.patch.object(manager, '_is_file_included') as m:
             m.return_value = False
             self.manager.discover_files(['thing'], True)
-            self.assertEqual(self.manager.files_list, [])
-            self.assertEqual(self.manager.excluded_files, ['thing'])
+            self.assertEqual([], self.manager.files_list)
+            self.assertEqual(['thing'], self.manager.excluded_files)
 
     @mock.patch('os.path.isdir')
     def test_discover_files_exclude_cmdline(self, isdir):
@@ -219,8 +219,8 @@ class ManagerTests(testtools.TestCase):
         with mock.patch.object(manager, '_is_file_included') as m:
             m.return_value = True
             self.manager.discover_files(['thing'], True)
-            self.assertEqual(self.manager.files_list, ['thing'])
-            self.assertEqual(self.manager.excluded_files, [])
+            self.assertEqual(['thing'], self.manager.files_list)
+            self.assertEqual([], self.manager.excluded_files)
 
     def test_run_tests_keyboardinterrupt(self):
         # Test that bandit manager exits when there is a keyboard interrupt
diff --git a/tests/unit/core/test_test_set.py b/tests/unit/core/test_test_set.py
index b0001e6..a7ee712 100644
--- a/tests/unit/core/test_test_set.py
+++ b/tests/unit/core/test_test_set.py
@@ -66,73 +66,73 @@ class BanditTestSetTests(testtools.TestCase):
 
     def test_has_defaults(self):
         ts = test_set.BanditTestSet(self.config)
-        self.assertEqual(len(ts.get_tests('Str')), 1)
+        self.assertEqual(1, len(ts.get_tests('Str')))
 
     def test_profile_include_id(self):
         profile = {'include': ['B000']}
         ts = test_set.BanditTestSet(self.config, profile)
-        self.assertEqual(len(ts.get_tests('Str')), 1)
+        self.assertEqual(1, len(ts.get_tests('Str')))
 
     def test_profile_exclude_id(self):
         profile = {'exclude': ['B000']}
         ts = test_set.BanditTestSet(self.config, profile)
-        self.assertEqual(len(ts.get_tests('Str')), 0)
+        self.assertEqual(0, len(ts.get_tests('Str')))
 
     def test_profile_include_none(self):
         profile = {'include': []}  # same as no include
         ts = test_set.BanditTestSet(self.config, profile)
-        self.assertEqual(len(ts.get_tests('Str')), 1)
+        self.assertEqual(1, len(ts.get_tests('Str')))
 
     def test_profile_exclude_none(self):
         profile = {'exclude': []}  # same as no exclude
         ts = test_set.BanditTestSet(self.config, profile)
-        self.assertEqual(len(ts.get_tests('Str')), 1)
+        self.assertEqual(1, len(ts.get_tests('Str')))
 
     def test_profile_has_builtin_blacklist(self):
         ts = test_set.BanditTestSet(self.config)
-        self.assertEqual(len(ts.get_tests('Import')), 1)
-        self.assertEqual(len(ts.get_tests('ImportFrom')), 1)
-        self.assertEqual(len(ts.get_tests('Call')), 1)
+        self.assertEqual(1, len(ts.get_tests('Import')))
+        self.assertEqual(1, len(ts.get_tests('ImportFrom')))
+        self.assertEqual(1, len(ts.get_tests('Call')))
 
     def test_profile_exclude_builtin_blacklist(self):
         profile = {'exclude': ['B001']}
         ts = test_set.BanditTestSet(self.config, profile)
-        self.assertEqual(len(ts.get_tests('Import')), 0)
-        self.assertEqual(len(ts.get_tests('ImportFrom')), 0)
-        self.assertEqual(len(ts.get_tests('Call')), 0)
+        self.assertEqual(0, len(ts.get_tests('Import')))
+        self.assertEqual(0, len(ts.get_tests('ImportFrom')))
+        self.assertEqual(0, len(ts.get_tests('Call')))
 
     def test_profile_exclude_builtin_blacklist_specific(self):
         profile = {'exclude': ['B302', 'B401']}
         ts = test_set.BanditTestSet(self.config, profile)
-        self.assertEqual(len(ts.get_tests('Import')), 0)
-        self.assertEqual(len(ts.get_tests('ImportFrom')), 0)
-        self.assertEqual(len(ts.get_tests('Call')), 0)
+        self.assertEqual(0, len(ts.get_tests('Import')))
+        self.assertEqual(0, len(ts.get_tests('ImportFrom')))
+        self.assertEqual(0, len(ts.get_tests('Call')))
 
     def test_profile_filter_blacklist_none(self):
         ts = test_set.BanditTestSet(self.config)
         blacklist = ts.get_tests('Import')[0]
 
-        self.assertEqual(len(blacklist._config['Import']), 2)
-        self.assertEqual(len(blacklist._config['ImportFrom']), 2)
-        self.assertEqual(len(blacklist._config['Call']), 2)
+        self.assertEqual(2, len(blacklist._config['Import']))
+        self.assertEqual(2, len(blacklist._config['ImportFrom']))
+        self.assertEqual(2, len(blacklist._config['Call']))
 
     def test_profile_filter_blacklist_one(self):
         profile = {'exclude': ['B401']}
         ts = test_set.BanditTestSet(self.config, profile)
         blacklist = ts.get_tests('Import')[0]
 
-        self.assertEqual(len(blacklist._config['Import']), 1)
-        self.assertEqual(len(blacklist._config['ImportFrom']), 1)
-        self.assertEqual(len(blacklist._config['Call']), 1)
+        self.assertEqual(1, len(blacklist._config['Import']))
+        self.assertEqual(1, len(blacklist._config['ImportFrom']))
+        self.assertEqual(1, len(blacklist._config['Call']))
 
     def test_profile_filter_blacklist_include(self):
         profile = {'include': ['B001', 'B401']}
         ts = test_set.BanditTestSet(self.config, profile)
         blacklist = ts.get_tests('Import')[0]
 
-        self.assertEqual(len(blacklist._config['Import']), 1)
-        self.assertEqual(len(blacklist._config['ImportFrom']), 1)
-        self.assertEqual(len(blacklist._config['Call']), 1)
+        self.assertEqual(1, len(blacklist._config['Import']))
+        self.assertEqual(1, len(blacklist._config['ImportFrom']))
+        self.assertEqual(1, len(blacklist._config['Call']))
 
     def test_profile_filter_blacklist_all(self):
         profile = {'exclude': ['B401', 'B302']}
@@ -140,9 +140,9 @@ class BanditTestSetTests(testtools.TestCase):
 
         # if there is no blacklist data for a node type then we wont add a
         # blacklist test to it, as this would be pointless.
-        self.assertEqual(len(ts.get_tests('Import')), 0)
-        self.assertEqual(len(ts.get_tests('ImportFrom')), 0)
-        self.assertEqual(len(ts.get_tests('Call')), 0)
+        self.assertEqual(0, len(ts.get_tests('Import')))
+        self.assertEqual(0, len(ts.get_tests('ImportFrom')))
+        self.assertEqual(0, len(ts.get_tests('Call')))
 
     def test_profile_blacklist_compat(self):
         data = [utils.build_conf_dict(
@@ -157,4 +157,4 @@ class BanditTestSetTests(testtools.TestCase):
 
         self.assertNotIn('Import', blacklist._config)
         self.assertNotIn('ImportFrom', blacklist._config)
-        self.assertEqual(len(blacklist._config['Call']), 1)
+        self.assertEqual(1, len(blacklist._config['Call']))
-- 
2.13.6

From 8f1b50b5cce2ea241dbee334c5f58234b8656849 Mon Sep 17 00:00:00 2001
From: Rajath Agasthya <rajathagasthya@gmail.com>
Date: Sat, 29 Jul 2017 01:33:26 -0700
Subject: [PATCH 30/43] Do not flag new way of escaping in jinja2 plugin

Makes escaping using select_autoescape function valid by checking
for ast.Call instance and if func id == select_autoescape.

Example:

from jinja2 import Environment, select_autoescape
env = Environment(autoescape=select_autoescape(['html', 'htm', 'xml']),
                    loader=PackageLoader('mypackage'))

Change-Id: I47c6b346332a6d9f7c4c57dd45ab7636c78996a1
Closes-Bug: #1684249
---
 bandit/plugins/jinja2_templates.py  | 25 ++++++++++++++++++-------
 examples/jinja2_templating.py       | 12 +++++++++++-
 tests/functional/test_functional.py |  4 ++--
 3 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/bandit/plugins/jinja2_templates.py b/bandit/plugins/jinja2_templates.py
index fc3a2bc..2adc77a 100644
--- a/bandit/plugins/jinja2_templates.py
+++ b/bandit/plugins/jinja2_templates.py
@@ -47,13 +47,16 @@ false. A HIGH severity warning is generated in either of these scenarios.
     14
 
     >> Issue: By default, jinja2 sets autoescape to False. Consider using
-    autoescape=True to mitigate XSS vulnerabilities.
+    autoescape=True or use the select_autoescape function to mitigate XSS
+    vulnerabilities.
        Severity: High   Confidence: High
        Location: ./examples/jinja2_templating.py:15
     14
     15  Environment(loader=templateLoader,
     16              load=templateLoader)
     17
+    18  Environment(autoescape=select_autoescape(['html', 'htm', 'xml']),
+    19              loader=templateLoader)
 
 
 .. seealso::
@@ -93,13 +96,19 @@ def jinja2_autoescape_false(context):
                             confidence=bandit.HIGH,
                             text="Using jinja2 templates with autoescape="
                                  "False is dangerous and can lead to XSS. "
-                                 "Use autoescape=True to mitigate XSS "
+                                 "Use autoescape=True or use the "
+                                 "select_autoescape function to mitigate XSS "
                                  "vulnerabilities."
                         )
                     # found autoescape
                     if getattr(node, 'arg', None) == 'autoescape':
-                        if (getattr(node.value, 'id', None) == 'True' or
-                                getattr(node.value, 'value', None) is True):
+                        value = getattr(node, 'value', None)
+                        if (getattr(value, 'id', None) == 'True' or
+                                getattr(value, 'value', None) is True):
+                            return
+                        # Check if select_autoescape function is used.
+                        elif isinstance(value, ast.Call) and getattr(
+                                value.func, 'id', None) == 'select_autoescape':
                             return
                         else:
                             return bandit.Issue(
@@ -107,8 +116,9 @@ def jinja2_autoescape_false(context):
                                 confidence=bandit.MEDIUM,
                                 text="Using jinja2 templates with autoescape="
                                      "False is dangerous and can lead to XSS. "
-                                     "Ensure autoescape=True to mitigate XSS "
-                                     "vulnerabilities."
+                                     "Ensure autoescape=True or use the "
+                                     "select_autoescape function to mitigate "
+                                     "XSS vulnerabilities."
                             )
             # We haven't found a keyword named autoescape, indicating default
             # behavior
@@ -116,5 +126,6 @@ def jinja2_autoescape_false(context):
                 severity=bandit.HIGH,
                 confidence=bandit.HIGH,
                 text="By default, jinja2 sets autoescape to False. Consider "
-                     "using autoescape=True to mitigate XSS vulnerabilities."
+                     "using autoescape=True or use the select_autoescape "
+                     "function to mitigate XSS vulnerabilities."
             )
diff --git a/examples/jinja2_templating.py b/examples/jinja2_templating.py
index d79617f..d5aaa2d 100644
--- a/examples/jinja2_templating.py
+++ b/examples/jinja2_templating.py
@@ -1,5 +1,5 @@
 import jinja2
-from jinja2 import Environment
+from jinja2 import Environment, select_autoescape
 templateLoader = jinja2.FileSystemLoader( searchpath="/" )
 something = ''
 
@@ -14,3 +14,13 @@ Environment(loader=templateLoader,
 
 Environment(loader=templateLoader,
             load=templateLoader)
+
+Environment(loader=templateLoader, autoescape=select_autoescape())
+
+Environment(loader=templateLoader,
+            autoescape=select_autoescape(['html', 'htm', 'xml']))
+
+
+def fake_func():
+    return 'foobar'
+Environment(loader=templateLoader, autoescape=fake_func())
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index 4522481..5ea8220 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -441,8 +441,8 @@ class FunctionalTests(testtools.TestCase):
     def test_jinja2_templating(self):
         '''Test jinja templating for potential XSS bugs.'''
         expect = {
-            'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4},
-            'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 3}
+            'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5},
+            'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 3}
         }
         self.check_example('jinja2_templating.py', expect)
 
-- 
2.13.6

From d54a65f0f018754a156cc4d105300629d9f3259a Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Fri, 18 Aug 2017 11:28:07 +0000
Subject: [PATCH 31/43] Updated from global requirements

Change-Id: I9f572d41be228b523bc048cf491812016947d5b5
---
 requirements.txt      | 2 +-
 test-requirements.txt | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index af36648..affa923 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,6 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 GitPython>=1.0.1 # BSD License (3 clause)
-PyYAML>=3.10.0 # MIT
+PyYAML>=3.10 # MIT
 six>=1.9.0 # MIT
 stevedore>=1.20.0 # Apache-2.0
diff --git a/test-requirements.txt b/test-requirements.txt
index 7a4378b..b9a8990 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,7 +4,7 @@
 coverage!=4.4,>=4.0 # Apache-2.0
 fixtures>=3.0.0 # Apache-2.0/BSD
 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
-mock>=2.0 # BSD
+mock>=2.0.0 # BSD
 python-subunit>=0.0.18 # Apache-2.0/BSD
 testrepository>=0.0.18 # Apache-2.0/BSD
 testscenarios>=0.4 # Apache-2.0/BSD
@@ -14,6 +14,6 @@ oslotest>=1.10.0 # Apache-2.0
 sphinx>=1.6.2 # BSD
 oslosphinx>=4.7.0 # Apache-2.0
 beautifulsoup4>=4.6.0 # MIT
-reno!=2.3.1,>=1.8.0 # Apache-2.0
+reno>=2.5.0 # Apache-2.0
 
 pylint==1.4.5 # GPLv2
-- 
2.13.6

From 05c52da55980bda5358cc337878e27839d3fc6ba Mon Sep 17 00:00:00 2001
From: lhinds <lhinds@redhat.com>
Date: Thu, 24 Aug 2017 19:32:38 +0100
Subject: [PATCH 32/43] Adds simple handler to provide failed line numbers

Change adds `as err` handler to provide line number, if config yaml
fails to parse.

Example output

[config]  ERROR   while scanning a simple key
  in "config.yaml", line 5, column 1
could not find expected ':'
  in "config.yaml", line 6, column 1
[main]  ERROR   config.yaml : Error parsing file.

Change-Id: If764a123c0dd8871dcd98f58be48c6bf0034f1d4
Closes-Bug: #1621552
---
 bandit/core/config.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/bandit/core/config.py b/bandit/core/config.py
index acde78a..7369f06 100644
--- a/bandit/core/config.py
+++ b/bandit/core/config.py
@@ -49,7 +49,8 @@ class BanditConfig(object):
             try:
                 self._config = yaml.safe_load(f)
                 self.validate(config_file)
-            except yaml.YAMLError:
+            except yaml.YAMLError as err:
+                LOG.error(err)
                 raise utils.ConfigError("Error parsing file.", config_file)
 
             # valid config must be a dict
-- 
2.13.6

From f4800cdbac253069e0cf3d5b2c55d02b8a95bbdf Mon Sep 17 00:00:00 2001
From: lhinds <lhinds@redhat.com>
Date: Fri, 25 Aug 2017 15:27:47 +0100
Subject: [PATCH 33/43] Incorrect Test ID in docstring

B223 was incorrectly referenced with B222

Change-Id: I922fcb69a04e8c3a21ff71aac0d53679b50e928f
Signed-off-by: lhinds <lhinds@redhat.com>
---
 bandit/blacklists/calls.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py
index 075bdce..87eccc0 100644
--- a/bandit/blacklists/calls.py
+++ b/bandit/blacklists/calls.py
@@ -289,7 +289,7 @@ behavior that does not validate certificates or perform hostname checks.
 +------+---------------------+------------------------------------+-----------+
 | ID   |  Name               |  Calls                             |  Severity |
 +======+=====================+====================================+===========+
-| B322 | unverified_context  | - ssl._create_unverified_context   | Medium    |
+| B323 | unverified_context  | - ssl._create_unverified_context   | Medium    |
 +------+---------------------+------------------------------------+-----------+
 
 """
-- 
2.13.6

From 277daaf09437cce61c4c913bbe9d55ca87d738d1 Mon Sep 17 00:00:00 2001
From: sudhir_agarwal <sudhir.agarwal@nectechnologies.in>
Date: Mon, 17 Jul 2017 11:25:14 +0530
Subject: [PATCH 35/43] Remove unused None from dict.get()

Since the default value is None when can't get a key from a dict,
So there is no need to use dict.get('key', None).

Change-Id: If22a4a6dbfd010a0b9574b42c23ba19a2c54dd6d
---
 bandit/core/docs_utils.py           | 4 ++--
 tests/functional/test_functional.py | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/bandit/core/docs_utils.py b/bandit/core/docs_utils.py
index 8f268e0..96ab884 100644
--- a/bandit/core/docs_utils.py
+++ b/bandit/core/docs_utils.py
@@ -24,11 +24,11 @@ def get_url(bid):
     # later though.
     from bandit.core import extension_loader
 
-    info = extension_loader.MANAGER.plugins_by_id.get(bid, None)
+    info = extension_loader.MANAGER.plugins_by_id.get(bid)
     if info is not None:
         return BASE_URL + ('plugins/%s.html' % info.plugin.__name__)
 
-    info = extension_loader.MANAGER.blacklist_by_id.get(bid, None)
+    info = extension_loader.MANAGER.blacklist_by_id.get(bid)
     if info is not None:
         template = 'blacklists/blacklist_{kind}.html#{id}-{name}'
         info['name'] = info['name'].replace('_', '-')
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index 5ea8220..b13edc5 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -105,7 +105,7 @@ class FunctionalTests(testtools.TestCase):
                 for rank in C.RANKING:
                     label = '{0}.{1}'.format(criteria, rank)
                     expected = 0
-                    if expect['issues'].get(criteria, None).get(rank, None):
+                    if expect['issues'].get(criteria).get(rank):
                         expected = expect['issues'][criteria][rank]
                     self.assertEqual(expected, m['_totals'][label])
 
-- 
2.13.6

From 1a3d28b44064c84fccf6d922a4f0816add0bf0f9 Mon Sep 17 00:00:00 2001
From: shangxiaobj <shangxiaobj@inspur.com>
Date: Wed, 13 Sep 2017 00:11:27 -0700
Subject: [PATCH 36/43] [Trivialfix]Fix typos

Fix the typos in bandit.

Change-Id: I93db489ae27c3f4490e988d342802f3f29f9255f
---
 examples/imports-function.py      | 2 +-
 tests/functional/test_baseline.py | 2 +-
 tests/unit/cli/test_main.py       | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/examples/imports-function.py b/examples/imports-function.py
index 7f9ac1d..bff8aa4 100644
--- a/examples/imports-function.py
+++ b/examples/imports-function.py
@@ -7,6 +7,6 @@ subprocess = __import__("subprocess")
 # see bug https://bugs.launchpad.net/bandit/+bug/1396333
 __import__()
 
-# TODO(??): bandit can not find this one unfortunatly (no symbol tab)
+# TODO(??): bandit can not find this one unfortunately (no symbol tab)
 a = 'subprocess'
 __import__(a)
diff --git a/tests/functional/test_baseline.py b/tests/functional/test_baseline.py
index cad7bf1..ff8fc73 100644
--- a/tests/functional/test_baseline.py
+++ b/tests/functional/test_baseline.py
@@ -222,7 +222,7 @@ class BaselineFunctionalTests(testtools.TestCase):
                                  "new_candidates-all.py"}
         target_directory, baseline_code = (self._create_baseline(
                                            baseline_report_files))
-        # assert the intial baseline found results
+        # assert the initial baseline found results
         self.assertEqual(1, baseline_code)
         baseline_report = os.path.join(target_directory,
                                        self.baseline_report_file)
diff --git a/tests/unit/cli/test_main.py b/tests/unit/cli/test_main.py
index 7e79ca5..adc95cd 100644
--- a/tests/unit/cli/test_main.py
+++ b/tests/unit/cli/test_main.py
@@ -151,7 +151,7 @@ class BanditCLIMainTests(testtools.TestCase):
                          option_name))
 
     def test_log_option_source_no_values(self):
-        # Test that None is returned when no command arguement or ini value are
+        # Test that None is returned when no command argument or ini value are
         # provided
         option_name = 'aggregate'
         self.assertIsNone(bandit._log_option_source(None, None, option_name))
-- 
2.13.6

From dab37aace404db7f368084d9a97164e1f4d98fbf Mon Sep 17 00:00:00 2001
From: zhangyangyang <zhangyangyang@unionpay.com>
Date: Wed, 20 Sep 2017 21:44:47 +0800
Subject: [PATCH 37/43] Cleanup test-requirements

python-subunit is not used directly anywhere
and it is dependency of both testrepository
and os-testr
(probably was used by some tox wrapper script before)

Change-Id: Id2df62be0364a262ff4b0056ffb7a6b779cc2813
---
 test-requirements.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test-requirements.txt b/test-requirements.txt
index b9a8990..b126073 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -5,7 +5,6 @@ coverage!=4.4,>=4.0 # Apache-2.0
 fixtures>=3.0.0 # Apache-2.0/BSD
 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
 mock>=2.0.0 # BSD
-python-subunit>=0.0.18 # Apache-2.0/BSD
 testrepository>=0.0.18 # Apache-2.0/BSD
 testscenarios>=0.4 # Apache-2.0/BSD
 testtools>=1.4.0 # MIT
-- 
2.13.6

From a98519927be935eb6f331226c1fcc272822d4a68 Mon Sep 17 00:00:00 2001
From: Rajath Agasthya <rajathagasthya@gmail.com>
Date: Fri, 15 Sep 2017 18:25:55 -0700
Subject: [PATCH 38/43] Plugin to flag insecure hash functions created using
 hashlib.new()

Currently, insecure hash function usage by calling hashlib.md5()
is flagged in B303. But these hash functions can also be obtained using
hashlib.new(), by passing 'md4' or 'md5' as an argument. This plugin
checks such usage.

Change-Id: I8d368aea287e1287e5f638b48c4297d355037839
Closes-Bug: #1708582
---
 README.rst                                       |  1 +
 bandit/plugins/hashlib_new_insecure_functions.py | 63 ++++++++++++++++++++++++
 examples/hashlib_new_insecure_functions.py       | 16 ++++++
 setup.cfg                                        |  3 ++
 tests/functional/test_functional.py              |  8 +++
 5 files changed, 91 insertions(+)
 create mode 100644 bandit/plugins/hashlib_new_insecure_functions.py
 create mode 100644 examples/hashlib_new_insecure_functions.py

diff --git a/README.rst b/README.rst
index 41aede1..f53de9f 100644
--- a/README.rst
+++ b/README.rst
@@ -174,6 +174,7 @@ Usage::
       B321  ftplib
       B322  input
       B323  unverified_context
+      B324  hashlib_new_insecure_functions
       B401  import_telnetlib
       B402  import_ftplib
       B403  import_pickle
diff --git a/bandit/plugins/hashlib_new_insecure_functions.py b/bandit/plugins/hashlib_new_insecure_functions.py
new file mode 100644
index 0000000..46436c2
--- /dev/null
+++ b/bandit/plugins/hashlib_new_insecure_functions.py
@@ -0,0 +1,63 @@
+# -*- coding:utf-8 -*-
+#
+# 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"""
+==========================================================================
+B324: Test for use of insecure md4 and md5 hash functions in hashlib.new()
+==========================================================================
+
+This plugin checks for the usage of the insecure MD4 and MD5 hash functions
+in ``hashlib.new`` function. The ``hashlib.new`` function provides the ability
+to construct a new hashing object using the named algorithm. This can be used
+to create insecure hash functions like MD4 and MD5 if they are passed as
+algorithm names to this function.
+
+This is similar to B303 blacklist check, except that this checks for insecure
+hash functions created using ``hashlib.new`` function.
+
+:Example:
+
+    >> Issue: [B324:hashlib_new] Use of insecure MD4 or MD5 hash function.
+       Severity: Medium   Confidence: High
+       Location: examples/hashlib_new_insecure_funcs.py:3
+    2
+    3  md5_hash = hashlib.new('md5', string='test')
+    4  print(md5_hash)
+
+
+.. versionadded:: 1.5.0
+
+"""
+
+import bandit
+from bandit.core import test_properties as test
+
+
+@test.test_id('B324')
+@test.checks('Call')
+def hashlib_new(context):
+    if isinstance(context.call_function_name_qual, str):
+        qualname_list = context.call_function_name_qual.split('.')
+        func = qualname_list[-1]
+        if 'hashlib' in qualname_list and func == 'new':
+            args = context.call_args
+            keywords = context.call_keywords
+            name = args[0] if args else keywords['name']
+            if name.lower() in ('md4', 'md5'):
+                return bandit.Issue(
+                    severity=bandit.MEDIUM,
+                    confidence=bandit.HIGH,
+                    text="Use of insecure MD4 or MD5 hash function.",
+                    lineno=context.node.lineno,
+                )
diff --git a/examples/hashlib_new_insecure_functions.py b/examples/hashlib_new_insecure_functions.py
new file mode 100644
index 0000000..eeddcca
--- /dev/null
+++ b/examples/hashlib_new_insecure_functions.py
@@ -0,0 +1,16 @@
+import hashlib
+
+hashlib.new('md5')
+
+hashlib.new('md4', 'test')
+
+hashlib.new(name='md5', string='test')
+
+hashlib.new('MD4', string='test')
+
+hashlib.new(string='test', name='MD5')
+
+# Test that plugin does not flag valid hash functions.
+hashlib.new('sha256')
+
+hashlib.new('SHA512')
diff --git a/setup.cfg b/setup.cfg
index cb3aad6..a8fdbbb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -81,6 +81,9 @@ bandit.plugins =
     # bandit/plugins/injection_sql.py
     hardcoded_sql_expressions = bandit.plugins.injection_sql:hardcoded_sql_expressions
 
+    # bandit/plugins/hashlib_new_insecure_functions.py
+    hashlib_new_insecure_functions = bandit.plugins.hashlib_new_insecure_functions:hashlib_new
+
     # bandit/plugins/injection_wildcard.py
     linux_commands_wildcard_injection = bandit.plugins.injection_wildcard:linux_commands_wildcard_injection
 
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index db6e3db..60dd585 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -697,3 +697,11 @@ class FunctionalTests(testtools.TestCase):
             'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
         }
         self.check_example('unverified_context.py', expect)
+
+    def test_hashlib_new_insecure_functions(self):
+        '''Test insecure hash functions created by `hashlib.new`.'''
+        expect = {
+            'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 5, 'HIGH': 0},
+            'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5}
+        }
+        self.check_example('hashlib_new_insecure_functions.py', expect)
-- 
2.13.6

From d159335700938f25ebd2606c066e3895e2a3d577 Mon Sep 17 00:00:00 2001
From: Marek Cermak <macermak@redhat.com>
Date: Mon, 9 Oct 2017 15:57:56 +0200
Subject: [PATCH 39/43] Custom formatter

Implements: custom formatter

Custom formatter can be used to output a machine-readable, easily
parsable and customizable format using set of predefined tags
to suite various needs.

Output string is formatted using python string.format() standards
and therefore provides familiar usage.

Usage: bandit --format custom [--msg-template MSG-TEMPLATE] targets

See bandit --help for additional information and list of available tags

modified:   bandit/cli/main.py
modified:   bandit/core/manager.py
modified:   README.rst
modified:   setup.cfg
new file:   bandit/formatters/custom.py

Change-Id: I900c9689cddb048db58608c443305e05e7a4be14
Signed-off-by: Marek Cermak <macermak@redhat.com>
---
 README.rst                       |  64 ++++++++++++++-
 bandit/cli/main.py               |  45 ++++++++++-
 bandit/core/manager.py           |  14 +++-
 bandit/formatters/custom.py      | 163 +++++++++++++++++++++++++++++++++++++++
 doc/source/man/bandit.rst        |  35 ++++++++-
 setup.cfg                        |   1 +
 tests/functional/test_runtime.py |   2 +-
 7 files changed, 311 insertions(+), 13 deletions(-)
 create mode 100644 bandit/formatters/custom.py

diff --git a/README.rst b/README.rst
index 41aede1..de3773f 100644
--- a/README.rst
+++ b/README.rst
@@ -87,8 +87,9 @@ Usage::
     $ bandit -h
     usage: bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE]
                   [-p PROFILE] [-t TESTS] [-s SKIPS] [-l] [-i]
-                  [-f {csv,html,json,screen,txt,xml,yaml}] [-o [OUTPUT_FILE]] [-v]
-                  [-d] [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
+                  [-f {csv,custom,html,json,screen,txt,xml,yaml}]
+                  [--msg-template MSG_TEMPLATE] [-o [OUTPUT_FILE]] [-v] [-d]
+                  [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
                   [--ini INI_PATH] [--version]
                   targets [targets ...]
 
@@ -118,8 +119,12 @@ Usage::
                             (-l for LOW, -ll for MEDIUM, -lll for HIGH)
       -i, --confidence      report only issues of a given confidence level or
                             higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)
-      -f {csv,html,json,screen,txt,xml,yaml}, --format {csv,html,json,screen,txt,xml,yaml}
+      -f {csv,custom,html,json,screen,txt,xml,yaml}, --format {csv,custom,html,json,screen,txt,xml,yaml}
                             specify output format
+      --msg-template        MSG_TEMPLATE
+                            specify output message template (only usable with
+                            --format custom), see CUSTOM FORMAT section for list
+                            of available values
       -o [OUTPUT_FILE], --output [OUTPUT_FILE]
                             write report to filename
       -v, --verbose         output extra information like excluded and included
@@ -137,7 +142,33 @@ Usage::
                             arguments
       --version             show program's version number and exit
 
+    CUSTOM FORMATTING
+    -----------------
+
+    Available tags:
+
+        {abspath}, {relpath}, {line},  {test_id},
+        {severity}, {msg}, {confidence}, {range}
+
+    Example usage:
+
+        Default template:
+        bandit -r examples/ --format custom --msg-template \
+        "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
+
+        Provides same output as:
+        bandit -r examples/ --format custom
+
+        Tags can also be formatted in python string.format() style:
+        bandit -r examples/ --format custom --msg-template \
+        "{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
+
+        See python documentation for more information about formatting style:
+        https://docs.python.org/3.4/library/string.html
+
     The following tests were discovered and loaded:
+    -----------------------------------------------
+
       B101  assert_used
       B102  exec_used
       B103  set_bad_file_permissions
@@ -339,6 +370,33 @@ To register your plugin, you have two options:
         bandit.plugins =
             mako = bandit_mako
 
+
+Custom Formatting
+-----------------
+
+Available tags:
+
+::
+    {abspath}, {relpath}, {line},  {test_id},
+    {severity}, {msg}, {confidence}, {range}
+
+Example usage:
+
+  Default template::
+    bandit -r examples/ --format custom --msg-template \
+    "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
+
+  Provides same output as::
+    bandit -r examples/ --format custom
+
+  Tags can also be formatted in python string.format() style::
+    bandit -r examples/ --format custom --msg-template \
+    "{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
+
+See python documentation for more information about formatting style:
+https://docs.python.org/3.4/library/string.html
+
+
 Contributing
 ------------
 Contributions to Bandit are always welcome! We can be found on
diff --git a/bandit/cli/main.py b/bandit/cli/main.py
index 423e95c..2c4a403 100644
--- a/bandit/cli/main.py
+++ b/bandit/cli/main.py
@@ -18,6 +18,7 @@ import fnmatch
 import logging
 import os
 import sys
+import textwrap
 
 
 import bandit
@@ -206,6 +207,13 @@ def main():
         choices=sorted(extension_mgr.formatter_names)
     )
     parser.add_argument(
+        '--msg-template', action='store',
+        default=None, help='specify output message template'
+                           ' (only usable with --format custom),'
+                           ' see CUSTOM FORMAT section'
+                           ' for list of available values',
+    )
+    parser.add_argument(
         '-o', '--output', dest='output_file', action='store', nargs='?',
         type=argparse.FileType('w'), default=sys.stdout,
         help='write report to filename'
@@ -253,11 +261,41 @@ def main():
             blacklist_info.append('%s\t%s' % (b['id'], b['name']))
 
     plugin_list = '\n\t'.join(sorted(set(plugin_info + blacklist_info)))
-    parser.epilog = ('The following tests were discovered and'
-                     ' loaded:\n\t{0}\n'.format(plugin_list))
+    dedent_text = textwrap.dedent('''
+    CUSTOM FORMATTING
+    -----------------
+
+    Available tags:
+
+        {abspath}, {relpath}, {line},  {test_id},
+        {severity}, {msg}, {confidence}, {range}
+
+    Example usage:
+
+        Default template:
+        bandit -r examples/ --format custom --msg-template \\
+        "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
+
+        Provides same output as:
+        bandit -r examples/ --format custom
+
+        Tags can also be formatted in python string.format() style:
+        bandit -r examples/ --format custom --msg-template \\
+        "{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
+
+        See python documentation for more information about formatting style:
+        https://docs.python.org/3.4/library/string.html
+
+    The following tests were discovered and loaded:
+    -----------------------------------------------
+    ''')
+    parser.epilog = dedent_text + "\t{0}".format(plugin_list)
 
     # setup work - parse arguments, and initialize BanditManager
     args = parser.parse_args()
+    # Check if `--msg-template` is not present without custom formatter
+    if args.output_format != 'custom' and args.msg_template is not None:
+        parser.error("--msg-template can only be used with --format=custom")
 
     try:
         b_conf = b_config.BanditConfig(config_file=args.config_file)
@@ -341,7 +379,8 @@ def main():
                          sev_level,
                          conf_level,
                          args.output_file,
-                         args.output_format)
+                         args.output_format,
+                         args.msg_template)
 
     # return an exit code of 1 if there are results, 0 otherwise
     if b_mgr.results_count(sev_filter=sev_level, conf_filter=conf_level) > 0:
diff --git a/bandit/core/manager.py b/bandit/core/manager.py
index d4febce..cb8b574 100644
--- a/bandit/core/manager.py
+++ b/bandit/core/manager.py
@@ -136,7 +136,7 @@ class BanditManager(object):
         return len(self.get_issue_list(sev_filter, conf_filter))
 
     def output_results(self, lines, sev_level, conf_level, output_file,
-                       output_format):
+                       output_format, template=None):
         '''Outputs results from the result store
 
         :param lines: How many surrounding lines to show per result
@@ -144,6 +144,9 @@ class BanditManager(object):
         :param conf_level: Which confidence levels to show (LOW, MEDIUM, HIGH)
         :param output_file: File to store results
         :param output_format: output format plugin name
+        :param template: Output template with non-terminal tags <N>
+                         (default:  {abspath}:{line}:
+                         {test_id}[bandit]: {severity}: {msg})
         :return: -
         '''
         try:
@@ -153,8 +156,13 @@ class BanditManager(object):
 
             formatter = formatters_mgr[output_format]
             report_func = formatter.plugin
-            report_func(self, fileobj=output_file, sev_level=sev_level,
-                        conf_level=conf_level, lines=lines)
+            if output_format == 'custom':
+                report_func(self, fileobj=output_file, sev_level=sev_level,
+                            conf_level=conf_level, lines=lines,
+                            template=template)
+            else:
+                report_func(self, fileobj=output_file, sev_level=sev_level,
+                            conf_level=conf_level, lines=lines)
 
         except Exception as e:
             raise RuntimeError("Unable to output report using '%s' formatter: "
diff --git a/bandit/formatters/custom.py b/bandit/formatters/custom.py
new file mode 100644
index 0000000..864ff4f
--- /dev/null
+++ b/bandit/formatters/custom.py
@@ -0,0 +1,163 @@
+# Copyright (c) 2017 Hewlett Packard Enterprise
+# -*- coding:utf-8 -*-
+#
+# 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"""
+================
+Custom Formatter
+================
+
+This formatter outputs the issues in custom machine-readable format.
+
+default template: {abspath}:{line}: {test_id}[bandit]: {severity}: {msg}
+
+:Example:
+
+/usr/lib/python3.6/site-packages/openlp/core/utils/__init__.py: \
+405: B310[bandit]: MEDIUM: Audit url open for permitted schemes. \
+Allowing use of file:/ or custom schemes is often unexpected.
+
+"""
+
+import logging
+import os
+import re
+import string
+import sys
+
+from bandit.core import test_properties
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SafeMapper(dict):
+    """Safe mapper to handle format key errors"""
+    @classmethod  # To prevent PEP8 warnings in the test suite
+    def __missing__(cls, key):
+        return "{%s}" % key
+
+
+@test_properties.accepts_baseline
+def report(manager, fileobj, sev_level, conf_level, lines=-1, template=None):
+    """Prints issues in custom format
+
+    :param manager: the bandit manager object
+    :param fileobj: The output file object, which may be sys.stdout
+    :param sev_level: Filtering severity level
+    :param conf_level: Filtering confidence level
+    :param lines: Number of lines to report, -1 for all
+    :param template: Output template with non-terminal tags <N>
+                    (default: '{abspath}:{line}:
+                    {test_id}[bandit]: {severity}: {msg}')
+    """
+
+    machine_output = {'results': [], 'errors': []}
+    for (fname, reason) in manager.get_skipped():
+        machine_output['errors'].append({'filename': fname,
+                                         'reason': reason})
+
+    results = manager.get_issue_list(sev_level=sev_level,
+                                     conf_level=conf_level)
+
+    msg_template = template
+    if template is None:
+        msg_template = "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
+
+    # Dictionary of non-terminal tags that will be expanded
+    tag_mapper = {
+        'abspath': lambda issue: os.path.abspath(issue.fname),
+        'relpath': lambda issue: os.path.relpath(issue.fname),
+        'line': lambda issue: issue.lineno,
+        'test_id': lambda issue: issue.test_id,
+        'severity': lambda issue: issue.severity,
+        'msg': lambda issue: issue.text,
+        'confidence': lambda issue: issue.confidence,
+        'range': lambda issue: issue.linerange
+    }
+
+    # Create dictionary with tag sets to speed up search for similar tags
+    tag_sim_dict = dict(
+        [(tag, set(tag)) for tag, _ in tag_mapper.items()]
+    )
+
+    # Parse the format_string template and check the validity of tags
+    try:
+        parsed_template_orig = list(string.Formatter().parse(msg_template))
+        # of type (literal_text, field_name, fmt_spec, conversion)
+
+        # Check the format validity only, ignore keys
+        string.Formatter().vformat(msg_template, (), SafeMapper(line=0))
+    except ValueError as e:
+        LOG.error("Template is not in valid format: %s", e.args[0])
+        sys.exit(2)
+
+    tag_set = {t[1] for t in parsed_template_orig if t[1] is not None}
+    if not tag_set:
+        LOG.error("No tags were found in the template. Are you missing '{}'?")
+        sys.exit(2)
+
+    def get_similar_tag(tag):
+        similarity_list = [(len(set(tag) & t_set), t)
+                           for t, t_set in tag_sim_dict.items()]
+        return sorted(similarity_list)[-1][1]
+
+    tag_blacklist = []
+    for tag in tag_set:
+        # check if the tag is in dictionary
+        if tag not in tag_mapper:
+            similar_tag = get_similar_tag(tag)
+            LOG.warning(
+                "Tag '%s' was not recognized and will be skipped, "
+                "did you mean to use '%s'?", tag, similar_tag
+            )
+            tag_blacklist += [tag]
+
+    # Compose the message template back with the valid values only
+    msg_parsed_template_list = []
+    for literal_text, field_name, fmt_spec, conversion in parsed_template_orig:
+        if literal_text:
+            # if there is '{' or '}', double it to prevent expansion
+            literal_text = re.sub('{', '{{', literal_text)
+            literal_text = re.sub('}', '}}', literal_text)
+            msg_parsed_template_list.append(literal_text)
+
+        if field_name is not None:
+            if field_name in tag_blacklist:
+                msg_parsed_template_list.append(field_name)
+                continue
+            # Append the fmt_spec part
+            params = [field_name, fmt_spec, conversion]
+            markers = ['', ':', '!']
+            msg_parsed_template_list.append(
+                ['{'] +
+                ["%s" % (m + p) if p else ''
+                 for m, p in zip(markers, params)] +
+                ['}']
+            )
+
+    msg_parsed_template = "".join([item for lst in msg_parsed_template_list
+                                   for item in lst]) + "\n"
+    limit = lines if lines > 0 else None
+    with fileobj:
+        for defect in results[:limit]:
+            evaluated_tags = SafeMapper(
+                (k, v(defect)) for k, v in tag_mapper.items()
+            )
+            output = msg_parsed_template.format(**evaluated_tags)
+
+            fileobj.write(output)
+
+    if fileobj.name != sys.stdout.name:
+        LOG.info("Result written to file: %s", fileobj.name)
diff --git a/doc/source/man/bandit.rst b/doc/source/man/bandit.rst
index 04a3a43..363b857 100644
--- a/doc/source/man/bandit.rst
+++ b/doc/source/man/bandit.rst
@@ -7,8 +7,9 @@ SYNOPSIS
 
 bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE]
             [-p PROFILE] [-t TESTS] [-s SKIPS] [-l] [-i]
-            [-f {csv,html,json,screen,txt,xml,yaml}] [-o OUTPUT_FILE] [-v]
-            [-d] [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
+            [-f {csv,custom,html,json,screen,txt,xml,yaml}]
+            [--msg-template MSG_TEMPLATE] [-o OUTPUT_FILE] [-v] [-d]
+            [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
             [--ini INI_PATH] [--version]
             targets [targets ...]
 
@@ -43,8 +44,12 @@ OPTIONS
                         (-l for LOW, -ll for MEDIUM, -lll for HIGH)
   -i, --confidence      report only issues of a given confidence level or
                         higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)
-  -f {csv,html,json,screen,txt,xml,yaml}, --format {csv,html,json,screen,txt,xml,yaml}
+  -f {csv,custom,html,json,screen,txt,xml,yaml}, --format {csv,custom,html,json,screen,txt,xml,yaml}
                         specify output format
+  --msg-template MSG_TEMPLATE
+                        specify output message template (only usable with
+                        --format custom), see CUSTOM FORMAT section for list
+                        of available values
   -o OUTPUT_FILE, --output OUTPUT_FILE
                         write report to filename
   -v, --verbose         output extra information like excluded and included
@@ -62,6 +67,30 @@ OPTIONS
                         arguments
   --version             show program's version number and exit
 
+CUSTOM FORMATTING
+-----------------
+
+Available tags:
+
+    {abspath}, {relpath}, {line},  {test_id},
+    {severity}, {msg}, {confidence}, {range}
+
+Example usage:
+
+    Default template:
+    bandit -r examples/ --format custom --msg-template \
+    "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
+
+    Provides same output as:
+    bandit -r examples/ --format custom
+
+    Tags can also be formatted in python string.format() style:
+    bandit -r examples/ --format custom --msg-template \
+    "{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
+
+    See python documentation for more information about formatting style:
+    https://docs.python.org/3.4/library/string.html
+
 FILES
 =====
 
diff --git a/setup.cfg b/setup.cfg
index cb3aad6..78bcb8e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -37,6 +37,7 @@ bandit.formatters =
     html = bandit.formatters.html:report
     screen = bandit.formatters.screen:report
     yaml = bandit.formatters.yaml:report
+    custom = bandit.formatters.custom:report
 bandit.plugins =
     # bandit/plugins/app_debug.py
     flask_debug_true = bandit.plugins.app_debug:flask_debug_true
diff --git a/tests/functional/test_runtime.py b/tests/functional/test_runtime.py
index 2fe8ff2..5fa1997 100644
--- a/tests/functional/test_runtime.py
+++ b/tests/functional/test_runtime.py
@@ -77,7 +77,7 @@ class RuntimeTests(testtools.TestCase):
         self.assertIn("tests were discovered and loaded:", output)
 
     def test_help_in_readme(self):
-        replace_list = [' ', '\t']
+        replace_list = [' ', '\t', '\n']
         (retcode, output) = self._test_runtime(['bandit', '-h'])
         for i in replace_list:
             output = output.replace(i, '')
-- 
2.13.6

From ce108f0edadcb0ff6ec11c83b93738dfc94c7654 Mon Sep 17 00:00:00 2001
From: Gage Hugo <gagehugo@gmail.com>
Date: Thu, 2 Nov 2017 13:49:04 -0500
Subject: [PATCH 40/43] Migrate to stestr

This change migrates the testing suite from using ostestr and testr
to using stester. Also cleaned up a missing space from tox.ini.

Change-Id: I886401a1efce6cb617a4db7a90ec9454bbea1d71
---
 .stestr.conf          |  4 ++++
 .testr.conf           |  7 -------
 test-requirements.txt |  2 +-
 tox.ini               | 15 ++++++++++-----
 5 files changed, 16 insertions(+), 14 deletions(-)
 create mode 100644 .stestr.conf
 delete mode 100644 .testr.conf

diff --git a/.stestr.conf b/.stestr.conf
new file mode 100644
index 0000000..64fe016
--- /dev/null
+++ b/.stestr.conf
@@ -0,0 +1,4 @@
+[DEFAULT]
+test_path=${OS_TEST_PATH:-./tests/unit}
+top_dir=./
+group_regex=.*(test_cert_setup)
diff --git a/.testr.conf b/.testr.conf
deleted file mode 100644
index 35d9ba4..0000000
--- a/.testr.conf
+++ /dev/null
@@ -1,7 +0,0 @@
-[DEFAULT]
-test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
-             OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
-             OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
-             ${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
-test_id_option=--load-list $IDFILE
-test_list_option=--list
diff --git a/test-requirements.txt b/test-requirements.txt
index b126073..1fe8d4e 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -5,7 +5,7 @@ coverage!=4.4,>=4.0 # Apache-2.0
 fixtures>=3.0.0 # Apache-2.0/BSD
 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
 mock>=2.0.0 # BSD
-testrepository>=0.0.18 # Apache-2.0/BSD
+stestr>=1.0.0 # Apache-2.0
 testscenarios>=0.4 # Apache-2.0/BSD
 testtools>=1.4.0 # MIT
 oslotest>=1.10.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index 780c7d4..6f22625 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,13 +10,14 @@ setenv =
     VIRTUAL_ENV={envdir}
     BRANCH_NAME=master
     CLIENT_NAME=bandit
-   VIRTUAL_ENV={envdir}
+    VIRTUAL_ENV={envdir}
 deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
 commands =
-          coverage erase
-          python setup.py testr --coverage --slowest --testr-args='{posargs}'
-          coverage report -m
+    find bandit -type f -name "*.pyc" -delete
+    stestr run {posargs}
+whitelist_externals =
+    find
 passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
 
 [testenv:debug]
@@ -50,8 +51,12 @@ commands = bandit-baseline -r bandit -ll -ii
 [testenv:cover]
 deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
+setenv =
+    {[testenv]setenv}
+    PYTHON=coverage run --source bandit --parallel-mode
 commands =
-    python setup.py testr --coverage --testr-args='{posargs}'
+    coverage erase
+    stestr run '{posargs}'
     coverage report
 
 [testenv:openstack_coverage]
-- 
2.13.6

From a7a8070426ece60ff1ceb2bd196f67a7339db00c Mon Sep 17 00:00:00 2001
From: Andreas Jaeger <aj@suse.com>
Date: Fri, 17 Nov 2017 10:07:29 +0100
Subject: [PATCH 41/43] Remove setting of version/release from releasenotes

Release notes are version independent, so remove version/release
values. We've found that projects now require the service package
to be installed in order to build release notes, and this is entirely
due to the current convention of pulling in the version information.

Release notes should not need installation in order to build, so this
unnecessary version setting needs to be removed.

This is needed for new release notes publishing, see
I56909152975f731a9d2c21b2825b972195e48ee8 and the discussion starting
at
http://lists.openstack.org/pipermail/openstack-dev/2017-November/124480.html
.

Change-Id: I096e956fa44f0dfa9b8210a221bcbe5afb385634
---
 releasenotes/source/conf.py | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 2982e1c..834b07d 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -55,17 +55,12 @@ master_doc = 'index'
 project = u'Bandit Release Notes'
 copyright = u'2016, Bandit Developers'
 
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-import pbr.version
-bandit_version = pbr.version.VersionInfo('bandit')
+# Release notes do not need a version number in the title, they
+# cover multiple releases.
 # The full version, including alpha/beta/rc tags.
-release = bandit_version.version_string_with_vcs()
+release = ''
 # The short X.Y version.
-version = bandit_version.canonical_version_string()
+version = ''
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-- 
2.13.6

From e907ddd94ee58a23850e054be77f5a88a85c28f0 Mon Sep 17 00:00:00 2001
From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
Date: Thu, 23 Nov 2017 18:40:40 +0000
Subject: [PATCH 42/43] Updated from global requirements

Change-Id: Ic98daa2b0750da0240ac1d4f9f4e4bc504a7ac07
---
 requirements.txt      | 2 +-
 test-requirements.txt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index affa923..7f1b79d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,5 +3,5 @@
 # process, which may cause wedges in the gate later.
 GitPython>=1.0.1 # BSD License (3 clause)
 PyYAML>=3.10 # MIT
-six>=1.9.0 # MIT
+six>=1.10.0 # MIT
 stevedore>=1.20.0 # Apache-2.0
diff --git a/test-requirements.txt b/test-requirements.txt
index 1fe8d4e..e41ac9f 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -7,7 +7,7 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
 mock>=2.0.0 # BSD
 stestr>=1.0.0 # Apache-2.0
 testscenarios>=0.4 # Apache-2.0/BSD
-testtools>=1.4.0 # MIT
+testtools>=2.2.0 # MIT
 oslotest>=1.10.0 # Apache-2.0
 
 sphinx>=1.6.2 # BSD
-- 
2.13.6

From 0b3cd391c1ab1678488f01a7bc0e13160cfa9f55 Mon Sep 17 00:00:00 2001
From: Marek Cermak <macermak@redhat.com>
Date: Fri, 24 Nov 2017 17:40:32 +0100
Subject: [PATCH 43/43] Remove extra section from README.rst

Follow up patch for review/marek_cermak/formatter-custom.

Adressing comment by Gage Hugo: remove extra section from README.rst

Change-Id: I177861d404592ba4b9d7b953bbb983963d53b653
modified:   README.rst
---
 README.rst | 27 ---------------------------
 1 file changed, 27 deletions(-)

diff --git a/README.rst b/README.rst
index de3773f..589ad60 100644
--- a/README.rst
+++ b/README.rst
@@ -370,33 +370,6 @@ To register your plugin, you have two options:
         bandit.plugins =
             mako = bandit_mako
 
-
-Custom Formatting
------------------
-
-Available tags:
-
-::
-    {abspath}, {relpath}, {line},  {test_id},
-    {severity}, {msg}, {confidence}, {range}
-
-Example usage:
-
-  Default template::
-    bandit -r examples/ --format custom --msg-template \
-    "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
-
-  Provides same output as::
-    bandit -r examples/ --format custom
-
-  Tags can also be formatted in python string.format() style::
-    bandit -r examples/ --format custom --msg-template \
-    "{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
-
-See python documentation for more information about formatting style:
-https://docs.python.org/3.4/library/string.html
-
-
 Contributing
 ------------
 Contributions to Bandit are always welcome! We can be found on
-- 
2.13.6