From 2d2170273b78c855d8910e8cbba0fa0ea77745dc Mon Sep 17 00:00:00 2001 From: lhinds Date: Thu, 26 Apr 2018 11:21:32 +0100 Subject: [PATCH] Project Migration to PyCQA This change rehomes the project to PyCQA[1] as reported to the openstack-dev mailing list [1] [0] https://github.com/PyCQA/bandit [1] http://lists.openstack.org/pipermail/openstack-dev/2018-April/129386.html Change-Id: I6aad329a60799ea24a3d9bc49e35c3c35ed9dc3b --- .coveragerc | 3 - .gitignore | 19 - .stestr.conf | 4 - .zuul.yaml | 179 ----- LICENSE | 176 ----- README.rst | 429 +--------- bandit/__init__.py | 31 - bandit/blacklists/__init__.py | 0 bandit/blacklists/calls.py | 544 ------------- bandit/blacklists/imports.py | 305 -------- bandit/blacklists/utils.py | 22 - bandit/cli/__init__.py | 0 bandit/cli/baseline.py | 224 ------ bandit/cli/config_generator.py | 186 ----- bandit/cli/main.py | 401 ---------- bandit/core/__init__.py | 27 - bandit/core/blacklisting.py | 75 -- bandit/core/config.py | 243 ------ bandit/core/constants.py | 42 - bandit/core/context.py | 339 -------- bandit/core/docs_utils.py | 53 -- bandit/core/extension_loader.py | 118 --- bandit/core/issue.py | 139 ---- bandit/core/manager.py | 398 ---------- bandit/core/meta_ast.py | 57 -- bandit/core/metrics.py | 103 --- bandit/core/node_visitor.py | 279 ------- bandit/core/test_properties.py | 85 -- bandit/core/test_set.py | 123 --- bandit/core/tester.py | 111 --- bandit/core/utils.py | 336 -------- bandit/formatters/__init__.py | 0 bandit/formatters/csv.py | 76 -- bandit/formatters/custom.py | 163 ---- bandit/formatters/html.py | 387 ---------- bandit/formatters/json.py | 152 ---- bandit/formatters/screen.py | 182 ----- bandit/formatters/text.py | 164 ---- bandit/formatters/utils.py | 43 -- bandit/formatters/xml.py | 91 --- bandit/formatters/yaml.py | 131 ---- bandit/plugins/__init__.py | 0 bandit/plugins/app_debug.py | 69 -- bandit/plugins/asserts.py | 65 -- .../crypto_request_no_cert_validation.py | 72 -- bandit/plugins/exec.py | 67 -- bandit/plugins/exec_as_root.py | 94 --- .../plugins/general_bad_file_permissions.py | 89 --- bandit/plugins/general_bind_all_interfaces.py | 59 -- bandit/plugins/general_hardcoded_password.py | 215 ------ bandit/plugins/general_hardcoded_tmp.py | 86 --- .../plugins/hashlib_new_insecure_functions.py | 63 -- bandit/plugins/injection_paramiko.py | 74 -- bandit/plugins/injection_shell.py | 634 --------------- bandit/plugins/injection_sql.py | 115 --- bandit/plugins/injection_wildcard.py | 149 ---- bandit/plugins/insecure_ssl_tls.py | 274 ------- bandit/plugins/jinja2_templates.py | 131 ---- bandit/plugins/mako_templates.py | 76 -- bandit/plugins/secret_config_option.py | 115 --- bandit/plugins/try_except_continue.py | 110 --- bandit/plugins/try_except_pass.py | 110 --- bandit/plugins/weak_cryptographic_key.py | 140 ---- bandit/plugins/yaml_load.py | 69 -- bindep.txt | 2 - doc/requirements.txt | 7 - doc/source/blacklists/blacklist_calls.rst | 5 - doc/source/blacklists/blacklist_imports.rst | 5 - doc/source/blacklists/index.rst | 69 -- doc/source/conf.py | 88 --- doc/source/config.rst | 82 -- doc/source/formatters/csv.rst | 5 - doc/source/formatters/html.rst | 5 - doc/source/formatters/index.rst | 42 - doc/source/formatters/json.rst | 5 - doc/source/formatters/screen.rst | 5 - doc/source/formatters/text.rst | 5 - doc/source/formatters/xml.rst | 5 - doc/source/formatters/yaml.rst | 5 - doc/source/index.rst | 27 - doc/source/man/bandit.rst | 128 --- doc/source/plugins/b101_assert_used.rst | 5 - doc/source/plugins/b102_exec_used.rst | 5 - .../plugins/b103_set_bad_file_permissions.rst | 5 - .../b104_hardcoded_bind_all_interfaces.rst | 5 - .../b105_hardcoded_password_string.rst | 8 - .../b106_hardcoded_password_funcarg.rst | 8 - .../b107_hardcoded_password_funcdef.rst | 8 - .../plugins/b108_hardcoded_tmp_directory.rst | 5 - ...ssword_config_option_not_marked_secret.rst | 5 - doc/source/plugins/b110_try_except_pass.rst | 5 - ...1_execute_with_run_as_root_equals_true.rst | 5 - .../plugins/b112_try_except_continue.rst | 5 - doc/source/plugins/b201_flask_debug_true.rst | 5 - .../b501_request_with_no_cert_validation.rst | 5 - .../plugins/b502_ssl_with_bad_version.rst | 8 - .../plugins/b503_ssl_with_bad_defaults.rst | 8 - .../plugins/b504_ssl_with_no_version.rst | 8 - .../plugins/b505_weak_cryptographic_key.rst | 5 - doc/source/plugins/b506_yaml_load.rst | 5 - doc/source/plugins/b601_paramiko_calls.rst | 5 - ...ubprocess_popen_with_shell_equals_true.rst | 8 - ...3_subprocess_without_shell_equals_true.rst | 8 - ..._other_function_with_shell_equals_true.rst | 8 - .../b605_start_process_with_a_shell.rst | 8 - .../b606_start_process_with_no_shell.rst | 8 - .../b607_start_process_with_partial_path.rst | 8 - .../b608_hardcoded_sql_expressions.rst | 5 - ...b609_linux_commands_wildcard_injection.rst | 5 - .../plugins/b701_jinja2_autoescape_false.rst | 5 - .../plugins/b702_use_of_mako_templates.rst | 5 - doc/source/plugins/index.rst | 120 --- examples/__init__.py | 0 examples/assert.py | 1 - examples/binding.py | 5 - examples/cipher-modes.py | 12 - examples/ciphers.py | 76 -- examples/crypto-md5.py | 32 - examples/eval.py | 15 - examples/exec-as-root.py | 26 - examples/exec-py2.py | 2 - examples/exec-py3.py | 1 - examples/flask_debug.py | 19 - examples/ftplib.py | 9 - examples/hardcoded-passwords.py | 24 - examples/hardcoded-tmp.py | 21 - examples/hashlib_new_insecure_functions.py | 16 - examples/httplib_https.py | 8 - examples/httpoxy_cgihandler.py | 10 - examples/httpoxy_twisted_directory.py | 7 - examples/httpoxy_twisted_script.py | 7 - examples/imports-aliases.py | 15 - examples/imports-from.py | 7 - examples/imports-function.py | 12 - examples/imports-with-importlib.py | 5 - examples/imports.py | 4 - examples/init-py-test/__init__.py | 0 examples/init-py-test/subdirectory-okay.py | 3 - examples/input.py | 1 - examples/jinja2_templating.py | 26 - examples/mako_templating.py | 11 - examples/mark_safe.py | 4 - examples/marshal_deserialize.py | 12 - examples/mktemp.py | 10 - examples/multiline_statement.py | 6 - examples/new_candidates-all.py | 24 - examples/new_candidates-none.py | 8 - examples/new_candidates-nosec.py | 18 - examples/new_candidates-some.py | 20 - examples/nonsense.py | 1 - examples/nonsense2.py | Bin 40 -> 0 bytes examples/nosec.py | 5 - examples/okay.py | 1 - examples/os-chmod-py2.py | 17 - examples/os-chmod-py3.py | 17 - examples/os-exec.py | 11 - examples/os-popen.py | 15 - examples/os-spawn.py | 10 - examples/os-startfile.py | 5 - examples/os_system.py | 3 - examples/paramiko_injection.py | 11 - examples/partial_path_process.py | 13 - examples/pickle_deserialize.py | 29 - examples/popen_wrappers.py | 15 - examples/pycrypto.py | 11 - examples/random_module.py | 16 - examples/requests-ssl-verify-disabled.py | 16 - examples/secret-config-option.py | 28 - examples/skip.py | 7 - examples/sql_statements.py | 39 - examples/ssl-insecure-version.py | 36 - examples/subprocess_shell.py | 33 - examples/telnetlib.py | 19 - examples/try_except_continue.py | 32 - examples/try_except_pass.py | 36 - examples/unverified_context.py | 7 - examples/urlopen.py | 59 -- examples/utils-shell.py | 8 - examples/weak_cryptographic_key_sizes.py | 66 -- examples/wildcard-injection.py | 16 - examples/xml_etree_celementtree.py | 18 - examples/xml_etree_elementtree.py | 18 - examples/xml_expatbuilder.py | 10 - examples/xml_expatreader.py | 5 - examples/xml_lxml.py | 9 - examples/xml_minidom.py | 14 - examples/xml_pulldom.py | 14 - examples/xml_sax.py | 37 - examples/xml_xmlrpc.py | 10 - examples/yaml_load.py | 12 - lower-constraints.txt | 38 - .../bandit-integration-barbican/run.yaml | 28 - .../legacy/bandit-integration-glance/run.yaml | 28 - .../bandit-integration-glance_store/run.yaml | 28 - .../bandit-integration-keystone/run.yaml | 29 - .../run.yaml | 29 - .../legacy/bandit-integration-magnum/run.yaml | 28 - .../bandit-integration-oslo.config/run.yaml | 28 - .../bandit-integration-oslo.log/run.yaml | 28 - .../bandit-integration-oslo.service/run.yaml | 29 - .../bandit-integration-oslo.utils/run.yaml | 28 - .../bandit-integration-oslo.vmware/run.yaml | 28 - .../run.yaml | 29 - .../run.yaml | 29 - .../legacy/bandit-integration-sahara/run.yaml | 28 - pylintrc | 73 -- .../add-pycrypto-warn-c430f40f1d0fb44a.yaml | 6 - .../add-url-in-json-64f90161ab613a54.yaml | 5 - .../add-url-in-yaml-0bfdcc93f5b6d118.yaml | 5 - .../notes/add_reno-b8585fc3ffe775cb.yaml | 3 - .../notes/target-in-ini-81802418b1cc970f.yaml | 5 - releasenotes/source/_static/.placeholder | 0 releasenotes/source/_templates/.placeholder | 0 releasenotes/source/conf.py | 272 ------- releasenotes/source/index.rst | 21 - releasenotes/source/unreleased.rst | 5 - requirements.txt | 7 - scripts/integration-test.sh | 54 -- scripts/main.py | 22 - setup.cfg | 128 --- setup.py | 29 - test-requirements.txt | 15 - tests/__init__.py | 0 tests/functional/__init__.py | 0 tests/functional/test_baseline.py | 308 -------- tests/functional/test_functional.py | 731 ------------------ tests/functional/test_runtime.py | 130 ---- tests/unit/__init__.py | 0 tests/unit/cli/__init__.py | 0 tests/unit/cli/test_baseline.py | 298 ------- tests/unit/cli/test_config_generator.py | 99 --- tests/unit/cli/test_main.py | 289 ------- tests/unit/core/__init__.py | 0 tests/unit/core/test_blacklisting.py | 43 -- tests/unit/core/test_config.py | 264 ------- tests/unit/core/test_context.py | 266 ------- tests/unit/core/test_issue.py | 135 ---- tests/unit/core/test_manager.py | 303 -------- tests/unit/core/test_meta_ast.py | 41 - tests/unit/core/test_test_set.py | 160 ---- tests/unit/core/test_util.py | 305 -------- tests/unit/formatters/__init__.py | 0 tests/unit/formatters/test_csv.py | 66 -- tests/unit/formatters/test_html.py | 160 ---- tests/unit/formatters/test_json.py | 98 --- tests/unit/formatters/test_screen.py | 213 ----- tests/unit/formatters/test_text.py | 200 ----- tests/unit/formatters/test_xml.py | 84 -- tests/unit/formatters/test_yaml.py | 98 --- tools/openstack_coverage.py | 276 ------- tox.ini | 105 --- 251 files changed, 7 insertions(+), 17068 deletions(-) delete mode 100644 .coveragerc delete mode 100644 .gitignore delete mode 100644 .stestr.conf delete mode 100644 .zuul.yaml delete mode 100644 LICENSE delete mode 100644 bandit/__init__.py delete mode 100644 bandit/blacklists/__init__.py delete mode 100644 bandit/blacklists/calls.py delete mode 100644 bandit/blacklists/imports.py delete mode 100644 bandit/blacklists/utils.py delete mode 100644 bandit/cli/__init__.py delete mode 100644 bandit/cli/baseline.py delete mode 100644 bandit/cli/config_generator.py delete mode 100644 bandit/cli/main.py delete mode 100644 bandit/core/__init__.py delete mode 100644 bandit/core/blacklisting.py delete mode 100644 bandit/core/config.py delete mode 100644 bandit/core/constants.py delete mode 100644 bandit/core/context.py delete mode 100644 bandit/core/docs_utils.py delete mode 100644 bandit/core/extension_loader.py delete mode 100644 bandit/core/issue.py delete mode 100644 bandit/core/manager.py delete mode 100644 bandit/core/meta_ast.py delete mode 100644 bandit/core/metrics.py delete mode 100644 bandit/core/node_visitor.py delete mode 100644 bandit/core/test_properties.py delete mode 100644 bandit/core/test_set.py delete mode 100644 bandit/core/tester.py delete mode 100644 bandit/core/utils.py delete mode 100644 bandit/formatters/__init__.py delete mode 100644 bandit/formatters/csv.py delete mode 100644 bandit/formatters/custom.py delete mode 100644 bandit/formatters/html.py delete mode 100644 bandit/formatters/json.py delete mode 100644 bandit/formatters/screen.py delete mode 100644 bandit/formatters/text.py delete mode 100644 bandit/formatters/utils.py delete mode 100644 bandit/formatters/xml.py delete mode 100644 bandit/formatters/yaml.py delete mode 100644 bandit/plugins/__init__.py delete mode 100644 bandit/plugins/app_debug.py delete mode 100644 bandit/plugins/asserts.py delete mode 100644 bandit/plugins/crypto_request_no_cert_validation.py delete mode 100644 bandit/plugins/exec.py delete mode 100644 bandit/plugins/exec_as_root.py delete mode 100644 bandit/plugins/general_bad_file_permissions.py delete mode 100644 bandit/plugins/general_bind_all_interfaces.py delete mode 100644 bandit/plugins/general_hardcoded_password.py delete mode 100644 bandit/plugins/general_hardcoded_tmp.py delete mode 100644 bandit/plugins/hashlib_new_insecure_functions.py delete mode 100644 bandit/plugins/injection_paramiko.py delete mode 100644 bandit/plugins/injection_shell.py delete mode 100644 bandit/plugins/injection_sql.py delete mode 100644 bandit/plugins/injection_wildcard.py delete mode 100644 bandit/plugins/insecure_ssl_tls.py delete mode 100644 bandit/plugins/jinja2_templates.py delete mode 100644 bandit/plugins/mako_templates.py delete mode 100644 bandit/plugins/secret_config_option.py delete mode 100644 bandit/plugins/try_except_continue.py delete mode 100644 bandit/plugins/try_except_pass.py delete mode 100644 bandit/plugins/weak_cryptographic_key.py delete mode 100644 bandit/plugins/yaml_load.py delete mode 100644 bindep.txt delete mode 100644 doc/requirements.txt delete mode 100644 doc/source/blacklists/blacklist_calls.rst delete mode 100644 doc/source/blacklists/blacklist_imports.rst delete mode 100644 doc/source/blacklists/index.rst delete mode 100644 doc/source/conf.py delete mode 100644 doc/source/config.rst delete mode 100644 doc/source/formatters/csv.rst delete mode 100644 doc/source/formatters/html.rst delete mode 100644 doc/source/formatters/index.rst delete mode 100644 doc/source/formatters/json.rst delete mode 100644 doc/source/formatters/screen.rst delete mode 100644 doc/source/formatters/text.rst delete mode 100644 doc/source/formatters/xml.rst delete mode 100644 doc/source/formatters/yaml.rst delete mode 100644 doc/source/index.rst delete mode 100644 doc/source/man/bandit.rst delete mode 100644 doc/source/plugins/b101_assert_used.rst delete mode 100644 doc/source/plugins/b102_exec_used.rst delete mode 100644 doc/source/plugins/b103_set_bad_file_permissions.rst delete mode 100644 doc/source/plugins/b104_hardcoded_bind_all_interfaces.rst delete mode 100644 doc/source/plugins/b105_hardcoded_password_string.rst delete mode 100644 doc/source/plugins/b106_hardcoded_password_funcarg.rst delete mode 100644 doc/source/plugins/b107_hardcoded_password_funcdef.rst delete mode 100644 doc/source/plugins/b108_hardcoded_tmp_directory.rst delete mode 100644 doc/source/plugins/b109_password_config_option_not_marked_secret.rst delete mode 100644 doc/source/plugins/b110_try_except_pass.rst delete mode 100644 doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst delete mode 100644 doc/source/plugins/b112_try_except_continue.rst delete mode 100644 doc/source/plugins/b201_flask_debug_true.rst delete mode 100644 doc/source/plugins/b501_request_with_no_cert_validation.rst delete mode 100644 doc/source/plugins/b502_ssl_with_bad_version.rst delete mode 100644 doc/source/plugins/b503_ssl_with_bad_defaults.rst delete mode 100644 doc/source/plugins/b504_ssl_with_no_version.rst delete mode 100644 doc/source/plugins/b505_weak_cryptographic_key.rst delete mode 100644 doc/source/plugins/b506_yaml_load.rst delete mode 100644 doc/source/plugins/b601_paramiko_calls.rst delete mode 100644 doc/source/plugins/b602_subprocess_popen_with_shell_equals_true.rst delete mode 100644 doc/source/plugins/b603_subprocess_without_shell_equals_true.rst delete mode 100644 doc/source/plugins/b604_any_other_function_with_shell_equals_true.rst delete mode 100644 doc/source/plugins/b605_start_process_with_a_shell.rst delete mode 100644 doc/source/plugins/b606_start_process_with_no_shell.rst delete mode 100644 doc/source/plugins/b607_start_process_with_partial_path.rst delete mode 100644 doc/source/plugins/b608_hardcoded_sql_expressions.rst delete mode 100644 doc/source/plugins/b609_linux_commands_wildcard_injection.rst delete mode 100644 doc/source/plugins/b701_jinja2_autoescape_false.rst delete mode 100644 doc/source/plugins/b702_use_of_mako_templates.rst delete mode 100644 doc/source/plugins/index.rst delete mode 100644 examples/__init__.py delete mode 100644 examples/assert.py delete mode 100644 examples/binding.py delete mode 100644 examples/cipher-modes.py delete mode 100644 examples/ciphers.py delete mode 100644 examples/crypto-md5.py delete mode 100644 examples/eval.py delete mode 100644 examples/exec-as-root.py delete mode 100644 examples/exec-py2.py delete mode 100644 examples/exec-py3.py delete mode 100644 examples/flask_debug.py delete mode 100644 examples/ftplib.py delete mode 100644 examples/hardcoded-passwords.py delete mode 100644 examples/hardcoded-tmp.py delete mode 100644 examples/hashlib_new_insecure_functions.py delete mode 100644 examples/httplib_https.py delete mode 100644 examples/httpoxy_cgihandler.py delete mode 100644 examples/httpoxy_twisted_directory.py delete mode 100644 examples/httpoxy_twisted_script.py delete mode 100644 examples/imports-aliases.py delete mode 100644 examples/imports-from.py delete mode 100644 examples/imports-function.py delete mode 100644 examples/imports-with-importlib.py delete mode 100644 examples/imports.py delete mode 100644 examples/init-py-test/__init__.py delete mode 100644 examples/init-py-test/subdirectory-okay.py delete mode 100644 examples/input.py delete mode 100644 examples/jinja2_templating.py delete mode 100644 examples/mako_templating.py delete mode 100644 examples/mark_safe.py delete mode 100644 examples/marshal_deserialize.py delete mode 100644 examples/mktemp.py delete mode 100644 examples/multiline_statement.py delete mode 100644 examples/new_candidates-all.py delete mode 100644 examples/new_candidates-none.py delete mode 100644 examples/new_candidates-nosec.py delete mode 100644 examples/new_candidates-some.py delete mode 100644 examples/nonsense.py delete mode 100644 examples/nonsense2.py delete mode 100644 examples/nosec.py delete mode 100644 examples/okay.py delete mode 100644 examples/os-chmod-py2.py delete mode 100644 examples/os-chmod-py3.py delete mode 100644 examples/os-exec.py delete mode 100644 examples/os-popen.py delete mode 100644 examples/os-spawn.py delete mode 100644 examples/os-startfile.py delete mode 100644 examples/os_system.py delete mode 100644 examples/paramiko_injection.py delete mode 100644 examples/partial_path_process.py delete mode 100644 examples/pickle_deserialize.py delete mode 100644 examples/popen_wrappers.py delete mode 100644 examples/pycrypto.py delete mode 100644 examples/random_module.py delete mode 100644 examples/requests-ssl-verify-disabled.py delete mode 100644 examples/secret-config-option.py delete mode 100644 examples/skip.py delete mode 100644 examples/sql_statements.py delete mode 100644 examples/ssl-insecure-version.py delete mode 100644 examples/subprocess_shell.py delete mode 100644 examples/telnetlib.py delete mode 100644 examples/try_except_continue.py delete mode 100644 examples/try_except_pass.py delete mode 100644 examples/unverified_context.py delete mode 100644 examples/urlopen.py delete mode 100644 examples/utils-shell.py delete mode 100644 examples/weak_cryptographic_key_sizes.py delete mode 100644 examples/wildcard-injection.py delete mode 100644 examples/xml_etree_celementtree.py delete mode 100644 examples/xml_etree_elementtree.py delete mode 100644 examples/xml_expatbuilder.py delete mode 100644 examples/xml_expatreader.py delete mode 100644 examples/xml_lxml.py delete mode 100644 examples/xml_minidom.py delete mode 100644 examples/xml_pulldom.py delete mode 100644 examples/xml_sax.py delete mode 100644 examples/xml_xmlrpc.py delete mode 100644 examples/yaml_load.py delete mode 100644 lower-constraints.txt delete mode 100644 playbooks/legacy/bandit-integration-barbican/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-glance/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-glance_store/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-keystone/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-keystonemiddleware/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-magnum/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-oslo.config/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-oslo.log/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-oslo.service/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-oslo.utils/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-oslo.vmware/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-python-keystoneclient/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-python-magnumclient/run.yaml delete mode 100644 playbooks/legacy/bandit-integration-sahara/run.yaml delete mode 100644 pylintrc delete mode 100644 releasenotes/notes/add-pycrypto-warn-c430f40f1d0fb44a.yaml delete mode 100644 releasenotes/notes/add-url-in-json-64f90161ab613a54.yaml delete mode 100644 releasenotes/notes/add-url-in-yaml-0bfdcc93f5b6d118.yaml delete mode 100644 releasenotes/notes/add_reno-b8585fc3ffe775cb.yaml delete mode 100644 releasenotes/notes/target-in-ini-81802418b1cc970f.yaml delete mode 100644 releasenotes/source/_static/.placeholder delete mode 100644 releasenotes/source/_templates/.placeholder delete mode 100644 releasenotes/source/conf.py delete mode 100644 releasenotes/source/index.rst delete mode 100644 releasenotes/source/unreleased.rst delete mode 100644 requirements.txt delete mode 100644 scripts/integration-test.sh delete mode 100644 scripts/main.py delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100644 tests/__init__.py delete mode 100644 tests/functional/__init__.py delete mode 100644 tests/functional/test_baseline.py delete mode 100644 tests/functional/test_functional.py delete mode 100644 tests/functional/test_runtime.py delete mode 100644 tests/unit/__init__.py delete mode 100644 tests/unit/cli/__init__.py delete mode 100644 tests/unit/cli/test_baseline.py delete mode 100644 tests/unit/cli/test_config_generator.py delete mode 100644 tests/unit/cli/test_main.py delete mode 100644 tests/unit/core/__init__.py delete mode 100644 tests/unit/core/test_blacklisting.py delete mode 100644 tests/unit/core/test_config.py delete mode 100644 tests/unit/core/test_context.py delete mode 100644 tests/unit/core/test_issue.py delete mode 100644 tests/unit/core/test_manager.py delete mode 100644 tests/unit/core/test_meta_ast.py delete mode 100644 tests/unit/core/test_test_set.py delete mode 100644 tests/unit/core/test_util.py delete mode 100644 tests/unit/formatters/__init__.py delete mode 100644 tests/unit/formatters/test_csv.py delete mode 100644 tests/unit/formatters/test_html.py delete mode 100644 tests/unit/formatters/test_json.py delete mode 100644 tests/unit/formatters/test_screen.py delete mode 100644 tests/unit/formatters/test_text.py delete mode 100644 tests/unit/formatters/test_xml.py delete mode 100644 tests/unit/formatters/test_yaml.py delete mode 100755 tools/openstack_coverage.py delete mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 8b87fce8..00000000 --- a/.coveragerc +++ /dev/null @@ -1,3 +0,0 @@ -[report] -include = bandit/* -omit = bandit/tests/functional/* diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 40996b39..00000000 --- a/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -env* -venv* -*.pyc -.DS_Store -*.egg -*.egg-info -.eggs/ -.idea/ -.tox -.stestr -build/* -cover/* -.coverage* -doc/build/* -ChangeLog -doc/source/api -.*.sw? -AUTHORS -releasenotes/build diff --git a/.stestr.conf b/.stestr.conf deleted file mode 100644 index 64fe016a..00000000 --- a/.stestr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_path=${OS_TEST_PATH:-./tests/unit} -top_dir=./ -group_regex=.*(test_cert_setup) diff --git a/.zuul.yaml b/.zuul.yaml deleted file mode 100644 index be7e0b6b..00000000 --- a/.zuul.yaml +++ /dev/null @@ -1,179 +0,0 @@ -- job: - name: bandit-integration-barbican - parent: legacy-base - run: playbooks/legacy/bandit-integration-barbican/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/barbican - - openstack/requirements - -- job: - name: bandit-integration-glance - parent: legacy-base - run: playbooks/legacy/bandit-integration-glance/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/glance - - openstack/requirements - -- job: - name: bandit-integration-glance_store - parent: legacy-base - run: playbooks/legacy/bandit-integration-glance_store/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/glance - - openstack/glance_store - - openstack/requirements - -- job: - name: bandit-integration-keystone - parent: legacy-base - run: playbooks/legacy/bandit-integration-keystone/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/keystone - - openstack/requirements - -- job: - name: bandit-integration-keystonemiddleware - parent: legacy-base - run: playbooks/legacy/bandit-integration-keystonemiddleware/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/keystone - - openstack/keystonemiddleware - - openstack/requirements - -- job: - name: bandit-integration-magnum - parent: legacy-base - run: playbooks/legacy/bandit-integration-magnum/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/magnum - - openstack/requirements - -- job: - name: bandit-integration-oslo.config - parent: legacy-base - run: playbooks/legacy/bandit-integration-oslo.config/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/oslo.config - - openstack/requirements - -- job: - name: bandit-integration-oslo.log - parent: legacy-base - run: playbooks/legacy/bandit-integration-oslo.log/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/oslo.log - - openstack/requirements - -- job: - name: bandit-integration-oslo.service - parent: legacy-base - run: playbooks/legacy/bandit-integration-oslo.service/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/oslo.service - - openstack/requirements - -- job: - name: bandit-integration-oslo.utils - parent: legacy-base - run: playbooks/legacy/bandit-integration-oslo.utils/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/oslo.utils - - openstack/requirements - -- job: - name: bandit-integration-oslo.vmware - parent: legacy-base - run: playbooks/legacy/bandit-integration-oslo.vmware/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/oslo.vmware - - openstack/requirements - -- job: - name: bandit-integration-python-keystoneclient - parent: legacy-base - run: playbooks/legacy/bandit-integration-python-keystoneclient/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/keystone - - openstack/python-keystoneclient - - openstack/requirements - -- job: - name: bandit-integration-python-magnumclient - parent: legacy-base - run: playbooks/legacy/bandit-integration-python-magnumclient/run.yaml - timeout: 1800 - required-projects: - - openstack/bandit - - openstack/magnum - - openstack/python-magnumclient - - openstack/requirements - -- job: - name: bandit-integration-sahara - parent: legacy-base - run: playbooks/legacy/bandit-integration-sahara/run.yaml - timeout: 1800 - required-projects: - - openstack/ara - - openstack/bandit - - openstack/requirements - - openstack/sahara - -- project: - check: - jobs: - - bandit-integration-barbican - - bandit-integration-glance - - bandit-integration-keystone - - bandit-integration-glance_store - - bandit-integration-keystonemiddleware - - bandit-integration-magnum - - bandit-integration-oslo.config - - bandit-integration-oslo.log - - bandit-integration-oslo.service - - bandit-integration-oslo.utils - - bandit-integration-oslo.vmware - - bandit-integration-python-keystoneclient - - bandit-integration-python-magnumclient - - bandit-integration-sahara - - openstack-tox-lower-constraints - gate: - jobs: - - bandit-integration-barbican - - bandit-integration-glance - - bandit-integration-keystone - - bandit-integration-glance_store - - bandit-integration-keystonemiddleware - - bandit-integration-magnum - - bandit-integration-oslo.config - - bandit-integration-oslo.log - - bandit-integration-oslo.service - - bandit-integration-oslo.utils - - bandit-integration-oslo.vmware - - bandit-integration-python-keystoneclient - - bandit-integration-python-magnumclient - - openstack-tox-lower-constraints diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 68c771a0..00000000 --- a/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - diff --git a/README.rst b/README.rst index b5c59898..d84c01fb 100644 --- a/README.rst +++ b/README.rst @@ -1,428 +1,13 @@ Bandit ====== -.. image:: https://governance.openstack.org/badges/bandit.svg - :target: https://governance.openstack.org/reference/tags/index.html - :alt: Bandit team and repository tags +This project is no longer maintained in OpenStack. -.. image:: https://img.shields.io/pypi/v/bandit.svg - :target: https://pypi.python.org/pypi/bandit/ - :alt: Latest Version +Please visit PyCQA to raise issues or make contributions: -.. image:: https://img.shields.io/pypi/pyversions/bandit.svg - :target: https://pypi.python.org/pypi/bandit/ - :alt: Python Versions +https://github.com/PyCQA/bandit -.. image:: https://img.shields.io/pypi/format/bandit.svg - :target: https://pypi.python.org/pypi/bandit/ - :alt: Format - -.. image:: https://img.shields.io/badge/license-Apache%202-blue.svg - :target: https://git.openstack.org/cgit/openstack/bandit/plain/LICENSE - :alt: License - -A security linter from OpenStack Security - -* Free software: Apache license -* Documentation: https://wiki.openstack.org/wiki/Security/Projects/Bandit -* Source: https://git.openstack.org/cgit/openstack/bandit -* Bugs: https://bugs.launchpad.net/bandit - -Overview --------- -Bandit is a tool designed to find common security issues in Python code. To do -this Bandit processes each file, builds an AST from it, and runs appropriate -plugins against the AST nodes. Once Bandit has finished scanning all the files -it generates a report. - -Installation ------------- -Bandit is distributed on PyPI. The best way to install it is with pip: - - -Create a virtual environment (optional):: - - virtualenv bandit-env - -Install Bandit:: - - pip install bandit - # Or if you're working with a Python 3.5 project - pip3.5 install bandit - -Run Bandit:: - - bandit -r path/to/your/code - - -Bandit can also be installed from source. To do so, download the source tarball -from PyPI, then install it:: - - python setup.py install - - -Usage ------ -Example usage across a code tree:: - - bandit -r ~/openstack-repo/keystone - -Example usage across the ``examples/`` directory, showing three lines of -context and only reporting on the high-severity issues:: - - bandit examples/*.py -n 3 -lll - -Bandit can be run with profiles. To run Bandit against the examples directory -using only the plugins listed in the ``ShellInjection`` profile:: - - bandit examples/*.py -p ShellInjection - -Bandit also supports passing lines of code to scan using standard input. To -run Bandit with standard input:: - - cat examples/imports.py | bandit - - -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,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 ...]] - - Bandit - a Python source code security analyzer - - positional arguments: - targets source file(s) or directory(s) to be tested - - optional arguments: - -h, --help show this help message and exit - -r, --recursive find and process files in subdirectories - -a {file,vuln}, --aggregate {file,vuln} - aggregate output by vulnerability (default) or by - filename - -n CONTEXT_LINES, --number CONTEXT_LINES - maximum number of code lines to output for each issue - -c CONFIG_FILE, --configfile CONFIG_FILE - optional config file to use for selecting plugins and - overriding defaults - -p PROFILE, --profile PROFILE - profile to use (defaults to executing all tests) - -t TESTS, --tests TESTS - comma-separated list of test IDs to run - -s SKIPS, --skip SKIPS - comma-separated list of test IDs to skip - -l, --level report only issues of a given severity level or higher - (-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,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 - files - -d, --debug turn on debug mode - --ignore-nosec do not skip lines with # nosec comments - -x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS - comma-separated list of paths to exclude from scan - (note that these are in addition to the excluded paths - provided in the config file) - -b BASELINE, --baseline BASELINE - path of a baseline report to compare against (only - JSON-formatted files are accepted) - --ini INI_PATH path to a .bandit file that supplies command line - 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 - B104 hardcoded_bind_all_interfaces - B105 hardcoded_password_string - B106 hardcoded_password_funcarg - B107 hardcoded_password_default - B108 hardcoded_tmp_directory - B109 password_config_option_not_marked_secret - B110 try_except_pass - B111 execute_with_run_as_root_equals_true - B112 try_except_continue - B201 flask_debug_true - B301 pickle - B302 marshal - B303 md5 - B304 ciphers - B305 cipher_modes - B306 mktemp_q - B307 eval - B308 mark_safe - B309 httpsconnection - B310 urllib_urlopen - B311 random - B312 telnetlib - B313 xml_bad_cElementTree - B314 xml_bad_ElementTree - B315 xml_bad_expatreader - B316 xml_bad_expatbuilder - B317 xml_bad_sax - B318 xml_bad_minidom - B319 xml_bad_pulldom - B320 xml_bad_etree - B321 ftplib - B322 input - B323 unverified_context - B324 hashlib_new_insecure_functions - B401 import_telnetlib - B402 import_ftplib - B403 import_pickle - B404 import_subprocess - B405 import_xml_etree - B406 import_xml_sax - B407 import_xml_expat - B408 import_xml_minidom - B409 import_xml_pulldom - B410 import_lxml - B411 import_xmlrpclib - B412 import_httpoxy - B501 request_with_no_cert_validation - B502 ssl_with_bad_version - B503 ssl_with_bad_defaults - B504 ssl_with_no_version - B505 weak_cryptographic_key - B506 yaml_load - B601 paramiko_calls - B602 subprocess_popen_with_shell_equals_true - B603 subprocess_without_shell_equals_true - B604 any_other_function_with_shell_equals_true - B605 start_process_with_a_shell - B606 start_process_with_no_shell - B607 start_process_with_partial_path - B608 hardcoded_sql_expressions - B609 linux_commands_wildcard_injection - B701 jinja2_autoescape_false - B702 use_of_mako_templates - - -Configuration -------------- -An optional config file may be supplied and may include: - - lists of tests which should or shouldn't be run - - exclude_dirs - sections of the path, that if matched, will be excluded from - scanning - - overridden plugin settings - may provide different settings for some - plugins - -Per Project Command Line Args ------------------------------ -Projects may include a `.bandit` file that specifies command line arguments -that should be supplied for that project. The currently supported arguments -are: - - - targets: comma separated list of target dirs/files to run bandit on - - exclude: comma separated list of excluded paths - - skips: comma separated list of tests to skip - - tests: comma separated list of tests to run - -To use this, put a .bandit file in your project's directory. For example: - -:: - - [bandit] - exclude: /test - -:: - - [bandit] - tests: B101,B102,B301 - - -Exclusions ----------- -In the event that a line of code triggers a Bandit issue, but that the line -has been reviewed and the issue is a false positive or acceptable for some -other reason, the line can be marked with a ``# nosec`` and any results -associated with it will not be reported. - -For example, although this line may cause Bandit to report a potential -security issue, it will not be reported:: - - self.process = subprocess.Popen('/bin/echo', shell=True) # nosec - - -Vulnerability Tests -------------------- -Vulnerability tests or "plugins" are defined in files in the plugins directory. - -Tests are written in Python and are autodiscovered from the plugins directory. -Each test can examine one or more type of Python statements. Tests are marked -with the types of Python statements they examine (for example: function call, -string, import, etc). - -Tests are executed by the ``BanditNodeVisitor`` object as it visits each node -in the AST. - -Test results are maintained in the ``BanditResultStore`` and aggregated for -output at the completion of a test run. - - -Writing Tests -------------- -To write a test: - - Identify a vulnerability to build a test for, and create a new file in - examples/ that contains one or more cases of that vulnerability. - - Consider the vulnerability you're testing for, mark the function with one - or more of the appropriate decorators: - - @checks('Call') - - @checks('Import', 'ImportFrom') - - @checks('Str') - - Create a new Python source file to contain your test, you can reference - existing tests for examples. - - The function that you create should take a parameter "context" which is - an instance of the context class you can query for information about the - current element being examined. You can also get the raw AST node for - more advanced use cases. Please see the context.py file for more. - - Extend your Bandit configuration file as needed to support your new test. - - Execute Bandit against the test file you defined in examples/ and ensure - that it detects the vulnerability. Consider variations on how this - vulnerability might present itself and extend the example file and the test - function accordingly. - - -Extending Bandit ----------------- - -Bandit allows users to write and register extensions for checks and formatters. -Bandit will load plugins from two entry-points: - -- `bandit.formatters` -- `bandit.plugins` - -Formatters need to accept 4 things: - -- `result_store`: An instance of `bandit.core.BanditResultStore` -- `file_list`: The list of files which were inspected in the scope -- `scores`: The scores awarded to each file in the scope -- `excluded_files`: The list of files that were excluded from the scope - -Plugins tend to take advantage of the `bandit.checks` decorator which allows -the author to register a check for a particular type of AST node. For example - -:: - - @bandit.checks('Call') - def prohibit_unsafe_deserialization(context): - if 'unsafe_load' in context.call_function_name_qual: - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text="Unsafe deserialization detected." - ) - -To register your plugin, you have two options: - -1. If you're using setuptools directly, add something like the following to - your ``setup`` call:: - - # If you have an imaginary bson formatter in the bandit_bson module - # and a function called `formatter`. - entry_points={'bandit.formatters': ['bson = bandit_bson:formatter']} - # Or a check for using mako templates in bandit_mako that - entry_points={'bandit.plugins': ['mako = bandit_mako']} - -2. If you're using pbr, add something like the following to your `setup.cfg` - file:: - - [entry_points] - bandit.formatters = - bson = bandit_bson:formatter - bandit.plugins = - mako = bandit_mako - -Contributing ------------- -Contributions to Bandit are always welcome! We can be found on -#openstack-security on Freenode IRC. - -The best way to get started with Bandit is to grab the source:: - - git clone https://git.openstack.org/openstack/bandit.git - -You can test any changes with tox:: - - pip install tox - tox -e pep8 - tox -e py27 - tox -e py35 - tox -e docs - tox -e cover - -Reporting Bugs --------------- -Bugs should be reported on Launchpad. To file a bug against Bandit, visit: -https://bugs.launchpad.net/bandit/+filebug - -Under Which Version of Python Should I Install Bandit? ------------------------------------------------------- -The answer to this question depends on the project(s) you will be running -Bandit against. If your project is only compatible with Python 2.7, you -should install Bandit to run under Python 2.7. If your project is only -compatible with Python 3.5, then use 3.5 respectively. If your project supports -both, you *could* run Bandit with both versions but you don't have to. - -Bandit uses the `ast` module from Python's standard library in order to -analyze your Python code. The `ast` module is only able to parse Python code -that is valid in the version of the interpreter from which it is imported. In -other words, if you try to use Python 2.7's `ast` module to parse code written -for 3.5 that uses, for example, `yield from` with asyncio, then you'll have -syntax errors that will prevent Bandit from working properly. Alternatively, -if you are relying on 2.7's octal notation of `0777` then you'll have a syntax -error if you run Bandit on 3.x. - - -References -========== - -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: -https://greentreesnakes.readthedocs.org/en/latest/ - -Documentation of the various types of AST nodes that Bandit currently covers -or could be extended to cover: -https://greentreesnakes.readthedocs.org/en/latest/nodes.html +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". diff --git a/bandit/__init__.py b/bandit/__init__.py deleted file mode 100644 index 53d6d230..00000000 --- a/bandit/__init__.py +++ /dev/null @@ -1,31 +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 pbr.version - -from bandit.core import config # noqa -from bandit.core import context # noqa -from bandit.core import manager # noqa -from bandit.core import meta_ast # noqa -from bandit.core import node_visitor # noqa -from bandit.core import test_set # noqa -from bandit.core import tester # noqa -from bandit.core import utils # noqa -from bandit.core.constants import * # noqa -from bandit.core.issue import * # noqa -from bandit.core.test_properties import * # noqa - -__version__ = pbr.version.VersionInfo('bandit').version_string() diff --git a/bandit/blacklists/__init__.py b/bandit/blacklists/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py deleted file mode 100644 index 52a23e9a..00000000 --- a/bandit/blacklists/calls.py +++ /dev/null @@ -1,544 +0,0 @@ -# -*- 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. - -r""" -==================================================== -Blacklist various Python calls known to be dangerous -==================================================== - -This blacklist data checks for a number of Python calls known to have possible -security implications. The following blacklist tests are run against any -function calls encoutered in the scanned code base, triggered by encoutering -ast.Call nodes. - -B301: pickle ------------- - -Pickle library appears to be in use, possible security issue. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B301 | pickle | - pickle.loads | Medium | -| | | - pickle.load | | -| | | - pickle.Unpickler | | -| | | - cPickle.loads | | -| | | - cPickle.load | | -| | | - cPickle.Unpickler | | -+------+---------------------+------------------------------------+-----------+ - -B302: marshal -------------- - -Deserialization with the marshal module is possibly dangerous. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B302 | marshal | - marshal.load | Medium | -| | | - marshal.loads | | -+------+---------------------+------------------------------------+-----------+ - -B303: md5 ---------- - -Use of insecure MD2, MD4, MD5, or SHA1 hash function. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B303 | md5 | - hashlib.md5 | Medium | -| | | - hashlib.sha1 | | -| | | - Crypto.Hash.MD2.new | | -| | | - Crypto.Hash.MD4.new | | -| | | - Crypto.Hash.MD5.new | | -| | | - Crypto.Hash.SHA.new | | -| | | - Cryptodome.Hash.MD2.new | | -| | | - Cryptodome.Hash.MD4.new | | -| | | - Cryptodome.Hash.MD5.new | | -| | | - Cryptodome.Hash.SHA.new | | -| | | - cryptography.hazmat.primitives | | -| | | .hashes.MD5 | | -| | | - cryptography.hazmat.primitives | | -| | | .hashes.SHA1 | | -+------+---------------------+------------------------------------+-----------+ - -B304 - B305: ciphers and modes ------------------------------- - -Use of insecure cipher or cipher mode. Replace with a known secure cipher such -as AES. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B304 | ciphers | - Crypto.Cipher.ARC2.new | High | -| | | - Crypto.Cipher.ARC4.new | | -| | | - Crypto.Cipher.Blowfish.new | | -| | | - Crypto.Cipher.DES.new | | -| | | - Crypto.Cipher.XOR.new | | -| | | - Cryptodome.Cipher.ARC2.new | | -| | | - Cryptodome.Cipher.ARC4.new | | -| | | - Cryptodome.Cipher.Blowfish.new | | -| | | - Cryptodome.Cipher.DES.new | | -| | | - Cryptodome.Cipher.XOR.new | | -| | | - cryptography.hazmat.primitives | | -| | | .ciphers.algorithms.ARC4 | | -| | | - cryptography.hazmat.primitives | | -| | | .ciphers.algorithms.Blowfish | | -| | | - cryptography.hazmat.primitives | | -| | | .ciphers.algorithms.IDEA | | -+------+---------------------+------------------------------------+-----------+ -| B305 | cipher_modes | - cryptography.hazmat.primitives | Medium | -| | | .ciphers.modes.ECB | | -+------+---------------------+------------------------------------+-----------+ - -B306: mktemp_q --------------- - -Use of insecure and deprecated function (mktemp). - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B306 | mktemp_q | - tempfile.mktemp | Medium | -+------+---------------------+------------------------------------+-----------+ - -B307: eval ----------- - -Use of possibly insecure function - consider using safer ast.literal_eval. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B307 | eval | - eval | Medium | -+------+---------------------+------------------------------------+-----------+ - -B308: mark_safe ---------------- - -Use of mark_safe() may expose cross-site scripting vulnerabilities and should -be reviewed. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B308 | mark_safe | - django.utils.safestring.mark_safe| Medium | -+------+---------------------+------------------------------------+-----------+ - -B309: httpsconnection ---------------------- - -Use of HTTPSConnection on older versions of Python prior to 2.7.9 and 3.4.3 do -not provide security, see https://wiki.openstack.org/wiki/OSSN/OSSN-0033 - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B309 | httpsconnection | - httplib.HTTPSConnection | Medium | -| | | - http.client.HTTPSConnection | | -| | | - six.moves.http_client | | -| | | .HTTPSConnection | | -+------+---------------------+------------------------------------+-----------+ - -B310: urllib_urlopen --------------------- - -Audit url open for permitted schemes. Allowing use of 'file:'' or custom -schemes is often unexpected. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B310 | urllib_urlopen | - urllib.urlopen | Medium | -| | | - 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 | | -+------+---------------------+------------------------------------+-----------+ - -B311: random ------------- - -Standard pseudo-random generators are not suitable for security/cryptographic -purposes. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B311 | random | - random.random | Low | -| | | - random.randrange | | -| | | - random.randint | | -| | | - random.choice | | -| | | - random.uniform | | -| | | - random.triangular | | -+------+---------------------+------------------------------------+-----------+ - -B312: telnetlib ---------------- - -Telnet-related functions are being called. Telnet is considered insecure. Use -SSH or some other encrypted protocol. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B312 | telnetlib | - telnetlib.\* | High | -+------+---------------------+------------------------------------+-----------+ - -B313 - B320: XML ----------------- - -Most of this is based off of Christian Heimes' work on defusedxml: -https://pypi.python.org/pypi/defusedxml/#defusedxml-sax - -Using various XLM methods to parse untrusted XML data is known to be vulnerable -to XML attacks. Methods should be replaced with their defusedxml equivalents. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B313 | xml_bad_cElementTree| - xml.etree.cElementTree.parse | Medium | -| | | - xml.etree.cElementTree.iterparse | | -| | | - xml.etree.cElementTree.fromstring| | -| | | - xml.etree.cElementTree.XMLParser | | -+------+---------------------+------------------------------------+-----------+ -| B314 | xml_bad_ElementTree | - xml.etree.ElementTree.parse | Medium | -| | | - xml.etree.ElementTree.iterparse | | -| | | - xml.etree.ElementTree.fromstring | | -| | | - xml.etree.ElementTree.XMLParser | | -+------+---------------------+------------------------------------+-----------+ -| B315 | xml_bad_expatreader | - xml.sax.expatreader.create_parser| Medium | -+------+---------------------+------------------------------------+-----------+ -| B316 | xml_bad_expatbuilder| - xml.dom.expatbuilder.parse | Medium | -| | | - xml.dom.expatbuilder.parseString | | -+------+---------------------+------------------------------------+-----------+ -| B317 | xml_bad_sax | - xml.sax.parse | Medium | -| | | - xml.sax.parseString | | -| | | - xml.sax.make_parser | | -+------+---------------------+------------------------------------+-----------+ -| B318 | xml_bad_minidom | - xml.dom.minidom.parse | Medium | -| | | - xml.dom.minidom.parseString | | -+------+---------------------+------------------------------------+-----------+ -| B319 | xml_bad_pulldom | - xml.dom.pulldom.parse | Medium | -| | | - xml.dom.pulldom.parseString | | -+------+---------------------+------------------------------------+-----------+ -| B320 | xml_bad_etree | - lxml.etree.parse | Medium | -| | | - lxml.etree.fromstring | | -| | | - lxml.etree.RestrictedElement | | -| | | - lxml.etree.GlobalParserTLS | | -| | | - lxml.etree.getDefaultParser | | -| | | - lxml.etree.check_docinfo | | -+------+---------------------+------------------------------------+-----------+ - -B321: ftplib ------------- - -FTP-related functions are being called. FTP is considered insecure. Use -SSH/SFTP/SCP or some other encrypted protocol. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| B321 | ftplib | - ftplib.\* | High | -+------+---------------------+------------------------------------+-----------+ - -B322: input ------------- - -The input method in Python 2 will read from standard input, evaluate and -run the resulting string as python source code. This is similar, though in -many ways worse, then using eval. On Python 2, use raw_input instead, input -is safe in Python 3. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Calls | Severity | -+======+=====================+====================================+===========+ -| 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 | -+======+=====================+====================================+===========+ -| B323 | unverified_context | - ssl._create_unverified_context | Medium | -+------+---------------------+------------------------------------+-----------+ - -""" - -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', - 'hashlib.sha1', - 'Crypto.Hash.MD2.new', - 'Crypto.Hash.MD4.new', - 'Crypto.Hash.MD5.new', - 'Crypto.Hash.SHA.new', - 'Cryptodome.Hash.MD2.new', - 'Cryptodome.Hash.MD4.new', - 'Cryptodome.Hash.MD5.new', - 'Cryptodome.Hash.SHA.new', - 'cryptography.hazmat.primitives.hashes.MD5', - 'cryptography.hazmat.primitives.hashes.SHA1'], - 'Use of insecure MD2, MD4, MD5, or SHA1 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', - 'Cryptodome.Cipher.ARC2.new', - 'Cryptodome.Cipher.ARC4.new', - 'Cryptodome.Cipher.Blowfish.new', - 'Cryptodome.Cipher.DES.new', - 'Cryptodome.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', ['django.utils.safestring.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 on older versions of Python prior to 2.7.9 ' - 'and 3.4.3 do 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 functions 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 or make sure ' - 'defusedxml.defuse_stdlib() is called') - - 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'], - ('Using {name} to parse untrusted XML data is known to be ' - 'vulnerable to XML attacks. Replace {name} with its ' - 'defusedxml equivalent function.') - )) - - # end of XML tests - - sets.append(utils.build_conf_dict( - 'ftplib', 'B321', ['ftplib.*'], - 'FTP-related functions are being called. FTP is considered ' - 'insecure. Use SSH/SFTP/SCP or some other encrypted protocol.', - 'HIGH' - )) - - sets.append(utils.build_conf_dict( - 'input', 'B322', ['input'], - 'The input method in Python 2 will read from standard input, ' - 'evaluate and run the resulting string as python source code. This ' - 'is similar, though in many ways worse, then using eval. On Python ' - '2, use raw_input instead, input is safe in Python 3.', - '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/bandit/blacklists/imports.py b/bandit/blacklists/imports.py deleted file mode 100644 index 9bb55994..00000000 --- a/bandit/blacklists/imports.py +++ /dev/null @@ -1,305 +0,0 @@ -# -*- 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. - -r""" -====================================================== -Blacklist various Python imports known to be dangerous -====================================================== - -This blacklist data checks for a number of Python modules known to have -possible security implications. The following blacklist tests are run against -any import statements or calls encountered in the scanned code base. - -Note that the XML rules listed here are mostly based off of Christian Heimes' -work on defusedxml: https://pypi.python.org/pypi/defusedxml - -B401: import_telnetlib ----------------------- - -A telnet-related module is being imported. Telnet is considered insecure. Use -SSH or some other encrypted protocol. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B401 | import_telnetlib | - telnetlib | high | -+------+---------------------+------------------------------------+-----------+ - -B402: import_ftplib -------------------- -A FTP-related module is being imported. FTP is considered insecure. Use -SSH/SFTP/SCP or some other encrypted protocol. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B402 | inport_ftplib | - ftplib | high | -+------+---------------------+------------------------------------+-----------+ - -B403: import_pickle -------------------- - -Consider possible security implications associated with these modules. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B403 | import_pickle | - pickle | low | -| | | - cPickle | | -+------+---------------------+------------------------------------+-----------+ - -B404: import_subprocess ------------------------ - -Consider possible security implications associated with these modules. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B404 | import_subprocess | - subprocess | low | -+------+---------------------+------------------------------------+-----------+ - - -B405: import_xml_etree ----------------------- - -Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package, -or make sure defusedxml.defuse_stdlib() is called. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B405 | import_xml_etree | - xml.etree.cElementTree | low | -| | | - xml.etree.ElementTree | | -+------+---------------------+------------------------------------+-----------+ - -B406: import_xml_sax --------------------- - -Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package, -or make sure defusedxml.defuse_stdlib() is called. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B406 | import_xml_sax | - xml.sax | low | -+------+---------------------+------------------------------------+-----------+ - -B407: import_xml_expat ----------------------- - -Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package, -or make sure defusedxml.defuse_stdlib() is called. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B407 | import_xml_expat | - xml.dom.expatbuilder | low | -+------+---------------------+------------------------------------+-----------+ - -B408: import_xml_minidom ------------------------- - -Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package, -or make sure defusedxml.defuse_stdlib() is called. - - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B408 | import_xml_minidom | - xml.dom.minidom | low | -+------+---------------------+------------------------------------+-----------+ - -B409: import_xml_pulldom ------------------------- - -Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package, -or make sure defusedxml.defuse_stdlib() is called. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B409 | import_xml_pulldom | - xml.dom.pulldom | low | -+------+---------------------+------------------------------------+-----------+ - -B410: import_lxml ------------------ - -Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B410 | import_lxml | - lxml | low | -+------+---------------------+------------------------------------+-----------+ - -B411: import_xmlrpclib ----------------------- - -XMLRPC is particularly dangerous as it is also concerned with communicating -data over a network. Use defused.xmlrpc.monkey_patch() function to monkey-patch -xmlrpclib and mitigate remote XML attacks. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B411 | import_xmlrpclib | - xmlrpclib | high | -+------+---------------------+------------------------------------+-----------+ - -B412: import_httpoxy --------------------- -httpoxy is a set of vulnerabilities that affect application code running in -CGI, or CGI-like environments. The use of CGI for web applications should be -avoided to prevent this class of attack. More details are available -at https://httpoxy.org/. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B412 | import_httpoxy | - wsgiref.handlers.CGIHandler | high | -| | | - twisted.web.twcgi.CGIScript | | -+------+---------------------+------------------------------------+-----------+ - -B413: import_pycrypto ---------------------- -pycrypto library is known to have publicly disclosed buffer overflow -vulnerability https://github.com/dlitz/pycrypto/issues/176. It is no longer -actively maintained and has been deprecated in favor of pyca/cryptography -library. - -+------+---------------------+------------------------------------+-----------+ -| ID | Name | Imports | Severity | -+======+=====================+====================================+===========+ -| B413 | import_pycrypto | - Crypto.Cipher | high | -| | | - Crypto.Hash | | -| | | - Crypto.IO | | -| | | - Crypto.Protocol | | -| | | - Crypto.PublicKey | | -| | | - Crypto.Random | | -| | | - Crypto.Signature | | -| | | - Crypto.Util | | -+------+---------------------+------------------------------------+-----------+ - -""" - -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( - 'import_telnetlib', '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( - 'import_ftplib', '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( - 'import_pickle', 'B403', ['pickle', 'cPickle'], - 'Consider possible security implications associated with ' - '{name} module.', 'LOW' - )) - - sets.append(utils.build_conf_dict( - 'import_subprocess', 'B404', ['subprocess'], - '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 - - xml_msg = ('Using {name} to parse untrusted XML data is known to be ' - 'vulnerable to XML attacks. Replace {name} with the equivalent ' - 'defusedxml package, or make sure defusedxml.defuse_stdlib() ' - 'is called.') - lxml_msg = ('Using {name} to parse untrusted XML data is known to be ' - 'vulnerable to XML attacks. Replace {name} with the ' - 'equivalent defusedxml package.') - - sets.append(utils.build_conf_dict( - 'import_xml_etree', 'B405', - ['xml.etree.cElementTree', 'xml.etree.ElementTree'], xml_msg, 'LOW')) - - sets.append(utils.build_conf_dict( - 'import_xml_sax', 'B406', ['xml.sax'], xml_msg, 'LOW')) - - sets.append(utils.build_conf_dict( - 'import_xml_expat', 'B407', ['xml.dom.expatbuilder'], xml_msg, 'LOW')) - - sets.append(utils.build_conf_dict( - 'import_xml_minidom', 'B408', ['xml.dom.minidom'], xml_msg, 'LOW')) - - sets.append(utils.build_conf_dict( - 'import_xml_pulldom', 'B409', ['xml.dom.pulldom'], xml_msg, 'LOW')) - - sets.append(utils.build_conf_dict( - 'import_lxml', 'B410', ['lxml'], lxml_msg, 'LOW')) - - sets.append(utils.build_conf_dict( - 'import_xmlrpclib', 'B411', ['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')) - - sets.append(utils.build_conf_dict( - 'import_httpoxy', 'B412', - ['wsgiref.handlers.CGIHandler', 'twisted.web.twcgi.CGIScript', - 'twisted.web.twcgi.CGIDirectory'], - 'Consider possible security implications associated with ' - '{name} module.', 'HIGH' - )) - - sets.append(utils.build_conf_dict( - 'import_pycrypto', 'B413', - ['Crypto.Cipher', - 'Crypto.Hash', - 'Crypto.IO', - 'Crypto.Protocol', - 'Crypto.PublicKey', - 'Crypto.Random', - 'Crypto.Signature', - 'Crypto.Util'], - 'The pyCrypto library and its module {name} are no longer actively ' - 'maintained and have been deprecated. ' - 'Consider using pyca/cryptography library.', 'HIGH')) - - return {'Import': sets, 'ImportFrom': sets, 'Call': sets} diff --git a/bandit/blacklists/utils.py b/bandit/blacklists/utils.py deleted file mode 100644 index f14e4872..00000000 --- a/bandit/blacklists/utils.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- 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/cli/__init__.py b/bandit/cli/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bandit/cli/baseline.py b/bandit/cli/baseline.py deleted file mode 100644 index fcc3d08d..00000000 --- a/bandit/cli/baseline.py +++ /dev/null @@ -1,224 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2015 Hewlett-Packard Enterprise -# -# 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. - -# ############################################################################# -# Bandit Baseline is a tool that runs Bandit against a Git commit, and compares -# the current commit findings to the parent commit findings. - -# To do this it checks out the parent commit, runs Bandit (with any provided -# filters or profiles), checks out the current commit, runs Bandit, and then -# reports on any new findings. -# ############################################################################# - -import argparse -import contextlib -import logging -import os -import shutil -import subprocess -import sys -import tempfile - -import git - -bandit_args = sys.argv[1:] -baseline_tmp_file = '_bandit_baseline_run.json_' -current_commit = None -default_output_format = 'terminal' -LOG = logging.getLogger(__name__) -repo = None -report_basename = 'bandit_baseline_result' -valid_baseline_formats = ['txt', 'html', 'json'] - - -def main(): - # our cleanup function needs this and can't be passed arguments - global current_commit - global repo - - parent_commit = None - output_format = None - repo = None - report_fname = None - - init_logger() - - output_format, repo, report_fname = initialize() - - if not repo: - sys.exit(2) - - # #################### Find current and parent commits #################### - try: - commit = repo.commit() - current_commit = commit.hexsha - LOG.info('Got current commit: [%s]', commit.name_rev) - - commit = commit.parents[0] - parent_commit = commit.hexsha - LOG.info('Got parent commit: [%s]', commit.name_rev) - - except git.GitCommandError: - LOG.error("Unable to get current or parent commit") - sys.exit(2) - except IndexError: - LOG.error("Parent commit not available") - sys.exit(2) - - # #################### Run Bandit against both commits #################### - output_type = (['-f', 'txt'] if output_format == default_output_format - else ['-o', report_fname]) - - with baseline_setup() as t: - - bandit_tmpfile = "{}/{}".format(t, baseline_tmp_file) - - steps = [{'message': 'Getting Bandit baseline results', - 'commit': parent_commit, - 'args': bandit_args + ['-f', 'json', '-o', bandit_tmpfile]}, - - {'message': 'Comparing Bandit results to baseline', - 'commit': current_commit, - 'args': bandit_args + ['-b', bandit_tmpfile] + output_type}] - - return_code = None - - for step in steps: - repo.head.reset(commit=step['commit'], working_tree=True) - - LOG.info(step['message']) - - bandit_command = ['bandit'] + step['args'] - - try: - output = subprocess.check_output(bandit_command) - except subprocess.CalledProcessError as e: - output = e.output - return_code = e.returncode - else: - return_code = 0 - output = output.decode('utf-8') # subprocess returns bytes - - if return_code not in [0, 1]: - LOG.error("Error running command: %s\nOutput: %s\n", - bandit_args, output) - - # #################### Output and exit #################################### - # print output or display message about written report - if output_format == default_output_format: - print(output) - else: - LOG.info("Successfully wrote %s", report_fname) - - # exit with the code the last Bandit run returned - sys.exit(return_code) - - -# #################### Clean up before exit ################################### -@contextlib.contextmanager -def baseline_setup(): - d = tempfile.mkdtemp() - yield d - shutil.rmtree(d, True) - - if repo: - repo.head.reset(commit=current_commit, working_tree=True) - - -# #################### Setup logging ########################################## -def init_logger(): - LOG.handlers = [] - log_level = logging.INFO - log_format_string = "[%(levelname)7s ] %(message)s" - logging.captureWarnings(True) - LOG.setLevel(log_level) - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(logging.Formatter(log_format_string)) - LOG.addHandler(handler) - - -# #################### Perform initialization and validate assumptions ######## -def initialize(): - valid = True - - # #################### Parse Args ######################################### - parser = argparse.ArgumentParser( - description='Bandit Baseline - Generates Bandit results compared to "' - 'a baseline', - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog='Additional Bandit arguments such as severity filtering (-ll) ' - 'can be added and will be passed to Bandit.' - ) - - parser.add_argument('targets', metavar='targets', type=str, nargs='+', - help='source file(s) or directory(s) to be tested') - - parser.add_argument('-f', dest='output_format', action='store', - default='terminal', help='specify output format', - choices=valid_baseline_formats) - - args, unknown = parser.parse_known_args() - - # #################### Setup Output ####################################### - # set the output format, or use a default if not provided - output_format = (args.output_format if args.output_format - else default_output_format) - - if output_format == default_output_format: - LOG.info("No output format specified, using %s", default_output_format) - - # set the report name based on the output format - report_fname = "{}.{}".format(report_basename, output_format) - - # #################### Check Requirements ################################# - try: - repo = git.Repo(os.getcwd()) - - except git.exc.InvalidGitRepositoryError: - LOG.error("Bandit baseline must be called from a git project root") - valid = False - - except git.exc.GitCommandNotFound: - LOG.error("Git command not found") - valid = False - - else: - if repo.is_dirty(): - LOG.error("Current working directory is dirty and must be " - "resolved") - valid = False - - # if output format is specified, we need to be able to write the report - if output_format != default_output_format and os.path.exists(report_fname): - LOG.error("File %s already exists, aborting", report_fname) - valid = False - - # Bandit needs to be able to create this temp file - if os.path.exists(baseline_tmp_file): - LOG.error("Temporary file %s needs to be removed prior to running", - baseline_tmp_file) - valid = False - - # we must validate -o is not provided, as it will mess up Bandit baseline - if '-o' in bandit_args: - LOG.error("Bandit baseline must not be called with the -o option") - valid = False - - return (output_format, repo, report_fname) if valid else (None, None, None) - - -if __name__ == '__main__': - main() diff --git a/bandit/cli/config_generator.py b/bandit/cli/config_generator.py deleted file mode 100644 index 5d532d00..00000000 --- a/bandit/cli/config_generator.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2015 Red Hat Inc. -# -# 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 __future__ import print_function - -import argparse -import importlib -import logging -import os -import sys - -import yaml - -from bandit.core import extension_loader - -PROG_NAME = 'bandit_conf_generator' -LOG = logging.getLogger(__name__) - - -template = """ -### Bandit config file generated from: -# '{cli}' - -### This config may optionally select a subset of tests to run or skip by -### filling out the 'tests' and 'skips' lists given below. If no tests are -### specified for inclusion then it is assumed all tests are desired. The skips -### set will remove specific tests from the include set. This can be controlled -### using the -t/-s CLI options. Note that the same test ID should not appear -### in both 'tests' and 'skips', this would be nonsensical and is detected by -### Bandit at runtime. - -# Available tests: -{test_list} - -# (optional) list included test IDs here, eg '[B101, B406]': -{test} - -# (optional) list skipped test IDs here, eg '[B101, B406]': -{skip} - -### (optional) plugin settings - some test plugins require configuration data -### that may be given here, per-plugin. All bandit test plugins have a built in -### set of sensible defaults and these will be used if no configuration is -### provided. It is not necessary to provide settings for every (or any) plugin -### if the defaults are acceptable. - -{settings} -""" - - -def init_logger(): - LOG.handlers = [] - log_level = logging.INFO - log_format_string = "[%(levelname)5s]: %(message)s" - logging.captureWarnings(True) - LOG.setLevel(log_level) - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(logging.Formatter(log_format_string)) - LOG.addHandler(handler) - - -def parse_args(): - help_description = """Bandit Config Generator - - This tool is used to generate an optional profile. The profile may be used - to include or skip tests and override values for plugins. - - When used to store an output profile, this tool will output a template that - includes all plugins and their default settings. Any settings which aren't - being overridden can be safely removed from the profile and default values - will be used. Bandit will prefer settings from the profile over the built - in values.""" - - parser = argparse.ArgumentParser( - description=help_description, - formatter_class=argparse.RawTextHelpFormatter) - - parser.add_argument('--show-defaults', dest='show_defaults', - action='store_true', - help='show the default settings values for each ' - 'plugin but do not output a profile') - parser.add_argument('-o', '--out', dest='output_file', - action='store', - help='output file to save profile') - parser.add_argument( - '-t', '--tests', dest='tests', - action='store', default=None, type=str, - help='list of test names to run') - parser.add_argument( - '-s', '--skip', dest='skips', - action='store', default=None, type=str, - help='list of test names to skip') - args = parser.parse_args() - - if not args.output_file and not args.show_defaults: - parser.print_help() - parser.exit(1) - - return args - - -def get_config_settings(): - config = {} - for plugin in extension_loader.MANAGER.plugins: - fn_name = plugin.name - function = plugin.plugin - - # if a function takes config... - if hasattr(function, '_takes_config'): - fn_module = importlib.import_module(function.__module__) - - # call the config generator if it exists - if hasattr(fn_module, 'gen_config'): - config[fn_name] = fn_module.gen_config(function._takes_config) - - return yaml.safe_dump(config, default_flow_style=False) - - -def main(): - init_logger() - args = parse_args() - - yaml_settings = get_config_settings() - - if args.show_defaults: - print(yaml_settings) - - if args.output_file: - if os.path.exists(os.path.abspath(args.output_file)): - LOG.error("File %s already exists, exiting", args.output_file) - sys.exit(2) - - try: - with open(args.output_file, 'w') as f: - skips = args.skips.split(',') if args.skips else [] - tests = args.tests.split(',') if args.tests else [] - - for skip in skips: - if not extension_loader.MANAGER.check_id(skip): - raise RuntimeError('unknown ID in skips: %s' % skip) - - for test in tests: - if not extension_loader.MANAGER.check_id(test): - raise RuntimeError('unknown ID in tests: %s' % test) - - tpl = "# {0} : {1}" - 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 ( - extension_loader.MANAGER.blacklist_by_id.items())] - test_list.extend(others) - test_list.sort() - - contents = template.format( - cli=" ".join(sys.argv), - settings=yaml_settings, - test_list="\n".join(test_list), - skip='skips: ' + str(skips) if skips else 'skips:', - test='tests: ' + str(tests) if tests else 'tests:') - f.write(contents) - - except IOError: - LOG.error("Unable to open %s for writing", args.output_file) - - except Exception as e: - LOG.error("Error: %s", e) - - else: - LOG.info("Successfully wrote profile: %s", args.output_file) - - return 0 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bandit/cli/main.py b/bandit/cli/main.py deleted file mode 100644 index d6548b70..00000000 --- a/bandit/cli/main.py +++ /dev/null @@ -1,401 +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 argparse -import fnmatch -import logging -import os -import sys -import textwrap - - -import bandit -from bandit.core import config as b_config -from bandit.core import constants -from bandit.core import manager as b_manager -from bandit.core import utils - - -BASE_CONFIG = 'bandit.yaml' -LOG = logging.getLogger() - - -def _init_logger(debug=False, log_format=None): - '''Initialize the logger - - :param debug: Whether to enable debug mode - :return: An instantiated logging instance - ''' - LOG.handlers = [] - log_level = logging.INFO - if debug: - log_level = logging.DEBUG - - if not log_format: - # default log format - log_format_string = constants.log_format_string - else: - log_format_string = log_format - - logging.captureWarnings(True) - - LOG.setLevel(log_level) - handler = logging.StreamHandler(sys.stderr) - handler.setFormatter(logging.Formatter(log_format_string)) - LOG.addHandler(handler) - LOG.debug("logging initialized") - - -def _get_options_from_ini(ini_path, target): - """Return a dictionary of config options or None if we can't load any.""" - ini_file = None - - if ini_path: - ini_file = ini_path - else: - bandit_files = [] - - for t in target: - for root, dirnames, filenames in os.walk(t): - for filename in fnmatch.filter(filenames, '.bandit'): - bandit_files.append(os.path.join(root, filename)) - - if len(bandit_files) > 1: - LOG.error('Multiple .bandit files found - scan separately or ' - 'choose one with --ini\n\t%s', ', '.join(bandit_files)) - sys.exit(2) - - elif len(bandit_files) == 1: - ini_file = bandit_files[0] - LOG.info('Found project level .bandit file: %s', bandit_files[0]) - - if ini_file: - return utils.parse_ini_file(ini_file) - else: - return None - - -def _init_extensions(): - from bandit.core import extension_loader as ext_loader - return ext_loader.MANAGER - - -def _log_option_source(arg_val, ini_val, option_name): - """It's useful to show the source of each option.""" - if arg_val: - LOG.info("Using command line arg for %s", option_name) - return arg_val - elif ini_val: - LOG.info("Using ini file for %s", option_name) - return ini_val - else: - return None - - -def _running_under_virtualenv(): - if hasattr(sys, 'real_prefix'): - return True - elif sys.prefix != getattr(sys, 'base_prefix', sys.prefix): - return True - - -def _get_profile(config, profile_name, config_path): - profile = {} - if profile_name: - profiles = config.get_option('profiles') or {} - profile = profiles.get(profile_name) - if profile is None: - raise utils.ProfileNotFound(config_path, profile_name) - LOG.debug("read in legacy profile '%s': %s", profile_name, profile) - else: - profile['include'] = set(config.get_option('tests') or []) - profile['exclude'] = set(config.get_option('skips') or []) - return profile - - -def _log_info(args, profile): - inc = ",".join([t for t in profile['include']]) or "None" - exc = ",".join([t for t in profile['exclude']]) or "None" - LOG.info("profile include tests: %s", inc) - LOG.info("profile exclude tests: %s", exc) - LOG.info("cli include tests: %s", args.tests) - LOG.info("cli exclude tests: %s", args.skips) - - -def main(): - # bring our logging stuff up as early as possible - debug = ('-d' in sys.argv or '--debug' in sys.argv) - _init_logger(debug) - extension_mgr = _init_extensions() - - baseline_formatters = [f.name for f in filter(lambda x: - hasattr(x.plugin, - '_accepts_baseline'), - extension_mgr.formatters)] - - # now do normal startup - parser = argparse.ArgumentParser( - description='Bandit - a Python source code security analyzer', - formatter_class=argparse.RawDescriptionHelpFormatter - ) - parser.add_argument( - 'targets', metavar='targets', type=str, nargs='*', - help='source file(s) or directory(s) to be tested' - ) - parser.add_argument( - '-r', '--recursive', dest='recursive', - action='store_true', help='find and process files in subdirectories' - ) - parser.add_argument( - '-a', '--aggregate', dest='agg_type', - action='store', default='file', type=str, - choices=['file', 'vuln'], - help='aggregate output by vulnerability (default) or by filename' - ) - parser.add_argument( - '-n', '--number', dest='context_lines', - action='store', default=3, type=int, - help='maximum number of code lines to output for each issue' - ) - parser.add_argument( - '-c', '--configfile', dest='config_file', - action='store', default=None, type=str, - help='optional config file to use for selecting plugins and ' - 'overriding defaults' - ) - parser.add_argument( - '-p', '--profile', dest='profile', - action='store', default=None, type=str, - help='profile to use (defaults to executing all tests)' - ) - parser.add_argument( - '-t', '--tests', dest='tests', - action='store', default=None, type=str, - help='comma-separated list of test IDs to run' - ) - parser.add_argument( - '-s', '--skip', dest='skips', - action='store', default=None, type=str, - help='comma-separated list of test IDs to skip' - ) - parser.add_argument( - '-l', '--level', dest='severity', action='count', - default=1, help='report only issues of a given severity level or ' - 'higher (-l for LOW, -ll for MEDIUM, -lll for HIGH)' - ) - parser.add_argument( - '-i', '--confidence', dest='confidence', action='count', - default=1, help='report only issues of a given confidence level or ' - 'higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)' - ) - output_format = 'screen' if sys.stdout.isatty() else 'txt' - parser.add_argument( - '-f', '--format', dest='output_format', action='store', - default=output_format, help='specify output format', - 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' - ) - parser.add_argument( - '-v', '--verbose', dest='verbose', action='store_true', - help='output extra information like excluded and included files' - ) - parser.add_argument( - '-d', '--debug', dest='debug', action='store_true', - help='turn on debug mode' - ) - parser.add_argument( - '--ignore-nosec', dest='ignore_nosec', action='store_true', - help='do not skip lines with # nosec comments' - ) - parser.add_argument( - '-x', '--exclude', dest='excluded_paths', action='store', - default='', help='comma-separated list of paths to exclude from scan ' - '(note that these are in addition to the excluded ' - 'paths provided in the config file)' - ) - parser.add_argument( - '-b', '--baseline', dest='baseline', action='store', - default=None, help='path of a baseline report to compare against ' - '(only JSON-formatted files are accepted)' - ) - parser.add_argument( - '--ini', dest='ini_path', action='store', default=None, - help='path to a .bandit file that supplies command line arguments' - ) - parser.add_argument( - '--version', action='version', - version='%(prog)s {version}'.format(version=bandit.__version__) - ) - parser.set_defaults(debug=False) - parser.set_defaults(verbose=False) - parser.set_defaults(ignore_nosec=False) - - plugin_info = ["%s\t%s" % (a[0], a[1].name) for a in - extension_mgr.plugins_by_id.items()] - blacklist_info = [] - for a in extension_mgr.blacklist.items(): - for b in a[1]: - blacklist_info.append('%s\t%s' % (b['id'], b['name'])) - - plugin_list = '\n\t'.join(sorted(set(plugin_info + blacklist_info))) - 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) - except utils.ConfigError as e: - LOG.error(e) - sys.exit(2) - - # Handle .bandit files in projects to pass cmdline args from file - ini_options = _get_options_from_ini(args.ini_path, args.targets) - if ini_options: - # prefer command line, then ini file - args.excluded_paths = _log_option_source(args.excluded_paths, - ini_options.get('exclude'), - 'excluded paths') - - args.skips = _log_option_source(args.skips, ini_options.get('skips'), - 'skipped tests') - - args.tests = _log_option_source(args.tests, ini_options.get('tests'), - 'selected tests') - ini_targets = ini_options.get('targets') - if ini_targets: - ini_targets = ini_targets.split(',') - args.targets = _log_option_source(args.targets, ini_targets, - 'selected targets') - # TODO(tmcpeak): any other useful options to pass from .bandit? - - if not args.targets: - LOG.error("No targets found in CLI or ini files, exiting.") - sys.exit(2) - # if the log format string was set in the options, reinitialize - if b_conf.get_option('log_format'): - log_format = b_conf.get_option('log_format') - _init_logger(debug, log_format=log_format) - - try: - profile = _get_profile(b_conf, args.profile, args.config_file) - _log_info(args, profile) - - profile['include'].update(args.tests.split(',') if args.tests else []) - profile['exclude'].update(args.skips.split(',') if args.skips else []) - extension_mgr.validate_profile(profile) - - except (utils.ProfileNotFound, ValueError) as e: - LOG.error(e) - sys.exit(2) - - b_mgr = b_manager.BanditManager(b_conf, args.agg_type, args.debug, - profile=profile, verbose=args.verbose, - ignore_nosec=args.ignore_nosec) - - if args.baseline is not None: - try: - with open(args.baseline) as bl: - data = bl.read() - b_mgr.populate_baseline(data) - except IOError: - LOG.warning("Could not open baseline report: %s", args.baseline) - sys.exit(2) - - if args.output_format not in baseline_formatters: - LOG.warning('Baseline must be used with one of the following ' - 'formats: ' + str(baseline_formatters)) - sys.exit(2) - - if args.output_format != "json": - if args.config_file: - LOG.info("using config: %s", args.config_file) - - LOG.info("running on Python %d.%d.%d", sys.version_info.major, - sys.version_info.minor, sys.version_info.micro) - - # initiate file discovery step within Bandit Manager - b_mgr.discover_files(args.targets, args.recursive, args.excluded_paths) - - if not b_mgr.b_ts.tests: - LOG.error('No tests would be run, please check the profile.') - sys.exit(2) - - # initiate execution of tests within Bandit Manager - b_mgr.run_tests() - LOG.debug(b_mgr.b_ma) - LOG.debug(b_mgr.metrics) - - # trigger output of results by Bandit Manager - sev_level = constants.RANKING[args.severity - 1] - conf_level = constants.RANKING[args.confidence - 1] - b_mgr.output_results(args.context_lines, - sev_level, - conf_level, - args.output_file, - 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: - sys.exit(1) - else: - sys.exit(0) - - -if __name__ == '__main__': - main() diff --git a/bandit/core/__init__.py b/bandit/core/__init__.py deleted file mode 100644 index 319eae59..00000000 --- a/bandit/core/__init__.py +++ /dev/null @@ -1,27 +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. - -from bandit.core import config # noqa -from bandit.core import context # noqa -from bandit.core import manager # noqa -from bandit.core import meta_ast # noqa -from bandit.core import node_visitor # noqa -from bandit.core import test_set # noqa -from bandit.core import tester # noqa -from bandit.core import utils # noqa -from bandit.core.constants import * # noqa -from bandit.core.issue import * # noqa -from bandit.core.test_properties import * # noqa diff --git a/bandit/core/blacklisting.py b/bandit/core/blacklisting.py deleted file mode 100644 index 99f06457..00000000 --- a/bandit/core/blacklisting.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- 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 issue - - -def report_issue(check, name): - return issue.Issue( - severity=check.get('level', 'MEDIUM'), confidence='HIGH', - text=check['message'].replace('{name}', name), - ident=name, test_id=check.get("id", 'LEGACY')) - - -def blacklist(context, config): - """Generic blacklist test, B001. - - This generic blacklist test will be called for any encountered node with - defined blacklist data available. This data is loaded via plugins using - the 'bandit.blacklists' entry point. Please see the documentation for more - details. Each blacklist datum has a unique bandit ID that may be used for - filtering purposes, or alternatively all blacklisting can be filtered using - the id of this built in test, 'B001'. - """ - blacklists = config - node_type = context.node.__class__.__name__ - - if node_type == 'Call': - func = context.node.func - if isinstance(func, ast.Name) and func.id == '__import__': - if len(context.node.args): - if isinstance(context.node.args[0], ast.Str): - name = context.node.args[0].s - else: - # TODO(??): import through a variable, need symbol tab - name = "UNKNOWN" - else: - name = "" # handle '__import__()' - else: - name = context.call_function_name_qual - # In the case the Call is an importlib.import, treat the first - # argument name as an actual import module name. - if name in ["importlib.import_module", "importlib.__import__"]: - name = context.call_args[0] - 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/config.py b/bandit/core/config.py deleted file mode 100644 index 7369f061..00000000 --- a/bandit/core/config.py +++ /dev/null @@ -1,243 +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 logging - -import yaml - -from bandit.core import constants -from bandit.core import extension_loader -from bandit.core import utils - - -LOG = logging.getLogger(__name__) - - -class BanditConfig(object): - def __init__(self, config_file=None): - '''Attempt to initialize a config dictionary from a yaml file. - - Error out if loading the yaml file fails for any reason. - :param config_file: The Bandit yaml config file - - :raises bandit.utils.ConfigError: If the config is invalid or - unreadable. - ''' - self.config_file = config_file - self._config = {} - - if config_file: - try: - f = open(config_file, 'r') - except IOError: - raise utils.ConfigError("Could not read config file.", - config_file) - - try: - self._config = yaml.safe_load(f) - self.validate(config_file) - except yaml.YAMLError as err: - LOG.error(err) - raise utils.ConfigError("Error parsing file.", config_file) - - # valid config must be a dict - if not isinstance(self._config, dict): - raise utils.ConfigError("Error parsing file.", config_file) - - self.convert_legacy_config() - - else: - # use sane defaults - self._config['plugin_name_pattern'] = '*.py' - self._config['include'] = ['*.py', '*.pyw'] - - self._init_settings() - - def get_option(self, option_string): - '''Returns the option from the config specified by the option_string. - - '.' can be used to denote levels, for example to retrieve the options - from the 'a' profile you can use 'profiles.a' - :param option_string: The string specifying the option to retrieve - :return: The object specified by the option_string, or None if it can't - be found. - ''' - option_levels = option_string.split('.') - cur_item = self._config - for level in option_levels: - if cur_item and (level in cur_item): - cur_item = cur_item[level] - else: - return None - - return cur_item - - def get_setting(self, setting_name): - if setting_name in self._settings: - return self._settings[setting_name] - else: - return None - - @property - def config(self): - '''Property to return the config dictionary - - :return: Config dictionary - ''' - return self._config - - def _init_settings(self): - '''This function calls a set of other functions (one per setting) - - This function calls a set of other functions (one per setting) to build - out the _settings dictionary. Each other function will set values from - the config (if set), otherwise use defaults (from constants if - possible). - :return: - - ''' - self._settings = {} - self._init_plugin_name_pattern() - - def _init_plugin_name_pattern(self): - '''Sets settings['plugin_name_pattern'] from default or config file.''' - plugin_name_pattern = constants.plugin_name_pattern - if self.get_option('plugin_name_pattern'): - plugin_name_pattern = self.get_option('plugin_name_pattern') - self._settings['plugin_name_pattern'] = plugin_name_pattern - - def convert_legacy_config(self): - updated_profiles = self.convert_names_to_ids() - bad_calls, bad_imports = self.convert_legacy_blacklist_data() - - if updated_profiles: - self.convert_legacy_blacklist_tests(updated_profiles, - bad_calls, bad_imports) - self._config['profiles'] = updated_profiles - - def convert_names_to_ids(self): - '''Convert test names to IDs, unknown names are left unchanged.''' - extman = extension_loader.MANAGER - - updated_profiles = {} - 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. - include = set((extman.get_plugin_id(i) or i) - for i in (profile.get('include') or [])) - exclude = set((extman.get_plugin_id(i) or i) - for i in (profile.get('exclude') or [])) - updated_profiles[name] = {'include': include, 'exclude': exclude} - return updated_profiles - - def convert_legacy_blacklist_data(self): - '''Detect legacy blacklist data and convert it to new format.''' - bad_calls_list = [] - bad_imports_list = [] - - 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 item.items(): - val['name'] = key - val['message'] = val['message'].replace('{func}', '{name}') - bad_calls_list.append(val) - - 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 item.items(): - val['name'] = key - val['message'] = val['message'].replace('{module}', '{name}') - val['qualnames'] = val['imports'] - del val['imports'] - bad_imports_list.append(val) - - if bad_imports_list or bad_calls_list: - LOG.warning('Legacy blacklist data found in config, overriding ' - 'data plugins') - return bad_calls_list, bad_imports_list - - @staticmethod - def convert_legacy_blacklist_tests(profiles, bad_imports, bad_calls): - '''Detect old blacklist tests, convert to use new builtin.''' - def _clean_set(name, data): - if name in data: - data.remove(name) - data.add('B001') - - for name, profile in profiles.items(): - blacklist = {} - include = profile['include'] - exclude = profile['exclude'] - - name = 'blacklist_calls' - if name in include and name not in exclude: - blacklist.setdefault('Call', []).extend(bad_calls) - - _clean_set(name, include) - _clean_set(name, exclude) - - name = 'blacklist_imports' - if name in include and name not in exclude: - blacklist.setdefault('Import', []).extend(bad_imports) - blacklist.setdefault('ImportFrom', []).extend(bad_imports) - blacklist.setdefault('Call', []).extend(bad_imports) - - _clean_set(name, include) - _clean_set(name, exclude) - _clean_set('blacklist_import_func', include) - _clean_set('blacklist_import_func', exclude) - - # This can happen with a legacy config that includes - # blacklist_calls but exclude blacklist_imports for example - if 'B001' in include and 'B001' in exclude: - exclude.remove('B001') - - profile['blacklist'] = blacklist - - def validate(self, path): - '''Validate the config data.''' - legacy = False - message = ("Config file has an include or exclude reference " - "to legacy test '{0}' but no configuration data for " - "it. Configuration data is required for this test. " - "Please consider switching to the new config file " - "format, the tool 'bandit-config-generator' can help " - "you with this.") - - def _test(key, block, exclude, include): - if key in exclude or key in include: - if self._config.get(block) is None: - raise utils.ConfigError(message.format(key), path) - - if 'profiles' in self._config: - legacy = True - for profile in self._config['profiles'].values(): - inc = profile.get('include') or set() - exc = profile.get('exclude') or set() - - _test('blacklist_imports', 'blacklist_imports', inc, exc) - _test('blacklist_import_func', 'blacklist_imports', inc, exc) - _test('blacklist_calls', 'blacklist_calls', inc, exc) - - # show deprecation message - if legacy: - LOG.warning("Config file '%s' contains deprecated legacy config " - "data. Please consider upgrading to the new config " - "format. The tool 'bandit-config-generator' can help " - "you with this. Support for legacy configs will be " - "removed in a future bandit version.", path) diff --git a/bandit/core/constants.py b/bandit/core/constants.py deleted file mode 100644 index dbacc85c..00000000 --- a/bandit/core/constants.py +++ /dev/null @@ -1,42 +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. - -# default plugin name pattern -plugin_name_pattern = '*.py' - -# default progress increment -progress_increment = 50 - -RANKING = ['UNDEFINED', 'LOW', 'MEDIUM', 'HIGH'] -RANKING_VALUES = {'UNDEFINED': 1, 'LOW': 3, 'MEDIUM': 5, 'HIGH': 10} -CRITERIA = [('SEVERITY', 'UNDEFINED'), ('CONFIDENCE', 'UNDEFINED')] - -# add each ranking to globals, to allow direct access in module name space -for rank in RANKING: - globals()[rank] = rank - -CONFIDENCE_DEFAULT = 'UNDEFINED' - -# A list of values Python considers to be False. -# These can be useful in tests to check if a value is True or False. -# We don't handle the case of user-defined classes being false. -# These are only useful when we have a constant in code. If we -# have a variable we cannot determine if False. -# See https://docs.python.org/2/library/stdtypes.html#truth-value-testing -FALSE_VALUES = [None, False, 'False', 0, 0.0, 0j, '', (), [], {}] - -# override with "log_format" option in config file -log_format_string = '[%(module)s]\t%(levelname)s\t%(message)s' diff --git a/bandit/core/context.py b/bandit/core/context.py deleted file mode 100644 index cf5fe42d..00000000 --- a/bandit/core/context.py +++ /dev/null @@ -1,339 +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 _ast - -import six - -from bandit.core import utils - - -class Context(object): - def __init__(self, context_object=None): - '''Initialize the class with a context, empty dict otherwise - - :param context_object: The context object to create class from - :return: - - ''' - if context_object is not None: - self._context = context_object - else: - self._context = dict() - - def __repr__(self): - '''Generate representation of object for printing / interactive use - - Most likely only interested in non-default properties, so we return - the string version of _context. - - Example string returned: - , 'function': None, - 'name': 'socket', 'imports': set(['socket']), 'module': None, - 'filename': 'examples/binding.py', - 'call': <_ast.Call object at 0x110252510>, 'lineno': 3, - 'import_aliases': {}, 'qualname': 'socket.socket'}> - - :return: A string representation of the object - ''' - return "" % self._context - - @property - def call_args(self): - '''Get a list of function args - - :return: A list of function args - ''' - args = [] - for arg in self._context['call'].args: - if hasattr(arg, 'attr'): - args.append(arg.attr) - else: - args.append(self._get_literal_value(arg)) - return args - - @property - def call_args_count(self): - '''Get the number of args a function call has - - :return: The number of args a function call has - ''' - if 'call' in self._context and hasattr(self._context['call'], 'args'): - return len(self._context['call'].args) - else: - return None - - @property - def call_function_name(self): - '''Get the name (not FQ) of a function call - - :return: The name (not FQ) of a function call - ''' - if 'name' in self._context: - return self._context['name'] - else: - return None - - @property - def call_function_name_qual(self): - '''Get the FQ name of a function call - - :return: The FQ name of a function call - ''' - if 'qualname' in self._context: - return self._context['qualname'] - else: - return None - - @property - def call_keywords(self): - '''Get a dictionary of keyword parameters - - :return: A dictionary of keyword parameters for a call as strings - ''' - if ('call' in self._context and - hasattr(self._context['call'], 'keywords')): - return_dict = {} - for li in self._context['call'].keywords: - if hasattr(li.value, 'attr'): - return_dict[li.arg] = li.value.attr - else: - return_dict[li.arg] = self._get_literal_value(li.value) - return return_dict - else: - return None - - @property - def node(self): - '''Get the raw AST node associated with the context - - :return: The raw AST node associated with the context - ''' - if 'node' in self._context: - return self._context['node'] - else: - return None - - @property - def string_val(self): - '''Get the value of a standalone unicode or string object - - :return: value of a standalone unicode or string object - ''' - if 'str' in self._context: - return self._context['str'] - else: - return None - - @property - def bytes_val(self): - '''Get the value of a standalone bytes object (py3 only) - - :return: value of a standalone bytes object - ''' - return self._context.get('bytes') - - @property - def string_val_as_escaped_bytes(self): - '''Get escaped value of the object. - - Turn the value of a string or bytes object into byte sequence with - unknown, control, and \\ characters escaped. - - This function should be used when looking for a known sequence in a - potentially badly encoded string in the code. - - :return: sequence of printable ascii bytes representing original string - ''' - val = self.string_val - if val is not None: - # it's any of str or unicode in py2, or str in py3 - return val.encode('unicode_escape') - - val = self.bytes_val - if val is not None: - return utils.escaped_bytes_representation(val) - - return None - - @property - def statement(self): - '''Get the raw AST for the current statement - - :return: The raw AST for the current statement - ''' - if 'statement' in self._context: - return self._context['statement'] - else: - return None - - @property - def function_def_defaults_qual(self): - '''Get a list of fully qualified default values in a function def - - :return: List of defaults - ''' - defaults = [] - if 'node' in self._context: - for default in self._context['node'].args.defaults: - defaults.append(utils.get_qual_attr( - default, - self._context['import_aliases'])) - return defaults - - def _get_literal_value(self, literal): - '''Utility function to turn AST literals into native Python types - - :param literal: The AST literal to convert - :return: The value of the AST literal - ''' - if isinstance(literal, _ast.Num): - literal_value = literal.n - - elif isinstance(literal, _ast.Str): - literal_value = literal.s - - elif isinstance(literal, _ast.List): - return_list = list() - for li in literal.elts: - return_list.append(self._get_literal_value(li)) - literal_value = return_list - - elif isinstance(literal, _ast.Tuple): - return_tuple = tuple() - for ti in literal.elts: - return_tuple = return_tuple + (self._get_literal_value(ti),) - literal_value = return_tuple - - elif isinstance(literal, _ast.Set): - return_set = set() - for si in literal.elts: - return_set.add(self._get_literal_value(si)) - literal_value = return_set - - elif isinstance(literal, _ast.Dict): - literal_value = dict(zip(literal.keys, literal.values)) - - elif isinstance(literal, _ast.Ellipsis): - # what do we want to do with this? - literal_value = None - - elif isinstance(literal, _ast.Name): - literal_value = literal.id - - # NOTE(sigmavirus24): NameConstants are only part of the AST in Python - # 3. NameConstants tend to refer to things like True and False. This - # prevents them from being re-assigned in Python 3. - elif six.PY3 and isinstance(literal, _ast.NameConstant): - literal_value = str(literal.value) - - # NOTE(sigmavirus24): Bytes are only part of the AST in Python 3 - elif six.PY3 and isinstance(literal, _ast.Bytes): - literal_value = literal.s - - else: - literal_value = None - - return literal_value - - def get_call_arg_value(self, argument_name): - '''Gets the value of a named argument in a function call. - - :return: named argument value - ''' - kwd_values = self.call_keywords - if kwd_values is not None and argument_name in kwd_values: - return kwd_values[argument_name] - - def check_call_arg_value(self, argument_name, argument_values=None): - '''Checks for a value of a named argument in a function call. - - Returns none if the specified argument is not found. - :param argument_name: A string - name of the argument to look for - :param argument_values: the value, or list of values to test against - :return: Boolean True if argument found and matched, False if - found and not matched, None if argument not found at all - ''' - arg_value = self.get_call_arg_value(argument_name) - if arg_value is not None: - if not isinstance(argument_values, list): - # if passed a single value, or a tuple, convert to a list - argument_values = list((argument_values,)) - for val in argument_values: - if arg_value == val: - return True - return False - else: - # argument name not found, return None to allow testing for this - # eventuality - return None - - def get_lineno_for_call_arg(self, argument_name): - '''Get the line number for a specific named argument - - In case the call is split over multiple lines, get the correct one for - the argument. - :param argument_name: A string - name of the argument to look for - :return: Integer - the line number of the found argument, or -1 - ''' - for key in self.node.keywords: - if key.arg == argument_name: - return key.value.lineno - - def get_call_arg_at_position(self, position_num): - '''Returns positional argument at the specified position (if it exists) - - :param position_num: The index of the argument to return the value for - :return: Value of the argument at the specified position if it exists - ''' - if ('call' in self._context and - hasattr(self._context['call'], 'args') and - position_num < len(self._context['call'].args)): - return self._get_literal_value( - self._context['call'].args[position_num] - ) - else: - return None - - def is_module_being_imported(self, module): - '''Check for the specified module is currently being imported - - :param module: The module name to look for - :return: True if the module is found, False otherwise - ''' - return 'module' in self._context and self._context['module'] == module - - def is_module_imported_exact(self, module): - '''Check if a specified module has been imported; only exact matches. - - :param module: The module name to look for - :return: True if the module is found, False otherwise - ''' - return ('imports' in self._context and - module in self._context['imports']) - - def is_module_imported_like(self, module): - '''Check if a specified module has been imported - - Check if a specified module has been imported; specified module exists - as part of any import statement. - :param module: The module name to look for - :return: True if the module is found, False otherwise - ''' - if 'imports' in self._context: - for imp in self._context['imports']: - if module in imp: - return True - return False diff --git a/bandit/core/docs_utils.py b/bandit/core/docs_utils.py deleted file mode 100644 index c5d72cdc..00000000 --- a/bandit/core/docs_utils.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- 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. - -# where our docs are hosted -BASE_URL = 'https://docs.openstack.org/bandit/latest/' - - -def get_url(bid): - # NOTE(tkelsey): for some reason this import can't be found when stevedore - # loads up the formatter plugin that imports this file. It is available - # later though. - from bandit.core import extension_loader - - info = extension_loader.MANAGER.plugins_by_id.get(bid) - if info is not None: - return '%splugins/%s_%s.html' % (BASE_URL, bid.lower(), - info.plugin.__name__) - - 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('_', '-') - - if info['id'].startswith('B3'): # B3XX - # Some of the links are combined, so we have exception cases - if info['id'] in ['B304', 'B305']: - info['id'] = 'b304-b305' - info['name'] = 'ciphers-and-modes' - elif info['id'] in ['B313', 'B314', 'B315', 'B316', 'B317', - 'B318', 'B319', 'B320']: - info['id'] = 'b313-b320' - ext = template.format( - kind='calls', id=info['id'], name=info['name']) - else: - ext = template.format( - kind='imports', id=info['id'], name=info['name']) - - return BASE_URL + ext.lower() - - return BASE_URL # no idea, give the docs main page diff --git a/bandit/core/extension_loader.py b/bandit/core/extension_loader.py deleted file mode 100644 index 4b0dcba1..00000000 --- a/bandit/core/extension_loader.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- 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. - -from __future__ import print_function - -import sys - -import six -from stevedore import extension - -from bandit.core import utils - - -class Manager(object): - # These IDs are for bandit built in tests - builtin = [ - 'B001' # Built in blacklist test - ] - - def __init__(self, formatters_namespace='bandit.formatters', - 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( - namespace=formatters_namespace, - invoke_on_load=False, - verify_requirements=False, - ) - self.formatters = list(self.formatters_mgr) - self.formatter_names = self.formatters_mgr.names() - - def load_plugins(self, plugins_namespace): - self.plugins_mgr = extension.ExtensionManager( - namespace=plugins_namespace, - invoke_on_load=False, - verify_requirements=False, - ) - - def test_has_id(plugin): - if not hasattr(plugin.plugin, "_test_id"): - # logger not setup yet, so using print - print("WARNING: Test '%s' has no ID, skipping." % plugin.name, - file=sys.stderr) - return False - return True - - self.plugins = list(filter(test_has_id, list(self.plugins_mgr))) - self.plugin_names = [plugin.name for plugin in self.plugins] - self.plugins_by_id = {p.plugin._test_id: p for p in self.plugins} - self.plugins_by_name = {p.name: p for p in self.plugins} - - def get_plugin_id(self, plugin_name): - if plugin_name in self.plugins_by_name: - return self.plugins_by_name[plugin_name].plugin._test_id - return None - - 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 item.plugin().items(): - utils.check_ast_node(key) - self.blacklist.setdefault(key, []).extend(val) - - self.blacklist_by_id = {} - self.blacklist_by_name = {} - for val in six.itervalues(self.blacklist): - for b in val: - self.blacklist_by_id[b['id']] = b - self.blacklist_by_name[b['name']] = b - - def validate_profile(self, profile): - '''Validate that everything in the configured profiles looks good.''' - for inc in profile['include']: - if not self.check_id(inc): - raise ValueError('Unknown test found in profile: %s' % inc) - - for exc in profile['exclude']: - if not self.check_id(exc): - raise ValueError('Unknown test found in profile: %s' % exc) - - union = set(profile['include']) & set(profile['exclude']) - if len(union) > 0: - raise ValueError('Non-exclusive include/exclude test sets: %s' % - union) - - def check_id(self, test): - return ( - test in self.plugins_by_id or - test in self.blacklist_by_id or - test in self.builtin) - -# 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 -# this attribute on the module and not have to reload the entry-points. -MANAGER = Manager() diff --git a/bandit/core/issue.py b/bandit/core/issue.py deleted file mode 100644 index 32149c61..00000000 --- a/bandit/core/issue.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2015 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 __future__ import division -from __future__ import unicode_literals - -import linecache - -from six import moves - -from bandit.core import constants - - -class Issue(object): - def __init__(self, severity, confidence=constants.CONFIDENCE_DEFAULT, - text="", ident=None, lineno=None, test_id=""): - self.severity = severity - self.confidence = confidence - if isinstance(text, bytes): - text = text.decode('utf-8') - self.text = text - self.ident = ident - self.fname = "" - self.test = "" - self.test_id = test_id - self.lineno = lineno - self.linerange = [] - - def __str__(self): - return ("Issue: '%s' from %s:%s: Severity: %s Confidence: " - "%s at %s:%i") % (self.text, self.test_id, - (self.ident or self.test), self.severity, - self.confidence, self.fname, self.lineno) - - def __eq__(self, other): - # if the issue text, severity, confidence, and filename match, it's - # the same issue from our perspective - match_types = ['text', 'severity', 'confidence', 'fname', 'test', - 'test_id'] - return all(getattr(self, field) == getattr(other, field) - for field in match_types) - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return id(self) - - def filter(self, severity, confidence): - '''Utility to filter on confidence and severity - - This function determines whether an issue should be included by - comparing the severity and confidence rating of the issue to minimum - thresholds specified in 'severity' and 'confidence' respectively. - - Formatters should call manager.filter_results() directly. - - This will return false if either the confidence or severity of the - issue are lower than the given threshold values. - - :param severity: Severity threshold - :param confidence: Confidence threshold - :return: True/False depending on whether issue meets threshold - - ''' - rank = constants.RANKING - return (rank.index(self.severity) >= rank.index(severity) and - rank.index(self.confidence) >= rank.index(confidence)) - - def get_code(self, max_lines=3, tabbed=False): - '''Gets lines of code from a file the generated this issue. - - :param max_lines: Max lines of context to return - :param tabbed: Use tabbing in the output - :return: strings of code - ''' - lines = [] - max_lines = max(max_lines, 1) - lmin = max(1, self.lineno - max_lines // 2) - lmax = lmin + len(self.linerange) + max_lines - 1 - - tmplt = "%i\t%s" if tabbed else "%i %s" - for line in moves.xrange(lmin, lmax): - text = linecache.getline(self.fname, line) - - if isinstance(text, bytes): - text = text.decode('utf-8') - - if not len(text): - break - lines.append(tmplt % (line, text)) - return ''.join(lines) - - def as_dict(self, with_code=True): - '''Convert the issue to a dict of values for outputting.''' - out = { - 'filename': self.fname, - 'test_name': self.test, - 'test_id': self.test_id, - 'issue_severity': self.severity, - 'issue_confidence': self.confidence, - 'issue_text': self.text.encode('utf-8').decode('utf-8'), - 'line_number': self.lineno, - 'line_range': self.linerange, - } - - if with_code: - out['code'] = self.get_code() - return out - - def from_dict(self, data, with_code=True): - self.code = data["code"] - self.fname = data["filename"] - self.severity = data["issue_severity"] - self.confidence = data["issue_confidence"] - self.text = data["issue_text"] - self.test = data["test_name"] - self.test_id = data["test_id"] - self.lineno = data["line_number"] - self.linerange = data["line_range"] - - -def issue_from_dict(data): - i = Issue(severity=data["issue_severity"]) - i.from_dict(data) - return i diff --git a/bandit/core/manager.py b/bandit/core/manager.py deleted file mode 100644 index cb8b574b..00000000 --- a/bandit/core/manager.py +++ /dev/null @@ -1,398 +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 collections -import fnmatch -import json -import logging -import os -import sys -import traceback - -from bandit.core import constants as b_constants -from bandit.core import extension_loader -from bandit.core import issue -from bandit.core import meta_ast as b_meta_ast -from bandit.core import metrics -from bandit.core import node_visitor as b_node_visitor -from bandit.core import test_set as b_test_set - - -LOG = logging.getLogger(__name__) - - -class BanditManager(object): - - scope = [] - - def __init__(self, config, agg_type, debug=False, verbose=False, - profile=None, ignore_nosec=False): - '''Get logger, config, AST handler, and result store ready - - :param config: config options object - :type config: bandit.core.BanditConfig - :param agg_type: aggregation type - :param debug: Whether to show debug messages or not - :param verbose: Whether to show verbose output - :param profile_name: Optional name of profile to use (from cmd line) - :param ignore_nosec: Whether to ignore #nosec or not - :return: - ''' - self.debug = debug - self.verbose = verbose - if not profile: - profile = {} - self.ignore_nosec = ignore_nosec - self.b_conf = config - self.files_list = [] - self.excluded_files = [] - self.b_ma = b_meta_ast.BanditMetaAst() - self.skipped = [] - self.results = [] - self.baseline = [] - self.agg_type = agg_type - self.metrics = metrics.Metrics() - self.b_ts = b_test_set.BanditTestSet(config, profile) - - # set the increment of after how many files to show progress - self.progress = b_constants.progress_increment - self.scores = [] - - def get_skipped(self): - ret = [] - # "skip" is a tuple of name and reason, decode just the name - for skip in self.skipped: - if isinstance(skip[0], bytes): - ret.append((skip[0].decode('utf-8'), skip[1])) - else: - ret.append(skip) - return ret - - def get_issue_list(self, - sev_level=b_constants.LOW, - conf_level=b_constants.LOW): - return self.filter_results(sev_level, conf_level) - - def populate_baseline(self, data): - '''Populate a baseline set of issues from a JSON report - - This will populate a list of baseline issues discovered from a previous - run of bandit. Later this baseline can be used to filter out the result - set, see filter_results. - ''' - items = [] - try: - jdata = json.loads(data) - items = [issue.issue_from_dict(j) for j in jdata["results"]] - except Exception as e: - LOG.warning("Failed to load baseline data: %s", e) - self.baseline = items - - def filter_results(self, sev_filter, conf_filter): - '''Returns a list of results filtered by the baseline - - This works by checking the number of results returned from each file we - process. If the number of results is different to the number reported - for the same file in the baseline, then we return all results for the - file. We can't reliably return just the new results, as line numbers - will likely have changed. - - :param sev_filter: severity level filter to apply - :param conf_filter: confidence level filter to apply - ''' - - results = [i for i in self.results if - i.filter(sev_filter, conf_filter)] - - if not self.baseline: - return results - - unmatched = _compare_baseline_results(self.baseline, results) - # if it's a baseline we'll return a dictionary of issues and a list of - # candidate issues - return _find_candidate_matches(unmatched, results) - - def results_count(self, sev_filter=b_constants.LOW, - conf_filter=b_constants.LOW): - '''Return the count of results - - :param sev_filter: Severity level to filter lower - :param conf_filter: Confidence level to filter - :return: Number of results in the set - ''' - return len(self.get_issue_list(sev_filter, conf_filter)) - - def output_results(self, lines, sev_level, conf_level, output_file, - output_format, template=None): - '''Outputs results from the result store - - :param lines: How many surrounding lines to show per result - :param sev_level: Which severity levels to show (LOW, MEDIUM, HIGH) - :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 - (default: {abspath}:{line}: - {test_id}[bandit]: {severity}: {msg}) - :return: - - ''' - try: - formatters_mgr = extension_loader.MANAGER.formatters_mgr - if output_format not in formatters_mgr: - output_format = 'screen' if sys.stdout.isatty() else 'txt' - - formatter = formatters_mgr[output_format] - report_func = formatter.plugin - 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: " - "%s" % (output_format, str(e))) - - def discover_files(self, targets, recursive=False, excluded_paths=''): - '''Add tests directly and from a directory to the test set - - :param targets: The command line list of files and directories - :param recursive: True/False - whether to add all files from dirs - :return: - ''' - # We'll mantain a list of files which are added, and ones which have - # been explicitly excluded - files_list = set() - excluded_files = set() - - excluded_path_strings = self.b_conf.get_option('exclude_dirs') or [] - included_globs = self.b_conf.get_option('include') or ['*.py'] - - # if there are command line provided exclusions add them to the list - if excluded_paths: - for path in excluded_paths.split(','): - excluded_path_strings.append(path) - - # build list of files we will analyze - for fname in targets: - # if this is a directory and recursive is set, find all files - if os.path.isdir(fname): - if recursive: - new_files, newly_excluded = _get_files_from_dir( - fname, - included_globs=included_globs, - excluded_path_strings=excluded_path_strings - ) - files_list.update(new_files) - excluded_files.update(newly_excluded) - else: - LOG.warning("Skipping directory (%s), use -r flag to " - "scan contents", fname) - - else: - # if the user explicitly mentions a file on command line, - # we'll scan it, regardless of whether it's in the included - # file types list - if _is_file_included(fname, included_globs, - excluded_path_strings, - enforce_glob=False): - files_list.add(fname) - else: - excluded_files.add(fname) - - self.files_list = sorted(files_list) - self.excluded_files = sorted(excluded_files) - - def run_tests(self): - '''Runs through all files in the scope - - :return: - - ''' - # display progress, if number of files warrants it - if len(self.files_list) > self.progress: - sys.stderr.write("%s [" % len(self.files_list)) - - # if we have problems with a file, we'll remove it from the files_list - # and add it to the skipped list instead - new_files_list = list(self.files_list) - - for count, fname in enumerate(self.files_list): - LOG.debug("working on file : %s", fname) - - if len(self.files_list) > self.progress: - # is it time to update the progress indicator? - if count % self.progress == 0: - sys.stderr.write("%s.. " % count) - sys.stderr.flush() - try: - if fname == '-': - sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) - self._parse_file('', sys.stdin, new_files_list) - else: - with open(fname, 'rb') as fdata: - self._parse_file(fname, fdata, new_files_list) - except IOError as e: - self.skipped.append((fname, e.strerror)) - new_files_list.remove(fname) - - if len(self.files_list) > self.progress: - sys.stderr.write("]\n") - sys.stderr.flush() - - # reflect any files which may have been skipped - self.files_list = new_files_list - - # do final aggregation of metrics - self.metrics.aggregate() - - def _parse_file(self, fname, fdata, new_files_list): - try: - # parse the current file - data = fdata.read() - lines = data.splitlines() - self.metrics.begin(fname) - self.metrics.count_locs(lines) - if self.ignore_nosec: - nosec_lines = set() - else: - nosec_lines = set( - lineno + 1 for - (lineno, line) in enumerate(lines) - if b'#nosec' in line or b'# nosec' in line) - score = self._execute_ast_visitor(fname, data, nosec_lines) - self.scores.append(score) - self.metrics.count_issues([score, ]) - except KeyboardInterrupt as e: - sys.exit(2) - except SyntaxError as e: - self.skipped.append((fname, - "syntax error while parsing AST from file")) - new_files_list.remove(fname) - except Exception as e: - LOG.error("Exception occurred when executing tests against " - "%s. Run \"bandit --debug %s\" to see the full " - "traceback.", fname, fname) - self.skipped.append((fname, 'exception while scanning file')) - new_files_list.remove(fname) - LOG.debug(" Exception string: %s", e) - LOG.debug(" Exception traceback: %s", traceback.format_exc()) - - def _execute_ast_visitor(self, fname, data, nosec_lines): - '''Execute AST parse on each file - - :param fname: The name of the file being parsed - :param data: Original file contents - :param lines: The lines of code to process - :return: The accumulated test score - ''' - score = [] - res = b_node_visitor.BanditNodeVisitor(fname, self.b_ma, - self.b_ts, self.debug, - nosec_lines, self.metrics) - - score = res.process(data) - self.results.extend(res.tester.results) - return score - - -def _get_files_from_dir(files_dir, included_globs=None, - excluded_path_strings=None): - if not included_globs: - included_globs = ['*.py'] - if not excluded_path_strings: - excluded_path_strings = [] - - files_list = set() - excluded_files = set() - - for root, subdirs, files in os.walk(files_dir): - for filename in files: - path = os.path.join(root, filename) - if _is_file_included(path, included_globs, excluded_path_strings): - files_list.add(path) - else: - excluded_files.add(path) - - return files_list, excluded_files - - -def _is_file_included(path, included_globs, excluded_path_strings, - enforce_glob=True): - '''Determine if a file should be included based on filename - - This utility function determines if a file should be included based - on the file name, a list of parsed extensions, excluded paths, and a flag - specifying whether extensions should be enforced. - - :param path: Full path of file to check - :param parsed_extensions: List of parsed extensions - :param excluded_paths: List of paths from which we should not include files - :param enforce_glob: Can set to false to bypass extension check - :return: Boolean indicating whether a file should be included - ''' - return_value = False - - # if this is matches a glob of files we look at, and it isn't in an - # excluded path - if _matches_glob_list(path, included_globs) or not enforce_glob: - if not any(x in path for x in excluded_path_strings): - return_value = True - - return return_value - - -def _matches_glob_list(filename, glob_list): - for glob in glob_list: - if fnmatch.fnmatch(filename, glob): - return True - return False - - -def _compare_baseline_results(baseline, results): - """Compare a baseline list of issues to list of results - - This function compares a baseline set of issues to a current set of issues - to find results that weren't present in the baseline. - - :param baseline: Baseline list of issues - :param results: Current list of issues - :return: List of unmatched issues - """ - return [a for a in results if a not in baseline] - - -def _find_candidate_matches(unmatched_issues, results_list): - """Returns a dictionary with issue candidates - - For example, let's say we find a new command injection issue in a file - which used to have two. Bandit can't tell which of the command injection - issues in the file are new, so it will show all three. The user should - be able to pick out the new one. - - :param unmatched_issues: List of issues that weren't present before - :param results_list: Master list of current Bandit findings - :return: A dictionary with a list of candidates for each issue - """ - - issue_candidates = collections.OrderedDict() - - for unmatched in unmatched_issues: - issue_candidates[unmatched] = ([i for i in results_list if - unmatched == i]) - - return issue_candidates diff --git a/bandit/core/meta_ast.py b/bandit/core/meta_ast.py deleted file mode 100644 index d9654b7b..00000000 --- a/bandit/core/meta_ast.py +++ /dev/null @@ -1,57 +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 collections -import logging - - -LOG = logging.getLogger(__name__) - - -class BanditMetaAst(object): - - nodes = collections.OrderedDict() - - def __init__(self): - pass - - def add_node(self, node, parent_id, depth): - '''Add a node to the AST node collection - - :param node: The AST node to add - :param parent_id: The ID of the node's parent - :param depth: The depth of the node - :return: - - ''' - node_id = hex(id(node)) - LOG.debug('adding node : %s [%s]', node_id, depth) - self.nodes[node_id] = { - 'raw': node, 'parent_id': parent_id, 'depth': depth - } - - def __str__(self): - '''Dumps a listing of all of the nodes - - Dumps a listing of all of the nodes for debugging purposes - :return: - - ''' - tmpstr = "" - for k, v in self.nodes.items(): - tmpstr += "Node: %s\n" % k - tmpstr += "\t%s\n" % str(v) - tmpstr += "Length: %s\n" % len(self.nodes) - return tmpstr diff --git a/bandit/core/metrics.py b/bandit/core/metrics.py deleted file mode 100644 index aed616ee..00000000 --- a/bandit/core/metrics.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2015 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 collections - -from bandit.core import constants - - -class Metrics(object): - """Bandit metric gathering. - - This class is a singleton used to gather and process metrics collected when - processing a code base with bandit. Metric collection is stateful, that - is, an active metric block will be set when requested and all subsequent - operations will effect that metric block until it is replaced by a setting - a new one. - """ - - def __init__(self): - self.data = dict() - self.data['_totals'] = {'loc': 0, 'nosec': 0} - - # initialize 0 totals for criteria and rank; this will be reset later - for rank in constants.RANKING: - for criteria in constants.CRITERIA: - self.data['_totals']['{0}.{1}'.format(criteria[0], rank)] = 0 - - def begin(self, fname): - """Begin a new metric block. - - This starts a new metric collection name "fname" and makes is active. - - :param fname: the metrics unique name, normally the file name. - """ - self.data[fname] = {'loc': 0, 'nosec': 0} - self.current = self.data[fname] - - def note_nosec(self, num=1): - """Note a "nosec" commnet. - - Increment the currently active metrics nosec count. - - :param num: number of nosecs seen, defaults to 1 - """ - self.current['nosec'] += num - - def count_locs(self, lines): - """Count lines of code. - - We count lines that are not empty and are not comments. The result is - added to our currently active metrics loc count (normally this is 0). - - :param lines: lines in the file to process - """ - def proc(line): - tmp = line.strip() - return bool(tmp and not tmp.startswith(b'#')) - - self.current['loc'] += sum(proc(line) for line in lines) - - def count_issues(self, scores): - self.current.update(self._get_issue_counts(scores)) - - def aggregate(self): - """Do final aggregation of metrics.""" - c = collections.Counter() - for fname in self.data: - c.update(self.data[fname]) - self.data['_totals'] = dict(c) - - @staticmethod - def _get_issue_counts(scores): - """Get issue counts aggregated by confidence/severity rankings. - - :param scores: list of scores to aggregate / count - :return: aggregated total (count) of issues identified - """ - issue_counts = {} - for score in scores: - for (criteria, default) in constants.CRITERIA: - for i, rank in enumerate(constants.RANKING): - label = '{0}.{1}'.format(criteria, rank) - if label not in issue_counts: - issue_counts[label] = 0 - count = ( - score[criteria][i] / - constants.RANKING_VALUES[rank] - ) - issue_counts[label] += count - return issue_counts diff --git a/bandit/core/node_visitor.py b/bandit/core/node_visitor.py deleted file mode 100644 index b9c51ebe..00000000 --- a/bandit/core/node_visitor.py +++ /dev/null @@ -1,279 +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 ast -import logging -import operator - -from bandit.core import constants -from bandit.core import tester as b_tester -from bandit.core import utils as b_utils - - -LOG = logging.getLogger(__name__) - - -class BanditNodeVisitor(object): - def __init__(self, fname, metaast, testset, - debug, nosec_lines, metrics): - self.debug = debug - self.nosec_lines = nosec_lines - self.seen = 0 - self.scores = { - 'SEVERITY': [0] * len(constants.RANKING), - 'CONFIDENCE': [0] * len(constants.RANKING) - } - self.depth = 0 - self.fname = fname - self.metaast = metaast - self.testset = testset - self.imports = set() - self.import_aliases = {} - self.tester = b_tester.BanditTester( - self.testset, self.debug, nosec_lines) - - # in some cases we can't determine a qualified name - try: - self.namespace = b_utils.get_module_qualname_from_path(fname) - except b_utils.InvalidModulePath: - LOG.info('Unable to find qualified name for module: %s', - self.fname) - self.namespace = "" - LOG.debug('Module qualified name: %s', self.namespace) - self.metrics = metrics - - def visit_ClassDef(self, node): - '''Visitor for AST ClassDef node - - Add class name to current namespace for all descendants. - :param node: Node being inspected - :return: - - ''' - # For all child nodes, add this class name to current namespace - self.namespace = b_utils.namespace_path_join(self.namespace, node.name) - - def visit_FunctionDef(self, node): - '''Visitor for AST FunctionDef nodes - - add relevant information about the node to - the context for use in tests which inspect function definitions. - Add the function name to the current namespace for all descendants. - :param node: The node that is being inspected - :return: - - ''' - - self.context['function'] = node - qualname = self.namespace + '.' + b_utils.get_func_name(node) - name = qualname.split('.')[-1] - - self.context['qualname'] = qualname - self.context['name'] = name - - # For all child nodes and any tests run, add this function name to - # current namespace - self.namespace = b_utils.namespace_path_join(self.namespace, name) - self.update_scores(self.tester.run_tests(self.context, 'FunctionDef')) - - def visit_Call(self, node): - '''Visitor for AST Call nodes - - add relevant information about the node to - the context for use in tests which inspect function calls. - :param node: The node that is being inspected - :return: - - ''' - - self.context['call'] = node - qualname = b_utils.get_call_name(node, self.import_aliases) - name = qualname.split('.')[-1] - - self.context['qualname'] = qualname - self.context['name'] = name - - self.update_scores(self.tester.run_tests(self.context, 'Call')) - - def visit_Import(self, node): - '''Visitor for AST Import nodes - - add relevant information about node to - the context for use in tests which inspect imports. - :param node: The node that is being inspected - :return: - - ''' - for nodename in node.names: - if nodename.asname: - self.import_aliases[nodename.asname] = nodename.name - self.imports.add(nodename.name) - self.context['module'] = nodename.name - self.update_scores(self.tester.run_tests(self.context, 'Import')) - - def visit_ImportFrom(self, node): - '''Visitor for AST ImportFrom nodes - - add relevant information about node to - the context for use in tests which inspect imports. - :param node: The node that is being inspected - :return: - - ''' - module = node.module - if module is None: - return self.visit_Import(node) - - for nodename in node.names: - # TODO(ljfisher) Names in import_aliases could be overridden - # by local definitions. If this occurs bandit will see the - # name in import_aliases instead of the local definition. - # We need better tracking of names. - if nodename.asname: - self.import_aliases[nodename.asname] = ( - module + "." + nodename.name - ) - else: - # Even if import is not aliased we need an entry that maps - # name to module.name. For example, with 'from a import b' - # b should be aliased to the qualified name a.b - self.import_aliases[nodename.name] = (module + '.' + - nodename.name) - self.imports.add(module + "." + nodename.name) - self.context['module'] = module - self.context['name'] = nodename.name - self.update_scores(self.tester.run_tests(self.context, 'ImportFrom')) - - def visit_Str(self, node): - '''Visitor for AST String nodes - - add relevant information about node to - the context for use in tests which inspect strings. - :param node: The node that is being inspected - :return: - - ''' - self.context['str'] = node.s - if not isinstance(node.parent, ast.Expr): # docstring - self.context['linerange'] = b_utils.linerange_fix(node.parent) - self.update_scores(self.tester.run_tests(self.context, 'Str')) - - def visit_Bytes(self, node): - '''Visitor for AST Bytes nodes - - add relevant information about node to - the context for use in tests which inspect strings. - :param node: The node that is being inspected - :return: - - ''' - self.context['bytes'] = node.s - if not isinstance(node.parent, ast.Expr): # docstring - self.context['linerange'] = b_utils.linerange_fix(node.parent) - self.update_scores(self.tester.run_tests(self.context, 'Bytes')) - - def pre_visit(self, node): - self.context = {} - self.context['imports'] = self.imports - self.context['import_aliases'] = self.import_aliases - - if self.debug: - LOG.debug(ast.dump(node)) - self.metaast.add_node(node, '', self.depth) - - if hasattr(node, 'lineno'): - self.context['lineno'] = node.lineno - - if node.lineno in self.nosec_lines: - LOG.debug("skipped, nosec") - self.metrics.note_nosec() - return False - - self.context['node'] = node - self.context['linerange'] = b_utils.linerange_fix(node) - self.context['filename'] = self.fname - - self.seen += 1 - LOG.debug("entering: %s %s [%s]", hex(id(node)), type(node), - self.depth) - self.depth += 1 - LOG.debug(self.context) - return True - - def visit(self, node): - name = node.__class__.__name__ - method = 'visit_' + name - visitor = getattr(self, method, None) - if visitor is not None: - if self.debug: - LOG.debug("%s called (%s)", method, ast.dump(node)) - visitor(node) - else: - self.update_scores(self.tester.run_tests(self.context, name)) - - def post_visit(self, node): - self.depth -= 1 - LOG.debug("%s\texiting : %s", self.depth, hex(id(node))) - - # HACK(tkelsey): this is needed to clean up post-recursion stuff that - # gets setup in the visit methods for these node types. - if isinstance(node, ast.FunctionDef) or isinstance(node, ast.ClassDef): - self.namespace = b_utils.namespace_path_split(self.namespace)[0] - - def generic_visit(self, node): - """Drive the visitor.""" - for _, value in ast.iter_fields(node): - if isinstance(value, list): - max_idx = len(value) - 1 - for idx, item in enumerate(value): - if isinstance(item, ast.AST): - if idx < max_idx: - setattr(item, 'sibling', value[idx + 1]) - else: - setattr(item, 'sibling', None) - setattr(item, 'parent', node) - - if self.pre_visit(item): - self.visit(item) - self.generic_visit(item) - self.post_visit(item) - - elif isinstance(value, ast.AST): - setattr(value, 'sibling', None) - setattr(value, 'parent', node) - - if self.pre_visit(value): - self.visit(value) - self.generic_visit(value) - self.post_visit(value) - - def update_scores(self, scores): - '''Score updater - - Since we moved from a single score value to a map of scores per - severity, this is needed to update the stored list. - :param score: The score list to update our scores with - ''' - # we'll end up with something like: - # SEVERITY: {0, 0, 0, 10} where 10 is weighted by finding and level - for score_type in self.scores: - self.scores[score_type] = list(map( - operator.add, self.scores[score_type], scores[score_type] - )) - - def process(self, data): - '''Main process loop - - Build and process the AST - :param lines: lines code to process - :return score: the aggregated score for the current file - ''' - f_ast = ast.parse(data) - self.generic_visit(f_ast) - return self.scores diff --git a/bandit/core/test_properties.py b/bandit/core/test_properties.py deleted file mode 100644 index cb37c7c6..00000000 --- a/bandit/core/test_properties.py +++ /dev/null @@ -1,85 +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 logging - -from bandit.core import utils - -LOG = logging.getLogger(__name__) - - -def checks(*args): - '''Decorator function to set checks to be run.''' - def wrapper(func): - if not hasattr(func, "_checks"): - func._checks = [] - func._checks.extend(utils.check_ast_node(a) for a in args) - - LOG.debug('checks() decorator executed') - LOG.debug(' func._checks: %s', func._checks) - return func - return wrapper - - -def takes_config(*args): - '''Test function takes config - - Use of this delegate before a test function indicates that it should be - passed data from the config file. Passing a name parameter allows - aliasing tests and thus sharing config options. - ''' - name = "" - - def _takes_config(func): - if not hasattr(func, "_takes_config"): - func._takes_config = name - return func - - if len(args) == 1 and callable(args[0]): - name = args[0].__name__ - return _takes_config(args[0]) - else: - name = args[0] - return _takes_config - - -def test_id(id_val): - '''Test function identifier - - Use this decorator before a test function indicates its simple ID - ''' - def _has_id(func): - if not hasattr(func, "_test_id"): - func._test_id = id_val - return func - return _has_id - - -def accepts_baseline(*args): - """Decorator to indicate formatter accepts baseline results - - Use of this decorator before a formatter indicates that it is able to deal - with baseline results. Specifically this means it has a way to display - candidate results and know when it should do so. - """ - def wrapper(func): - if not hasattr(func, '_accepts_baseline'): - func._accepts_baseline = True - - LOG.debug('accepts_baseline() decorator executed on %s', func.__name__) - - return func - return wrapper(args[0]) diff --git a/bandit/core/test_set.py b/bandit/core/test_set.py deleted file mode 100644 index b2213e8d..00000000 --- a/bandit/core/test_set.py +++ /dev/null @@ -1,123 +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 importlib -import logging - - -from bandit.core import blacklisting -from bandit.core import extension_loader - - -LOG = logging.getLogger(__name__) - - -class BanditTestSet(object): - def __init__(self, config, profile=None): - if not profile: - profile = {} - extman = extension_loader.MANAGER - filtering = self._get_filter(config, profile) - self.plugins = [p for p in extman.plugins - if p.plugin._test_id in filtering] - self.plugins.extend(self._load_builtins(filtering, profile)) - self._load_tests(config, self.plugins) - - @staticmethod - def _get_filter(config, profile): - extman = extension_loader.MANAGER - - inc = set(profile.get('include', [])) - exc = set(profile.get('exclude', [])) - - all_blacklist_tests = set() - 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 - # follows: - # B001,B401 means B401 - # B401 means B401 - # B001 means all blacklist tests - if 'B001' in inc: - if not inc.intersection(all_blacklist_tests): - inc.update(all_blacklist_tests) - inc.discard('B001') - if 'B001' in exc: - if not exc.intersection(all_blacklist_tests): - exc.update(all_blacklist_tests) - exc.discard('B001') - - if inc: - filtered = inc - else: - filtered = set(extman.plugins_by_id.keys()) - filtered.update(extman.builtin) - filtered.update(all_blacklist_tests) - return filtered - exc - - def _load_builtins(self, filtering, profile): - '''loads up builtin functions, so they can be filtered.''' - - class Wrapper(object): - def __init__(self, name, plugin): - self.name = name - self.plugin = plugin - - extman = extension_loader.MANAGER - blacklist = profile.get('blacklist') - if not blacklist: # not overridden by legacy data - blacklist = {} - for node, tests in extman.blacklist.items(): - values = [t for t in tests if t['id'] in filtering] - if values: - blacklist[node] = values - - if not blacklist: - return [] - - # this dresses up the blacklist to look like a plugin, but - # the '_checks' data comes from the blacklist information. - # the '_config' is the filtered blacklist data set. - setattr(blacklisting.blacklist, "_test_id", 'B001') - setattr(blacklisting.blacklist, "_checks", blacklist.keys()) - setattr(blacklisting.blacklist, "_config", blacklist) - return [Wrapper('blacklist', blacklisting.blacklist)] - - def _load_tests(self, config, plugins): - '''Builds a dict mapping tests to node types.''' - self.tests = {} - for plugin in plugins: - if hasattr(plugin.plugin, '_takes_config'): - # TODO(??): config could come from profile ... - cfg = config.get_option(plugin.plugin._takes_config) - if cfg is None: - genner = importlib.import_module(plugin.plugin.__module__) - cfg = genner.gen_config(plugin.plugin._takes_config) - plugin.plugin._config = cfg - for check in plugin.plugin._checks: - self.tests.setdefault(check, []).append(plugin.plugin) - LOG.debug('added function %s (%s) targeting %s', - plugin.name, plugin.plugin._test_id, check) - - def get_tests(self, checktype): - '''Returns all tests that are of type checktype - - :param checktype: The type of test to filter on - :return: A list of tests which are of the specified type - ''' - return self.tests.get(checktype) or [] diff --git a/bandit/core/tester.py b/bandit/core/tester.py deleted file mode 100644 index 562766e1..00000000 --- a/bandit/core/tester.py +++ /dev/null @@ -1,111 +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 copy -import logging -import warnings - -from bandit.core import constants -from bandit.core import context as b_context -from bandit.core import utils - -warnings.formatwarning = utils.warnings_formatter -LOG = logging.getLogger(__name__) - - -class BanditTester(object): - def __init__(self, testset, debug, nosec_lines): - self.results = [] - self.testset = testset - self.last_result = None - self.debug = debug - self.nosec_lines = nosec_lines - - def run_tests(self, raw_context, checktype): - '''Runs all tests for a certain type of check, for example - - Runs all tests for a certain type of check, for example 'functions' - store results in results. - - :param raw_context: Raw context dictionary - :param checktype: The type of checks to run - :param nosec_lines: Lines which should be skipped because of nosec - :return: a score based on the number and type of test results - ''' - - scores = { - 'SEVERITY': [0] * len(constants.RANKING), - 'CONFIDENCE': [0] * len(constants.RANKING) - } - - tests = self.testset.get_tests(checktype) - for test in tests: - name = test.__name__ - # execute test with the an instance of the context class - temp_context = copy.copy(raw_context) - context = b_context.Context(temp_context) - try: - if hasattr(test, '_config'): - result = test(context, test._config) - else: - result = test(context) - - # if we have a result, record it and update scores - if (result is not None and - result.lineno not in self.nosec_lines and - temp_context['lineno'] not in self.nosec_lines): - - if isinstance(temp_context['filename'], bytes): - result.fname = temp_context['filename'].decode('utf-8') - else: - result.fname = temp_context['filename'] - - if result.lineno is None: - result.lineno = temp_context['lineno'] - result.linerange = temp_context['linerange'] - result.test = name - if result.test_id == "": - result.test_id = test._test_id - - self.results.append(result) - - LOG.debug("Issue identified by %s: %s", name, result) - sev = constants.RANKING.index(result.severity) - val = constants.RANKING_VALUES[result.severity] - scores['SEVERITY'][sev] += val - con = constants.RANKING.index(result.confidence) - val = constants.RANKING_VALUES[result.confidence] - scores['CONFIDENCE'][con] += val - - except Exception as e: - self.report_error(name, context, e) - if self.debug: - raise - LOG.debug("Returning scores: %s", scores) - return scores - - @staticmethod - def report_error(test, context, error): - what = "Bandit internal error running: " - what += "%s " % test - what += "on file %s at line %i: " % ( - context._context['filename'], - context._context['lineno'] - ) - what += str(error) - import traceback - what += traceback.format_exc() - LOG.error(what) diff --git a/bandit/core/utils.py b/bandit/core/utils.py deleted file mode 100644 index a16f5642..00000000 --- a/bandit/core/utils.py +++ /dev/null @@ -1,336 +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 _ast -import ast -import logging -import os.path -import sys - -try: - import configparser -except ImportError: - import ConfigParser as configparser - -LOG = logging.getLogger(__name__) - - -"""Various helper functions.""" - - -def _get_attr_qual_name(node, aliases): - '''Get a the full name for the attribute node. - - This will resolve a pseudo-qualified name for the attribute - rooted at node as long as all the deeper nodes are Names or - Attributes. This will give you how the code referenced the name but - will not tell you what the name actually refers to. If we - encounter a node without a static name we punt with an - empty string. If this encounters something more complex, such as - foo.mylist[0](a,b) we just return empty string. - - :param node: AST Name or Attribute node - :param aliases: Import aliases dictionary - :returns: Qualified name referred to by the attribute or name. - ''' - if isinstance(node, _ast.Name): - if node.id in aliases: - return aliases[node.id] - return node.id - elif isinstance(node, _ast.Attribute): - name = '%s.%s' % (_get_attr_qual_name(node.value, aliases), node.attr) - if name in aliases: - return aliases[name] - return name - else: - return "" - - -def get_call_name(node, aliases): - if isinstance(node.func, _ast.Name): - if deepgetattr(node, 'func.id') in aliases: - return aliases[deepgetattr(node, 'func.id')] - return deepgetattr(node, 'func.id') - elif isinstance(node.func, _ast.Attribute): - return _get_attr_qual_name(node.func, aliases) - else: - return "" - - -def get_func_name(node): - return node.name # TODO(tkelsey): get that qualname using enclosing scope - - -def get_qual_attr(node, aliases): - prefix = "" - if isinstance(node, _ast.Attribute): - try: - val = deepgetattr(node, 'value.id') - if val in aliases: - prefix = aliases[val] - else: - prefix = deepgetattr(node, 'value.id') - except Exception: - # NOTE(tkelsey): degrade gracefully when we can't get the fully - # qualified name for an attr, just return its base name. - pass - - return "%s.%s" % (prefix, node.attr) - else: - return "" # TODO(tkelsey): process other node types - - -def deepgetattr(obj, attr): - """Recurses through an attribute chain to get the ultimate value.""" - for key in attr.split('.'): - obj = getattr(obj, key) - return obj - - -class InvalidModulePath(Exception): - pass - - -class ConfigError(Exception): - """Raised when the config file fails validation.""" - def __init__(self, message, config_file): - self.config_file = config_file - self.message = "{0} : {1}".format(config_file, message) - super(ConfigError, self).__init__(self.message) - - -class ProfileNotFound(Exception): - """Raised when chosen profile cannot be found.""" - def __init__(self, config_file, profile): - self.config_file = config_file - self.profile = profile - message = 'Unable to find profile (%s) in config file: %s' % ( - self.profile, self.config_file) - super(ProfileNotFound, self).__init__(message) - - -def warnings_formatter(message, category=UserWarning, filename='', lineno=-1, - line=''): - '''Monkey patch for warnings.warn to suppress cruft output.''' - return "{0}\n".format(message) - - -def get_module_qualname_from_path(path): - '''Get the module's qualified name by analysis of the path. - - Resolve the absolute pathname and eliminate symlinks. This could result in - an incorrect name if symlinks are used to restructure the python lib - directory. - - Starting from the right-most directory component look for __init__.py in - the directory component. If it exists then the directory name is part of - the module name. Move left to the subsequent directory components until a - directory is found without __init__.py. - - :param: Path to module file. Relative paths will be resolved relative to - current working directory. - :return: fully qualified module name - ''' - - (head, tail) = os.path.split(path) - if head == '' or tail == '': - raise InvalidModulePath('Invalid python file path: "%s"' - ' Missing path or file name' % (path)) - - qname = [os.path.splitext(tail)[0]] - while head not in ['/', '.', '']: - if os.path.isfile(os.path.join(head, '__init__.py')): - (head, tail) = os.path.split(head) - qname.insert(0, tail) - else: - break - - qualname = '.'.join(qname) - return qualname - - -def namespace_path_join(base, name): - '''Extend the current namespace path with an additional name - - Take a namespace path (i.e., package.module.class) and extends it - with an additional name (i.e., package.module.class.subclass). - This is similar to how os.path.join works. - - :param base: (String) The base namespace path. - :param name: (String) The new name to append to the base path. - :returns: (String) A new namespace path resulting from combination of - base and name. - ''' - return '%s.%s' % (base, name) - - -def namespace_path_split(path): - '''Split the namespace path into a pair (head, tail). - - Tail will be the last namespace path component and head will - be everything leading up to that in the path. This is similar to - os.path.split. - - :param path: (String) A namespace path. - :returns: (String, String) A tuple where the first component is the base - path and the second is the last path component. - ''' - return tuple(path.rsplit('.', 1)) - - -def escaped_bytes_representation(b): - '''PY3 bytes need escaping for comparison with other strings. - - In practice it turns control characters into acceptable codepoints then - encodes them into bytes again to turn unprintable bytes into printable - escape sequences. - - This is safe to do for the whole range 0..255 and result matches - unicode_escape on a unicode string. - ''' - return b.decode('unicode_escape').encode('unicode_escape') - - -def linerange(node): - """Get line number range from a node.""" - strip = {"body": None, "orelse": None, - "handlers": None, "finalbody": None} - for key in strip.keys(): - if hasattr(node, key): - strip[key] = getattr(node, key) - setattr(node, key, []) - - lines_min = 9999999999 - lines_max = -1 - for n in ast.walk(node): - if hasattr(n, 'lineno'): - lines_min = min(lines_min, n.lineno) - lines_max = max(lines_max, n.lineno) - - for key in strip.keys(): - if strip[key] is not None: - setattr(node, key, strip[key]) - - if lines_max > -1: - return list(range(lines_min, lines_max + 1)) - return [0, 1] - - -def linerange_fix(node): - """Try and work around a known Python bug with multi-line strings.""" - # deal with multiline strings lineno behavior (Python issue #16806) - lines = linerange(node) - if hasattr(node, 'sibling') and hasattr(node.sibling, 'lineno'): - start = min(lines) - delta = node.sibling.lineno - start - if delta > 1: - return list(range(start, node.sibling.lineno)) - return lines - - -def concat_string(node, stop=None): - '''Builds a string from a ast.BinOp chain. - - This will build a string from a series of ast.Str nodes wrapped in - ast.BinOp nodes. Something like "a" + "b" + "c" or "a %s" % val etc. - The provided node can be any participant in the BinOp chain. - - :param node: (ast.Str or ast.BinOp) The node to process - :param stop: (ast.Str or ast.BinOp) Optional base node to stop at - :returns: (Tuple) the root node of the expression, the string value - ''' - def _get(node, bits, stop=None): - if node != stop: - bits.append( - _get(node.left, bits, stop) - if isinstance(node.left, ast.BinOp) - else node.left) - bits.append( - _get(node.right, bits, stop) - if isinstance(node.right, ast.BinOp) - else node.right) - - bits = [node] - while isinstance(node.parent, ast.BinOp): - node = node.parent - if isinstance(node, ast.BinOp): - _get(node, bits, stop) - return (node, " ".join([x.s for x in bits if isinstance(x, ast.Str)])) - - -def get_called_name(node): - '''Get a function name from an ast.Call node. - - An ast.Call node representing a method call with present differently to one - wrapping a function call: thing.call() vs call(). This helper will grab the - unqualified call name correctly in either case. - - :param node: (ast.Call) the call node - :returns: (String) the function name - ''' - func = node.func - try: - return func.attr if isinstance(func, ast.Attribute) else func.id - except AttributeError: - return "" - - -def get_path_for_function(f): - '''Get the path of the file where the function is defined. - - :returns: the path, or None if one could not be found or f is not a real - function - ''' - - if hasattr(f, "__module__"): - module_name = f.__module__ - elif hasattr(f, "im_func"): - module_name = f.im_func.__module__ - else: - LOG.warning("Cannot resolve file where %s is defined", f) - return None - - module = sys.modules[module_name] - if hasattr(module, "__file__"): - return module.__file__ - else: - LOG.warning("Cannot resolve file path for module %s", module_name) - return None - - -def parse_ini_file(f_loc): - config = configparser.ConfigParser() - try: - config.read(f_loc) - return {k: v for k, v in config.items('bandit')} - - except (configparser.Error, KeyError, TypeError): - LOG.warning("Unable to parse config file %s or missing [bandit] " - "section", f_loc) - - return None - - -def check_ast_node(name): - 'Check if the given name is that of a valid AST node.' - try: - node = getattr(ast, name) - if issubclass(node, ast.AST): - return name - except AttributeError: # nosec(tkelsey): catching expected exception - pass - - raise TypeError("Error: %s is not a valid node type in AST" % name) diff --git a/bandit/formatters/__init__.py b/bandit/formatters/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bandit/formatters/csv.py b/bandit/formatters/csv.py deleted file mode 100644 index 1c867d72..00000000 --- a/bandit/formatters/csv.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- 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""" -============= -CSV Formatter -============= - -This formatter outputs the issues in a comma separated values format. - -:Example: - -.. 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] - -.. versionadded:: 0.11.0 - -""" -# Necessary for this formatter to work when imported on Python 2. Importing -# the standard library's csv module conflicts with the name of this module. -from __future__ import absolute_import - -import csv -import logging -import sys - -LOG = logging.getLogger(__name__) - - -def report(manager, fileobj, sev_level, conf_level, lines=-1): - '''Prints issues in CSV 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 - ''' - - results = manager.get_issue_list(sev_level=sev_level, - conf_level=conf_level) - - with fileobj: - fieldnames = ['filename', - 'test_name', - 'test_id', - 'issue_severity', - 'issue_confidence', - 'issue_text', - 'line_number', - 'line_range'] - - writer = csv.DictWriter(fileobj, fieldnames=fieldnames, - extrasaction='ignore') - writer.writeheader() - for result in results: - writer.writerow(result.as_dict(with_code=False)) - - if fileobj.name != sys.stdout.name: - LOG.info("CSV output written to file: %s", fileobj.name) diff --git a/bandit/formatters/custom.py b/bandit/formatters/custom.py deleted file mode 100644 index 864ff4f3..00000000 --- a/bandit/formatters/custom.py +++ /dev/null @@ -1,163 +0,0 @@ -# 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 - (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/bandit/formatters/html.py b/bandit/formatters/html.py deleted file mode 100644 index d9211e2e..00000000 --- a/bandit/formatters/html.py +++ /dev/null @@ -1,387 +0,0 @@ -# Copyright (c) 2015 Rackspace, Inc. -# Copyright (c) 2015 Hewlett Packard Enterprise -# -# 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""" -============== -HTML formatter -============== - -This formatter outputs the issues as HTML. - -:Example: - -.. code-block:: html - - - - - - - - - Bandit Report - - - - - - - -
-
-
- Metrics:
-
- Total lines of code: 9
- Total lines skipped (#nosec): 0 -
-
- - - - -
-
- -
-
- yaml_load: Use of unsafe yaml load. Allows - instantiation of arbitrary objects. Consider yaml.safe_load().
- Test ID: B506
- Severity: MEDIUM
- Confidence: HIGH
- File: examples/yaml_load.py
- More info: - https://docs.openstack.org/bandit/latest/plugins/yaml_load.html -
- -
-
-    5       ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})
-    6       y = yaml.load(ystr)
-    7       yaml.dump(y)
-    
-
- - -
-
- -
- - - - -.. versionadded:: 0.14.0 - -""" - -import cgi -import logging -import sys - -from bandit.core import docs_utils -from bandit.core import test_properties -from bandit.formatters import utils - -LOG = logging.getLogger(__name__) - - -@test_properties.accepts_baseline -def report(manager, fileobj, sev_level, conf_level, lines=-1): - """Writes issues to 'fileobj' in HTML 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 - """ - - header_block = u""" - - - - - - - - Bandit Report - - - - -""" - - report_block = u""" - -{metrics} -{skipped} - -
-
- {results} -
- - - -""" - - issue_block = u""" -
-
- {test_name}: {test_text}
- Test ID: {test_id}
- Severity: {severity}
- Confidence: {confidence}
- File: {path}
- More info: {url}
-{code} -{candidates} -
-
-""" - - code_block = u""" -
-
-{code}
-
-
-""" - - candidate_block = u""" -
-
-Candidates: -{candidate_list} -
-""" - - candidate_issue = u""" -
-
-
{code}
-
-
-""" - - skipped_block = u""" -
-
-
-Skipped files:

-{files_list} -
-
-""" - - metrics_block = u""" -
-
-
- Metrics:
-
- Total lines of code: {loc}
- Total lines skipped (#nosec): {nosec} -
-
- -""" - - issues = manager.get_issue_list(sev_level=sev_level, conf_level=conf_level) - - baseline = not isinstance(issues, list) - - # build the skipped string to insert in the report - skipped_str = ''.join('%s reason: %s
' % (fname, reason) - for fname, reason in manager.get_skipped()) - if skipped_str: - skipped_text = skipped_block.format(files_list=skipped_str) - else: - skipped_text = '' - - # build the results string to insert in the report - results_str = '' - for index, issue in enumerate(issues): - if not baseline or len(issues[issue]) == 1: - candidates = '' - safe_code = cgi.escape(issue.get_code(lines, True). - strip('\n').lstrip(' ')) - code = code_block.format(code=safe_code) - else: - candidates_str = '' - code = '' - for candidate in issues[issue]: - candidate_code = cgi.escape(candidate.get_code(lines, True). - strip('\n').lstrip(' ')) - candidates_str += candidate_issue.format(code=candidate_code) - - candidates = candidate_block.format(candidate_list=candidates_str) - - url = docs_utils.get_url(issue.test_id) - results_str += issue_block.format(issue_no=index, - issue_class='issue-sev-{}'. - format(issue.severity.lower()), - test_name=issue.test, - test_id=issue.test_id, - test_text=issue.text, - severity=issue.severity, - confidence=issue.confidence, - path=issue.fname, code=code, - candidates=candidates, - url=url) - - # build the metrics string to insert in the report - metrics_summary = metrics_block.format( - loc=manager.metrics.data['_totals']['loc'], - nosec=manager.metrics.data['_totals']['nosec']) - - # build the report and output it - report_contents = report_block.format(metrics=metrics_summary, - skipped=skipped_text, - results=results_str) - - with fileobj: - wrapped_file = utils.wrap_file_object(fileobj) - wrapped_file.write(utils.convert_file_contents(header_block)) - wrapped_file.write(utils.convert_file_contents(report_contents)) - - if fileobj.name != sys.stdout.name: - LOG.info("HTML output written to file: %s", fileobj.name) diff --git a/bandit/formatters/json.py b/bandit/formatters/json.py deleted file mode 100644 index bc522f6b..00000000 --- a/bandit/formatters/json.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- 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""" -============== -JSON formatter -============== - -This formatter outputs the issues in JSON. - -:Example: - -.. code-block:: javascript - - { - "errors": [], - "generated_at": "2015-12-16T22:27:34Z", - "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": 5, - "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": 5, - "nosec": 0 - } - }, - "results": [ - { - "code": "4 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})\n5 - y = yaml.load(ystr)\n6 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().\n", - "line_number": 5, - "line_range": [ - 5 - ], - "more_info": "https://docs.openstack.org/bandit/latest/", - "test_name": "blacklist_calls", - "test_id": "B301" - } - ] - } - -.. versionadded:: 0.10.0 - -""" -# Necessary so we can import the standard library json module while continuing -# to name this file json.py. (Python 2 only) -from __future__ import absolute_import - -import datetime -import json -import logging -import operator -import sys - -from bandit.core import docs_utils -from bandit.core import test_properties - -LOG = logging.getLogger(__name__) - - -@test_properties.accepts_baseline -def report(manager, fileobj, sev_level, conf_level, lines=-1): - '''''Prints issues in JSON 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 - ''' - - 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) - - baseline = not isinstance(results, list) - - if baseline: - collector = [] - for r in results: - d = r.as_dict() - d['more_info'] = docs_utils.get_url(d['test_id']) - if len(results[r]) > 1: - d['candidates'] = [c.as_dict() for c in results[r]] - collector.append(d) - - else: - collector = [r.as_dict() for r in results] - for elem in collector: - elem['more_info'] = docs_utils.get_url(elem['test_id']) - - itemgetter = operator.itemgetter - if manager.agg_type == 'vuln': - machine_output['results'] = sorted(collector, - key=itemgetter('test_name')) - else: - machine_output['results'] = sorted(collector, - key=itemgetter('filename')) - - machine_output['metrics'] = manager.metrics.data - - # timezone agnostic format - TS_FORMAT = "%Y-%m-%dT%H:%M:%SZ" - - time_string = datetime.datetime.utcnow().strftime(TS_FORMAT) - machine_output['generated_at'] = time_string - - result = json.dumps(machine_output, sort_keys=True, - indent=2, separators=(',', ': ')) - - with fileobj: - fileobj.write(result) - - if fileobj.name != sys.stdout.name: - LOG.info("JSON output written to file: %s", fileobj.name) diff --git a/bandit/formatters/screen.py b/bandit/formatters/screen.py deleted file mode 100644 index d683b023..00000000 --- a/bandit/formatters/screen.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright (c) 2015 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""" -================ -Screen formatter -================ - -This formatter outputs the issues as color coded text. - -:Example: - -.. code-block:: none - - >> Issue: [B301:blacklist_calls] Use of unsafe yaml load. Allows - instantiation of arbitrary objects. Consider yaml.safe_load(). - - Severity: Medium Confidence: High - Location: examples/yaml_load.py:5 - 4 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3}) - 5 y = yaml.load(ystr) - 6 yaml.dump(y) - -.. versionadded:: 0.9.0 - -""" - -from __future__ import print_function - -import datetime -import logging -import sys - -from bandit.core import constants -from bandit.core import test_properties - -LOG = logging.getLogger(__name__) - -COLOR = { - 'DEFAULT': '\033[0m', - 'HEADER': '\033[95m', - 'LOW': '\033[94m', - 'MEDIUM': '\033[93m', - 'HIGH': '\033[91m', -} - - -def header(text, *args): - return u'%s%s%s' % (COLOR['HEADER'], (text % args), COLOR['DEFAULT']) - - -def get_verbose_details(manager): - bits = [] - bits.append(header(u'Files in scope (%i):', len(manager.files_list))) - tpl = u"\t%s (score: {SEVERITY: %i, CONFIDENCE: %i})" - bits.extend([tpl % (item, sum(score['SEVERITY']), sum(score['CONFIDENCE'])) - for (item, score) - in zip(manager.files_list, manager.scores)]) - bits.append(header(u'Files excluded (%i):', len(manager.excluded_files))) - bits.extend([u"\t%s" % fname for fname in manager.excluded_files]) - return '\n'.join([str(bit) for bit in bits]) - - -def get_metrics(manager): - bits = [] - bits.append(header("\nRun metrics:")) - for (criteria, default) in constants.CRITERIA: - bits.append("\tTotal issues (by %s):" % (criteria.lower())) - for rank in constants.RANKING: - bits.append("\t\t%s: %s" % ( - rank.capitalize(), - manager.metrics.data['_totals']['%s.%s' % (criteria, rank)])) - return '\n'.join([str(bit) for bit in bits]) - - -def _output_issue_str(issue, indent, show_lineno=True, show_code=True, - lines=-1): - # returns a list of lines that should be added to the existing lines list - bits = [] - bits.append("%s%s>> Issue: [%s:%s] %s" % ( - indent, COLOR[issue.severity], issue.test_id, issue.test, issue.text)) - - bits.append("%s Severity: %s Confidence: %s" % ( - indent, issue.severity.capitalize(), issue.confidence.capitalize())) - - bits.append("%s Location: %s:%s%s" % ( - indent, issue.fname, - issue.lineno if show_lineno else "", - COLOR['DEFAULT'])) - - if show_code: - bits.extend([indent + l for l in - issue.get_code(lines, True).split('\n')]) - - return '\n'.join([bit for bit in bits]) - - -def get_results(manager, sev_level, conf_level, lines): - bits = [] - issues = manager.get_issue_list(sev_level, conf_level) - baseline = not isinstance(issues, list) - candidate_indent = ' ' * 10 - - if not len(issues): - return u"\tNo issues identified." - - for issue in issues: - # if not a baseline or only one candidate we know the issue - if not baseline or len(issues[issue]) == 1: - bits.append(_output_issue_str(issue, "", lines=lines)) - - # otherwise show the finding and the candidates - else: - bits.append(_output_issue_str(issue, "", - show_lineno=False, - show_code=False)) - - bits.append(u'\n-- Candidate Issues --') - for candidate in issues[issue]: - bits.append(_output_issue_str(candidate, - candidate_indent, - lines=lines)) - bits.append('\n') - bits.append(u'-' * 50) - - return '\n'.join([bit for bit in bits]) - - -def do_print(bits): - # needed so we can mock this stuff - print('\n'.join([bit for bit in bits])) - - -@test_properties.accepts_baseline -def report(manager, fileobj, sev_level, conf_level, lines=-1): - """Prints discovered issues formatted for screen reading - - This makes use of VT100 terminal codes for colored text. - - :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 - """ - - bits = [] - bits.append(header("Run started:%s", datetime.datetime.utcnow())) - - if manager.verbose: - bits.append(get_verbose_details(manager)) - - bits.append(header("\nTest results:")) - bits.append(get_results(manager, sev_level, conf_level, lines)) - bits.append(header("\nCode scanned:")) - bits.append('\tTotal lines of code: %i' % - (manager.metrics.data['_totals']['loc'])) - - bits.append('\tTotal lines skipped (#nosec): %i' % - (manager.metrics.data['_totals']['nosec'])) - - bits.append(get_metrics(manager)) - skipped = manager.get_skipped() - bits.append(header("Files skipped (%i):", len(skipped))) - bits.extend(["\t%s (%s)" % skip for skip in skipped]) - do_print(bits) - - if fileobj.name != sys.stdout.name: - LOG.info("Screen formatter output was not written to file: %s, " - "consider '-f txt'", fileobj.name) diff --git a/bandit/formatters/text.py b/bandit/formatters/text.py deleted file mode 100644 index bc4bd4c7..00000000 --- a/bandit/formatters/text.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright (c) 2015 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""" -============== -Text Formatter -============== - -This formatter outputs the issues as plain text. - -:Example: - -.. code-block:: none - - >> Issue: [B301:blacklist_calls] Use of unsafe yaml load. Allows - instantiation of arbitrary objects. Consider yaml.safe_load(). - - Severity: Medium Confidence: High - Location: examples/yaml_load.py:5 - 4 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3}) - 5 y = yaml.load(ystr) - 6 yaml.dump(y) - -.. versionadded:: 0.9.0 - -""" - -from __future__ import print_function - -import datetime -import logging -import sys - -from bandit.core import constants -from bandit.core import test_properties -from bandit.formatters import utils - -LOG = logging.getLogger(__name__) - - -def get_verbose_details(manager): - bits = [] - bits.append(u'Files in scope (%i):' % len(manager.files_list)) - tpl = u"\t%s (score: {SEVERITY: %i, CONFIDENCE: %i})" - bits.extend([tpl % (item, sum(score['SEVERITY']), sum(score['CONFIDENCE'])) - for (item, score) - in zip(manager.files_list, manager.scores)]) - bits.append(u'Files excluded (%i):' % len(manager.excluded_files)) - bits.extend([u"\t%s" % fname for fname in manager.excluded_files]) - return '\n'.join([bit for bit in bits]) - - -def get_metrics(manager): - bits = [] - bits.append("\nRun metrics:") - for (criteria, default) in constants.CRITERIA: - bits.append("\tTotal issues (by %s):" % (criteria.lower())) - for rank in constants.RANKING: - bits.append("\t\t%s: %s" % ( - rank.capitalize(), - manager.metrics.data['_totals']['%s.%s' % (criteria, rank)])) - return '\n'.join([bit for bit in bits]) - - -def _output_issue_str(issue, indent, show_lineno=True, show_code=True, - lines=-1): - # returns a list of lines that should be added to the existing lines list - bits = [] - bits.append("%s>> Issue: [%s:%s] %s" % ( - indent, issue.test_id, issue.test, issue.text)) - - bits.append("%s Severity: %s Confidence: %s" % ( - indent, issue.severity.capitalize(), issue.confidence.capitalize())) - - bits.append("%s Location: %s:%s" % ( - indent, issue.fname, issue.lineno if show_lineno else "")) - - if show_code: - bits.extend([indent + l for l in - issue.get_code(lines, True).split('\n')]) - - return '\n'.join([bit for bit in bits]) - - -def get_results(manager, sev_level, conf_level, lines): - bits = [] - issues = manager.get_issue_list(sev_level, conf_level) - baseline = not isinstance(issues, list) - candidate_indent = ' ' * 10 - - if not len(issues): - return u"\tNo issues identified." - - for issue in issues: - # if not a baseline or only one candidate we know the issue - if not baseline or len(issues[issue]) == 1: - bits.append(_output_issue_str(issue, "", lines=lines)) - - # otherwise show the finding and the candidates - else: - bits.append(_output_issue_str(issue, "", - show_lineno=False, - show_code=False)) - - bits.append(u'\n-- Candidate Issues --') - for candidate in issues[issue]: - bits.append(_output_issue_str(candidate, - candidate_indent, - lines=lines)) - bits.append('\n') - bits.append(u'-' * 50) - return '\n'.join([bit for bit in bits]) - - -@test_properties.accepts_baseline -def report(manager, fileobj, sev_level, conf_level, lines=-1): - """Prints discovered issues in the text 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 - """ - - bits = [] - bits.append("Run started:%s" % datetime.datetime.utcnow()) - - if manager.verbose: - bits.append(get_verbose_details(manager)) - - bits.append("\nTest results:") - bits.append(get_results(manager, sev_level, conf_level, lines)) - bits.append("\nCode scanned:") - bits.append('\tTotal lines of code: %i' % - (manager.metrics.data['_totals']['loc'])) - - bits.append('\tTotal lines skipped (#nosec): %i' % - (manager.metrics.data['_totals']['nosec'])) - - skipped = manager.get_skipped() - bits.append(get_metrics(manager)) - bits.append("Files skipped (%i):" % len(skipped)) - bits.extend(["\t%s (%s)" % skip for skip in skipped]) - result = '\n'.join([bit for bit in bits]) + '\n' - - with fileobj: - wrapped_file = utils.wrap_file_object(fileobj) - wrapped_file.write(utils.convert_file_contents(result)) - - if fileobj.name != sys.stdout.name: - LOG.info("Text output written to file: %s", fileobj.name) diff --git a/bandit/formatters/utils.py b/bandit/formatters/utils.py deleted file mode 100644 index 48400d9a..00000000 --- a/bandit/formatters/utils.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2016 Rackspace, Inc. -# -# 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. -"""Utility functions for formatting plugins for Bandit.""" - -import io - -import six - - -def wrap_file_object(fileobj): - """Handle differences in Python 2 and 3 around writing bytes.""" - # If it's not an instance of IOBase, we're probably using Python 2 and - # that is less finnicky about writing text versus bytes to a file. - if not isinstance(fileobj, io.IOBase): - return fileobj - - # At this point we're using Python 3 and that will mangle text written to - # a file written in bytes mode. So, let's check if the file can handle - # text as opposed to bytes. - if isinstance(fileobj, io.TextIOBase): - return fileobj - - # Finally, we've determined that the fileobj passed in cannot handle text, - # so we use TextIOWrapper to handle the conversion for us. - return io.TextIOWrapper(fileobj) - - -def convert_file_contents(text): - """Convert text to built-in strings on Python 2.""" - if not six.PY2: - return text - return str(text.encode('utf-8')) diff --git a/bandit/formatters/xml.py b/bandit/formatters/xml.py deleted file mode 100644 index 2298f168..00000000 --- a/bandit/formatters/xml.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- 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""" -============= -XML Formatter -============= - -This formatter outputs the issues as XML. - -:Example: - -.. code-block:: xml - - - Test ID: B301 - Severity: MEDIUM Confidence: HIGH Use of unsafe yaml load. Allows - instantiation of arbitrary objects. Consider yaml.safe_load(). - - Location examples/yaml_load.py:5 - -.. versionadded:: 0.12.0 - -""" -# This future import is necessary here due to the xml import below on Python -# 2.7 -from __future__ import absolute_import - -import logging -import sys -from xml.etree import cElementTree as ET - -import six - -LOG = logging.getLogger(__name__) - - -def report(manager, fileobj, sev_level, conf_level, lines=-1): - '''Prints issues in XML 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 - ''' - - issues = manager.get_issue_list(sev_level=sev_level, conf_level=conf_level) - root = ET.Element('testsuite', name='bandit', tests=str(len(issues))) - - for issue in issues: - test = issue.test - testcase = ET.SubElement(root, 'testcase', - classname=issue.fname, name=test) - - text = 'Test ID: %s Severity: %s Confidence: %s\n%s\nLocation %s:%s' - text = text % (issue.test_id, issue.severity, issue.confidence, - issue.text, issue.fname, issue.lineno) - ET.SubElement(testcase, 'error', type=issue.severity, - message=issue.text).text = text - - tree = ET.ElementTree(root) - - if fileobj.name == sys.stdout.name: - if six.PY2: - fileobj = sys.stdout - else: - fileobj = sys.stdout.buffer - elif fileobj.mode == 'w': - fileobj.close() - fileobj = open(fileobj.name, "wb") - - with fileobj: - tree.write(fileobj, encoding='utf-8', xml_declaration=True) - - if fileobj.name != sys.stdout.name: - LOG.info("XML output written to file: %s", fileobj.name) diff --git a/bandit/formatters/yaml.py b/bandit/formatters/yaml.py deleted file mode 100644 index 3217b7c8..00000000 --- a/bandit/formatters/yaml.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2017 VMware, Inc. -# -# 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""" -============== -YAML Formatter -============== - -This formatter outputs the issues in a yaml format. - -:Example: - -.. code-block:: none - - 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 - more_info: https://docs.openstack.org/bandit/latest/ - test_id: B506 - test_name: yaml_load - -.. versionadded:: 1.4.1 - -""" -# Necessary for this formatter to work when imported on Python 2. Importing -# the standard library's yaml module conflicts with the name of this module. -from __future__ import absolute_import - -import datetime -import logging -import operator -import sys - -import yaml - -from bandit.core import docs_utils - -LOG = logging.getLogger(__name__) - - -def report(manager, fileobj, sev_level, conf_level, lines=-1): - '''Prints issues in YAML 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 - ''' - - 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) - - collector = [r.as_dict() for r in results] - for elem in collector: - elem['more_info'] = docs_utils.get_url(elem['test_id']) - - itemgetter = operator.itemgetter - if manager.agg_type == 'vuln': - machine_output['results'] = sorted(collector, - key=itemgetter('test_name')) - else: - machine_output['results'] = sorted(collector, - key=itemgetter('filename')) - - machine_output['metrics'] = manager.metrics.data - - for result in machine_output['results']: - if 'code' in result: - code = result['code'].replace('\n', '\\n') - result['code'] = code - - # timezone agnostic format - TS_FORMAT = "%Y-%m-%dT%H:%M:%SZ" - - time_string = datetime.datetime.utcnow().strftime(TS_FORMAT) - machine_output['generated_at'] = time_string - - yaml.safe_dump(machine_output, fileobj, default_flow_style=False) - - if fileobj.name != sys.stdout.name: - LOG.info("YAML output written to file: %s", fileobj.name) diff --git a/bandit/plugins/__init__.py b/bandit/plugins/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bandit/plugins/app_debug.py b/bandit/plugins/app_debug.py deleted file mode 100644 index 9c6a4308..00000000 --- a/bandit/plugins/app_debug.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2015 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""" -====================================================== -B201: Test for use of flask app with debug set to true -====================================================== - -Running Flask applications in debug mode results in the Werkzeug debugger -being enabled. This includes a feature that allows arbitrary code execution. -Documentation for both Flask [1]_ and Werkzeug [2]_ strongly suggests that -debug mode should never be enabled on production systems. - -Operating a production server with debug mode enabled was the probable cause -of the Patreon breach in 2015 [3]_. - -:Example: - -.. code-block:: none - - >> Issue: A Flask app appears to be run with debug=True, which exposes - the Werkzeug debugger and allows the execution of arbitrary code. - Severity: High Confidence: High - Location: examples/flask_debug.py:10 - 9 #bad - 10 app.run(debug=True) - 11 - -.. seealso:: - - .. [1] http://flask.pocoo.org/docs/0.10/quickstart/#debug-mode - .. [2] http://werkzeug.pocoo.org/docs/0.10/debug/ - .. [3] http://labs.detectify.com/post/130332638391/how-patreon-got-hacked-publicly-exposed-werkzeug # noqa - -.. versionadded:: 0.15.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -@test.test_id('B201') -@test.checks('Call') -def flask_debug_true(context): - if context.is_module_imported_like('flask'): - if context.call_function_name_qual.endswith('.run'): - if context.check_call_arg_value('debug', 'True'): - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.MEDIUM, - text="A Flask app appears to be run with debug=True, " - "which exposes the Werkzeug debugger and allows " - "the execution of arbitrary code.", - lineno=context.get_lineno_for_call_arg('debug'), - ) diff --git a/bandit/plugins/asserts.py b/bandit/plugins/asserts.py deleted file mode 100644 index ce0b0c4a..00000000 --- a/bandit/plugins/asserts.py +++ /dev/null @@ -1,65 +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""" -============================ -B101: Test for use of assert -============================ - -This plugin test checks for the use of the Python ``assert`` keyword. It was -discovered that some projects used assert to enforce interface constraints. -However, assert is removed with compiling to optimised byte code (python -o -producing \*.pyo files). This caused various protections to be removed. The use -of assert is also considered as general bad practice in OpenStack codebases. - -Please see -https://docs.python.org/2/reference/simple_stmts.html#the-assert-statement for -more info on ``assert`` - -:Example: - -.. code-block:: none - - >> Issue: Use of assert detected. The enclosed code will be removed when - compiling to optimised byte code. - Severity: Low Confidence: High - Location: ./examples/assert.py:1 - 1 assert logged_in - 2 display_assets() - -.. seealso:: - - - https://bugs.launchpad.net/juniperopenstack/+bug/1456193 - - https://bugs.launchpad.net/heat/+bug/1397883 - - https://docs.python.org/2/reference/simple_stmts.html#the-assert-statement - -.. versionadded:: 0.11.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -@test.test_id('B101') -@test.checks('Assert') -def assert_used(context): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.HIGH, - text=("Use of assert detected. The enclosed code " - "will be removed when compiling to optimised byte code.") - ) diff --git a/bandit/plugins/crypto_request_no_cert_validation.py b/bandit/plugins/crypto_request_no_cert_validation.py deleted file mode 100644 index 21881b93..00000000 --- a/bandit/plugins/crypto_request_no_cert_validation.py +++ /dev/null @@ -1,72 +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""" -============================================= -B501: Test for missing certificate validation -============================================= - -Encryption in general is typically critical to the security of many -applications. Using TLS can greatly increase security by guaranteeing the -identity of the party you are communicating with. This is accomplished by one -or both parties presenting trusted certificates during the connection -initialization phase of TLS. - -When request methods are used certificates are validated automatically which is -the desired behavior. If certificate validation is explicitly turned off -Bandit will return a HIGH severity error. - - -:Example: - -.. code-block:: none - - >> Issue: [request_with_no_cert_validation] Requests call with verify=False - disabling SSL certificate checks, security issue. - Severity: High Confidence: High - Location: examples/requests-ssl-verify-disabled.py:4 - 3 requests.get('https://gmail.com', verify=True) - 4 requests.get('https://gmail.com', verify=False) - 5 requests.post('https://gmail.com', verify=True) - -.. seealso:: - - - https://security.openstack.org/guidelines/dg_move-data-securely.html - - https://security.openstack.org/guidelines/dg_validate-certificates.html - -.. versionadded:: 0.9.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -@test.checks('Call') -@test.test_id('B501') -def request_with_no_cert_validation(context): - http_verbs = ('get', 'options', 'head', 'post', 'put', 'patch', 'delete') - if ('requests' in context.call_function_name_qual and - context.call_function_name in http_verbs): - if context.check_call_arg_value('verify', 'False'): - issue = bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text="Requests call with verify=False disabling SSL " - "certificate checks, security issue.", - lineno=context.get_lineno_for_call_arg('verify'), - ) - return issue diff --git a/bandit/plugins/exec.py b/bandit/plugins/exec.py deleted file mode 100644 index d9495371..00000000 --- a/bandit/plugins/exec.py +++ /dev/null @@ -1,67 +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""" -============================== -B102: Test for the use of exec -============================== - -This plugin test checks for the use of Python's `exec` method or keyword. The -Python docs succinctly describe why the use of `exec` is risky. - -:Example: - -.. code-block:: none - - >> Issue: Use of exec detected. - Severity: Medium Confidence: High - Location: ./examples/exec-py2.py:2 - 1 exec("do evil") - 2 exec "do evil" - -.. seealso:: - - - https://docs.python.org/2.0/ref/exec.html - - TODO: add info on exec and similar to sec best practice and link here - -.. versionadded:: 0.9.0 -""" - -import six - -import bandit -from bandit.core import test_properties as test - - -def exec_issue(): - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.HIGH, - text="Use of exec detected." - ) - - -if six.PY2: - @test.checks('Exec') - @test.test_id('B102') - def exec_used(context): - return exec_issue() -else: - @test.checks('Call') - @test.test_id('B102') - def exec_used(context): - if context.call_function_name_qual == 'exec': - return exec_issue() diff --git a/bandit/plugins/exec_as_root.py b/bandit/plugins/exec_as_root.py deleted file mode 100644 index 558ed483..00000000 --- a/bandit/plugins/exec_as_root.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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""" -================================================== -B111: Test for the use of rootwrap running as root -================================================== - -Running commands as root dramatically increase their potential risk. Running -commands with restricted user privileges provides defense in depth against -command injection attacks, or developer and configuration error. This plugin -test checks for specific methods being called with a keyword parameter -`run_as_root` set to True, a common OpenStack idiom. - - -**Config Options:** - -This test plugin takes a similarly named configuration block, -`execute_with_run_as_root_equals_true`, providing a list, `function_names`, of -function names. A call to any of these named functions will be checked for a -`run_as_root` keyword parameter, and if True, will report a Low severity -issue. - -.. code-block:: yaml - - execute_with_run_as_root_equals_true: - function_names: - - ceilometer.utils.execute - - cinder.utils.execute - - neutron.agent.linux.utils.execute - - nova.utils.execute - - nova.utils.trycmd - - -:Example: - -.. code-block:: none - - >> Issue: Execute with run_as_root=True identified, possible security - issue. - Severity: Low Confidence: Medium - Location: ./examples/exec-as-root.py:26 - 25 nova_utils.trycmd('gcc --version') - 26 nova_utils.trycmd('gcc --version', run_as_root=True) - 27 - -.. seealso:: - - - https://security.openstack.org/guidelines/dg_rootwrap-recommendations-and-plans.html # noqa - - https://security.openstack.org/guidelines/dg_use-oslo-rootwrap-securely.html - -.. versionadded:: 0.10.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -def gen_config(name): - if name == 'execute_with_run_as_root_equals_true': - return {'function_names': - ['ceilometer.utils.execute', - 'cinder.utils.execute', - 'neutron.agent.linux.utils.execute', - 'nova.utils.execute', - 'nova.utils.trycmd']} - - -@test.takes_config -@test.checks('Call') -@test.test_id('B111') -def execute_with_run_as_root_equals_true(context, config): - - if (context.call_function_name_qual in config['function_names']): - if context.check_call_arg_value('run_as_root', 'True'): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.MEDIUM, - text="Execute with run_as_root=True identified, possible " - "security issue.", - lineno=context.get_lineno_for_call_arg('run_as_root'), - ) diff --git a/bandit/plugins/general_bad_file_permissions.py b/bandit/plugins/general_bad_file_permissions.py deleted file mode 100644 index 6b6a002b..00000000 --- a/bandit/plugins/general_bad_file_permissions.py +++ /dev/null @@ -1,89 +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""" -================================================== -B103: Test for setting permissive file permissions -================================================== - -POSIX based operating systems utilize a permissions model to protect access to -parts of the file system. This model supports three roles "owner", "group" -and "world" each role may have a combination of "read", "write" or "execute" -flags sets. Python provides ``chmod`` to manipulate POSIX style permissions. - -This plugin test looks for the use of ``chmod`` and will alert when it is used -to set particularly permissive control flags. A MEDIUM warning is generated if -a file is set to group executable and a HIGH warning is reported if a file is -set world writable. Warnings are given with HIGH confidence. - -:Example: - -.. code-block:: none - - >> Issue: Probable insecure usage of temp file/directory. - Severity: Medium Confidence: Medium - Location: ./examples/os-chmod-py2.py:15 - 14 os.chmod('/etc/hosts', 0o777) - 15 os.chmod('/tmp/oh_hai', 0x1ff) - 16 os.chmod('/etc/passwd', stat.S_IRWXU) - - >> Issue: Chmod setting a permissive mask 0777 on file (key_file). - Severity: High Confidence: High - Location: ./examples/os-chmod-py2.py:17 - 16 os.chmod('/etc/passwd', stat.S_IRWXU) - 17 os.chmod(key_file, 0o777) - 18 - -.. seealso:: - - - https://security.openstack.org/guidelines/dg_apply-restrictive-file-permissions.html # noqa - - https://en.wikipedia.org/wiki/File_system_permissions - - https://security.openstack.org - -.. versionadded:: 0.9.0 - -""" - -import stat - -import bandit -from bandit.core import test_properties as test - - -@test.checks('Call') -@test.test_id('B103') -def set_bad_file_permissions(context): - if 'chmod' in context.call_function_name: - if context.call_args_count == 2: - mode = context.get_call_arg_at_position(1) - - if (mode is not None and isinstance(mode, int) and - (mode & stat.S_IWOTH or mode & stat.S_IXGRP)): - # world writable is an HIGH, group executable is a MEDIUM - if mode & stat.S_IWOTH: - sev_level = bandit.HIGH - else: - sev_level = bandit.MEDIUM - - filename = context.get_call_arg_at_position(0) - if filename is None: - filename = 'NOT PARSED' - return bandit.Issue( - severity=sev_level, - confidence=bandit.HIGH, - text="Chmod setting a permissive mask %s on file (%s)." % - (oct(mode), filename) - ) diff --git a/bandit/plugins/general_bind_all_interfaces.py b/bandit/plugins/general_bind_all_interfaces.py deleted file mode 100644 index 056ecbf0..00000000 --- a/bandit/plugins/general_bind_all_interfaces.py +++ /dev/null @@ -1,59 +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""" -======================================== -B104: Test for binding to all interfaces -======================================== - -Binding to all network interfaces can potentially open up a service to traffic -on unintended interfaces, that may not be properly documented or secured. This -plugin test looks for a string pattern "0.0.0.0" that may indicate a hardcoded -binding to all network interfaces. - -:Example: - -.. code-block:: none - - >> Issue: Possible binding to all interfaces. - Severity: Medium Confidence: Medium - Location: ./examples/binding.py:4 - 3 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - 4 s.bind(('0.0.0.0', 31137)) - 5 s.bind(('192.168.0.1', 8080)) - -.. seealso:: - - - __TODO__ : add best practice info on binding to all interfaces, and link - here. - -.. versionadded:: 0.9.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -@test.checks('Str') -@test.test_id('B104') -def hardcoded_bind_all_interfaces(context): - if context.string_val == '0.0.0.0': - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.MEDIUM, - text="Possible binding to all interfaces." - ) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py deleted file mode 100644 index e9f7c3a5..00000000 --- a/bandit/plugins/general_hardcoded_password.py +++ /dev/null @@ -1,215 +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 ast -import sys - -import bandit -from bandit.core import test_properties as test - - -CANDIDATES = set(["password", "pass", "passwd", "pwd", "secret", "token", - "secrete"]) - - -def _report(value): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.MEDIUM, - text=("Possible hardcoded password: '%s'" % value)) - - -@test.checks('Str') -@test.test_id('B105') -def hardcoded_password_string(context): - """**B105: Test for use of hard-coded password strings** - - The use of hard-coded passwords increases the possibility of password - guessing tremendously. This plugin test looks for all string literals and - checks the following conditions: - - - assigned to a variable that looks like a password - - assigned to a dict key that looks like a password - - used in a comparison with a variable that looks like a password - - Variables are considered to look like a password if they have match any one - of: - - - "password" - - "pass" - - "passwd" - - "pwd" - - "secret" - - "token" - - "secrete" - - Note: this can be noisy and may generate false positives. - - **Config Options:** - - None - - :Example: - - .. code-block:: none - - >> Issue: Possible hardcoded password '(root)' - Severity: Low Confidence: Low - Location: ./examples/hardcoded-passwords.py:5 - 4 def someFunction2(password): - 5 if password == "root": - 6 print("OK, logged in") - - .. seealso:: - - - https://www.owasp.org/index.php/Use_of_hard-coded_password - - .. versionadded:: 0.9.0 - - """ - node = context.node - if isinstance(node.parent, ast.Assign): - # looks for "candidate='some_string'" - for targ in node.parent.targets: - if isinstance(targ, ast.Name) and targ.id in CANDIDATES: - return _report(node.s) - - elif isinstance(node.parent, ast.Index) and node.s in CANDIDATES: - # looks for "dict[candidate]='some_string'" - # assign -> subscript -> index -> string - assign = node.parent.parent.parent - if isinstance(assign, ast.Assign) and isinstance(assign.value, - ast.Str): - return _report(assign.value.s) - - elif isinstance(node.parent, ast.Compare): - # looks for "candidate == 'some_string'" - comp = node.parent - if isinstance(comp.left, ast.Name) and comp.left.id in CANDIDATES: - if isinstance(comp.comparators[0], ast.Str): - return _report(comp.comparators[0].s) - - -@test.checks('Call') -@test.test_id('B106') -def hardcoded_password_funcarg(context): - """**B106: Test for use of hard-coded password function arguments** - - The use of hard-coded passwords increases the possibility of password - guessing tremendously. This plugin test looks for all function calls being - passed a keyword argument that is a string literal. It checks that the - assigned local variable does not look like a password. - - Variables are considered to look like a password if they have match any one - of: - - - "password" - - "pass" - - "passwd" - - "pwd" - - "secret" - - "token" - - "secrete" - - Note: this can be noisy and may generate false positives. - - **Config Options:** - - None - - :Example: - - .. code-block:: none - - >> Issue: [B106:hardcoded_password_funcarg] Possible hardcoded - password: 'blerg' - Severity: Low Confidence: Medium - Location: ./examples/hardcoded-passwords.py:16 - 15 - 16 doLogin(password="blerg") - - .. seealso:: - - - https://www.owasp.org/index.php/Use_of_hard-coded_password - - .. versionadded:: 0.9.0 - - """ - # looks for "function(candidate='some_string')" - for kw in context.node.keywords: - if isinstance(kw.value, ast.Str) and kw.arg in CANDIDATES: - return _report(kw.value.s) - - -@test.checks('FunctionDef') -@test.test_id('B107') -def hardcoded_password_default(context): - """**B107: Test for use of hard-coded password argument defaults** - - The use of hard-coded passwords increases the possibility of password - guessing tremendously. This plugin test looks for all function definitions - that specify a default string literal for some argument. It checks that - the argument does not look like a password. - - Variables are considered to look like a password if they have match any one - of: - - - "password" - - "pass" - - "passwd" - - "pwd" - - "secret" - - "token" - - "secrete" - - Note: this can be noisy and may generate false positives. - - **Config Options:** - - None - - :Example: - - .. code-block:: none - - >> Issue: [B107:hardcoded_password_default] Possible hardcoded - password: 'Admin' - Severity: Low Confidence: Medium - Location: ./examples/hardcoded-passwords.py:1 - - 1 def someFunction(user, password="Admin"): - 2 print("Hi " + user) - - .. seealso:: - - - https://www.owasp.org/index.php/Use_of_hard-coded_password - - .. versionadded:: 0.9.0 - - """ - # looks for "def function(candidate='some_string')" - - # this pads the list of default values with "None" if nothing is given - defs = [None] * (len(context.node.args.args) - - len(context.node.args.defaults)) - defs.extend(context.node.args.defaults) - - # go through all (param, value)s and look for candidates - for key, val in zip(context.node.args.args, defs): - if isinstance(key, ast.Name) or isinstance(key, ast.arg): - check = key.arg if sys.version_info.major > 2 else key.id # Py3 - if isinstance(val, ast.Str) and check in CANDIDATES: - return _report(val.s) diff --git a/bandit/plugins/general_hardcoded_tmp.py b/bandit/plugins/general_hardcoded_tmp.py deleted file mode 100644 index 49a2304f..00000000 --- a/bandit/plugins/general_hardcoded_tmp.py +++ /dev/null @@ -1,86 +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""" -=================================================== -B108: Test for insecure usage of tmp file/directory -=================================================== - -Safely creating a temporary file or directory means following a number of rules -(see the references for more details). This plugin test looks for strings -starting with (configurable) commonly used temporary paths, for example: - - - /tmp - - /var/tmp - - /dev/shm - - etc - -**Config Options:** - -This test plugin takes a similarly named config block, -`hardcoded_tmp_directory`. The config block provides a Python list, `tmp_dirs`, -that lists string fragments indicating possible temporary file paths. Any -string starting with one of these fragments will report a MEDIUM confidence -issue. - -.. code-block:: yaml - - hardcoded_tmp_directory: - tmp_dirs: ['/tmp', '/var/tmp', '/dev/shm'] - - -:Example: - -.. code-block: none - - >> Issue: Probable insecure usage of temp file/directory. - Severity: Medium Confidence: Medium - Location: ./examples/hardcoded-tmp.py:1 - 1 f = open('/tmp/abc', 'w') - 2 f.write('def') - -.. seealso:: - - - https://security.openstack.org/guidelines/dg_using-temporary-files-securely.html # noqa - -.. versionadded:: 0.9.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -def gen_config(name): - if name == 'hardcoded_tmp_directory': - return {'tmp_dirs': ['/tmp', '/var/tmp', '/dev/shm']} - - -@test.takes_config -@test.checks('Str') -@test.test_id('B108') -def hardcoded_tmp_directory(context, config): - if config is not None and 'tmp_dirs' in config: - tmp_dirs = config['tmp_dirs'] - else: - tmp_dirs = ['/tmp', '/var/tmp', '/dev/shm'] - - if any(context.string_val.startswith(s) for s in tmp_dirs): - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.MEDIUM, - text="Probable insecure usage of temp file/directory." - ) diff --git a/bandit/plugins/hashlib_new_insecure_functions.py b/bandit/plugins/hashlib_new_insecure_functions.py deleted file mode 100644 index 46436c22..00000000 --- a/bandit/plugins/hashlib_new_insecure_functions.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- 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/bandit/plugins/injection_paramiko.py b/bandit/plugins/injection_paramiko.py deleted file mode 100644 index a87e13bd..00000000 --- a/bandit/plugins/injection_paramiko.py +++ /dev/null @@ -1,74 +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""" -============================================== -B601: Test for shell injection within Paramiko -============================================== - -Paramiko is a Python library designed to work with the SSH2 protocol for secure -(encrypted and authenticated) connections to remote machines. It is intended to -run commands on a remote host. These commands are run within a shell on the -target and are thus vulnerable to various shell injection attacks. Bandit -reports a MEDIUM issue when it detects the use of Paramiko's "exec_command" or -"invoke_shell" methods advising the user to check inputs are correctly -sanitized. - -:Example: - -.. code-block:: none - - >> Issue: Possible shell injection via Paramiko call, check inputs are - properly sanitized. - Severity: Medium Confidence: Medium - Location: ./examples/paramiko_injection.py:4 - 3 # this is not safe - 4 paramiko.exec_command('something; reallly; unsafe') - 5 - - >> Issue: Possible shell injection via Paramiko call, check inputs are - properly sanitized. - Severity: Medium Confidence: Medium - Location: ./examples/paramiko_injection.py:10 - 9 # this is not safe - 10 SSHClient.invoke_shell('something; bad; here\n') - 11 - -.. seealso:: - - - https://security.openstack.org - - https://github.com/paramiko/paramiko - - https://www.owasp.org/index.php/Command_Injection - -.. versionadded:: 0.12.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -@test.checks('Call') -@test.test_id('B601') -def paramiko_calls(context): - issue_text = ('Possible shell injection via Paramiko call, check inputs ' - 'are properly sanitized.') - for module in ['paramiko']: - if context.is_module_imported_like(module): - if context.call_function_name in ['exec_command', 'invoke_shell']: - return bandit.Issue(severity=bandit.MEDIUM, - confidence=bandit.MEDIUM, - text=issue_text) diff --git a/bandit/plugins/injection_shell.py b/bandit/plugins/injection_shell.py deleted file mode 100644 index 52f06f97..00000000 --- a/bandit/plugins/injection_shell.py +++ /dev/null @@ -1,634 +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 ast -import re - -import bandit -from bandit.core import test_properties as test - -# yuck, regex: starts with a windows drive letter (eg C:) -# or one of our path delimeter characters (/, \, .) -full_path_match = re.compile(r'^(?:[A-Za-z](?=\:)|[\\\/\.])') - - -def _evaluate_shell_call(context): - no_formatting = isinstance(context.node.args[0], ast.Str) - - if no_formatting: - return bandit.LOW - else: - return bandit.HIGH - - -def gen_config(name): - if name == 'shell_injection': - return { - # Start a process using the subprocess module, or one of its - # wrappers. - 'subprocess': - ['subprocess.Popen', - 'subprocess.call', - 'subprocess.check_call', - 'subprocess.check_output', - 'utils.execute', - 'utils.execute_with_timeout'], - - # Start a process with a function vulnerable to shell injection. - 'shell': - ['os.system', - 'os.popen', - 'os.popen2', - 'os.popen3', - 'os.popen4', - 'popen2.popen2', - 'popen2.popen3', - 'popen2.popen4', - 'popen2.Popen3', - 'popen2.Popen4', - 'commands.getoutput', - 'commands.getstatusoutput'], - - # Start a process with a function that is not vulnerable to shell - # injection. - 'no_shell': - ['os.execl', - 'os.execle', - 'os.execlp', - 'os.execlpe', - 'os.execv', - 'os.execve', - 'os.execvp', - 'os.execvpe', - 'os.spawnl', - 'os.spawnle', - 'os.spawnlp', - 'os.spawnlpe', - 'os.spawnv', - 'os.spawnve', - 'os.spawnvp', - 'os.spawnvpe', - 'os.startfile'] - } - - -@test.takes_config('shell_injection') -@test.checks('Call') -@test.test_id('B602') -def subprocess_popen_with_shell_equals_true(context, config): - """**B602: Test for use of popen with shell equals true** - - Python possesses many mechanisms to invoke an external executable. However, - doing so may present a security issue if appropriate care is not taken to - sanitize any user provided or variable input. - - This plugin test is part of a family of tests built to check for process - spawning and warn appropriately. Specifically, this test looks for the - spawning of a subprocess using a command shell. This type of subprocess - invocation is dangerous as it is vulnerable to various shell injection - attacks. Great care should be taken to sanitize all input in order to - mitigate this risk. Calls of this type are identified by a parameter of - 'shell=True' being given. - - Additionally, this plugin scans the command string given and adjusts its - reported severity based on how it is presented. If the command string is a - simple static string containing no special shell characters, then the - resulting issue has low severity. If the string is static, but contains - shell formatting characters or wildcards, then the reported issue is - medium. Finally, if the string is computed using Python's string - manipulation or formatting operations, then the reported issue has high - severity. These severity levels reflect the likelihood that the code is - vulnerable to injection. - - See also: - - - :doc:`../plugins/linux_commands_wildcard_injection` - - :doc:`../plugins/subprocess_without_shell_equals_true` - - :doc:`../plugins/start_process_with_no_shell` - - :doc:`../plugins/start_process_with_a_shell` - - :doc:`../plugins/start_process_with_partial_path` - - **Config Options:** - - This plugin test shares a configuration with others in the same family, - namely `shell_injection`. This configuration is divided up into three - sections, `subprocess`, `shell` and `no_shell`. They each list Python calls - that spawn subprocesses, invoke commands within a shell, or invoke commands - without a shell (by replacing the calling process) respectively. - - This plugin specifically scans for methods listed in `subprocess` section - that have shell=True specified. - - .. code-block:: yaml - - shell_injection: - - # Start a process using the subprocess module, or one of its - wrappers. - subprocess: - - subprocess.Popen - - subprocess.call - - - :Example: - - .. code-block:: none - - >> Issue: subprocess call with shell=True seems safe, but may be - changed in the future, consider rewriting without shell - Severity: Low Confidence: High - Location: ./examples/subprocess_shell.py:21 - 20 subprocess.check_call(['/bin/ls', '-l'], shell=False) - 21 subprocess.check_call('/bin/ls -l', shell=True) - 22 - - >> Issue: call with shell=True contains special shell characters, - consider moving extra logic into Python code - Severity: Medium Confidence: High - Location: ./examples/subprocess_shell.py:26 - 25 - 26 subprocess.Popen('/bin/ls *', shell=True) - 27 subprocess.Popen('/bin/ls %s' % ('something',), shell=True) - - >> Issue: subprocess call with shell=True identified, security issue. - Severity: High Confidence: High - Location: ./examples/subprocess_shell.py:27 - 26 subprocess.Popen('/bin/ls *', shell=True) - 27 subprocess.Popen('/bin/ls %s' % ('something',), shell=True) - 28 subprocess.Popen('/bin/ls {}'.format('something'), shell=True) - - .. seealso:: - - - https://security.openstack.org - - https://docs.python.org/2/library/subprocess.html#frequently-used-arguments # noqa - - https://security.openstack.org/guidelines/dg_use-subprocess-securely.html - - https://security.openstack.org/guidelines/dg_avoid-shell-true.html - - .. versionadded:: 0.9.0 - """ - if config and context.call_function_name_qual in config['subprocess']: - if context.check_call_arg_value('shell', 'True'): - if len(context.call_args) > 0: - sev = _evaluate_shell_call(context) - if sev == bandit.LOW: - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.HIGH, - text='subprocess call with shell=True seems safe, but ' - 'may be changed in the future, consider ' - 'rewriting without shell', - lineno=context.get_lineno_for_call_arg('shell'), - ) - else: - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text='subprocess call with shell=True identified, ' - 'security issue.', - lineno=context.get_lineno_for_call_arg('shell'), - ) - - -@test.takes_config('shell_injection') -@test.checks('Call') -@test.test_id('B603') -def subprocess_without_shell_equals_true(context, config): - """**B603: Test for use of subprocess with shell equals true** - - Python possesses many mechanisms to invoke an external executable. However, - doing so may present a security issue if appropriate care is not taken to - sanitize any user provided or variable input. - - This plugin test is part of a family of tests built to check for process - spawning and warn appropriately. Specifically, this test looks for the - spawning of a subprocess without the use of a command shell. This type of - subprocess invocation is not vulnerable to shell injection attacks, but - care should still be taken to ensure validity of input. - - Because this is a lesser issue than that described in - `subprocess_popen_with_shell_equals_true` a LOW severity warning is - reported. - - See also: - - - :doc:`../plugins/linux_commands_wildcard_injection` - - :doc:`../plugins/subprocess_popen_with_shell_equals_true` - - :doc:`../plugins/start_process_with_no_shell` - - :doc:`../plugins/start_process_with_a_shell` - - :doc:`../plugins/start_process_with_partial_path` - - **Config Options:** - - This plugin test shares a configuration with others in the same family, - namely `shell_injection`. This configuration is divided up into three - sections, `subprocess`, `shell` and `no_shell`. They each list Python calls - that spawn subprocesses, invoke commands within a shell, or invoke commands - without a shell (by replacing the calling process) respectively. - - This plugin specifically scans for methods listed in `subprocess` section - that have shell=False specified. - - .. code-block:: yaml - - shell_injection: - # Start a process using the subprocess module, or one of its - wrappers. - subprocess: - - subprocess.Popen - - subprocess.call - - :Example: - - .. code-block:: none - - >> Issue: subprocess call - check for execution of untrusted input. - Severity: Low Confidence: High - Location: ./examples/subprocess_shell.py:23 - 22 - 23 subprocess.check_output(['/bin/ls', '-l']) - 24 - - .. seealso:: - - - https://security.openstack.org - - https://docs.python.org/2/library/subprocess.html#frequently-used-arguments # noqa - - https://security.openstack.org/guidelines/dg_avoid-shell-true.html - - https://security.openstack.org/guidelines/dg_use-subprocess-securely.html - - .. versionadded:: 0.9.0 - """ - if config and context.call_function_name_qual in config['subprocess']: - if not context.check_call_arg_value('shell', 'True'): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.HIGH, - text='subprocess call - check for execution of untrusted ' - 'input.', - lineno=context.get_lineno_for_call_arg('shell'), - ) - - -@test.takes_config('shell_injection') -@test.checks('Call') -@test.test_id('B604') -def any_other_function_with_shell_equals_true(context, config): - """**B604: Test for any function with shell equals true** - - Python possesses many mechanisms to invoke an external executable. However, - doing so may present a security issue if appropriate care is not taken to - sanitize any user provided or variable input. - - This plugin test is part of a family of tests built to check for process - spawning and warn appropriately. Specifically, this plugin test - interrogates method calls for the presence of a keyword parameter `shell` - equalling true. It is related to detection of shell injection issues and is - intended to catch custom wrappers to vulnerable methods that may have been - created. - - See also: - - - :doc:`../plugins/linux_commands_wildcard_injection` - - :doc:`../plugins/subprocess_popen_with_shell_equals_true` - - :doc:`../plugins/subprocess_without_shell_equals_true` - - :doc:`../plugins/start_process_with_no_shell` - - :doc:`../plugins/start_process_with_a_shell` - - :doc:`../plugins/start_process_with_partial_path` - - **Config Options:** - - This plugin test shares a configuration with others in the same family, - namely `shell_injection`. This configuration is divided up into three - sections, `subprocess`, `shell` and `no_shell`. They each list Python calls - that spawn subprocesses, invoke commands within a shell, or invoke commands - without a shell (by replacing the calling process) respectively. - - Specifically, this plugin excludes those functions listed under the - subprocess section, these methods are tested in a separate specific test - plugin and this exclusion prevents duplicate issue reporting. - - .. code-block:: yaml - - shell_injection: - # Start a process using the subprocess module, or one of its - wrappers. - subprocess: [subprocess.Popen, subprocess.call, - subprocess.check_call, subprocess.check_output, - utils.execute, utils.execute_with_timeout] - - - :Example: - - .. code-block:: none - - >> Issue: Function call with shell=True parameter identified, possible - security issue. - Severity: Medium Confidence: High - Location: ./examples/subprocess_shell.py:9 - 8 pop('/bin/gcc --version', shell=True) - 9 Popen('/bin/gcc --version', shell=True) - 10 - - .. seealso:: - - - https://security.openstack.org/guidelines/dg_avoid-shell-true.html - - https://security.openstack.org/guidelines/dg_use-subprocess-securely.html # noqa - - .. versionadded:: 0.9.0 - """ - if config and context.call_function_name_qual not in config['subprocess']: - if context.check_call_arg_value('shell', 'True'): - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.LOW, - text='Function call with shell=True parameter identified, ' - 'possible security issue.', - lineno=context.get_lineno_for_call_arg('shell'), - ) - - -@test.takes_config('shell_injection') -@test.checks('Call') -@test.test_id('B605') -def start_process_with_a_shell(context, config): - """**B605: Test for starting a process with a shell** - - Python possesses many mechanisms to invoke an external executable. However, - doing so may present a security issue if appropriate care is not taken to - sanitize any user provided or variable input. - - This plugin test is part of a family of tests built to check for process - spawning and warn appropriately. Specifically, this test looks for the - spawning of a subprocess using a command shell. This type of subprocess - invocation is dangerous as it is vulnerable to various shell injection - attacks. Great care should be taken to sanitize all input in order to - mitigate this risk. Calls of this type are identified by the use of certain - commands which are known to use shells. Bandit will report a LOW - severity warning. - - See also: - - - :doc:`../plugins/linux_commands_wildcard_injection` - - :doc:`../plugins/subprocess_without_shell_equals_true` - - :doc:`../plugins/start_process_with_no_shell` - - :doc:`../plugins/start_process_with_partial_path` - - :doc:`../plugins/subprocess_popen_with_shell_equals_true` - - **Config Options:** - - This plugin test shares a configuration with others in the same family, - namely `shell_injection`. This configuration is divided up into three - sections, `subprocess`, `shell` and `no_shell`. They each list Python calls - that spawn subprocesses, invoke commands within a shell, or invoke commands - without a shell (by replacing the calling process) respectively. - - This plugin specifically scans for methods listed in `shell` section. - - .. code-block:: yaml - - shell_injection: - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - - :Example: - - .. code-block:: none - - >> Issue: Starting a process with a shell: check for injection. - Severity: Low Confidence: Medium - Location: examples/os_system.py:3 - 2 - 3 os.system('/bin/echo hi') - - .. seealso:: - - - https://security.openstack.org - - https://docs.python.org/2/library/os.html#os.system - - https://docs.python.org/2/library/subprocess.html#frequently-used-arguments # noqa - - https://security.openstack.org/guidelines/dg_use-subprocess-securely.html - - .. versionadded:: 0.10.0 - """ - if config and context.call_function_name_qual in config['shell']: - if len(context.call_args) > 0: - sev = _evaluate_shell_call(context) - if sev == bandit.LOW: - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.HIGH, - text='Starting a process with a shell: ' - 'Seems safe, but may be changed in the future, ' - 'consider rewriting without shell' - ) - else: - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text='Starting a process with a shell, possible injection' - ' detected, security issue.' - ) - - -@test.takes_config('shell_injection') -@test.checks('Call') -@test.test_id('B606') -def start_process_with_no_shell(context, config): - """**B606: Test for starting a process with no shell** - - Python possesses many mechanisms to invoke an external executable. However, - doing so may present a security issue if appropriate care is not taken to - sanitize any user provided or variable input. - - This plugin test is part of a family of tests built to check for process - spawning and warn appropriately. Specifically, this test looks for the - spawning of a subprocess in a way that doesn't use a shell. Although this - is generally safe, it maybe useful for penetration testing workflows to - track where external system calls are used. As such a LOW severity message - is generated. - - See also: - - - :doc:`../plugins/linux_commands_wildcard_injection` - - :doc:`../plugins/subprocess_without_shell_equals_true` - - :doc:`../plugins/start_process_with_a_shell` - - :doc:`../plugins/start_process_with_partial_path` - - :doc:`../plugins/subprocess_popen_with_shell_equals_true` - - **Config Options:** - - This plugin test shares a configuration with others in the same family, - namely `shell_injection`. This configuration is divided up into three - sections, `subprocess`, `shell` and `no_shell`. They each list Python calls - that spawn subprocesses, invoke commands within a shell, or invoke commands - without a shell (by replacing the calling process) respectively. - - This plugin specifically scans for methods listed in `no_shell` section. - - .. code-block:: yaml - - shell_injection: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - - :Example: - - .. code-block:: none - - >> Issue: [start_process_with_no_shell] Starting a process without a - shell. - Severity: Low Confidence: Medium - Location: examples/os-spawn.py:8 - 7 os.spawnv(mode, path, args) - 8 os.spawnve(mode, path, args, env) - 9 os.spawnvp(mode, file, args) - - .. seealso:: - - - https://security.openstack.org - - https://docs.python.org/2/library/os.html#os.system - - https://docs.python.org/2/library/subprocess.html#frequently-used-arguments # noqa - - https://security.openstack.org/guidelines/dg_use-subprocess-securely.html - - .. versionadded:: 0.10.0 - """ - - if config and context.call_function_name_qual in config['no_shell']: - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.MEDIUM, - text='Starting a process without a shell.' - ) - - -@test.takes_config('shell_injection') -@test.checks('Call') -@test.test_id('B607') -def start_process_with_partial_path(context, config): - """**B607: Test for starting a process with a partial path** - - Python possesses many mechanisms to invoke an external executable. If the - desired executable path is not fully qualified relative to the filesystem - root then this may present a potential security risk. - - In POSIX environments, the `PATH` environment variable is used to specify a - set of standard locations that will be searched for the first matching - named executable. While convenient, this behavior may allow a malicious - actor to exert control over a system. If they are able to adjust the - contents of the `PATH` variable, or manipulate the file system, then a - bogus executable may be discovered in place of the desired one. This - executable will be invoked with the user privileges of the Python process - that spawned it, potentially a highly privileged user. - - This test will scan the parameters of all configured Python methods, - looking for paths that do not start at the filesystem root, that is, do not - have a leading '/' character. - - **Config Options:** - - This plugin test shares a configuration with others in the same family, - namely `shell_injection`. This configuration is divided up into three - sections, `subprocess`, `shell` and `no_shell`. They each list Python calls - that spawn subprocesses, invoke commands within a shell, or invoke commands - without a shell (by replacing the calling process) respectively. - - This test will scan parameters of all methods in all sections. Note that - methods are fully qualified and de-aliased prior to checking. - - .. code-block:: yaml - - shell_injection: - # Start a process using the subprocess module, or one of its - wrappers. - subprocess: - - subprocess.Popen - - subprocess.call - - # Start a process with a function vulnerable to shell injection. - shell: - - os.system - - os.popen - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - # Start a process with a function that is not vulnerable to shell - injection. - no_shell: - - os.execl - - os.execle - - - :Example: - - .. code-block:: none - - >> Issue: Starting a process with a partial executable path - Severity: Low Confidence: High - Location: ./examples/partial_path_process.py:3 - 2 from subprocess import Popen as pop - 3 pop('gcc --version', shell=False) - - .. seealso:: - - - https://security.openstack.org - - https://docs.python.org/2/library/os.html#process-management - - .. versionadded:: 0.13.0 - """ - - if config and len(context.call_args): - if(context.call_function_name_qual in config['subprocess'] or - context.call_function_name_qual in config['shell'] or - context.call_function_name_qual in config['no_shell']): - - node = context.node.args[0] - # some calls take an arg list, check the first part - if isinstance(node, ast.List): - node = node.elts[0] - - # make sure the param is a string literal and not a var name - if isinstance(node, ast.Str) and not full_path_match.match(node.s): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.HIGH, - text='Starting a process with a partial executable path' - ) diff --git a/bandit/plugins/injection_sql.py b/bandit/plugins/injection_sql.py deleted file mode 100644 index afb63a5b..00000000 --- a/bandit/plugins/injection_sql.py +++ /dev/null @@ -1,115 +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""" -============================ -B608: Test for SQL injection -============================ - -An SQL injection attack consists of insertion or "injection" of a SQL query via -the input data given to an application. It is a very common attack vector. This -plugin test looks for strings that resemble SQL statements that are involved in -some form of string building operation. For example: - - - "SELECT %s FROM derp;" % var - - "SELECT thing FROM " + tab - - "SELECT " + val + " FROM " + tab + ... - - "SELECT {} FROM derp;".format(var) - -Unless care is taken to sanitize and control the input data when building such -SQL statement strings, an injection attack becomes possible. If strings of this -nature are discovered, a LOW confidence issue is reported. In order to boost -result confidence, this plugin test will also check to see if the discovered -string is in use with standard Python DBAPI calls `execute` or `executemany`. -If so, a MEDIUM issue is reported. For example: - - - cursor.execute("SELECT %s FROM derp;" % var) - - -:Example: - -.. code-block:: none - - >> Issue: Possible SQL injection vector through string-based query - construction. - Severity: Medium Confidence: Low - Location: ./examples/sql_statements_without_sql_alchemy.py:4 - 3 query = "DELETE FROM foo WHERE id = '%s'" % identifier - 4 query = "UPDATE foo SET value = 'b' WHERE id = '%s'" % identifier - 5 - -.. seealso:: - - - https://www.owasp.org/index.php/SQL_Injection - - https://security.openstack.org/guidelines/dg_parameterize-database-queries.html # noqa - -.. versionadded:: 0.9.0 - -""" - -import ast -import re - -import bandit -from bandit.core import test_properties as test -from bandit.core import utils - -SIMPLE_SQL_RE = re.compile( - r'(select\s.*from\s|' - r'delete\s+from\s|' - r'insert\s+into\s.*values\s|' - r'update\s.*set\s)', - re.IGNORECASE | re.DOTALL, -) - - -def _check_string(data): - return SIMPLE_SQL_RE.search(data) is not None - - -def _evaluate_ast(node): - wrapper = None - statement = '' - - if isinstance(node.parent, ast.BinOp): - out = utils.concat_string(node, node.parent) - wrapper = out[0].parent - statement = out[1] - elif (isinstance(node.parent, ast.Attribute) - and node.parent.attr == 'format'): - statement = node.s - # Hierarchy for "".format() is Wrapper -> Call -> Attribute -> Str - wrapper = node.parent.parent.parent - - if isinstance(wrapper, ast.Call): # wrapped in "execute" call? - names = ['execute', 'executemany'] - name = utils.get_called_name(wrapper) - return (name in names, statement) - else: - return (False, statement) - - -@test.checks('Str') -@test.test_id('B608') -def hardcoded_sql_expressions(context): - val = _evaluate_ast(context.node) - if _check_string(val[1]): - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.MEDIUM if val[0] else bandit.LOW, - text="Possible SQL injection vector through string-based " - "query construction." - ) diff --git a/bandit/plugins/injection_wildcard.py b/bandit/plugins/injection_wildcard.py deleted file mode 100644 index 9f990827..00000000 --- a/bandit/plugins/injection_wildcard.py +++ /dev/null @@ -1,149 +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""" -======================================== -B609: Test for use of wildcard injection -======================================== - -Python provides a number of methods that emulate the behavior of standard Linux -command line utilities. Like their Linux counterparts, these commands may take -a wildcard "\*" character in place of a file system path. This is interpreted -to mean "any and all files or folders" and can be used to build partially -qualified paths, such as "/home/user/\*". - -The use of partially qualified paths may result in unintended consequences if -an unexpected file or symlink is placed into the path location given. This -becomes particularly dangerous when combined with commands used to manipulate -file permissions or copy data off of a system. - -This test plugin looks for usage of the following commands in conjunction with -wild card parameters: - -- 'chown' -- 'chmod' -- 'tar' -- 'rsync' - -As well as any method configured in the shell or subprocess injection test -configurations. - - -**Config Options:** - -This plugin test shares a configuration with others in the same family, namely -`shell_injection`. This configuration is divided up into three sections, -`subprocess`, `shell` and `no_shell`. They each list Python calls that spawn -subprocesses, invoke commands within a shell, or invoke commands without a -shell (by replacing the calling process) respectively. - -This test will scan parameters of all methods in all sections. Note that -methods are fully qualified and de-aliased prior to checking. - - -.. code-block:: yaml - - shell_injection: - # Start a process using the subprocess module, or one of its wrappers. - subprocess: - - subprocess.Popen - - subprocess.call - - # Start a process with a function vulnerable to shell injection. - shell: - - os.system - - os.popen - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - # Start a process with a function that is not vulnerable to shell - injection. - no_shell: - - os.execl - - os.execle - - -:Example: - -.. code-block:: none - - >> Issue: Possible wildcard injection in call: subprocess.Popen - Severity: High Confidence: Medium - Location: ./examples/wildcard-injection.py:8 - 7 o.popen2('/bin/chmod *') - 8 subp.Popen('/bin/chown *', shell=True) - 9 - - >> Issue: subprocess call - check for execution of untrusted input. - Severity: Low Confidence: High - Location: ./examples/wildcard-injection.py:11 - 10 # Not vulnerable to wildcard injection - 11 subp.Popen('/bin/rsync *') - 12 subp.Popen("/bin/chmod *") - - -.. seealso:: - - - https://security.openstack.org - - https://en.wikipedia.org/wiki/Wildcard_character - - http://www.defensecode.com/public/DefenseCode_Unix_WildCards_Gone_Wild.txt - -.. versionadded:: 0.9.0 - -""" - -import bandit -from bandit.core import test_properties as test -from bandit.plugins import injection_shell # NOTE(tkelsey): shared config - - -gen_config = injection_shell.gen_config - - -@test.takes_config('shell_injection') -@test.checks('Call') -@test.test_id('B609') -def linux_commands_wildcard_injection(context, config): - if not ('shell' in config and 'subprocess' in config): - return - - vulnerable_funcs = ['chown', 'chmod', 'tar', 'rsync'] - if context.call_function_name_qual in config['shell'] or ( - context.call_function_name_qual in config['subprocess'] and - context.check_call_arg_value('shell', 'True')): - if context.call_args_count >= 1: - call_argument = context.get_call_arg_at_position(0) - argument_string = '' - if isinstance(call_argument, list): - for li in call_argument: - argument_string = argument_string + ' %s' % li - elif isinstance(call_argument, str): - argument_string = call_argument - - if argument_string != '': - for vulnerable_func in vulnerable_funcs: - if( - vulnerable_func in argument_string and - '*' in argument_string - ): - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.MEDIUM, - text="Possible wildcard injection in call: %s" % - context.call_function_name_qual, - lineno=context.get_lineno_for_call_arg('shell'), - ) diff --git a/bandit/plugins/insecure_ssl_tls.py b/bandit/plugins/insecure_ssl_tls.py deleted file mode 100644 index 65ef4afd..00000000 --- a/bandit/plugins/insecure_ssl_tls.py +++ /dev/null @@ -1,274 +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 get_bad_proto_versions(config): - return config['bad_protocol_versions'] - - -def gen_config(name): - if name == 'ssl_with_bad_version': - return {'bad_protocol_versions': - ['PROTOCOL_SSLv2', - 'SSLv2_METHOD', - 'SSLv23_METHOD', - 'PROTOCOL_SSLv3', # strict option - 'PROTOCOL_TLSv1', # strict option - 'SSLv3_METHOD', # strict option - 'TLSv1_METHOD']} # strict option - - -@test.takes_config -@test.checks('Call') -@test.test_id('B502') -def ssl_with_bad_version(context, config): - """**B502: Test for SSL use with bad version used** - - Several highly publicized exploitable flaws have been discovered - in all versions of SSL and early versions of TLS. It is strongly - recommended that use of the following known broken protocol versions be - avoided: - - - SSL v2 - - SSL v3 - - TLS v1 - - TLS v1.1 - - This plugin test scans for calls to Python methods with parameters that - indicate the used broken SSL/TLS protocol versions. Currently, detection - supports methods using Python's native SSL/TLS support and the pyOpenSSL - module. A HIGH severity warning will be reported whenever known broken - protocol versions are detected. - - It is worth noting that native support for TLS 1.2 is only available in - more recent Python versions, specifically 2.7.9 and up, and 3.x - - See also: - - - :doc:`../plugins/ssl_with_bad_defaults` - - :doc:`../plugins/ssl_with_no_version` - - A note on 'SSLv23': - - Amongst the available SSL/TLS versions provided by Python/pyOpenSSL there - exists the option to use SSLv23. This very poorly named option actually - means "use the highest version of SSL/TLS supported by both the server and - client". This may (and should be) a version well in advance of SSL v2 or - v3. Bandit can scan for the use of SSLv23 if desired, but its detection - does not necessarily indicate a problem. - - When using SSLv23 it is important to also provide flags to explicitly - exclude bad versions of SSL/TLS from the protocol versions considered. Both - the Python native and pyOpenSSL modules provide the ``OP_NO_SSLv2`` and - ``OP_NO_SSLv3`` flags for this purpose. - - - **Config Options:** - - .. code-block:: yaml - - ssl_with_bad_version: - bad_protocol_versions: - - PROTOCOL_SSLv2 - - SSLv2_METHOD - - SSLv23_METHOD - - PROTOCOL_SSLv3 # strict option - - PROTOCOL_TLSv1 # strict option - - SSLv3_METHOD # strict option - - TLSv1_METHOD # strict option - - - :Example: - - .. code-block:: none - - >> Issue: ssl.wrap_socket call with insecure SSL/TLS protocol version - identified, security issue. - Severity: High Confidence: High - Location: ./examples/ssl-insecure-version.py:13 - 12 # strict tests - 13 ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3) - 14 ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1) - - .. seealso:: - - - http://heartbleed.com/ - - https://poodlebleed.com/ - - https://security.openstack.org/ - - https://security.openstack.org/guidelines/dg_move-data-securely.html - - .. versionadded:: 0.9.0 - """ - bad_ssl_versions = get_bad_proto_versions(config) - if context.call_function_name_qual == 'ssl.wrap_socket': - if context.check_call_arg_value('ssl_version', bad_ssl_versions): - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text="ssl.wrap_socket call with insecure SSL/TLS protocol " - "version identified, security issue.", - lineno=context.get_lineno_for_call_arg('ssl_version'), - ) - elif context.call_function_name_qual == 'pyOpenSSL.SSL.Context': - if context.check_call_arg_value('method', bad_ssl_versions): - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text="SSL.Context call with insecure SSL/TLS protocol " - "version identified, security issue.", - lineno=context.get_lineno_for_call_arg('method'), - ) - - elif (context.call_function_name_qual != 'ssl.wrap_socket' and - context.call_function_name_qual != 'pyOpenSSL.SSL.Context'): - if (context.check_call_arg_value('method', bad_ssl_versions) or - context.check_call_arg_value('ssl_version', bad_ssl_versions)): - lineno = (context.get_lineno_for_call_arg('method') or - context.get_lineno_for_call_arg('ssl_version')) - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.MEDIUM, - text="Function call with insecure SSL/TLS protocol " - "identified, possible security issue.", - lineno=lineno, - ) - - -@test.takes_config("ssl_with_bad_version") -@test.checks('FunctionDef') -@test.test_id('B503') -def ssl_with_bad_defaults(context, config): - """**B503: Test for SSL use with bad defaults specified** - - This plugin is part of a family of tests that detect the use of known bad - versions of SSL/TLS, please see :doc:`../plugins/ssl_with_bad_version` for - a complete discussion. Specifically, this plugin test scans for Python - methods with default parameter values that specify the use of broken - SSL/TLS protocol versions. Currently, detection supports methods using - Python's native SSL/TLS support and the pyOpenSSL module. A MEDIUM severity - warning will be reported whenever known broken protocol versions are - detected. - - See also: - - - :doc:`../plugins/ssl_with_bad_version` - - :doc:`../plugins/ssl_with_no_version` - - **Config Options:** - - This test shares the configuration provided for the standard - :doc:`../plugins/ssl_with_bad_version` test, please refer to its - documentation. - - :Example: - - .. code-block:: none - - >> Issue: Function definition identified with insecure SSL/TLS protocol - version by default, possible security issue. - Severity: Medium Confidence: Medium - Location: ./examples/ssl-insecure-version.py:28 - 27 - 28 def open_ssl_socket(version=SSL.SSLv2_METHOD): - 29 pass - - .. seealso:: - - - http://heartbleed.com/ - - https://poodlebleed.com/ - - https://security.openstack.org/ - - https://security.openstack.org/guidelines/dg_move-data-securely.html - - .. versionadded:: 0.9.0 - """ - - bad_ssl_versions = get_bad_proto_versions(config) - for default in context.function_def_defaults_qual: - val = default.split(".")[-1] - if val in bad_ssl_versions: - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.MEDIUM, - text="Function definition identified with insecure SSL/TLS " - "protocol version by default, possible security " - "issue." - ) - - -@test.checks('Call') -@test.test_id('B504') -def ssl_with_no_version(context): - """**B504: Test for SSL use with no version specified** - - This plugin is part of a family of tests that detect the use of known bad - versions of SSL/TLS, please see :doc:`../plugins/ssl_with_bad_version` for - a complete discussion. Specifically, This plugin test scans for specific - methods in Python's native SSL/TLS support and the pyOpenSSL module that - configure the version of SSL/TLS protocol to use. These methods are known - to provide default value that maximize compatibility, but permit use of the - aforementioned broken protocol versions. A LOW severity warning will be - reported whenever this is detected. - - See also: - - - :doc:`../plugins/ssl_with_bad_version` - - :doc:`../plugins/ssl_with_bad_defaults` - - **Config Options:** - - This test shares the configuration provided for the standard - :doc:`../plugins/ssl_with_bad_version` test, please refer to its - documentation. - - :Example: - - .. code-block:: none - - >> Issue: ssl.wrap_socket call with no SSL/TLS protocol version - specified, the default SSLv23 could be insecure, possible security - issue. - Severity: Low Confidence: Medium - Location: ./examples/ssl-insecure-version.py:23 - 22 - 23 ssl.wrap_socket() - 24 - - .. seealso:: - - - http://heartbleed.com/ - - https://poodlebleed.com/ - - https://security.openstack.org/ - - https://security.openstack.org/guidelines/dg_move-data-securely.html - - .. versionadded:: 0.9.0 - """ - if context.call_function_name_qual == 'ssl.wrap_socket': - if context.check_call_arg_value('ssl_version') is None: - # check_call_arg_value() returns False if the argument is found - # but does not match the supplied value (or the default None). - # It returns None if the arg_name passed doesn't exist. This - # tests for that (ssl_version is not specified). - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.MEDIUM, - text="ssl.wrap_socket call with no SSL/TLS protocol version " - "specified, the default SSLv23 could be insecure, " - "possible security issue.", - lineno=context.get_lineno_for_call_arg('ssl_version'), - ) diff --git a/bandit/plugins/jinja2_templates.py b/bandit/plugins/jinja2_templates.py deleted file mode 100644 index 2adc77a6..00000000 --- a/bandit/plugins/jinja2_templates.py +++ /dev/null @@ -1,131 +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""" -========================================== -B701: Test for not auto escaping in jinja2 -========================================== - -Jinja2 is a Python HTML templating system. It is typically used to build web -applications, though appears in other places well, notably the Ansible -automation system. When configuring the Jinja2 environment, the option to use -autoescaping on input can be specified. When autoescaping is enabled, Jinja2 -will filter input strings to escape any HTML content submitted via template -variables. Without escaping HTML input the application becomes vulnerable to -Cross Site Scripting (XSS) attacks. - -Unfortunately, autoescaping is False by default. Thus this plugin test will -warn on omission of an autoescape setting, as well as an explicit setting of -false. A HIGH severity warning is generated in either of these scenarios. - -:Example: - -.. code-block:: none - - >> Issue: Using jinja2 templates with autoescape=False is dangerous and can - lead to XSS. Use autoescape=True to mitigate XSS vulnerabilities. - Severity: High Confidence: High - Location: ./examples/jinja2_templating.py:11 - 10 templateEnv = jinja2.Environment(autoescape=False, - loader=templateLoader) - 11 Environment(loader=templateLoader, - 12 load=templateLoader, - 13 autoescape=False) - 14 - - >> Issue: By default, jinja2 sets autoescape to False. Consider using - 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:: - - - `OWASP XSS `_ - - https://realpython.com/blog/python/primer-on-jinja-templating/ - - http://jinja.pocoo.org/docs/dev/api/#autoescaping - - https://security.openstack.org - - https://security.openstack.org/guidelines/dg_cross-site-scripting-xss.html - -.. versionadded:: 0.10.0 - -""" - -import ast - -import bandit -from bandit.core import test_properties as test - - -@test.checks('Call') -@test.test_id('B701') -def jinja2_autoescape_false(context): - # check type just to be safe - if isinstance(context.call_function_name_qual, str): - qualname_list = context.call_function_name_qual.split('.') - func = qualname_list[-1] - if 'jinja2' in qualname_list and func == 'Environment': - for node in ast.walk(context.node): - if isinstance(node, ast.keyword): - # definite autoescape = False - if (getattr(node, 'arg', None) == 'autoescape' and - (getattr(node.value, 'id', None) == 'False' or - getattr(node.value, 'value', None) is False)): - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text="Using jinja2 templates with autoescape=" - "False is dangerous and can lead to XSS. " - "Use autoescape=True or use the " - "select_autoescape function to mitigate XSS " - "vulnerabilities." - ) - # found autoescape - if getattr(node, 'arg', None) == 'autoescape': - 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( - severity=bandit.HIGH, - confidence=bandit.MEDIUM, - text="Using jinja2 templates with autoescape=" - "False is dangerous and can lead to XSS. " - "Ensure autoescape=True or use the " - "select_autoescape function to mitigate " - "XSS vulnerabilities." - ) - # We haven't found a keyword named autoescape, indicating default - # behavior - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text="By default, jinja2 sets autoescape to False. Consider " - "using autoescape=True or use the select_autoescape " - "function to mitigate XSS vulnerabilities." - ) diff --git a/bandit/plugins/mako_templates.py b/bandit/plugins/mako_templates.py deleted file mode 100644 index cf315c66..00000000 --- a/bandit/plugins/mako_templates.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- 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""" -==================================== -B702: Test for use of mako templates -==================================== - -Mako is a Python templating system often used to build web applications. It is -the default templating system used in Pylons and Pyramid. Unlike Jinja2 (an -alternative templating system), Mako has no environment wide variable escaping -mechanism. Because of this, all input variables must be carefully escaped -before use to prevent possible vulnerabilities to Cross Site Scripting (XSS) -attacks. - - -:Example: - -.. code-block:: none - - >> Issue: Mako templates allow HTML/JS rendering by default and are - inherently open to XSS attacks. Ensure variables in all templates are - properly sanitized via the 'n', 'h' or 'x' flags (depending on context). - For example, to HTML escape the variable 'data' do ${ data |h }. - Severity: Medium Confidence: High - Location: ./examples/mako_templating.py:10 - 9 - 10 mako.template.Template("hern") - 11 template.Template("hern") - - -.. seealso:: - - - http://www.makotemplates.org/ - - `OWASP XSS `_ - - https://security.openstack.org - - https://security.openstack.org/guidelines/dg_cross-site-scripting-xss.html - -.. versionadded:: 0.10.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -@test.checks('Call') -@test.test_id('B702') -def use_of_mako_templates(context): - # check type just to be safe - if isinstance(context.call_function_name_qual, str): - qualname_list = context.call_function_name_qual.split('.') - func = qualname_list[-1] - if 'mako' in qualname_list and func == 'Template': - # unlike Jinja2, mako does not have a template wide autoescape - # feature and thus each variable must be carefully sanitized. - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.HIGH, - text="Mako templates allow HTML/JS rendering by default and " - "are inherently open to XSS attacks. Ensure variables " - "in all templates are properly sanitized via the 'n', " - "'h' or 'x' flags (depending on context). For example, " - "to HTML escape the variable 'data' do ${ data |h }." - ) diff --git a/bandit/plugins/secret_config_option.py b/bandit/plugins/secret_config_option.py deleted file mode 100644 index 97555b5c..00000000 --- a/bandit/plugins/secret_config_option.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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""" -=============================================================== -B109: Test for a password based config option not marked secret -=============================================================== - -Passwords are sensitive and must be protected appropriately. In OpenStack -Oslo there is an option to mark options "secret" which will ensure that they -are not logged. This plugin detects usages of oslo configuration functions -that appear to deal with strings ending in 'password' and flag usages where -they have not been marked secret. - -If such a value is found a MEDIUM severity error is generated. If 'False' or -'None' are explicitly set, Bandit will return a MEDIUM confidence issue. If -Bandit can't determine the value of secret it will return a LOW confidence -issue. - - -**Config Options:** - -.. code-block:: yaml - - password_config_option_not_marked_secret: - function_names: - - oslo.config.cfg.StrOpt - - oslo_config.cfg.StrOpt - -:Example: - -.. code-block:: none - - >> Issue: [password_config_option_not_marked_secret] oslo config option - possibly not marked secret=True identified. - Severity: Medium Confidence: Low - Location: examples/secret-config-option.py:12 - 11 help="User's password"), - 12 cfg.StrOpt('nova_password', - 13 secret=secret, - 14 help="Nova user password"), - 15 ] - - >> Issue: [password_config_option_not_marked_secret] oslo config option not - marked secret=True identified, security issue. - Severity: Medium Confidence: Medium - Location: examples/secret-config-option.py:21 - 20 help="LDAP ubind ser name"), - 21 cfg.StrOpt('ldap_password', - 22 help="LDAP bind user password"), - 23 cfg.StrOpt('ldap_password_attribute', - -.. seealso:: - - - https://security.openstack.org/guidelines/dg_protect-sensitive-data-in-files.html # noqa - -.. versionadded:: 0.10.0 - -""" - -import bandit -from bandit.core import constants -from bandit.core import test_properties as test - - -def gen_config(name): - if name == 'password_config_option_not_marked_secret': - return {'function_names': - ['oslo.config.cfg.StrOpt', - 'oslo_config.cfg.StrOpt']} - - -@test.takes_config -@test.checks('Call') -@test.test_id('B109') -def password_config_option_not_marked_secret(context, config): - - if(context.call_function_name_qual in config['function_names'] and - context.get_call_arg_at_position(0) is not None and - context.get_call_arg_at_position(0).endswith('password')): - - # Checks whether secret=False or secret is not set (None). - # Returns True if argument found, and matches supplied values - # and None if argument not found at all. - if context.check_call_arg_value('secret', - constants.FALSE_VALUES) in [ - True, None]: - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.MEDIUM, - text="oslo config option not marked secret=True " - "identified, security issue.", - lineno=context.get_lineno_for_call_arg('secret'), - ) - # Checks whether secret is not True, for example when its set to a - # variable, secret=secret. - elif not context.check_call_arg_value('secret', 'True'): - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.LOW, - text="oslo config option possibly not marked secret=True " - "identified.", - lineno=context.get_lineno_for_call_arg('secret'), - ) diff --git a/bandit/plugins/try_except_continue.py b/bandit/plugins/try_except_continue.py deleted file mode 100644 index 1c70c54c..00000000 --- a/bandit/plugins/try_except_continue.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2016 IBM Corp. -# 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""" -============================================= -B112: Test for a continue in the except block -============================================= - -Errors in Python code bases are typically communicated using ``Exceptions``. -An exception object is 'raised' in the event of an error and can be 'caught' at -a later point in the program, typically some error handling or logging action -will then be performed. - -However, it is possible to catch an exception and silently ignore it while in -a loop. This is illustrated with the following example - -.. code-block:: python - - while keep_going: - try: - do_some_stuff() - except Exception: - continue - -This pattern is considered bad practice in general, but also represents a -potential security issue. A larger than normal volume of errors from a service -can indicate an attempt is being made to disrupt or interfere with it. Thus -errors should, at the very least, be logged. - -There are rare situations where it is desirable to suppress errors, but this is -typically done with specific exception types, rather than the base Exception -class (or no type). To accommodate this, the test may be configured to ignore -'try, except, continue' where the exception is typed. For example, the -following would not generate a warning if the configuration option -``checked_typed_exception`` is set to False: - -.. code-block:: python - - while keep_going: - try: - do_some_stuff() - except ZeroDivisionError: - continue - -**Config Options:** - -.. code-block:: yaml - - try_except_continue: - check_typed_exception: True - - -:Example: - -.. code-block:: none - - >> Issue: Try, Except, Continue detected. - Severity: Low Confidence: High - Location: ./examples/try_except_continue.py:5 - 4 a = i - 5 except: - 6 continue - -.. seealso:: - - - https://security.openstack.org - -.. versionadded:: 1.0.0 - -""" - -import ast - -import bandit -from bandit.core import test_properties as test - - -def gen_config(name): - if name == 'try_except_continue': - return {'check_typed_exception': False} - - -@test.takes_config -@test.checks('ExceptHandler') -@test.test_id('B112') -def try_except_continue(context, config): - node = context.node - if len(node.body) == 1: - if (not config['check_typed_exception'] and - node.type is not None and - getattr(node.type, 'id', None) != 'Exception'): - return - - if isinstance(node.body[0], ast.Continue): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.HIGH, - text=("Try, Except, Continue detected.")) diff --git a/bandit/plugins/try_except_pass.py b/bandit/plugins/try_except_pass.py deleted file mode 100644 index 9c82e843..00000000 --- a/bandit/plugins/try_except_pass.py +++ /dev/null @@ -1,110 +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""" -========================================= -B110: Test for a pass in the except block -========================================= - -Errors in Python code bases are typically communicated using ``Exceptions``. -An exception object is 'raised' in the event of an error and can be 'caught' at -a later point in the program, typically some error handling or logging action -will then be performed. - -However, it is possible to catch an exception and silently ignore it. This is -illustrated with the following example - -.. code-block:: python - - try: - do_some_stuff() - except Exception: - pass - -This pattern is considered bad practice in general, but also represents a -potential security issue. A larger than normal volume of errors from a service -can indicate an attempt is being made to disrupt or interfere with it. Thus -errors should, at the very least, be logged. - -There are rare situations where it is desirable to suppress errors, but this is -typically done with specific exception types, rather than the base Exception -class (or no type). To accommodate this, the test may be configured to ignore -'try, except, pass' where the exception is typed. For example, the following -would not generate a warning if the configuration option -``checked_typed_exception`` is set to False: - -.. code-block:: python - - try: - do_some_stuff() - except ZeroDivisionError: - pass - -**Config Options:** - -.. code-block:: yaml - - try_except_pass: - check_typed_exception: True - - -:Example: - -.. code-block:: none - - >> Issue: Try, Except, Pass detected. - Severity: Low Confidence: High - Location: ./examples/try_except_pass.py:4 - 3 a = 1 - 4 except: - 5 pass - -.. seealso:: - - - https://security.openstack.org - -.. versionadded:: 0.13.0 - -""" - -import ast - -import bandit -from bandit.core import test_properties as test - - -def gen_config(name): - if name == 'try_except_pass': - return {'check_typed_exception': False} - - -@test.takes_config -@test.checks('ExceptHandler') -@test.test_id('B110') -def try_except_pass(context, config): - node = context.node - if len(node.body) == 1: - if (not config['check_typed_exception'] and - node.type is not None and - getattr(node.type, 'id', None) != 'Exception'): - return - - if isinstance(node.body[0], ast.Pass): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.HIGH, - text=("Try, Except, Pass detected.") - ) diff --git a/bandit/plugins/weak_cryptographic_key.py b/bandit/plugins/weak_cryptographic_key.py deleted file mode 100644 index 1c80e647..00000000 --- a/bandit/plugins/weak_cryptographic_key.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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""" -========================================= -B505: Test for weak cryptographic key use -========================================= - -As computational power increases, so does the ability to break ciphers with -smaller key lengths. The recommended key length size for RSA and DSA algorithms -is 2048 and higher. 1024 bits and below are now considered breakable. EC key -length sizes are recommended to be 224 and higher with 160 and below considered -breakable. This plugin test checks for use of any key less than those limits -and returns a high severity error if lower than the lower threshold and a -medium severity error for those lower than the higher threshold. - -:Example: - -.. code-block:: none - - >> Issue: DSA key sizes below 1024 bits are considered breakable. - Severity: High Confidence: High - Location: examples/weak_cryptographic_key_sizes.py:36 - 35 # Also incorrect: without keyword args - 36 dsa.generate_private_key(512, - 37 backends.default_backend()) - 38 rsa.generate_private_key(3, - -.. seealso:: - - - http://csrc.nist.gov/publications/nistpubs/800-131A/sp800-131A.pdf - - https://security.openstack.org/guidelines/dg_strong-crypto.html - -.. versionadded:: 0.14.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -def gen_config(name): - if name == 'weak_cryptographic_key': - return { - 'weak_key_size_dsa_high': 1024, - 'weak_key_size_dsa_medium': 2048, - 'weak_key_size_rsa_high': 1024, - 'weak_key_size_rsa_medium': 2048, - 'weak_key_size_ec_high': 160, - 'weak_key_size_ec_medium': 224, - } - - -def _classify_key_size(config, key_type, key_size): - if isinstance(key_size, str): - # size provided via a variable - can't process it at the moment - return - - key_sizes = { - 'DSA': [(config['weak_key_size_dsa_high'], bandit.HIGH), - (config['weak_key_size_dsa_medium'], bandit.MEDIUM)], - 'RSA': [(config['weak_key_size_rsa_high'], bandit.HIGH), - (config['weak_key_size_rsa_medium'], bandit.MEDIUM)], - 'EC': [(config['weak_key_size_ec_high'], bandit.HIGH), - (config['weak_key_size_ec_medium'], bandit.MEDIUM)], - } - - for size, level in key_sizes[key_type]: - if key_size < size: - return bandit.Issue( - severity=level, - confidence=bandit.HIGH, - text='%s key sizes below %d bits are considered breakable. ' % - (key_type, size)) - - -def _weak_crypto_key_size_cryptography_io(context, config): - func_key_type = { - 'cryptography.hazmat.primitives.asymmetric.dsa.' - 'generate_private_key': 'DSA', - 'cryptography.hazmat.primitives.asymmetric.rsa.' - 'generate_private_key': 'RSA', - 'cryptography.hazmat.primitives.asymmetric.ec.' - 'generate_private_key': 'EC', - } - arg_position = { - 'DSA': 0, - 'RSA': 1, - 'EC': 0, - } - key_type = func_key_type.get(context.call_function_name_qual) - if key_type in ['DSA', 'RSA']: - key_size = (context.get_call_arg_value('key_size') or - context.get_call_arg_at_position(arg_position[key_type]) or - 2048) - return _classify_key_size(config, key_type, key_size) - elif key_type == 'EC': - curve_key_sizes = { - 'SECP192R1': 192, - 'SECT163K1': 163, - 'SECT163R2': 163, - } - curve = (context.get_call_arg_value('curve') or - context.call_args[arg_position[key_type]]) - key_size = curve_key_sizes[curve] if curve in curve_key_sizes else 224 - return _classify_key_size(config, key_type, key_size) - - -def _weak_crypto_key_size_pycrypto(context, config): - func_key_type = { - 'Crypto.PublicKey.DSA.generate': 'DSA', - 'Crypto.PublicKey.RSA.generate': 'RSA', - 'Cryptodome.PublicKey.DSA.generate': 'DSA', - 'Cryptodome.PublicKey.RSA.generate': 'RSA', - } - key_type = func_key_type.get(context.call_function_name_qual) - if key_type: - key_size = (context.get_call_arg_value('bits') or - context.get_call_arg_at_position(0) or - 2048) - return _classify_key_size(config, key_type, key_size) - - -@test.takes_config -@test.checks('Call') -@test.test_id('B505') -def weak_cryptographic_key(context, config): - return (_weak_crypto_key_size_cryptography_io(context, config) or - _weak_crypto_key_size_pycrypto(context, config)) diff --git a/bandit/plugins/yaml_load.py b/bandit/plugins/yaml_load.py deleted file mode 100644 index 01a93e92..00000000 --- a/bandit/plugins/yaml_load.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright (c) 2016 Rackspace, Inc. -# -# 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""" -=============================== -B506: Test for use of yaml load -=============================== - -This plugin test checks for the unsafe usage of the ``yaml.load`` function from -the PyYAML package. The yaml.load function provides the ability to construct -an arbitrary Python object, which may be dangerous if you receive a YAML -document from an untrusted source. The function yaml.safe_load limits this -ability to simple Python objects like integers or lists. - -Please see -http://pyyaml.org/wiki/PyYAMLDocumentation#LoadingYAML for more information -on ``yaml.load`` and yaml.safe_load - -:Example: - - >> Issue: [yaml_load] Use of unsafe yaml load. Allows instantiation of - arbitrary objects. Consider yaml.safe_load(). - Severity: Medium Confidence: High - Location: examples/yaml_load.py:5 - 4 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3}) - 5 y = yaml.load(ystr) - 6 yaml.dump(y) - - -.. seealso:: - - - http://pyyaml.org/wiki/PyYAMLDocumentation#LoadingYAML - -.. versionadded:: 1.0.0 - -""" - -import bandit -from bandit.core import test_properties as test - - -@test.test_id('B506') -@test.checks('Call') -def yaml_load(context): - if isinstance(context.call_function_name_qual, str): - qualname_list = context.call_function_name_qual.split('.') - func = qualname_list[-1] - if 'yaml' in qualname_list and func == 'load': - if not context.check_call_arg_value('Loader', 'SafeLoader'): - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.HIGH, - text="Use of unsafe yaml load. Allows instantiation of" - " arbitrary objects. Consider yaml.safe_load().", - lineno=context.node.lineno, - ) diff --git a/bindep.txt b/bindep.txt deleted file mode 100644 index 4f9b4254..00000000 --- a/bindep.txt +++ /dev/null @@ -1,2 +0,0 @@ -# This is a cross-platform list tracking distribution packages needed by tests; -# see http://docs.openstack.org/infra/bindep/ for additional information. diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index d3bd59ff..00000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# 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. -openstackdocstheme>=1.18.1 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD -reno>=2.5.0 # Apache-2.0 -oslosphinx>=4.7.0 # Apache-2.0 diff --git a/doc/source/blacklists/blacklist_calls.rst b/doc/source/blacklists/blacklist_calls.rst deleted file mode 100644 index 0897b6d3..00000000 --- a/doc/source/blacklists/blacklist_calls.rst +++ /dev/null @@ -1,5 +0,0 @@ ---------------- -blacklist_calls ---------------- - -.. automodule:: bandit.blacklists.calls diff --git a/doc/source/blacklists/blacklist_imports.rst b/doc/source/blacklists/blacklist_imports.rst deleted file mode 100644 index 3242cf88..00000000 --- a/doc/source/blacklists/blacklist_imports.rst +++ /dev/null @@ -1,5 +0,0 @@ ------------------ -blacklist_imports ------------------ - -.. automodule:: bandit.blacklists.imports diff --git a/doc/source/blacklists/index.rst b/doc/source/blacklists/index.rst deleted file mode 100644 index cc5e5b80..00000000 --- a/doc/source/blacklists/index.rst +++ /dev/null @@ -1,69 +0,0 @@ -Bandit Blacklist Plugins -======================== - -Bandit supports built in functionality to implement blacklisting of imports and -function calls, this functionality is provided by built in test 'B001'. This -test may be filtered as per normal plugin filtering rules. - -The exact calls and imports that are blacklisted, and the issues reported, are -controlled by plugin methods with the entry point 'bandit.blacklists' and can -be extended by third party plugins if desired. Blacklist plugins will be -discovered by Bandit at startup and called. The returned results are combined -into the final data set, subject to Bandit's normal test include/exclude rules -allowing for fine grained control over blacklisted items. By convention, -blacklisted calls should have IDs in the B3xx range and imports should have IDs -in the B4xx range. - -Plugin functions should return a dictionary mapping AST node types to -lists of blacklist data. Currently the following node types are supported: - -- Call, used for blacklisting calls. -- Import, used for blacklisting module imports (this also implicitly tests - ImportFrom and Call nodes where the invoked function is Pythons built in - '__import__()' method). - -Items in the data lists are Python dictionaries with the following structure: - -+-------------+----------------------------------------------------+ -| key | data meaning | -+=============+====================================================+ -| 'name' | The issue name string. | -+-------------+----------------------------------------------------+ -| 'id' | The bandit ID of the check, this must be unique | -| | and is used for filtering blacklist checks. | -+-------------+----------------------------------------------------+ -| 'qualnames' | A Python list of fully qualified name strings. | -+-------------+----------------------------------------------------+ -| 'message' | The issue message reported, this is a string that | -| | may contain the token '{name}' that will be | -| | substituted with the matched qualname in the final | -| | report. | -+-------------+----------------------------------------------------+ -| 'level' | The severity level reported. | -+-------------+----------------------------------------------------+ - -A utility method bandit.blacklists.utils.build_conf_dict is provided to aid -building these dictionaries. - -:Example: - .. code-block:: none - - >> Issue: [B317:blacklist] Using xml.sax.parse to parse untrusted XML data - is known to be vulnerable to XML attacks. Replace xml.sax.parse with its - defusedxml equivalent function. - Severity: Medium Confidence: High - Location: ./examples/xml_sax.py:24 - 23 sax.parseString(xmlString, ExampleContentHandler()) - 24 sax.parse('notaxmlfilethatexists.xml', ExampleContentHandler) - 25 - -Complete Plugin Listing ------------------------ - -.. toctree:: - :maxdepth: 1 - :glob: - - * - -.. versionadded:: 0.17.0 diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 1dffbd71..00000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- 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. - -import os -import sys - -sys.path.insert(0, os.path.abspath('../..')) -# -- General configuration ---------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', - # 'sphinx.ext.intersphinx', - 'oslosphinx' -] - -# autodoc generation is a bit aggressive and a nuisance when doing heavy -# text edit cycles. -# execute "export SPHINX_DEBUG=1" in your terminal to disable - -# The suffix of source filenames. -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Bandit' -copyright = u'2016, OpenStack Foundation' - -# If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = True - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -modindex_common_prefix = ['bandit.'] - - #-- Options for man page output -------------------------------------------- - -# Grouping the document tree for man pages. -# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual' - -man_pages = [ - ('man/bandit', 'bandit', u'Python source code security analyzer', - [u'OpenStack Security Group'], 1) -] - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -# html_theme_path = ["."] -# html_theme = '_theme' -# html_static_path = ['static'] -html_theme_options = {} - -# Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). -latex_documents = [ - ('index', - '%s.tex' % project, - u'%s Documentation' % project, - u'OpenStack Foundation', 'manual'), -] - -# Example configuration for intersphinx: refer to the Python standard library. -# intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/config.rst b/doc/source/config.rst deleted file mode 100644 index f88e05e7..00000000 --- a/doc/source/config.rst +++ /dev/null @@ -1,82 +0,0 @@ -Configuration -============= -Bandit is designed to be configurable and cover a wide range of needs, it may -be used as either a local developer utility or as part of a full CI/CD -pipeline. To provide for these various usage scenarios bandit can be configured -via a `YAML `_ file. This file is completely optional and in -many cases not needed, it may be specified on the command line by using `-c`. - -A bandit configuration file may choose the specific test plugins to run and -override the default configurations of those tests. An example config might -look like the following: - -.. code-block:: yaml - - ### profile may optionally select or skip tests - - # (optional) list included tests here: - tests: ['B201', 'B301'] - - # (optional) list skipped tests here: - skips: ['B101', 'B601'] - - ### override settings - used to set settings for plugins to non-default values - - any_other_function_with_shell_equals_true: - no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, - os.execvp, os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, - os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe, os.startfile] - shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, - popen2.popen2, popen2.popen3, popen2.popen4, popen2.Popen3, - popen2.Popen4, commands.getoutput, commands.getstatusoutput] - subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, - subprocess.check_output, - utils.execute, utils.execute_with_timeout] - -If you require several sets of tests for specific tasks, then you should create -several config files and pick from them using `-c`. If you only wish to control -the specific tests that are to be run (and not their parameters) then using -`-s` or `-t` on the command line may be more appropriate. - -Skipping Tests --------------- -The bandit config may contain optional lists of test IDs to either include -(`tests`) or exclude (`skips`). These lists are equivalent to using `-t` and -`-s` on the command line. If only `tests` is given then bandit will include -only those tests, effectively excluding all other tests. If only `skips` -is given then bandit will include all tests not in the skips list. If both are -given then bandit will include only tests in `tests` and then remove `skips` -from that set. It is an error to include the same test ID in both `tests` and -`skips`. - -Note that command line options `-t`/`-s` can still be used in conjunction with -`tests` and `skips` given in a config. The result is to concatenate `-t` with -`tests` and likewise for `-s` and `skips` before working out the tests to run. - -Generating a Config -------------------- -Bandit ships the tool `bandit-config-generator` designed to take the leg work -out of configuration. This tool can generate a configuration file -automatically. The generated configuration will include default config blocks -for all detected test and blacklist plugins. This data can then be deleted or -edited as needed to produce a minimal config as desired. The config generator -supports `-t` and `-s` command line options to specify a list of test IDs that -should be included or excluded respectively. If no options are given then the -generated config will not include `tests` or `skips` sections (but will provide -a complete list of all test IDs for reference when editing). - -Configuring Test Plugins ------------------------- -Bandit's configuration file is written in `YAML `_ and options -for each plugin test are provided under a section named to match the test -method. For example, given a test plugin called 'try_except_pass' its -configuration section might look like the following: - -.. code-block:: yaml - - try_except_pass: - check_typed_exception: True - -The specific content of the configuration block is determined by the plugin -test itself. See the `plugin test list `_ for complete -information on configuring each one. diff --git a/doc/source/formatters/csv.rst b/doc/source/formatters/csv.rst deleted file mode 100644 index 6f5db977..00000000 --- a/doc/source/formatters/csv.rst +++ /dev/null @@ -1,5 +0,0 @@ ---- -csv ---- - -.. automodule:: bandit.formatters.csv diff --git a/doc/source/formatters/html.rst b/doc/source/formatters/html.rst deleted file mode 100644 index d700f5b5..00000000 --- a/doc/source/formatters/html.rst +++ /dev/null @@ -1,5 +0,0 @@ ----- -html ----- - -.. automodule:: bandit.formatters.html diff --git a/doc/source/formatters/index.rst b/doc/source/formatters/index.rst deleted file mode 100644 index d206cab0..00000000 --- a/doc/source/formatters/index.rst +++ /dev/null @@ -1,42 +0,0 @@ -Bandit Report Formatters -======================== - -Bandit supports many different formatters to output various security issues in -python code. These formatters are created as plugins and new ones can be -created to extend the functionality offered by bandit today. - -Example Formatter ------------------ - -.. code-block:: python - - def report(manager, fileobj, sev_level, conf_level, lines=-1): - result = bson.dumps(issues) - with fileobj: - fileobj.write(result) - -To register your plugin, you have two options: - -1. If you're using setuptools directly, add something like the following to - your `setup` call:: - - # If you have an imaginary bson formatter in the bandit_bson module - # and a function called `formatter`. - entry_points={'bandit.formatters': ['bson = bandit_bson:formatter']} - -2. If you're using pbr, add something like the following to your `setup.cfg` - file:: - - [entry_points] - bandit.formatters = - bson = bandit_bson:formatter - - -Complete Formatter Listing ----------------------------- - -.. toctree:: - :maxdepth: 1 - :glob: - - * diff --git a/doc/source/formatters/json.rst b/doc/source/formatters/json.rst deleted file mode 100644 index d32187e4..00000000 --- a/doc/source/formatters/json.rst +++ /dev/null @@ -1,5 +0,0 @@ ----- -json ----- - -.. automodule:: bandit.formatters.json diff --git a/doc/source/formatters/screen.rst b/doc/source/formatters/screen.rst deleted file mode 100644 index dc214139..00000000 --- a/doc/source/formatters/screen.rst +++ /dev/null @@ -1,5 +0,0 @@ ------- -screen ------- - -.. automodule:: bandit.formatters.screen diff --git a/doc/source/formatters/text.rst b/doc/source/formatters/text.rst deleted file mode 100644 index a1a724ee..00000000 --- a/doc/source/formatters/text.rst +++ /dev/null @@ -1,5 +0,0 @@ ----- -text ----- - -.. automodule:: bandit.formatters.text diff --git a/doc/source/formatters/xml.rst b/doc/source/formatters/xml.rst deleted file mode 100644 index e84edf5d..00000000 --- a/doc/source/formatters/xml.rst +++ /dev/null @@ -1,5 +0,0 @@ ---- -xml ---- - -.. automodule:: bandit.formatters.xml diff --git a/doc/source/formatters/yaml.rst b/doc/source/formatters/yaml.rst deleted file mode 100644 index 020feae2..00000000 --- a/doc/source/formatters/yaml.rst +++ /dev/null @@ -1,5 +0,0 @@ ----- -yaml ----- - -.. automodule:: bandit.formatters.yaml diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index 8b71f88a..00000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,27 +0,0 @@ -Welcome to Bandit's developer documentation! -============================================ - -Bandit is a tool designed to find common security issues in Python code. To do -this, Bandit processes each file, builds an AST from it, and runs appropriate -plugins against the AST nodes. Once Bandit has finished scanning all the files, -it generates a report. - -This documentation is generated by the Sphinx toolkit and lives in the source -tree. - -Getting Started -=============== -.. toctree:: - :maxdepth: 1 - - config - plugins/index - blacklists/index - formatters/index - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/doc/source/man/bandit.rst b/doc/source/man/bandit.rst deleted file mode 100644 index 363b8575..00000000 --- a/doc/source/man/bandit.rst +++ /dev/null @@ -1,128 +0,0 @@ -====== -bandit -====== - -SYNOPSIS -======== - -bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE] - [-p PROFILE] [-t TESTS] [-s SKIPS] [-l] [-i] - [-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 ...] - -DESCRIPTION -=========== - -``bandit`` is a tool designed to find common security issues in Python code. To -do this Bandit processes each file, builds an AST from it, and runs appropriate -plugins against the AST nodes. Once Bandit has finished scanning all the files -it generates a report. - -OPTIONS -======= - - -h, --help show this help message and exit - -r, --recursive find and process files in subdirectories - -a {file,vuln}, --aggregate {file,vuln} - aggregate output by vulnerability (default) or by - filename - -n CONTEXT_LINES, --number CONTEXT_LINES - maximum number of code lines to output for each issue - -c CONFIG_FILE, --configfile CONFIG_FILE - optional config file to use for selecting plugins and - overriding defaults - -p PROFILE, --profile PROFILE - profile to use (defaults to executing all tests) - -t TESTS, --tests TESTS - comma-separated list of test IDs to run - -s SKIPS, --skip SKIPS - comma-separated list of test IDs to skip - -l, --level report only issues of a given severity level or higher - (-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,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 - files - -d, --debug turn on debug mode - --ignore-nosec do not skip lines with # nosec comments - -x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS - comma-separated list of paths to exclude from scan - (note that these are in addition to the excluded paths - provided in the config file) - -b BASELINE, --baseline BASELINE - path of a baseline report to compare against (only - JSON-formatted files are accepted) - --ini INI_PATH path to a .bandit file that supplies command line - 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 -===== - -.bandit - file that supplies command line arguments - -/etc/bandit/bandit.yaml - legacy bandit configuration file - -EXAMPLES -======== - -Example usage across a code tree:: - - bandit -r ~/openstack-repo/keystone - -Example usage across the ``examples/`` directory, showing three lines of -context and only reporting on the high-severity issues:: - - bandit examples/*.py -n 3 -lll - -Bandit can be run with profiles. To run Bandit against the examples directory -using only the plugins listed in the ShellInjection profile:: - - bandit examples/*.py -p ShellInjection - -Bandit also supports passing lines of code to scan using standard input. To -run Bandit with standard input:: - - cat examples/imports.py | bandit - - -SEE ALSO -======== - -pylint(1) diff --git a/doc/source/plugins/b101_assert_used.rst b/doc/source/plugins/b101_assert_used.rst deleted file mode 100644 index 1904a81a..00000000 --- a/doc/source/plugins/b101_assert_used.rst +++ /dev/null @@ -1,5 +0,0 @@ ------------------ -B101: assert_used ------------------ - -.. automodule:: bandit.plugins.asserts diff --git a/doc/source/plugins/b102_exec_used.rst b/doc/source/plugins/b102_exec_used.rst deleted file mode 100644 index 33197640..00000000 --- a/doc/source/plugins/b102_exec_used.rst +++ /dev/null @@ -1,5 +0,0 @@ ---------------- -B102: exec_used ---------------- - -.. automodule:: bandit.plugins.exec diff --git a/doc/source/plugins/b103_set_bad_file_permissions.rst b/doc/source/plugins/b103_set_bad_file_permissions.rst deleted file mode 100644 index d77826c9..00000000 --- a/doc/source/plugins/b103_set_bad_file_permissions.rst +++ /dev/null @@ -1,5 +0,0 @@ ------------------------------- -B103: set_bad_file_permissions ------------------------------- - -.. automodule:: bandit.plugins.general_bad_file_permissions diff --git a/doc/source/plugins/b104_hardcoded_bind_all_interfaces.rst b/doc/source/plugins/b104_hardcoded_bind_all_interfaces.rst deleted file mode 100644 index df56095a..00000000 --- a/doc/source/plugins/b104_hardcoded_bind_all_interfaces.rst +++ /dev/null @@ -1,5 +0,0 @@ ------------------------------------ -B104: hardcoded_bind_all_interfaces ------------------------------------ - -.. automodule:: bandit.plugins.general_bind_all_interfaces diff --git a/doc/source/plugins/b105_hardcoded_password_string.rst b/doc/source/plugins/b105_hardcoded_password_string.rst deleted file mode 100644 index 72dde9c1..00000000 --- a/doc/source/plugins/b105_hardcoded_password_string.rst +++ /dev/null @@ -1,8 +0,0 @@ -------------------------------- -B105: hardcoded_password_string -------------------------------- - -.. currentmodule:: bandit.plugins.general_hardcoded_password - -.. autofunction:: hardcoded_password_string - :noindex: diff --git a/doc/source/plugins/b106_hardcoded_password_funcarg.rst b/doc/source/plugins/b106_hardcoded_password_funcarg.rst deleted file mode 100644 index 2f668e24..00000000 --- a/doc/source/plugins/b106_hardcoded_password_funcarg.rst +++ /dev/null @@ -1,8 +0,0 @@ --------------------------------- -B106: hardcoded_password_funcarg --------------------------------- - -.. currentmodule:: bandit.plugins.general_hardcoded_password - -.. autofunction:: hardcoded_password_funcarg - :noindex: diff --git a/doc/source/plugins/b107_hardcoded_password_funcdef.rst b/doc/source/plugins/b107_hardcoded_password_funcdef.rst deleted file mode 100644 index ba82f407..00000000 --- a/doc/source/plugins/b107_hardcoded_password_funcdef.rst +++ /dev/null @@ -1,8 +0,0 @@ --------------------------------- -B107: hardcoded_password_default --------------------------------- - -.. currentmodule:: bandit.plugins.general_hardcoded_password - -.. autofunction:: hardcoded_password_default - :noindex: diff --git a/doc/source/plugins/b108_hardcoded_tmp_directory.rst b/doc/source/plugins/b108_hardcoded_tmp_directory.rst deleted file mode 100644 index 96754c5f..00000000 --- a/doc/source/plugins/b108_hardcoded_tmp_directory.rst +++ /dev/null @@ -1,5 +0,0 @@ ------------------------------ -B108: hardcoded_tmp_directory ------------------------------ - -.. automodule:: bandit.plugins.general_hardcoded_tmp diff --git a/doc/source/plugins/b109_password_config_option_not_marked_secret.rst b/doc/source/plugins/b109_password_config_option_not_marked_secret.rst deleted file mode 100644 index ac84217d..00000000 --- a/doc/source/plugins/b109_password_config_option_not_marked_secret.rst +++ /dev/null @@ -1,5 +0,0 @@ ----------------------------------------------- -B109: password_config_option_not_marked_secret ----------------------------------------------- - -.. automodule:: bandit.plugins.secret_config_option diff --git a/doc/source/plugins/b110_try_except_pass.rst b/doc/source/plugins/b110_try_except_pass.rst deleted file mode 100644 index d35ce06a..00000000 --- a/doc/source/plugins/b110_try_except_pass.rst +++ /dev/null @@ -1,5 +0,0 @@ ---------------------- -B110: try_except_pass ---------------------- - -.. automodule:: bandit.plugins.try_except_pass diff --git a/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst b/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst deleted file mode 100644 index 4093c36f..00000000 --- a/doc/source/plugins/b111_execute_with_run_as_root_equals_true.rst +++ /dev/null @@ -1,5 +0,0 @@ ------------------------------------------- -B111: execute_with_run_as_root_equals_true ------------------------------------------- - -.. automodule:: bandit.plugins.exec_as_root diff --git a/doc/source/plugins/b112_try_except_continue.rst b/doc/source/plugins/b112_try_except_continue.rst deleted file mode 100644 index 62508c65..00000000 --- a/doc/source/plugins/b112_try_except_continue.rst +++ /dev/null @@ -1,5 +0,0 @@ -------------------------- -B112: try_except_continue -------------------------- - -.. automodule:: bandit.plugins.try_except_continue diff --git a/doc/source/plugins/b201_flask_debug_true.rst b/doc/source/plugins/b201_flask_debug_true.rst deleted file mode 100644 index 1fa0cc77..00000000 --- a/doc/source/plugins/b201_flask_debug_true.rst +++ /dev/null @@ -1,5 +0,0 @@ ----------------------- -B201: flask_debug_true ----------------------- - -.. automodule:: bandit.plugins.app_debug diff --git a/doc/source/plugins/b501_request_with_no_cert_validation.rst b/doc/source/plugins/b501_request_with_no_cert_validation.rst deleted file mode 100644 index 4fbd418c..00000000 --- a/doc/source/plugins/b501_request_with_no_cert_validation.rst +++ /dev/null @@ -1,5 +0,0 @@ -------------------------------------- -B501: request_with_no_cert_validation -------------------------------------- - -.. automodule:: bandit.plugins.crypto_request_no_cert_validation diff --git a/doc/source/plugins/b502_ssl_with_bad_version.rst b/doc/source/plugins/b502_ssl_with_bad_version.rst deleted file mode 100644 index 16b5defd..00000000 --- a/doc/source/plugins/b502_ssl_with_bad_version.rst +++ /dev/null @@ -1,8 +0,0 @@ --------------------------- -B502: ssl_with_bad_version --------------------------- - -.. currentmodule:: bandit.plugins.insecure_ssl_tls - -.. autofunction:: ssl_with_bad_version - :noindex: diff --git a/doc/source/plugins/b503_ssl_with_bad_defaults.rst b/doc/source/plugins/b503_ssl_with_bad_defaults.rst deleted file mode 100644 index ebdb8bc1..00000000 --- a/doc/source/plugins/b503_ssl_with_bad_defaults.rst +++ /dev/null @@ -1,8 +0,0 @@ ---------------------------- -B503: ssl_with_bad_defaults ---------------------------- - -.. currentmodule:: bandit.plugins.insecure_ssl_tls - -.. autofunction:: ssl_with_bad_defaults - :noindex: diff --git a/doc/source/plugins/b504_ssl_with_no_version.rst b/doc/source/plugins/b504_ssl_with_no_version.rst deleted file mode 100644 index 2a8247b1..00000000 --- a/doc/source/plugins/b504_ssl_with_no_version.rst +++ /dev/null @@ -1,8 +0,0 @@ -------------------------- -B504: ssl_with_no_version -------------------------- - -.. currentmodule:: bandit.plugins.insecure_ssl_tls - -.. autofunction:: ssl_with_no_version - :noindex: diff --git a/doc/source/plugins/b505_weak_cryptographic_key.rst b/doc/source/plugins/b505_weak_cryptographic_key.rst deleted file mode 100644 index cb0def8e..00000000 --- a/doc/source/plugins/b505_weak_cryptographic_key.rst +++ /dev/null @@ -1,5 +0,0 @@ ----------------------------- -B505: weak_cryptographic_key ----------------------------- - -.. automodule:: bandit.plugins.weak_cryptographic_key diff --git a/doc/source/plugins/b506_yaml_load.rst b/doc/source/plugins/b506_yaml_load.rst deleted file mode 100644 index c5e880dd..00000000 --- a/doc/source/plugins/b506_yaml_load.rst +++ /dev/null @@ -1,5 +0,0 @@ ---------------- -B506: yaml_load ---------------- - -.. automodule:: bandit.plugins.yaml_load diff --git a/doc/source/plugins/b601_paramiko_calls.rst b/doc/source/plugins/b601_paramiko_calls.rst deleted file mode 100644 index a02a7382..00000000 --- a/doc/source/plugins/b601_paramiko_calls.rst +++ /dev/null @@ -1,5 +0,0 @@ --------------------- -B601: paramiko_calls --------------------- - -.. automodule:: bandit.plugins.injection_paramiko diff --git a/doc/source/plugins/b602_subprocess_popen_with_shell_equals_true.rst b/doc/source/plugins/b602_subprocess_popen_with_shell_equals_true.rst deleted file mode 100644 index 8b60c5da..00000000 --- a/doc/source/plugins/b602_subprocess_popen_with_shell_equals_true.rst +++ /dev/null @@ -1,8 +0,0 @@ ---------------------------------------------- -B602: subprocess_popen_with_shell_equals_true ---------------------------------------------- - -.. currentmodule:: bandit.plugins.injection_shell - -.. autofunction:: subprocess_popen_with_shell_equals_true - :noindex: diff --git a/doc/source/plugins/b603_subprocess_without_shell_equals_true.rst b/doc/source/plugins/b603_subprocess_without_shell_equals_true.rst deleted file mode 100644 index 733b505a..00000000 --- a/doc/source/plugins/b603_subprocess_without_shell_equals_true.rst +++ /dev/null @@ -1,8 +0,0 @@ ------------------------------------------- -B603: subprocess_without_shell_equals_true ------------------------------------------- - -.. currentmodule:: bandit.plugins.injection_shell - -.. autofunction:: subprocess_without_shell_equals_true - :noindex: diff --git a/doc/source/plugins/b604_any_other_function_with_shell_equals_true.rst b/doc/source/plugins/b604_any_other_function_with_shell_equals_true.rst deleted file mode 100644 index d9af8f7c..00000000 --- a/doc/source/plugins/b604_any_other_function_with_shell_equals_true.rst +++ /dev/null @@ -1,8 +0,0 @@ ------------------------------------------------ -B604: any_other_function_with_shell_equals_true ------------------------------------------------ - -.. currentmodule:: bandit.plugins.injection_shell - -.. autofunction:: any_other_function_with_shell_equals_true - :noindex: diff --git a/doc/source/plugins/b605_start_process_with_a_shell.rst b/doc/source/plugins/b605_start_process_with_a_shell.rst deleted file mode 100644 index 97667d8c..00000000 --- a/doc/source/plugins/b605_start_process_with_a_shell.rst +++ /dev/null @@ -1,8 +0,0 @@ --------------------------------- -B605: start_process_with_a_shell --------------------------------- - -.. currentmodule:: bandit.plugins.injection_shell - -.. autofunction:: start_process_with_a_shell - :noindex: diff --git a/doc/source/plugins/b606_start_process_with_no_shell.rst b/doc/source/plugins/b606_start_process_with_no_shell.rst deleted file mode 100644 index 65e8ba65..00000000 --- a/doc/source/plugins/b606_start_process_with_no_shell.rst +++ /dev/null @@ -1,8 +0,0 @@ ---------------------------------- -B606: start_process_with_no_shell ---------------------------------- - -.. currentmodule:: bandit.plugins.injection_shell - -.. autofunction:: start_process_with_no_shell - :noindex: diff --git a/doc/source/plugins/b607_start_process_with_partial_path.rst b/doc/source/plugins/b607_start_process_with_partial_path.rst deleted file mode 100644 index 36a77bc3..00000000 --- a/doc/source/plugins/b607_start_process_with_partial_path.rst +++ /dev/null @@ -1,8 +0,0 @@ -------------------------------------- -B607: start_process_with_partial_path -------------------------------------- - -.. currentmodule:: bandit.plugins.injection_shell - -.. autofunction:: start_process_with_partial_path - :noindex: diff --git a/doc/source/plugins/b608_hardcoded_sql_expressions.rst b/doc/source/plugins/b608_hardcoded_sql_expressions.rst deleted file mode 100644 index 89c13281..00000000 --- a/doc/source/plugins/b608_hardcoded_sql_expressions.rst +++ /dev/null @@ -1,5 +0,0 @@ -------------------------------- -B608: hardcoded_sql_expressions -------------------------------- - -.. automodule:: bandit.plugins.injection_sql diff --git a/doc/source/plugins/b609_linux_commands_wildcard_injection.rst b/doc/source/plugins/b609_linux_commands_wildcard_injection.rst deleted file mode 100644 index 8a33fc37..00000000 --- a/doc/source/plugins/b609_linux_commands_wildcard_injection.rst +++ /dev/null @@ -1,5 +0,0 @@ ---------------------------------------- -B609: linux_commands_wildcard_injection ---------------------------------------- - -.. automodule:: bandit.plugins.injection_wildcard diff --git a/doc/source/plugins/b701_jinja2_autoescape_false.rst b/doc/source/plugins/b701_jinja2_autoescape_false.rst deleted file mode 100644 index 6cc7eeb5..00000000 --- a/doc/source/plugins/b701_jinja2_autoescape_false.rst +++ /dev/null @@ -1,5 +0,0 @@ ------------------------------ -B701: jinja2_autoescape_false ------------------------------ - -.. automodule:: bandit.plugins.jinja2_templates diff --git a/doc/source/plugins/b702_use_of_mako_templates.rst b/doc/source/plugins/b702_use_of_mako_templates.rst deleted file mode 100644 index a9c1796f..00000000 --- a/doc/source/plugins/b702_use_of_mako_templates.rst +++ /dev/null @@ -1,5 +0,0 @@ ---------------------------- -B702: use_of_mako_templates ---------------------------- - -.. automodule:: bandit.plugins.mako_templates diff --git a/doc/source/plugins/index.rst b/doc/source/plugins/index.rst deleted file mode 100644 index 10ddd60e..00000000 --- a/doc/source/plugins/index.rst +++ /dev/null @@ -1,120 +0,0 @@ -Bandit Test Plugins -=================== - -Bandit supports many different tests to detect various security issues in -python code. These tests are created as plugins and new ones can be created to -extend the functionality offered by bandit today. - -Writing Tests -------------- -To write a test: - - Identify a vulnerability to build a test for, and create a new file in - examples/ that contains one or more cases of that vulnerability. - - Create a new Python source file to contain your test, you can reference - existing tests for examples. - - Consider the vulnerability you're testing for, mark the function with one - or more of the appropriate decorators: - - - @checks('Call') - - @checks('Import', 'ImportFrom') - - @checks('Str') - - - Register your plugin using the `bandit.plugins` entry point, see example. - - The function that you create should take a parameter "context" which is - an instance of the context class you can query for information about the - current element being examined. You can also get the raw AST node for - more advanced use cases. Please see the `context.py` file for more. - - Extend your Bandit configuration file as needed to support your new test. - - Execute Bandit against the test file you defined in `examples/` and ensure - that it detects the vulnerability. Consider variations on how this - vulnerability might present itself and extend the example file and the test - function accordingly. - -Config Generation ------------------ -In Bandit 1.0+ config files are optional. Plugins that need config settings are -required to implement a module global `gen_config` function. This function is -called with a single parameter, the test plugin name. It should return a -dictionary with keys being the config option names and values being the default -settings for each option. An example `gen_config` might look like the following: - -.. code-block:: python - - def gen_config(name): - if name == 'try_except_continue': - return {'check_typed_exception': False} - - -When no config file is specified, or when the chosen file has no section -pertaining to a given plugin, `gen_config` will be called to provide defaults. - -The config file generation tool `bandit-config-generator` will also call -`gen_config` on all discovered plugins to produce template config blocks. If -the defaults are acceptable then these blocks may be deleted to create a -minimal configuration, or otherwise edited as needed. The above example would -produce the following config snippet. - -.. code-block:: yaml - - try_except_continue: {check_typed_exception: false} - - -Example Test Plugin -------------------- - -.. code-block:: python - - @bandit.checks('Call') - def prohibit_unsafe_deserialization(context): - if 'unsafe_load' in context.call_function_name_qual: - return bandit.Issue( - severity=bandit.HIGH, - confidence=bandit.HIGH, - text="Unsafe deserialization detected." - ) - -To register your plugin, you have two options: - -1. If you're using setuptools directly, add something like the following to - your `setup` call:: - - # If you have an imaginary bson formatter in the bandit_bson module - # and a function called `formatter`. - entry_points={'bandit.formatters': ['bson = bandit_bson:formatter']} - # Or a check for using mako templates in bandit_mako that - entry_points={'bandit.plugins': ['mako = bandit_mako']} - -2. If you're using pbr, add something like the following to your `setup.cfg` - file:: - - [entry_points] - bandit.formatters = - bson = bandit_bson:formatter - bandit.plugins = - mako = bandit_mako - - -Plugin ID Groupings -------------------- - -======= =========== -ID Description -======= =========== -B1xx misc tests -B2xx application/framework misconfiguration -B3xx blacklists (calls) -B4xx blacklists (imports) -B5xx cryptography -B6xx injection -B7xx XSS -======= =========== - - -Complete Test Plugin Listing ----------------------------- - -.. toctree:: - :maxdepth: 1 - :glob: - - * diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/assert.py b/examples/assert.py deleted file mode 100644 index f8cc332e..00000000 --- a/examples/assert.py +++ /dev/null @@ -1 +0,0 @@ -assert True diff --git a/examples/binding.py b/examples/binding.py deleted file mode 100644 index fee24870..00000000 --- a/examples/binding.py +++ /dev/null @@ -1,5 +0,0 @@ -import socket - -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.bind(('0.0.0.0', 31137)) -s.bind(('192.168.0.1', 8080)) diff --git a/examples/cipher-modes.py b/examples/cipher-modes.py deleted file mode 100644 index ff1f86d5..00000000 --- a/examples/cipher-modes.py +++ /dev/null @@ -1,12 +0,0 @@ -from cryptography.hazmat.primitives.ciphers.modes import CBC -from cryptography.hazmat.primitives.ciphers.modes import ECB - - -# Insecure mode -mode = ECB(iv) - -# Secure cipher and mode -cipher = AES.new(key, blockalgo.MODE_CTR, iv) - -# Secure mode -mode = CBC(iv) diff --git a/examples/ciphers.py b/examples/ciphers.py deleted file mode 100644 index 7e0762d8..00000000 --- a/examples/ciphers.py +++ /dev/null @@ -1,76 +0,0 @@ -from Crypto.Cipher import ARC2 as pycrypto_arc2 -from Crypto.Cipher import ARC4 as pycrypto_arc4 -from Crypto.Cipher import Blowfish as pycrypto_blowfish -from Crypto.Cipher import DES as pycrypto_des -from Crypto.Cipher import XOR as pycrypto_xor -from Cryptodome.Cipher import ARC2 as pycryptodomex_arc2 -from Cryptodome.Cipher import ARC4 as pycryptodomex_arc4 -from Cryptodome.Cipher import Blowfish as pycryptodomex_blowfish -from Cryptodome.Cipher import DES as pycryptodomex_des -from Cryptodome.Cipher import XOR as pycryptodomex_xor -from Crypto.Hash import SHA -from Crypto import Random -from Crypto.Util import Counter -from cryptography.hazmat.primitives.ciphers import Cipher -from cryptography.hazmat.primitives.ciphers import algorithms -from cryptography.hazmat.primitives.ciphers import modes -from cryptography.hazmat.backends import default_backend -from struct import pack - -key = b'Sixteen byte key' -iv = Random.new().read(pycrypto_arc2.block_size) -cipher = pycrypto_arc2.new(key, pycrypto_arc2.MODE_CFB, iv) -msg = iv + cipher.encrypt(b'Attack at dawn') -cipher = pycryptodomex_arc2.new(key, pycryptodomex_arc2.MODE_CFB, iv) -msg = iv + cipher.encrypt(b'Attack at dawn') - -key = b'Very long and confidential key' -nonce = Random.new().read(16) -tempkey = SHA.new(key+nonce).digest() -cipher = pycrypto_arc4.new(tempkey) -msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL') -cipher = pycryptodomex_arc4.new(tempkey) -msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL') - -iv = Random.new().read(bs) -key = b'An arbitrarily long key' -plaintext = b'docendo discimus ' -plen = bs - divmod(len(plaintext),bs)[1] -padding = [plen]*plen -padding = pack('b'*plen, *padding) -bs = pycrypto_blowfish.block_size -cipher = pycrypto_blowfish.new(key, pycrypto_blowfish.MODE_CBC, iv) -msg = iv + cipher.encrypt(plaintext + padding) -bs = pycryptodomex_blowfish.block_size -cipher = pycryptodomex_blowfish.new(key, pycryptodomex_blowfish.MODE_CBC, iv) -msg = iv + cipher.encrypt(plaintext + padding) - -key = b'-8B key-' -plaintext = b'We are no longer the knights who say ni!' -nonce = Random.new().read(pycrypto_des.block_size/2) -ctr = Counter.new(pycrypto_des.block_size*8/2, prefix=nonce) -cipher = pycrypto_des.new(key, pycrypto_des.MODE_CTR, counter=ctr) -msg = nonce + cipher.encrypt(plaintext) -nonce = Random.new().read(pycryptodomex_des.block_size/2) -ctr = Counter.new(pycryptodomex_des.block_size*8/2, prefix=nonce) -cipher = pycryptodomex_des.new(key, pycryptodomex_des.MODE_CTR, counter=ctr) -msg = nonce + cipher.encrypt(plaintext) - -key = b'Super secret key' -plaintext = b'Encrypt me' -cipher = pycrypto_xor.new(key) -msg = cipher.encrypt(plaintext) -cipher = pycryptodomex_xor.new(key) -msg = cipher.encrypt(plaintext) - -cipher = Cipher(algorithms.ARC4(key), mode=None, backend=default_backend()) -encryptor = cipher.encryptor() -ct = encryptor.update(b"a secret message") - -cipher = Cipher(algorithms.Blowfish(key), mode=None, backend=default_backend()) -encryptor = cipher.encryptor() -ct = encryptor.update(b"a secret message") - -cipher = Cipher(algorithms.IDEA(key), mode=None, backend=default_backend()) -encryptor = cipher.encryptor() -ct = encryptor.update(b"a secret message") diff --git a/examples/crypto-md5.py b/examples/crypto-md5.py deleted file mode 100644 index 045740c3..00000000 --- a/examples/crypto-md5.py +++ /dev/null @@ -1,32 +0,0 @@ -from cryptography.hazmat.primitives import hashes -from Crypto.Hash import MD2 as pycrypto_md2 -from Crypto.Hash import MD4 as pycrypto_md4 -from Crypto.Hash import MD5 as pycrypto_md5 -from Crypto.Hash import SHA as pycrypto_sha -from Cryptodome.Hash import MD2 as pycryptodomex_md2 -from Cryptodome.Hash import MD4 as pycryptodomex_md4 -from Cryptodome.Hash import MD5 as pycryptodomex_md5 -from Cryptodome.Hash import SHA as pycryptodomex_sha -import hashlib - -hashlib.md5(1) -hashlib.md5(1).hexdigest() - -abc = str.replace(hashlib.md5("1"), "###") - -print(hashlib.md5("1")) - -hashlib.sha1(1) - -pycrypto_md2.new() -pycrypto_md4.new() -pycrypto_md5.new() -pycrypto_sha.new() - -pycryptodomex_md2.new() -pycryptodomex_md4.new() -pycryptodomex_md5.new() -pycryptodomex_sha.new() - -hashes.MD5() -hashes.SHA1() diff --git a/examples/eval.py b/examples/eval.py deleted file mode 100644 index 72a8174e..00000000 --- a/examples/eval.py +++ /dev/null @@ -1,15 +0,0 @@ -import os - -print(eval("1+1")) -print(eval("os.getcwd()")) -print(eval("os.chmod('%s', 0777)" % 'test.txt')) - - -# A user-defined method named "eval" should not get flagged. -class Test(object): - def eval(self): - print("hi") - def foo(self): - self.eval() - -Test().eval() diff --git a/examples/exec-as-root.py b/examples/exec-as-root.py deleted file mode 100644 index 071875b6..00000000 --- a/examples/exec-as-root.py +++ /dev/null @@ -1,26 +0,0 @@ -from ceilometer import utils as ceilometer_utils -from cinder import utils as cinder_utils -from neutron.agent.linux import utils as neutron_utils -from nova import utils as nova_utils - -# Ceilometer -ceilometer_utils.execute('gcc --version') -ceilometer_utils.execute('gcc --version', run_as_root=False) -ceilometer_utils.execute('gcc --version', run_as_root=True) - -# Cinder -cinder_utils.execute('gcc --version') -cinder_utils.execute('gcc --version', run_as_root=False) -cinder_utils.execute('gcc --version', run_as_root=True) - -# Neutron -neutron_utils.execute('gcc --version') -neutron_utils.execute('gcc --version', run_as_root=False) -neutron_utils.execute('gcc --version', run_as_root=True) - -# Nova -nova_utils.execute('gcc --version') -nova_utils.execute('gcc --version', run_as_root=False) -nova_utils.execute('gcc --version', run_as_root=True) -nova_utils.trycmd('gcc --version') -nova_utils.trycmd('gcc --version', run_as_root=True) diff --git a/examples/exec-py2.py b/examples/exec-py2.py deleted file mode 100644 index ae36c573..00000000 --- a/examples/exec-py2.py +++ /dev/null @@ -1,2 +0,0 @@ -exec("do evil") -exec "do evil" \ No newline at end of file diff --git a/examples/exec-py3.py b/examples/exec-py3.py deleted file mode 100644 index 17ac83a2..00000000 --- a/examples/exec-py3.py +++ /dev/null @@ -1 +0,0 @@ -exec("do evil") diff --git a/examples/flask_debug.py b/examples/flask_debug.py deleted file mode 100644 index 28429d73..00000000 --- a/examples/flask_debug.py +++ /dev/null @@ -1,19 +0,0 @@ -from flask import Flask - -app = Flask(__name__) - -@app.route('/') -def main(): - raise - -#bad -app.run(debug=True) - -#okay -app.run() -app.run(debug=False) - -#unrelated -run() -run(debug=True) -run(debug) diff --git a/examples/ftplib.py b/examples/ftplib.py deleted file mode 100644 index beaeb74a..00000000 --- a/examples/ftplib.py +++ /dev/null @@ -1,9 +0,0 @@ -from ftplib import FTP - -ftp = FTP('ftp.debian.org') -ftp.login() - -ftp.cwd('debian') -ftp.retrlines('LIST') - -ftp.quit() \ No newline at end of file diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py deleted file mode 100644 index 221c8f44..00000000 --- a/examples/hardcoded-passwords.py +++ /dev/null @@ -1,24 +0,0 @@ -def someFunction(user, password="Admin"): - print("Hi " + user) - -def someFunction2(password): - if password == "root": - print("OK, logged in") - -def noMatch(password): - if password == '': - print("No password!") - -def NoMatch2(password): - if password == "ajklawejrkl42348swfgkg": - print("Nice password!") - -def doLogin(password="blerg"): - pass - -def NoMatch3(a, b): - pass - -doLogin(password="blerg") -password = "blerg" -d["password"] = "blerg" diff --git a/examples/hardcoded-tmp.py b/examples/hardcoded-tmp.py deleted file mode 100644 index 9a9b8f24..00000000 --- a/examples/hardcoded-tmp.py +++ /dev/null @@ -1,21 +0,0 @@ -f = open('/tmp/abc', 'w') -f.write('def') -f.close() - -# ok -f = open('/abc/tmp', 'w') -f.write('def') -f.close() - -f = open('/var/tmp/123', 'w') -f.write('def') -f.close() - -f = open('/dev/shm/unit/test', 'w') -f.write('def') -f.close() - -# Negative test -f = open('/foo/bar', 'w') -f.write('def') -f.close() diff --git a/examples/hashlib_new_insecure_functions.py b/examples/hashlib_new_insecure_functions.py deleted file mode 100644 index eeddccac..00000000 --- a/examples/hashlib_new_insecure_functions.py +++ /dev/null @@ -1,16 +0,0 @@ -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/examples/httplib_https.py b/examples/httplib_https.py deleted file mode 100644 index f5de3f97..00000000 --- a/examples/httplib_https.py +++ /dev/null @@ -1,8 +0,0 @@ -import httplib -c = httplib.HTTPSConnection("example.com") - -import http.client -c = http.client.HTTPSConnection("example.com") - -import six -six.moves.http_client.HTTPSConnection("example.com") diff --git a/examples/httpoxy_cgihandler.py b/examples/httpoxy_cgihandler.py deleted file mode 100644 index aa9e165f..00000000 --- a/examples/httpoxy_cgihandler.py +++ /dev/null @@ -1,10 +0,0 @@ -import requests -import wsgiref.handlers - -def application(environ, start_response): - r = requests.get('https://192.168.0.42/private/api/foobar') - start_response('200 OK', [('Content-Type', 'text/plain')]) - return [r.content] - -if __name__ == '__main__': - wsgiref.handlers.CGIHandler().run(application) diff --git a/examples/httpoxy_twisted_directory.py b/examples/httpoxy_twisted_directory.py deleted file mode 100644 index 9850b55e..00000000 --- a/examples/httpoxy_twisted_directory.py +++ /dev/null @@ -1,7 +0,0 @@ -from twisted.internet import reactor -from twisted.web import static, server, twcgi - -root = static.File("/root") -root.putChild("cgi-bin", twcgi.CGIDirectory("/var/www/cgi-bin")) -reactor.listenTCP(80, server.Site(root)) -reactor.run() diff --git a/examples/httpoxy_twisted_script.py b/examples/httpoxy_twisted_script.py deleted file mode 100644 index 3de259cc..00000000 --- a/examples/httpoxy_twisted_script.py +++ /dev/null @@ -1,7 +0,0 @@ -from twisted.internet import reactor -from twisted.web import static, server, twcgi - -root = static.File("/root") -root.putChild("login.cgi", twcgi.CGIScript("/var/www/cgi-bin/login.py")) -reactor.listenTCP(80, server.Site(root)) -reactor.run() diff --git a/examples/imports-aliases.py b/examples/imports-aliases.py deleted file mode 100644 index 97d53422..00000000 --- a/examples/imports-aliases.py +++ /dev/null @@ -1,15 +0,0 @@ -from subprocess import Popen as pop -import hashlib as h -import hashlib as hh -import hashlib as hhh -import hashlib as hhhh -from pickle import loads as lp -import pickle as p - -pop('/bin/gcc --version', shell=True) - -h.md5('1') -hh.md5('2') -hhh.md5('3').hexdigest() -hhhh.md5('4') -lp({'key': 'value'}) diff --git a/examples/imports-from.py b/examples/imports-from.py deleted file mode 100644 index 422ed10c..00000000 --- a/examples/imports-from.py +++ /dev/null @@ -1,7 +0,0 @@ -from subprocess import Popen - -from ..foo import sys -from . import sys -from .. import sys -from .. import subprocess -from ..subprocess import Popen diff --git a/examples/imports-function.py b/examples/imports-function.py deleted file mode 100644 index bff8aa42..00000000 --- a/examples/imports-function.py +++ /dev/null @@ -1,12 +0,0 @@ -os = __import__("os") -pickle = __import__("pickle") -sys = __import__("sys") -subprocess = __import__("subprocess") - -# this has been reported in the wild, though it's invalid python -# see bug https://bugs.launchpad.net/bandit/+bug/1396333 -__import__() - -# TODO(??): bandit can not find this one unfortunately (no symbol tab) -a = 'subprocess' -__import__(a) diff --git a/examples/imports-with-importlib.py b/examples/imports-with-importlib.py deleted file mode 100644 index cc0bd659..00000000 --- a/examples/imports-with-importlib.py +++ /dev/null @@ -1,5 +0,0 @@ -import importlib -a = importlib.import_module('os') -b = importlib.import_module('pickle') -c = importlib.__import__('sys') -d = importlib.__import__('subprocess') diff --git a/examples/imports.py b/examples/imports.py deleted file mode 100644 index 6bfde58d..00000000 --- a/examples/imports.py +++ /dev/null @@ -1,4 +0,0 @@ -import os -import pickle -import sys -import subprocess diff --git a/examples/init-py-test/__init__.py b/examples/init-py-test/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/init-py-test/subdirectory-okay.py b/examples/init-py-test/subdirectory-okay.py deleted file mode 100644 index 8feea28a..00000000 --- a/examples/init-py-test/subdirectory-okay.py +++ /dev/null @@ -1,3 +0,0 @@ -# A sample test file in a subdirectory and its parents both containing -# an __init__.py file outlined in bug/1743042. -print('hopefully no vulnerabilities here') diff --git a/examples/input.py b/examples/input.py deleted file mode 100644 index 51a1f1e6..00000000 --- a/examples/input.py +++ /dev/null @@ -1 +0,0 @@ -input() diff --git a/examples/jinja2_templating.py b/examples/jinja2_templating.py deleted file mode 100644 index d5aaa2dd..00000000 --- a/examples/jinja2_templating.py +++ /dev/null @@ -1,26 +0,0 @@ -import jinja2 -from jinja2 import Environment, select_autoescape -templateLoader = jinja2.FileSystemLoader( searchpath="/" ) -something = '' - -Environment(loader=templateLoader, load=templateLoader, autoescape=True) -templateEnv = jinja2.Environment(autoescape=True, - loader=templateLoader ) -Environment(loader=templateLoader, load=templateLoader, autoescape=something) -templateEnv = jinja2.Environment(autoescape=False, loader=templateLoader ) -Environment(loader=templateLoader, - load=templateLoader, - autoescape=False) - -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/examples/mako_templating.py b/examples/mako_templating.py deleted file mode 100644 index 29dd38c9..00000000 --- a/examples/mako_templating.py +++ /dev/null @@ -1,11 +0,0 @@ -from mako.template import Template -import mako - -from mako import template - -Template("hello") - -# XXX(fletcher): for some reason, bandit is missing the one below. keeping it -# in for now so that if it gets fixed inadvertitently we know. -mako.template.Template("hern") -template.Template("hern") diff --git a/examples/mark_safe.py b/examples/mark_safe.py deleted file mode 100644 index f5abbd1a..00000000 --- a/examples/mark_safe.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.utils import safestring - -mystr = 'Hello World' -mystr = safestring.mark_safe(mystr) diff --git a/examples/marshal_deserialize.py b/examples/marshal_deserialize.py deleted file mode 100644 index 2b077d4b..00000000 --- a/examples/marshal_deserialize.py +++ /dev/null @@ -1,12 +0,0 @@ -import marshal -import tempfile - - -serialized = marshal.dumps({'a': 1}) -print(marshal.loads(serialized)) - -file_obj = tempfile.TemporaryFile() -marshal.dump(range(5), file_obj) -file_obj.seek(0) -print(marshal.load(file_obj)) -file_obj.close() diff --git a/examples/mktemp.py b/examples/mktemp.py deleted file mode 100644 index 9912ad42..00000000 --- a/examples/mktemp.py +++ /dev/null @@ -1,10 +0,0 @@ -from tempfile import mktemp -import tempfile.mktemp as mt -import tempfile as tmp - -foo = 'hi' - -mktemp(foo) -tempfile.mktemp('foo') -mt(foo) -tmp.mktemp(foo) diff --git a/examples/multiline_statement.py b/examples/multiline_statement.py deleted file mode 100644 index b463488d..00000000 --- a/examples/multiline_statement.py +++ /dev/null @@ -1,6 +0,0 @@ -import subprocess - -subprocess.check_output("/some_command", - "args", - shell=True, - universal_newlines=True) diff --git a/examples/new_candidates-all.py b/examples/new_candidates-all.py deleted file mode 100644 index 9d7bfc6f..00000000 --- a/examples/new_candidates-all.py +++ /dev/null @@ -1,24 +0,0 @@ -import xml -import yaml - -def subprocess_shell_cmd(): - # sample function with known subprocess shell cmd candidates - # candidate #1 - subprocess.Popen('/bin/ls *', shell=True) - # candidate #2 - subprocess.Popen('/bin/ls *', shell=True) # nosec - -def yaml_load(): - # sample function with known yaml.load candidates - temp_str = yaml.dump({'a': '1', 'b': '2'}) - # candidate #3 - y = yaml.load(temp_str) - # candidate #4 - y = yaml.load(temp_str) # nosec - -def xml_sax_make_parser(): - # sample function with known xml.sax.make_parser candidates - # candidate #5 - xml.sax.make_parser() - # candidate #6 - xml.sax.make_parser() # nosec diff --git a/examples/new_candidates-none.py b/examples/new_candidates-none.py deleted file mode 100644 index 1ab18099..00000000 --- a/examples/new_candidates-none.py +++ /dev/null @@ -1,8 +0,0 @@ -def subprocess_shell_cmd(): - # sample function with known subprocess shell cmd candidates - -def yaml_load(): - # sample function with known yaml.load candidates - -def xml_sax_make_parser(): - # sample function with known xml.sax.make_parser candidates diff --git a/examples/new_candidates-nosec.py b/examples/new_candidates-nosec.py deleted file mode 100644 index 2d9f659d..00000000 --- a/examples/new_candidates-nosec.py +++ /dev/null @@ -1,18 +0,0 @@ -import xml -import yaml - -def subprocess_shell_cmd(): - # sample function with known subprocess shell cmd candidates - # candidate #2 - subprocess.Popen('/bin/ls *', shell=True) # nosec - -def yaml_load(): - # sample function with known yaml.load candidates - temp_str = yaml.dump({'a': '1', 'b': '2'}) - # candidate #4 - y = yaml.load(temp_str) # nosec - -def xml_sax_make_parser(): - # sample function with known xml.sax.make_parser candidates - # candidate #6 - xml.sax.make_parser() # nosec diff --git a/examples/new_candidates-some.py b/examples/new_candidates-some.py deleted file mode 100644 index f1c3bab4..00000000 --- a/examples/new_candidates-some.py +++ /dev/null @@ -1,20 +0,0 @@ -import xml -import yaml - -def subprocess_shell_cmd(): - # sample function with known subprocess shell cmd candidates - # candidate #1 - subprocess.Popen('/bin/ls *', shell=True) - # candidate #2 - subprocess.Popen('/bin/ls *', shell=True) # nosec - -def yaml_load(): - # sample function with known yaml.load candidates - temp_str = yaml.dump({'a': '1', 'b': '2'}) - # candidate #4 - y = yaml.load(temp_str) # nosec - -def xml_sax_make_parser(): - # sample function with known xml.sax.make_parser candidates - # candidate #6 - xml.sax.make_parser() # nosec diff --git a/examples/nonsense.py b/examples/nonsense.py deleted file mode 100644 index 19cd1868..00000000 --- a/examples/nonsense.py +++ /dev/null @@ -1 +0,0 @@ -test(hi diff --git a/examples/nonsense2.py b/examples/nonsense2.py deleted file mode 100644 index e28fc4f4ecfc17c8bccab0b72adf1f8beee20f23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40 ucmb2|=HSqH#vRVUoR^wd#4y9|R}u#U0|NjAQVjk8 diff --git a/examples/nosec.py b/examples/nosec.py deleted file mode 100644 index fab06d28..00000000 --- a/examples/nosec.py +++ /dev/null @@ -1,5 +0,0 @@ -subprocess.Popen('/bin/ls *', shell=True) #nosec (on the line) -subprocess.Popen('/bin/ls *', #nosec (at the start of function call) - shell=True) -subprocess.Popen('/bin/ls *', - shell=True) #nosec (on the specific kwarg line) diff --git a/examples/okay.py b/examples/okay.py deleted file mode 100644 index 9951ff74..00000000 --- a/examples/okay.py +++ /dev/null @@ -1 +0,0 @@ -print('hopefully no vulnerabilities here') diff --git a/examples/os-chmod-py2.py b/examples/os-chmod-py2.py deleted file mode 100644 index 847512af..00000000 --- a/examples/os-chmod-py2.py +++ /dev/null @@ -1,17 +0,0 @@ -import os -import stat - -keyfile = 'foo' - -os.chmod('/etc/passwd', 0227) -os.chmod('/etc/passwd', 07) -os.chmod('/etc/passwd', 0664) -os.chmod('/etc/passwd', 0777) -os.chmod('/etc/passwd', 0o770) -os.chmod('/etc/passwd', 0o776) -os.chmod('/etc/passwd', 0o760) -os.chmod('~/.bashrc', 511) -os.chmod('/etc/hosts', 0o777) -os.chmod('/tmp/oh_hai', 0x1ff) -os.chmod('/etc/passwd', stat.S_IRWXU) -os.chmod(key_file, 0o777) diff --git a/examples/os-chmod-py3.py b/examples/os-chmod-py3.py deleted file mode 100644 index 65560f6a..00000000 --- a/examples/os-chmod-py3.py +++ /dev/null @@ -1,17 +0,0 @@ -import os -import stat - -keyfile = 'foo' - -os.chmod('/etc/passwd', 0o227) -os.chmod('/etc/passwd', 0o7) -os.chmod('/etc/passwd', 0o664) -os.chmod('/etc/passwd', 0o777) -os.chmod('/etc/passwd', 0o770) -os.chmod('/etc/passwd', 0o776) -os.chmod('/etc/passwd', 0o760) -os.chmod('~/.bashrc', 511) -os.chmod('/etc/hosts', 0o777) -os.chmod('/tmp/oh_hai', 0x1ff) -os.chmod('/etc/passwd', stat.S_IRWXU) -os.chmod(key_file, 0o777) diff --git a/examples/os-exec.py b/examples/os-exec.py deleted file mode 100644 index 8cbe3913..00000000 --- a/examples/os-exec.py +++ /dev/null @@ -1,11 +0,0 @@ -import os - -os.execl(path, arg0, arg1) -os.execle(path, arg0, arg1, env) -os.execlp(file, arg0, arg1) -os.execlpe(file, arg0, arg1, env) -os.execv(path, args) -os.execve(path, args, env) -os.execvp(file, args) -os.execvpe(file, args, env) - diff --git a/examples/os-popen.py b/examples/os-popen.py deleted file mode 100644 index a944d1cc..00000000 --- a/examples/os-popen.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -from os import popen -import os as o -from os import popen as pos - -os.popen('/bin/uname -av') -popen('/bin/uname -av') -o.popen('/bin/uname -av') -pos('/bin/uname -av') -os.popen2('/bin/uname -av') -os.popen3('/bin/uname -av') -os.popen4('/bin/uname -av') - -os.popen4('/bin/uname -av; rm -rf /') -os.popen4(some_var) diff --git a/examples/os-spawn.py b/examples/os-spawn.py deleted file mode 100644 index b6ccc0e6..00000000 --- a/examples/os-spawn.py +++ /dev/null @@ -1,10 +0,0 @@ -import os - -os.spawnl(mode, path) -os.spawnle(mode, path, env) -os.spawnlp(mode, file) -os.spawnlpe(mode, file, env) -os.spawnv(mode, path, args) -os.spawnve(mode, path, args, env) -os.spawnvp(mode, file, args) -os.spawnvpe(mode, file, args, env) diff --git a/examples/os-startfile.py b/examples/os-startfile.py deleted file mode 100644 index 4df534f1..00000000 --- a/examples/os-startfile.py +++ /dev/null @@ -1,5 +0,0 @@ -import os - -os.startfile('/bin/foo.docx') -os.startfile('/bin/bad.exe') -os.startfile('/bin/text.txt') diff --git a/examples/os_system.py b/examples/os_system.py deleted file mode 100644 index af8bf54c..00000000 --- a/examples/os_system.py +++ /dev/null @@ -1,3 +0,0 @@ -import os - -os.system('/bin/echo hi') diff --git a/examples/paramiko_injection.py b/examples/paramiko_injection.py deleted file mode 100644 index 6d303223..00000000 --- a/examples/paramiko_injection.py +++ /dev/null @@ -1,11 +0,0 @@ -import paramiko - -# this is not safe -paramiko.exec_command('something; really; unsafe') - -# this is safe -paramiko.connect('somehost') - -# this is not safe -SSHClient.invoke_shell('something; bad; here\n') - diff --git a/examples/partial_path_process.py b/examples/partial_path_process.py deleted file mode 100644 index 08d368dc..00000000 --- a/examples/partial_path_process.py +++ /dev/null @@ -1,13 +0,0 @@ -from subprocess import Popen as pop - -pop('gcc --version', shell=False) -pop('/bin/gcc --version', shell=False) -pop(var, shell=False) - -pop(['ls', '-l'], shell=False) -pop(['/bin/ls', '-l'], shell=False) - -pop('../ls -l', shell=False) - -pop('c:\hello\something', shell=False) -pop('c:/hello/something_else', shell=False) diff --git a/examples/pickle_deserialize.py b/examples/pickle_deserialize.py deleted file mode 100644 index 4c405bb6..00000000 --- a/examples/pickle_deserialize.py +++ /dev/null @@ -1,29 +0,0 @@ -import cPickle -import pickle -import StringIO - - -# pickle -pick = pickle.dumps({'a': 'b', 'c': 'd'}) -print(pickle.loads(pick)) - -file_obj = StringIO.StringIO() -pickle.dump([1, 2, '3'], file_obj) -file_obj.seek(0) -print(pickle.load(file_obj)) - -file_obj.seek(0) -print(pickle.Unpickler(file_obj).load()) - -# cPickle -serialized = cPickle.dumps({(): []}) -print(cPickle.loads(serialized)) - -file_obj = StringIO.StringIO() -cPickle.dump((1,), file_obj) -file_obj.seek(0) -print(cPickle.load(file_obj)) - -file_obj.seek(0) -print(cPickle.Unpickler(file_obj).load()) - diff --git a/examples/popen_wrappers.py b/examples/popen_wrappers.py deleted file mode 100644 index 5834cbd2..00000000 --- a/examples/popen_wrappers.py +++ /dev/null @@ -1,15 +0,0 @@ -import commands -import popen2 - - -print(commands.getstatusoutput('/bin/echo / | xargs ls')) -print(commands.getoutput('/bin/echo / | xargs ls')) - -# This one is safe. -print(commands.getstatus('/bin/echo / | xargs ls')) - -print(popen2.popen2('/bin/echo / | xargs ls')[0].read()) -print(popen2.popen3('/bin/echo / | xargs ls')[0].read()) -print(popen2.popen4('/bin/echo / | xargs ls')[0].read()) -print(popen2.Popen3('/bin/echo / | xargs ls').fromchild.read()) -print(popen2.Popen4('/bin/echo / | xargs ls').fromchild.read()) diff --git a/examples/pycrypto.py b/examples/pycrypto.py deleted file mode 100644 index fe8de076..00000000 --- a/examples/pycrypto.py +++ /dev/null @@ -1,11 +0,0 @@ -from Crypto.Cipher import AES -from Crypto import Random - -from . import CryptoMaterialsCacheEntry - - -def test_pycrypto(): - key = b'Sixteen byte key' - iv = Random.new().read(AES.block_size) - cipher = pycrypto_arc2.new(key, AES.MODE_CFB, iv) - factory = CryptoMaterialsCacheEntry() diff --git a/examples/random_module.py b/examples/random_module.py deleted file mode 100644 index ffdbb5be..00000000 --- a/examples/random_module.py +++ /dev/null @@ -1,16 +0,0 @@ -import random -import os -import somelib - -bad = random.random() -bad = random.randrange() -bad = random.randint() -bad = random.choice() -bad = random.uniform() -bad = random.triangular() - -good = os.urandom() -good = random.SystemRandom() - -unknown = random() -unknown = somelib.a.random() diff --git a/examples/requests-ssl-verify-disabled.py b/examples/requests-ssl-verify-disabled.py deleted file mode 100644 index c2f641ac..00000000 --- a/examples/requests-ssl-verify-disabled.py +++ /dev/null @@ -1,16 +0,0 @@ -import requests - -requests.get('https://gmail.com', verify=True) -requests.get('https://gmail.com', verify=False) -requests.post('https://gmail.com', verify=True) -requests.post('https://gmail.com', verify=False) -requests.put('https://gmail.com', verify=True) -requests.put('https://gmail.com', verify=False) -requests.delete('https://gmail.com', verify=True) -requests.delete('https://gmail.com', verify=False) -requests.patch('https://gmail.com', verify=True) -requests.patch('https://gmail.com', verify=False) -requests.options('https://gmail.com', verify=True) -requests.options('https://gmail.com', verify=False) -requests.head('https://gmail.com', verify=True) -requests.head('https://gmail.com', verify=False) diff --git a/examples/secret-config-option.py b/examples/secret-config-option.py deleted file mode 100644 index 029999c1..00000000 --- a/examples/secret-config-option.py +++ /dev/null @@ -1,28 +0,0 @@ -from oslo_config import cfg - - -# Correct -secret = True -opts = [ - cfg.StrOpt('admin_user', - help="User's name"), - cfg.StrOpt('admin_password', - secret=True, - help="User's password"), - cfg.StrOpt('nova_password', - secret=secret, - help="Nova user password"), -] - -# Incorrect: password not marked secret -ldap_opts = [ - cfg.StrOpt('ldap_user', - help="LDAP bind user name"), - cfg.StrOpt('ldap_password', - help="LDAP bind user password"), - cfg.StrOpt('ldap_password_attribute', - help="LDAP password attribute (default userPassword"), - cfg.StrOpt('user_password', - secret=False, - help="User password"), -] diff --git a/examples/skip.py b/examples/skip.py deleted file mode 100644 index 25494852..00000000 --- a/examples/skip.py +++ /dev/null @@ -1,7 +0,0 @@ -subprocess.call(["/bin/ls", "-l"]) -subprocess.call(["/bin/ls", "-l"]) #noqa -subprocess.call(["/bin/ls", "-l"]) # noqa -subprocess.call(["/bin/ls", "-l"]) # nosec -subprocess.call(["/bin/ls", "-l"]) -subprocess.call(["/bin/ls", "-l"]) #nosec -subprocess.call(["/bin/ls", "-l"]) diff --git a/examples/sql_statements.py b/examples/sql_statements.py deleted file mode 100644 index 1fabb709..00000000 --- a/examples/sql_statements.py +++ /dev/null @@ -1,39 +0,0 @@ -import sqlalchemy - -# bad -query = "SELECT * FROM foo WHERE id = '%s'" % identifier -query = "INSERT INTO foo VALUES ('a', 'b', '%s')" % value -query = "DELETE FROM foo WHERE id = '%s'" % identifier -query = "UPDATE foo SET value = 'b' WHERE id = '%s'" % identifier -query = """WITH cte AS (SELECT x FROM foo) -SELECT x FROM cte WHERE x = '%s'""" % identifier -# bad alternate forms -query = "SELECT * FROM foo WHERE id = '" + identifier + "'" -query = "SELECT * FROM foo WHERE id = '{}'".format(identifier) - -# bad -cur.execute("SELECT * FROM foo WHERE id = '%s'" % identifier) -cur.execute("INSERT INTO foo VALUES ('a', 'b', '%s')" % value) -cur.execute("DELETE FROM foo WHERE id = '%s'" % identifier) -cur.execute("UPDATE foo SET value = 'b' WHERE id = '%s'" % identifier) -# bad alternate forms -cur.execute("SELECT * FROM foo WHERE id = '" + identifier + "'") -cur.execute("SELECT * FROM foo WHERE id = '{}'".format(identifier)) - -# good -cur.execute("SELECT * FROM foo WHERE id = '%s'", identifier) -cur.execute("INSERT INTO foo VALUES ('a', 'b', '%s')", value) -cur.execute("DELETE FROM foo WHERE id = '%s'", identifier) -cur.execute("UPDATE foo SET value = 'b' WHERE id = '%s'", identifier) - -# bug: https://bugs.launchpad.net/bandit/+bug/1479625 -def a(): - def b(): - pass - return b - -a()("SELECT %s FROM foo" % val) - -# real world false positives -choices=[('server_list', _("Select from active instances"))] -print("delete from the cache as the first argument") diff --git a/examples/ssl-insecure-version.py b/examples/ssl-insecure-version.py deleted file mode 100644 index bd6f6591..00000000 --- a/examples/ssl-insecure-version.py +++ /dev/null @@ -1,36 +0,0 @@ -import ssl -from pyOpenSSL import SSL - -ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2) -SSL.Context(method=SSL.SSLv2_METHOD) -SSL.Context(method=SSL.SSLv23_METHOD) - -herp_derp(ssl_version=ssl.PROTOCOL_SSLv2) -herp_derp(method=SSL.SSLv2_METHOD) -herp_derp(method=SSL.SSLv23_METHOD) - -# strict tests -ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3) -ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1) -SSL.Context(method=SSL.SSLv3_METHOD) -SSL.Context(method=SSL.TLSv1_METHOD) - -herp_derp(ssl_version=ssl.PROTOCOL_SSLv3) -herp_derp(ssl_version=ssl.PROTOCOL_TLSv1) -herp_derp(method=SSL.SSLv3_METHOD) -herp_derp(method=SSL.TLSv1_METHOD) - -ssl.wrap_socket() - -def open_ssl_socket(version=ssl.PROTOCOL_SSLv2): - pass - -def open_ssl_socket(version=SSL.SSLv2_METHOD): - pass - -def open_ssl_socket(version=SSL.SSLv23_METHOD): - pass - -# this one will pass ok -def open_ssl_socket(version=SSL.TLSv1_1_METHOD): - pass diff --git a/examples/subprocess_shell.py b/examples/subprocess_shell.py deleted file mode 100644 index d0bd3cc9..00000000 --- a/examples/subprocess_shell.py +++ /dev/null @@ -1,33 +0,0 @@ -import subprocess -from subprocess import Popen as pop - - -def Popen(*args, **kwargs): - print('hi') - -pop('/bin/gcc --version', shell=True) -Popen('/bin/gcc --version', shell=True) - -subprocess.Popen('/bin/gcc --version', shell=True) -subprocess.Popen(['/bin/gcc', '--version'], shell=False) -subprocess.Popen(['/bin/gcc', '--version']) - -subprocess.call(["/bin/ls", - "-l" - ]) -subprocess.call('/bin/ls -l', shell=True) - -subprocess.check_call(['/bin/ls', '-l'], shell=False) -subprocess.check_call('/bin/ls -l', shell=True) - -subprocess.check_output(['/bin/ls', '-l']) -subprocess.check_output('/bin/ls -l', shell=True) - -subprocess.Popen('/bin/ls *', shell=True) -subprocess.Popen('/bin/ls %s' % ('something',), shell=True) -subprocess.Popen('/bin/ls {}'.format('something'), shell=True) - -command = "/bin/ls" + unknown_function() -subprocess.Popen(command, shell=True) - -subprocess.Popen('/bin/ls && cat /etc/passwd', shell=True) diff --git a/examples/telnetlib.py b/examples/telnetlib.py deleted file mode 100644 index 3b06e8d9..00000000 --- a/examples/telnetlib.py +++ /dev/null @@ -1,19 +0,0 @@ -import telnetlib -import getpass - -host = sys.argv[1] - -username = raw_input('Username:') -password = getpass.getpass() -tn = telnetlib.Telnet(host) - -tn.read_until("login: ") -tn.write(username + "\n") -if password: - tn.read_until("Password: ") - tn.write(password + "\n") - -tn.write("ls\n") -tn.write("exit\n") - -print(tn.read_all()) diff --git a/examples/try_except_continue.py b/examples/try_except_continue.py deleted file mode 100644 index 51c232c3..00000000 --- a/examples/try_except_continue.py +++ /dev/null @@ -1,32 +0,0 @@ -# bad -for i in {0,1}: - try: - a = i - except: - continue - - -# bad -while keep_trying: - try: - a = 1 - except Exception: - continue - - -# bad -for i in {0,2}: - try: - a = i - except ZeroDivisionError: - continue - except: - a = 2 - - -# good -while keep_trying: - try: - a = 1 - except: - a = 2 diff --git a/examples/try_except_pass.py b/examples/try_except_pass.py deleted file mode 100644 index 2ebda558..00000000 --- a/examples/try_except_pass.py +++ /dev/null @@ -1,36 +0,0 @@ -# bad -try: - a = 1 -except: - pass - - -# bad -try: - a = 1 -except Exception: - pass - - -# bad -try: - a = 1 -except ZeroDivisionError: - pass -except: - a = 2 - - -# good -try: - a = 1 -except: - a = 2 - - -# silly, but ok -try: - a = 1 -except: - pass - a = 2 diff --git a/examples/unverified_context.py b/examples/unverified_context.py deleted file mode 100644 index 0f454395..00000000 --- a/examples/unverified_context.py +++ /dev/null @@ -1,7 +0,0 @@ -import ssl - -# Correct -context = ssl.create_default_context() - -# Incorrect: unverified context -context = ssl._create_unverified_context() diff --git a/examples/urlopen.py b/examples/urlopen.py deleted file mode 100644 index 9445d93b..00000000 --- a/examples/urlopen.py +++ /dev/null @@ -1,59 +0,0 @@ -''' Example dangerous usage of urllib[2] opener functions - -The urllib and urllib2 opener functions and object can open http, ftp, -and file urls. Often, the ability to open file urls is overlooked leading -to code that can unexpectedly open files on the local server. This -could be used by an attacker to leak information about the server. -''' - - -import urllib -import urllib2 - -# Python 3 -import urllib.request - -# Six -import six - -def test_urlopen(): - # urllib - url = urllib.quote('file:///bin/ls') - urllib.urlopen(url, 'blah', 32) - urllib.urlretrieve('file:///bin/ls', '/bin/ls2') - opener = urllib.URLopener() - opener.open('file:///bin/ls') - opener.retrieve('file:///bin/ls') - opener = urllib.FancyURLopener() - opener.open('file:///bin/ls') - opener.retrieve('file:///bin/ls') - - # urllib2 - handler = urllib2.HTTPBasicAuthHandler() - handler.add_password(realm='test', - uri='http://mysite.com', - user='bob') - opener = urllib2.build_opener(handler) - urllib2.install_opener(opener) - urllib2.urlopen('file:///bin/ls') - urllib2.Request('file:///bin/ls') - - # Python 3 - urllib.request.urlopen('file:///bin/ls') - urllib.request.urlretrieve('file:///bin/ls', '/bin/ls2') - opener = urllib.request.URLopener() - opener.open('file:///bin/ls') - opener.retrieve('file:///bin/ls') - opener = urllib.request.FancyURLopener() - opener.open('file:///bin/ls') - opener.retrieve('file:///bin/ls') - - # Six - six.moves.urllib.request.urlopen('file:///bin/ls') - six.moves.urllib.request.urlretrieve('file:///bin/ls', '/bin/ls2') - opener = six.moves.urllib.request.URLopener() - opener.open('file:///bin/ls') - opener.retrieve('file:///bin/ls') - opener = six.moves.urllib.request.FancyURLopener() - opener.open('file:///bin/ls') - opener.retrieve('file:///bin/ls') diff --git a/examples/utils-shell.py b/examples/utils-shell.py deleted file mode 100644 index de157065..00000000 --- a/examples/utils-shell.py +++ /dev/null @@ -1,8 +0,0 @@ -import utils -import utils as u - -u.execute('/bin/gcc --version', shell=True) -utils.execute('/bin/gcc --version', shell=True) -u.execute_with_timeout('/bin/gcc --version', shell=True) -utils.execute_with_timeout('/bin/gcc --version', shell=True) -utils.execute_with_timeout(['/bin/gcc', '--version'], shell=False) diff --git a/examples/weak_cryptographic_key_sizes.py b/examples/weak_cryptographic_key_sizes.py deleted file mode 100644 index f2443b51..00000000 --- a/examples/weak_cryptographic_key_sizes.py +++ /dev/null @@ -1,66 +0,0 @@ -from cryptography.hazmat import backends -from cryptography.hazmat.primitives.asymmetric import dsa -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric import rsa -from Crypto.PublicKey import DSA as pycrypto_dsa -from Crypto.PublicKey import RSA as pycrypto_rsa -from Cryptodome.PublicKey import DSA as pycryptodomex_dsa -from Cryptodome.PublicKey import RSA as pycryptodomex_rsa - - -# Correct -dsa.generate_private_key(key_size=2048, - backend=backends.default_backend()) -ec.generate_private_key(curve=ec.SECP384R1, - backend=backends.default_backend()) -rsa.generate_private_key(public_exponent=65537, - key_size=2048, - backend=backends.default_backend()) -pycrypto_dsa.generate(bits=2048) -pycrypto_rsa.generate(bits=2048) -pycryptodomex_dsa.generate(bits=2048) -pycryptodomex_rsa.generate(bits=2048) - -# Also correct: without keyword args -dsa.generate_private_key(4096, - backends.default_backend()) -ec.generate_private_key(ec.SECP256K1, - backends.default_backend()) -rsa.generate_private_key(3, - 4096, - backends.default_backend()) -pycrypto_dsa.generate(4096) -pycrypto_rsa.generate(4096) -pycryptodomex_dsa.generate(4096) -pycryptodomex_rsa.generate(4096) - -# Incorrect: weak key sizes -dsa.generate_private_key(key_size=1024, - backend=backends.default_backend()) -ec.generate_private_key(curve=ec.SECT163R2, - backend=backends.default_backend()) -rsa.generate_private_key(public_exponent=65537, - key_size=1024, - backend=backends.default_backend()) -pycrypto_dsa.generate(bits=1024) -pycrypto_rsa.generate(bits=1024) -pycryptodomex_dsa.generate(bits=1024) -pycryptodomex_rsa.generate(bits=1024) - -# Also incorrect: without keyword args -dsa.generate_private_key(512, - backends.default_backend()) -ec.generate_private_key(ec.SECT163R2, - backends.default_backend()) -rsa.generate_private_key(3, - 512, - backends.default_backend()) -pycrypto_dsa.generate(512) -pycrypto_rsa.generate(512) -pycryptodomex_dsa.generate(512) -pycryptodomex_rsa.generate(512) - -# Don't crash when the size is variable -rsa.generate_private_key(public_exponent=65537, - key_size=some_key_size, - backend=backends.default_backend()) diff --git a/examples/wildcard-injection.py b/examples/wildcard-injection.py deleted file mode 100644 index 352c1715..00000000 --- a/examples/wildcard-injection.py +++ /dev/null @@ -1,16 +0,0 @@ -import os as o -import subprocess as subp - -# Vulnerable to wildcard injection -o.system("/bin/tar xvzf *") -o.system('/bin/chown *') -o.popen2('/bin/chmod *') -subp.Popen('/bin/chown *', shell=True) - -# Not vulnerable to wildcard injection -subp.Popen('/bin/rsync *') -subp.Popen("/bin/chmod *") -subp.Popen(['/bin/chown', '*']) -subp.Popen(["/bin/chmod", sys.argv[1], "*"], - stdin=subprocess.PIPE, stdout=subprocess.PIPE) -o.spawnvp(os.P_WAIT, 'tar', ['tar', 'xvzf', '*']) diff --git a/examples/xml_etree_celementtree.py b/examples/xml_etree_celementtree.py deleted file mode 100644 index d3fc3828..00000000 --- a/examples/xml_etree_celementtree.py +++ /dev/null @@ -1,18 +0,0 @@ -import xml.etree.cElementTree as badET -import defusedxml.cElementTree as goodET - -xmlString = "\nTove\nJani\nReminder\nDon't forget me this weekend!\n" - -# unsafe -tree = badET.fromstring(xmlString) -print(tree) -badET.parse('filethatdoesntexist.xml') -badET.iterparse('filethatdoesntexist.xml') -a = badET.XMLParser() - -# safe -tree = goodET.fromstring(xmlString) -print(tree) -goodET.parse('filethatdoesntexist.xml') -goodET.iterparse('filethatdoesntexist.xml') -a = goodET.XMLParser() diff --git a/examples/xml_etree_elementtree.py b/examples/xml_etree_elementtree.py deleted file mode 100644 index f7a37759..00000000 --- a/examples/xml_etree_elementtree.py +++ /dev/null @@ -1,18 +0,0 @@ -import xml.etree.ElementTree as badET -import defusedxml.ElementTree as goodET - -xmlString = "\nTove\nJani\nReminder\nDon't forget me this weekend!\n" - -# unsafe -tree = badET.fromstring(xmlString) -print(tree) -badET.parse('filethatdoesntexist.xml') -badET.iterparse('filethatdoesntexist.xml') -a = badET.XMLParser() - -# safe -tree = goodET.fromstring(xmlString) -print(tree) -goodET.parse('filethatdoesntexist.xml') -goodET.iterparse('filethatdoesntexist.xml') -a = goodET.XMLParser() diff --git a/examples/xml_expatbuilder.py b/examples/xml_expatbuilder.py deleted file mode 100644 index 1deb8575..00000000 --- a/examples/xml_expatbuilder.py +++ /dev/null @@ -1,10 +0,0 @@ -import xml.dom.expatbuilder as bad -import defusedxml.expatbuilder as good - -bad.parse('filethatdoesntexist.xml') -good.parse('filethatdoesntexist.xml') - -xmlString = "\nTove\nJani\nReminder\nDon't forget me this weekend!\n" - -bad.parseString(xmlString) -good.parseString(xmlString) diff --git a/examples/xml_expatreader.py b/examples/xml_expatreader.py deleted file mode 100644 index 12e6f965..00000000 --- a/examples/xml_expatreader.py +++ /dev/null @@ -1,5 +0,0 @@ -import xml.sax.expatreader as bad -import defusedxml.expatreader as good - -p = bad.create_parser() -b = good.create_parser() diff --git a/examples/xml_lxml.py b/examples/xml_lxml.py deleted file mode 100644 index dd12e538..00000000 --- a/examples/xml_lxml.py +++ /dev/null @@ -1,9 +0,0 @@ -import lxml.etree -import lxml -from lxml import etree -from defusedxml.lxml import fromstring -from defuxedxml import lxml as potatoe - -xmlString = "\nTove\nJani\nReminder\nDon't forget me this weekend!\n" -root = lxml.etree.fromstring(xmlString) -root = fromstring(xmlString) diff --git a/examples/xml_minidom.py b/examples/xml_minidom.py deleted file mode 100644 index 40e7aea2..00000000 --- a/examples/xml_minidom.py +++ /dev/null @@ -1,14 +0,0 @@ -from xml.dom.minidom import parseString as badParseString -from defusedxml.minidom import parseString as goodParseString -a = badParseString("Some data some more data") -print(a) -b = goodParseString("Some data some more data") -print(b) - - -from xml.dom.minidom import parse as badParse -from defusedxml.minidom import parse as goodParse -a = badParse("somfilethatdoesntexist.xml") -print(a) -b = goodParse("somefilethatdoesntexist.xml") -print(b) diff --git a/examples/xml_pulldom.py b/examples/xml_pulldom.py deleted file mode 100644 index d310115a..00000000 --- a/examples/xml_pulldom.py +++ /dev/null @@ -1,14 +0,0 @@ -from xml.dom.pulldom import parseString as badParseString -from defusedxml.pulldom import parseString as goodParseString -a = badParseString("Some data some more data") -print(a) -b = goodParseString("Some data some more data") -print(b) - - -from xml.dom.pulldom import parse as badParse -from defusedxml.pulldom import parse as goodParse -a = badParse("somfilethatdoesntexist.xml") -print(a) -b = goodParse("somefilethatdoesntexist.xml") -print(b) diff --git a/examples/xml_sax.py b/examples/xml_sax.py deleted file mode 100644 index d12eeb67..00000000 --- a/examples/xml_sax.py +++ /dev/null @@ -1,37 +0,0 @@ -import xml.sax -from xml import sax -import defusedxml.sax - -class ExampleContentHandler(xml.sax.ContentHandler): - def __init__(self): - xml.sax.ContentHandler.__init__(self) - - def startElement(self, name, attrs): - print('start:', name) - - def endElement(self, name): - print('end:', name) - - def characters(self, content): - print('chars:', content) - -def main(): - xmlString = "\nTove\nJani\nReminder\nDon't forget me this weekend!\n" - # bad - xml.sax.parseString(xmlString, ExampleContentHandler()) - xml.sax.parse('notaxmlfilethatexists.xml', ExampleContentHandler()) - sax.parseString(xmlString, ExampleContentHandler()) - sax.parse('notaxmlfilethatexists.xml', ExampleContentHandler) - - # good - defusedxml.sax.parseString(xmlString, ExampleContentHandler()) - - # bad - xml.sax.make_parser() - sax.make_parser() - print('nothing') - # good - defusedxml.sax.make_parser() - -if __name__ == "__main__": - main() diff --git a/examples/xml_xmlrpc.py b/examples/xml_xmlrpc.py deleted file mode 100644 index d60e8c9b..00000000 --- a/examples/xml_xmlrpc.py +++ /dev/null @@ -1,10 +0,0 @@ -import xmlrpclib -from SimpleXMLRPCServer import SimpleXMLRPCServer - -def is_even(n): - return n%2 == 0 - -server = SimpleXMLRPCServer(("localhost", 8000)) -print("Listening on port 8000...") -server.register_function(is_even, "is_even") -server.serve_forever() diff --git a/examples/yaml_load.py b/examples/yaml_load.py deleted file mode 100644 index c14be339..00000000 --- a/examples/yaml_load.py +++ /dev/null @@ -1,12 +0,0 @@ -import json -import yaml - -def test_yaml_load(): - ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3}) - y = yaml.load(ystr) - yaml.dump(y) - y = yaml.load(ystr, Loader=yaml.SafeLoader) - -def test_json_load(): - # no issue should be found - j = json.load("{}") diff --git a/lower-constraints.txt b/lower-constraints.txt deleted file mode 100644 index ff6f92df..00000000 --- a/lower-constraints.txt +++ /dev/null @@ -1,38 +0,0 @@ -appdirs==1.3.0 -astroid==1.3.8 -beautifulsoup4==4.6.0 -coverage==4.0 -extras==1.0.0 -fixtures==3.0.0 -flake8==2.5.5 -future==0.16.0 -gitdb==0.6.4 -GitPython==1.0.1 -hacking==1.0.0 -iso8601==0.1.11 -keystoneauth1==3.4.0 -linecache2==1.0.0 -logilab-common==1.4.1 -mccabe==0.2.1 -mock==2.0.0 -mox3==0.20.0 -os-client-config==1.28.0 -oslotest==3.2.0 -pbr==2.0.0 -pep8==1.5.7 -pyflakes==0.8.1 -pylint==1.4.5 -python-mimeparse==1.6.0 -python-subunit==1.0.0 -PyYAML==3.12 -requests==2.14.2 -requestsexceptions==1.2.0 -six==1.10.0 -smmap==0.9.0 -stestr==1.0.0 -stevedore==1.20.0 -testrepository==0.0.18 -testscenarios==0.4 -testtools==2.2.0 -traceback2==1.4.0 -unittest2==1.1.0 diff --git a/playbooks/legacy/bandit-integration-barbican/run.yaml b/playbooks/legacy/bandit-integration-barbican/run.yaml deleted file mode 100644 index 366bb9df..00000000 --- a/playbooks/legacy/bandit-integration-barbican/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-barbican from old job gate-bandit-integration-barbican - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/barbican - cd $WORKSPACE/openstack/bandit - tox -e integration openstack barbican \ - $WORKSPACE/openstack/barbican - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-glance/run.yaml b/playbooks/legacy/bandit-integration-glance/run.yaml deleted file mode 100644 index 2883cab5..00000000 --- a/playbooks/legacy/bandit-integration-glance/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-glance from old job gate-bandit-integration-glance - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/glance - cd $WORKSPACE/openstack/bandit - tox -e integration openstack glance \ - $WORKSPACE/openstack/glance - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-glance_store/run.yaml b/playbooks/legacy/bandit-integration-glance_store/run.yaml deleted file mode 100644 index c28c7339..00000000 --- a/playbooks/legacy/bandit-integration-glance_store/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-glance_store from old job gate-bandit-integration-glance_store - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/glance_store - cd $WORKSPACE/openstack/bandit - tox -e integration openstack glance_store \ - $WORKSPACE/openstack/glance_store - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-keystone/run.yaml b/playbooks/legacy/bandit-integration-keystone/run.yaml deleted file mode 100644 index a2fe73f6..00000000 --- a/playbooks/legacy/bandit-integration-keystone/run.yaml +++ /dev/null @@ -1,29 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-keystone from old job gate-bandit-integration-keystone - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/keystone - cd $WORKSPACE/openstack/bandit - tox -e integration openstack keystone \ - $WORKSPACE/openstack/keystone - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - diff --git a/playbooks/legacy/bandit-integration-keystonemiddleware/run.yaml b/playbooks/legacy/bandit-integration-keystonemiddleware/run.yaml deleted file mode 100644 index 3247d8cb..00000000 --- a/playbooks/legacy/bandit-integration-keystonemiddleware/run.yaml +++ /dev/null @@ -1,29 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-keystonemiddleware from old job - gate-bandit-integration-keystonemiddleware - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/keystonemiddleware - cd $WORKSPACE/openstack/bandit - tox -e integration openstack keystonemiddleware \ - $WORKSPACE/openstack/keystonemiddleware - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-magnum/run.yaml b/playbooks/legacy/bandit-integration-magnum/run.yaml deleted file mode 100644 index e4e759ff..00000000 --- a/playbooks/legacy/bandit-integration-magnum/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-magnum from old job gate-bandit-integration-magnum - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/magnum - cd $WORKSPACE/openstack/bandit - tox -e integration openstack magnum \ - $WORKSPACE/openstack/magnum - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-oslo.config/run.yaml b/playbooks/legacy/bandit-integration-oslo.config/run.yaml deleted file mode 100644 index ab4265ad..00000000 --- a/playbooks/legacy/bandit-integration-oslo.config/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-oslo.config from old job gate-bandit-integration-oslo.config - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/oslo.config - cd $WORKSPACE/openstack/bandit - tox -e integration openstack oslo.config \ - $WORKSPACE/openstack/oslo.config - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-oslo.log/run.yaml b/playbooks/legacy/bandit-integration-oslo.log/run.yaml deleted file mode 100644 index f2d166de..00000000 --- a/playbooks/legacy/bandit-integration-oslo.log/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-oslo.log from old job gate-bandit-integration-oslo.log - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/oslo.log - cd $WORKSPACE/openstack/bandit - tox -e integration openstack oslo.log \ - $WORKSPACE/openstack/oslo.log - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-oslo.service/run.yaml b/playbooks/legacy/bandit-integration-oslo.service/run.yaml deleted file mode 100644 index 6e39a505..00000000 --- a/playbooks/legacy/bandit-integration-oslo.service/run.yaml +++ /dev/null @@ -1,29 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-oslo.service from old job gate-bandit-integration-oslo.service - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/oslo.service - cd $WORKSPACE/openstack/bandit - tox -e integration openstack oslo.service \ - $WORKSPACE/openstack/oslo.service - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - diff --git a/playbooks/legacy/bandit-integration-oslo.utils/run.yaml b/playbooks/legacy/bandit-integration-oslo.utils/run.yaml deleted file mode 100644 index ff4bb5f4..00000000 --- a/playbooks/legacy/bandit-integration-oslo.utils/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-oslo.utils from old job gate-bandit-integration-oslo.utils - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/oslo.utils - cd $WORKSPACE/openstack/bandit - tox -e integration openstack oslo.utils \ - $WORKSPACE/openstack/oslo.utils - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-oslo.vmware/run.yaml b/playbooks/legacy/bandit-integration-oslo.vmware/run.yaml deleted file mode 100644 index ede34274..00000000 --- a/playbooks/legacy/bandit-integration-oslo.vmware/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-oslo.vmware from old job gate-bandit-integration-oslo.vmware - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/oslo.vmware - cd $WORKSPACE/openstack/bandit - tox -e integration openstack oslo.vmware \ - $WORKSPACE/openstack/oslo.vmware - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-python-keystoneclient/run.yaml b/playbooks/legacy/bandit-integration-python-keystoneclient/run.yaml deleted file mode 100644 index adc6ef83..00000000 --- a/playbooks/legacy/bandit-integration-python-keystoneclient/run.yaml +++ /dev/null @@ -1,29 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-python-keystoneclient from old - job gate-bandit-integration-python-keystoneclient - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/python-keystoneclient - cd $WORKSPACE/openstack/bandit - tox -e integration openstack python-keystoneclient \ - $WORKSPACE/openstack/python-keystoneclient - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-python-magnumclient/run.yaml b/playbooks/legacy/bandit-integration-python-magnumclient/run.yaml deleted file mode 100644 index ed849f5c..00000000 --- a/playbooks/legacy/bandit-integration-python-magnumclient/run.yaml +++ /dev/null @@ -1,29 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-python-magnumclient from old job - gate-bandit-integration-python-magnumclient - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/python-magnumclient - cd $WORKSPACE/openstack/bandit - tox -e integration openstack python-magnumclient \ - $WORKSPACE/openstack/python-magnumclient - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/legacy/bandit-integration-sahara/run.yaml b/playbooks/legacy/bandit-integration-sahara/run.yaml deleted file mode 100644 index ebd415d1..00000000 --- a/playbooks/legacy/bandit-integration-sahara/run.yaml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-bandit-integration-sahara from old job gate-bandit-integration-sahara - roles: - - bindep - - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -u - set -e - set -x - cd $WORKSPACE - /usr/zuul-env/bin/zuul-cloner --cache-dir /opt/git \ - git://git.openstack.org \ - openstack/bandit \ - openstack/sahara - cd $WORKSPACE/openstack/bandit - tox -e integration openstack sahara \ - $WORKSPACE/openstack/sahara - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/pylintrc b/pylintrc deleted file mode 100644 index 366b29a7..00000000 --- a/pylintrc +++ /dev/null @@ -1,73 +0,0 @@ -# The format of this file isn't really documented; just use --generate-rcfile - -[Messages Control] -# C0111: Don't require docstrings on every method -# C0301: Handled by pep8 -# C0325: Parens are required on print in py3x -# F0401: Imports are check by other linters -# W0511: TODOs in code comments are fine. -# W0142: *args and **kwargs are fine. -# W0622: Redefining id is fine. - -# TODO(browne): fix these in the future -# C0103: invalid-name -# E1101: no-member -# R0204: redefined-variable-type -# R0902: too-many-instance-attributes -# R0912: too-many-branches -# R0913: too-many-arguments -# R0914: too-many-locals -# R0915: too-many-statements -# W0110: deprecated-lambda -# W0141: bad-builtin -# W0201: attribute-defined-outside-init -# W0212: protected-access -# W0401: wildcard-import -# W0603: global-statement -# W0612: unused-variable -# W0613: unused-argument -# W0621: redefined-outer-name -# W0703: broad-except -disable=C0111,C0301,C0325,F0401,W0511,W0142,W0622,C0103,E1101,R0204,R0902,R0912,R0913,R0914,R0915,W0110,W0141,W0201,W0401,W0603,W0212,W0612,W0613,W0621,W0703 - -[Basic] -# Variable names can be 1 to 31 characters long, with lowercase and underscores -variable-rgx=[a-z_][a-z0-9_]{0,30}$ - -# Argument names can be 2 to 31 characters long, with lowercase and underscores -argument-rgx=[a-z_][a-z0-9_]{1,30}$ - -# Method names should be at least 3 characters long -# and be lowecased with underscores -method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$ - -# Module names matching manila-* are ok (files in bin/) -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(manila-[a-z0-9_-]+))$ - -# Don't require docstrings on tests. -no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$ - -[Design] -max-public-methods=100 -min-public-methods=0 -max-args=6 - -[Variables] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -# _ is used by our localization -additional-builtins=_ - -[Similarities] -# Minimum lines number of a similarity. -min-similarity-lines=10 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes diff --git a/releasenotes/notes/add-pycrypto-warn-c430f40f1d0fb44a.yaml b/releasenotes/notes/add-pycrypto-warn-c430f40f1d0fb44a.yaml deleted file mode 100644 index cc4166cb..00000000 --- a/releasenotes/notes/add-pycrypto-warn-c430f40f1d0fb44a.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - | - PyCrypto library is no longer actively maintained and should be replaced - with ``cryptography`` library. A new rule is added to detect and warn the - import and use of ``pycrypto`` module. diff --git a/releasenotes/notes/add-url-in-json-64f90161ab613a54.yaml b/releasenotes/notes/add-url-in-json-64f90161ab613a54.yaml deleted file mode 100644 index 7e8261c5..00000000 --- a/releasenotes/notes/add-url-in-json-64f90161ab613a54.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - | - [bug/1695890] The ``more_info`` URL link displayed in the HTML output is - now also available in the JSON output. diff --git a/releasenotes/notes/add-url-in-yaml-0bfdcc93f5b6d118.yaml b/releasenotes/notes/add-url-in-yaml-0bfdcc93f5b6d118.yaml deleted file mode 100644 index e402b972..00000000 --- a/releasenotes/notes/add-url-in-yaml-0bfdcc93f5b6d118.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - | - [bug/1746827] The ``more_info`` URL link displayed in the HTML and JSON - outputs is now also available in the YAML output. diff --git a/releasenotes/notes/add_reno-b8585fc3ffe775cb.yaml b/releasenotes/notes/add_reno-b8585fc3ffe775cb.yaml deleted file mode 100644 index 54d5cf8b..00000000 --- a/releasenotes/notes/add_reno-b8585fc3ffe775cb.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -other: - - Switch to reno for managing release notes. diff --git a/releasenotes/notes/target-in-ini-81802418b1cc970f.yaml b/releasenotes/notes/target-in-ini-81802418b1cc970f.yaml deleted file mode 100644 index 0fe31257..00000000 --- a/releasenotes/notes/target-in-ini-81802418b1cc970f.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - | - The 'targets' CLI arguments are now optional and can be specified in the - ini file. diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder deleted file mode 100644 index e69de29b..00000000 diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder deleted file mode 100644 index e69de29b..00000000 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py deleted file mode 100644 index 834b07dc..00000000 --- a/releasenotes/source/conf.py +++ /dev/null @@ -1,272 +0,0 @@ -# -*- 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. - -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'oslosphinx', - 'reno.sphinxext', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Bandit Release Notes' -copyright = u'2016, Bandit Developers' - -# Release notes do not need a version number in the title, they -# cover multiple releases. -# The full version, including alpha/beta/rc tags. -release = '' -# The short X.Y version. -version = '' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'banditReleaseNotesDoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'banditReleaseNotes.tex', - u'Bandit Release Notes Documentation', - u'Bandit Developers', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'banditReleaseNotes', - u'Bandit Release Notes Documentation', - [u'Bandit Developers'], 1) -] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'banditReleaseNotes', - u'Bandit Release Notes Documentation', - u'Bandit Developers', 'banditReleaseNotes', - 'Python source code security analyzer', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False - -# -- Options for Internationalization output ------------------------------ -locale_dirs = ['locale/'] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst deleted file mode 100644 index 7b443aac..00000000 --- a/releasenotes/source/index.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. - 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 -==================== - - .. toctree:: - :maxdepth: 1 - - unreleased diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst deleted file mode 100644 index 5860a469..00000000 --- a/releasenotes/source/unreleased.rst +++ /dev/null @@ -1,5 +0,0 @@ -========================== - Unreleased Release Notes -========================== - -.. release-notes:: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ab987fc9..00000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# 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. -GitPython>=1.0.1 # BSD License (3 clause) -PyYAML>=3.12 # MIT -six>=1.10.0 # MIT -stevedore>=1.20.0 # Apache-2.0 diff --git a/scripts/integration-test.sh b/scripts/integration-test.sh deleted file mode 100644 index 8435b007..00000000 --- a/scripts/integration-test.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -# Usage: integration-test.sh {organization} {project} {path-to-clone} -# Example usage: -# $ integration-test.sh openstack barbican -# $ integration-test.sh openstack keystone -# $ integration-test.sh openstack keystonemiddleware -# $ integration-test.sh openstack sahara -# $ integration-test.sh openstack python-keystoneclient \ -# /opt/openstack/python-keystoneclient -set -x -set -e - -if [[ $# -lt 2 ]]; then - echo "Script requires at least two arguments to run." - echo "Usage: $0 organization project [path-to-clone]" - exit 1 -fi - -REPO_ROOT=${REPO_ROOT:-git://git.openstack.org} -org=$1 -project=$2 - -if [[ $# -eq 3 ]] ; then - projectdir=$3 - clone=0 -else - projectdir=$project - clone=1 -fi - -workdir="$(pwd)" - -if [[ $clone -eq 1 ]] ; then - tempdir="$(mktemp -d)" - trap "rm -rf $tempdir" EXIT - - pushd $tempdir - git clone $REPO_ROOT/$org/$project --depth=1 -fi - -pushd $projectdir - # --notest allows us to create the tox-managed virtualenv without - # running any tests. - tox -e bandit --notest - # We then install our local version of bandit into the virtualenv - .tox/bandit/bin/pip install --force-reinstall -U $workdir - # And now we actually run the tests - tox -e bandit -popd - -if [[ $clone -eq 1 ]] ; then - popd -fi diff --git a/scripts/main.py b/scripts/main.py deleted file mode 100644 index 8c686924..00000000 --- a/scripts/main.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -# -*- 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. - - -from bandit import bandit - -if __name__ == '__main__': - bandit.main() diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index ddd7b9ae..00000000 --- a/setup.cfg +++ /dev/null @@ -1,128 +0,0 @@ -[metadata] -name = bandit -summary = Security oriented static analyser for python code. -description-file = - README.rst -author = OpenStack Security Group -author-email = openstack-dev@lists.openstack.org -home-page = https://wiki.openstack.org/wiki/Security/Projects/Bandit -classifier = - Environment :: OpenStack - Intended Audience :: Information Technology - Intended Audience :: System Administrators - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux - Operating System :: MacOS :: MacOS X - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - Topic :: Security - -[entry_points] -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 - txt = bandit.formatters.text:report - xml = bandit.formatters.xml:report - 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 - - # bandit/plugins/asserts.py - assert_used = bandit.plugins.asserts:assert_used - - # 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 - - # bandit/plugins/exec_as_root.py - execute_with_run_as_root_equals_true = bandit.plugins.exec_as_root:execute_with_run_as_root_equals_true - - # bandit/plugins/exec.py - exec_used = bandit.plugins.exec:exec_used - - # bandit/plugins/general_bad_File_permissions.py - set_bad_file_permissions = bandit.plugins.general_bad_file_permissions:set_bad_file_permissions - - # bandit/plugins/general_bind_all_interfaces.py - hardcoded_bind_all_interfaces = bandit.plugins.general_bind_all_interfaces:hardcoded_bind_all_interfaces - - # bandit/plugins/general_hardcoded_password.py - hardcoded_password_string = bandit.plugins.general_hardcoded_password:hardcoded_password_string - hardcoded_password_funcarg = bandit.plugins.general_hardcoded_password:hardcoded_password_funcarg - hardcoded_password_default = bandit.plugins.general_hardcoded_password:hardcoded_password_default - - # bandit/plugins/general_hardcoded_tmp.py - hardcoded_tmp_directory = bandit.plugins.general_hardcoded_tmp:hardcoded_tmp_directory - - # bandit/plugins/injection_paramiko.py - paramiko_calls = bandit.plugins.injection_paramiko:paramiko_calls - - # bandit/plugins/injection_shell.py - subprocess_popen_with_shell_equals_true = bandit.plugins.injection_shell:subprocess_popen_with_shell_equals_true - subprocess_without_shell_equals_true = bandit.plugins.injection_shell:subprocess_without_shell_equals_true - any_other_function_with_shell_equals_true = bandit.plugins.injection_shell:any_other_function_with_shell_equals_true - start_process_with_a_shell = bandit.plugins.injection_shell:start_process_with_a_shell - start_process_with_no_shell = bandit.plugins.injection_shell:start_process_with_no_shell - start_process_with_partial_path = bandit.plugins.injection_shell:start_process_with_partial_path - - # 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 - - # bandit/plugins/insecure_ssl_tls.py - ssl_with_bad_version = bandit.plugins.insecure_ssl_tls:ssl_with_bad_version - ssl_with_bad_defaults = bandit.plugins.insecure_ssl_tls:ssl_with_bad_defaults - ssl_with_no_version = bandit.plugins.insecure_ssl_tls:ssl_with_no_version - - # bandit/plugins/jinja2_templates.py - jinja2_autoescape_false = bandit.plugins.jinja2_templates:jinja2_autoescape_false - - # bandit/plugins/mako_templates.py - use_of_mako_templates = bandit.plugins.mako_templates:use_of_mako_templates - - # bandit/plugins/secret_config_options.py - password_config_option_not_marked_secret = bandit.plugins.secret_config_option:password_config_option_not_marked_secret - - # bandit/plugins/try_except_continue.py - try_except_continue = bandit.plugins.try_except_continue:try_except_continue - - # bandit/plugins/try_except_pass.py - try_except_pass = bandit.plugins.try_except_pass:try_except_pass - - # bandit/plugins/weak_cryptographic_key.py - weak_cryptographic_key = bandit.plugins.weak_cryptographic_key:weak_cryptographic_key - - # bandit/plugins/yaml_load.py - yaml_load = bandit.plugins.yaml_load:yaml_load - -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source - -[pbr] -autodoc_tree_index_modules = True -autodoc_tree_excludes = - examples* - -[bdist_wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 566d8443..00000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2013 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. - -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - -setuptools.setup( - setup_requires=['pbr>=2.0.0'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 1812ef18..00000000 --- a/test-requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -# 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.4,>=4.0 # Apache-2.0 -fixtures>=3.0.0 # Apache-2.0/BSD -hacking>=1.0.0 # Apache-2.0 -mock>=2.0.0 # BSD -stestr>=1.0.0 # Apache-2.0 -testscenarios>=0.4 # Apache-2.0/BSD -testtools>=2.2.0 # MIT -oslotest>=3.2.0 # Apache-2.0 - -beautifulsoup4>=4.6.0 # MIT - -pylint==1.4.5 # GPLv2 diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/functional/test_baseline.py b/tests/functional/test_baseline.py deleted file mode 100644 index ff8fc733..00000000 --- a/tests/functional/test_baseline.py +++ /dev/null @@ -1,308 +0,0 @@ -# Copyright 2016 IBM Corp. -# -# 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 os -import shutil -import subprocess - -import fixtures -import testtools - -new_candidates_all_total_lines = "Total lines of code: 12" -new_candidates_some_total_lines = "Total lines of code: 9" -new_candidates_no_nosec_lines = "Total lines skipped (#nosec): 0" -new_candidates_skip_nosec_lines = "Total lines skipped (#nosec): 3" -baseline_no_skipped_files = "Files skipped (0):" -baseline_no_issues_found = "No issues identified." -xml_sax_issue_id = "Issue: [B317:blacklist]" -yaml_load_issue_id = "Issue: [B506:yaml_load]" -shell_issue_id = "Issue: [B602:subprocess_popen_with_shell_equals_true]" -candidate_example_one = "subprocess.Popen('/bin/ls *', shell=True)" -candidate_example_two = "subprocess.Popen('/bin/ls *', shell=True) # nosec" -candidate_example_three = "y = yaml.load(temp_str)" -candidate_example_four = "y = yaml.load(temp_str) # nosec" -candidate_example_five = "xml.sax.make_parser()" -candidate_example_six = "xml.sax.make_parser() # nosec" - - -class BaselineFunctionalTests(testtools.TestCase): - - '''Functional tests for Bandit baseline. - - This set of tests is used to verify that the baseline comparison handles - finding and comparing results appropriately. The only comparison is the - number of candidates per file, meaning that any candidates found may - already exist in the baseline. In this case, all candidates are flagged - and a user will need to investigate the candidates related to that file. - ''' - - def setUp(self): - super(BaselineFunctionalTests, self).setUp() - self.examples_path = 'examples' - self.baseline_commands = ['bandit', '-r'] - self.baseline_report_file = "baseline_report.json" - - def _run_bandit_baseline(self, target_directory, baseline_file): - '''A helper method to run bandit baseline - - This method will run the bandit baseline test provided an existing - baseline report and the target directory containing the content to be - tested. - :param target_directory: Directory containing content to be compared - :param baseline_file: File containing an existing baseline report - :return The baseline test results and return code - ''' - cmds = self.baseline_commands + ['-b', baseline_file, target_directory] - process = subprocess.Popen(cmds, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, close_fds=True) - stdout, stderr = process.communicate() - return (stdout.decode('utf-8'), process.poll()) - - def _create_baseline(self, baseline_paired_files): - '''A helper method to create a baseline to use during baseline test - - This method will run bandit to create an initial baseline that can - then be used during the bandit baseline test. Since the file contents - of the baseline report can be extremely dynamic and difficult to create - ahead of time, we do this at runtime to reduce the risk of missing - something. To do this, we must temporary replace the file contents - with different code which will produce the proper baseline results to - be used during the baseline test. - :param baseline_paired_files A dictionary based set of files for which - to create the baseline report with. For each key file, a value file - is provided, which contains content to use in place of the key file - when the baseline report is created initially. - :return The target directory for the baseline test and the return code - of the bandit run to help determine whether the baseline report was - populated - ''' - target_directory = self.useFixture(fixtures.TempDir()).path - baseline_results = os.path.join(target_directory, - self.baseline_report_file) - for key_file, value_file in baseline_paired_files.items(): - shutil.copy(os.path.join(self.examples_path, value_file), - os.path.join(target_directory, key_file)) - cmds = self.baseline_commands + ['-f', 'json', '-o', baseline_results, - target_directory] - process = subprocess.Popen(cmds, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, close_fds=True) - stdout, stderr = process.communicate() - return_code = process.poll() - for key_file, value_file in baseline_paired_files.items(): - shutil.copy(os.path.join(self.examples_path, key_file), - os.path.join(target_directory, key_file)) - return (target_directory, return_code) - - def test_no_new_candidates(self): - '''Tests when there are no new candidates - - Test that bandit returns no issues found, as there are no new - candidates found compared with those found from the baseline. - ''' - baseline_report_files = {"new_candidates-all.py": - "new_candidates-all.py"} - target_directory, baseline_code = (self._create_baseline( - baseline_report_files)) - # assert the initial baseline found results - self.assertEqual(1, baseline_code) - baseline_report = os.path.join(target_directory, - self.baseline_report_file) - return_value, return_code = (self._run_bandit_baseline( - target_directory, baseline_report)) - # assert there were no results (no candidates found) - self.assertEqual(0, return_code) - self.assertIn(new_candidates_all_total_lines, return_value) - self.assertIn(new_candidates_skip_nosec_lines, return_value) - self.assertIn(baseline_no_skipped_files, return_value) - self.assertIn(baseline_no_issues_found, return_value) - - def test_no_existing_no_new_candidates(self): - '''Tests when there are no new or existing candidates - - Test file with no existing candidates from baseline and no new - candidates. - ''' - baseline_report_files = {"okay.py": "okay.py"} - target_directory, baseline_code = (self._create_baseline( - baseline_report_files)) - # assert the initial baseline found nothing - self.assertEqual(0, baseline_code) - baseline_report = os.path.join(target_directory, - self.baseline_report_file) - return_value, return_code = (self._run_bandit_baseline( - target_directory, baseline_report)) - # assert there were no results (no candidates found) - self.assertEqual(0, return_code) - self.assertIn("Total lines of code: 1", return_value) - self.assertIn(new_candidates_no_nosec_lines, return_value) - self.assertIn(baseline_no_skipped_files, return_value) - self.assertIn(baseline_no_issues_found, return_value) - - def test_no_existing_with_new_candidates(self): - '''Tests when there are new candidates and no existing candidates - - Test that bandit returns issues found in file that had no existing - candidates from baseline but now contain candidates. - ''' - baseline_report_files = {"new_candidates-all.py": - "new_candidates-none.py"} - target_directory, baseline_code = (self._create_baseline( - baseline_report_files)) - # assert the initial baseline found nothing - self.assertEqual(0, baseline_code) - baseline_report = os.path.join(target_directory, - self.baseline_report_file) - return_value, return_code = (self._run_bandit_baseline( - target_directory, baseline_report)) - # assert there were results (candidates found) - self.assertEqual(1, return_code) - self.assertIn(new_candidates_all_total_lines, return_value) - self.assertIn(new_candidates_skip_nosec_lines, return_value) - self.assertIn(baseline_no_skipped_files, return_value) - self.assertIn(xml_sax_issue_id, return_value) - self.assertIn(yaml_load_issue_id, return_value) - self.assertIn(shell_issue_id, return_value) - # candidate #1 - self.assertIn(candidate_example_one, return_value) - # candidate #3 - self.assertIn(candidate_example_three, return_value) - # candidate #5 - self.assertIn(candidate_example_five, return_value) - - def test_existing_and_new_candidates(self): - '''Tests when tere are new candidates and existing candidates - - Test that bandit returns issues found in file with existing - candidates. The new candidates should be returned in this case. - ''' - baseline_report_files = {"new_candidates-all.py": - "new_candidates-some.py"} - target_directory, baseline_code = (self._create_baseline( - baseline_report_files)) - # assert the initial baseline found results - self.assertEqual(1, baseline_code) - baseline_report = os.path.join(target_directory, - self.baseline_report_file) - return_value, return_code = (self._run_bandit_baseline( - target_directory, baseline_report)) - # assert there were results (candidates found) - self.assertEqual(1, return_code) - self.assertIn(new_candidates_all_total_lines, return_value) - self.assertIn(new_candidates_skip_nosec_lines, return_value) - self.assertIn(baseline_no_skipped_files, return_value) - self.assertIn(xml_sax_issue_id, return_value) - self.assertIn(yaml_load_issue_id, return_value) - # candidate #3 - self.assertIn(candidate_example_three, return_value) - # candidate #5 - self.assertIn(candidate_example_five, return_value) - - def test_no_new_candidates_include_nosec(self): - '''Test to check nosec references with no new candidates - - Test that nosec references are included during a baseline test, which - would normally be ignored. In this test case, there are no new - candidates even while including the nosec references. - ''' - self.baseline_commands.append('--ignore-nosec') - baseline_report_files = {"new_candidates-all.py": - "new_candidates-all.py"} - target_directory, baseline_code = (self._create_baseline( - baseline_report_files)) - # assert the initial baseline found results - self.assertEqual(1, baseline_code) - baseline_report = os.path.join(target_directory, - self.baseline_report_file) - return_value, return_code = (self._run_bandit_baseline( - target_directory, baseline_report)) - # assert there were no results (candidates found) - self.assertEqual(0, return_code) - self.assertIn(new_candidates_all_total_lines, return_value) - self.assertIn(new_candidates_no_nosec_lines, return_value) - self.assertIn(baseline_no_skipped_files, return_value) - self.assertIn(baseline_no_issues_found, return_value) - - def test_new_candidates_include_nosec_only_nosecs(self): - '''Test to check nosec references with new only nosec candidates - - Test that nosec references are included during a baseline test, which - would normally be ignored. In this test case, there are new candidates - which are specifically nosec references. - ''' - self.baseline_commands.append('--ignore-nosec') - baseline_report_files = {"new_candidates-nosec.py": - "new_candidates-none.py"} - target_directory, baseline_code = (self._create_baseline( - baseline_report_files)) - # assert the initial baseline found nothing - self.assertEqual(0, baseline_code) - baseline_report = os.path.join(target_directory, - self.baseline_report_file) - return_value, return_code = (self._run_bandit_baseline( - target_directory, baseline_report)) - # assert there were results (candidates found) - self.assertEqual(1, return_code) - self.assertIn(new_candidates_some_total_lines, return_value) - self.assertIn(new_candidates_no_nosec_lines, return_value) - self.assertIn(baseline_no_skipped_files, return_value) - self.assertIn(xml_sax_issue_id, return_value) - self.assertIn(yaml_load_issue_id, return_value) - self.assertIn(shell_issue_id, return_value) - # candidate #2 - self.assertIn(candidate_example_two, return_value) - # candidate #4 - self.assertIn(candidate_example_four, return_value) - # candidate #6 - self.assertIn(candidate_example_six, return_value) - - def test_new_candidates_include_nosec_new_nosecs(self): - '''Test to check nosec references with new candidates, including nosecs - - Test that nosec references are included during a baseline test, which - would normally be ignored. In this test case, there are new candidates - that also includes new nosec references as well. - ''' - self.baseline_commands.append('--ignore-nosec') - baseline_report_files = {"new_candidates-all.py": - "new_candidates-none.py"} - target_directory, baseline_code = (self._create_baseline( - baseline_report_files)) - # assert the initial baseline found nothing - self.assertEqual(0, baseline_code) - baseline_report = os.path.join(target_directory, - self.baseline_report_file) - return_value, return_code = (self._run_bandit_baseline( - target_directory, baseline_report)) - # assert there were results (candidates found) - self.assertEqual(1, return_code) - self.assertIn(new_candidates_all_total_lines, return_value) - self.assertIn(new_candidates_no_nosec_lines, return_value) - self.assertIn(baseline_no_skipped_files, return_value) - self.assertIn(xml_sax_issue_id, return_value) - self.assertIn(yaml_load_issue_id, return_value) - self.assertIn(shell_issue_id, return_value) - # candidate #1 - self.assertIn(candidate_example_one, return_value) - # candidate #2 - self.assertIn(candidate_example_two, return_value) - # candidate #3 - self.assertIn(candidate_example_three, return_value) - # candidate #4 - self.assertIn(candidate_example_four, return_value) - # candidate #5 - self.assertIn(candidate_example_five, return_value) - # candidate #6 - self.assertIn(candidate_example_six, return_value) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py deleted file mode 100644 index 13976883..00000000 --- a/tests/functional/test_functional.py +++ /dev/null @@ -1,731 +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 os - -import six -import testtools - -from bandit.core import config as b_config -from bandit.core import constants as C -from bandit.core import manager as b_manager -from bandit.core import metrics -from bandit.core import test_set as b_test_set - - -class FunctionalTests(testtools.TestCase): - - '''Functional tests for bandit test plugins. - - This set of tests runs bandit against each example file in turn - and records the score returned. This is compared to a known good value. - When new tests are added to an example the expected result should be - adjusted to match. - ''' - - def setUp(self): - super(FunctionalTests, self).setUp() - # NOTE(tkelsey): bandit is very sensitive to paths, so stitch - # them up here for the testing environment. - # - path = os.path.join(os.getcwd(), 'bandit', 'plugins') - b_conf = b_config.BanditConfig() - self.b_mgr = b_manager.BanditManager(b_conf, 'file') - self.b_mgr.b_conf._settings['plugins_dir'] = path - self.b_mgr.b_ts = b_test_set.BanditTestSet(config=b_conf) - - def run_example(self, example_script, ignore_nosec=False): - '''A helper method to run the specified test - - This method runs the test, which populates the self.b_mgr.scores - value. Call this directly if you need to run a test, but do not - need to test the resulting scores against specified values. - :param example_script: Filename of an example script to test - ''' - path = os.path.join(os.getcwd(), 'examples', example_script) - self.b_mgr.ignore_nosec = ignore_nosec - self.b_mgr.discover_files([path], True) - self.b_mgr.run_tests() - - def check_example(self, example_script, expect, ignore_nosec=False): - '''A helper method to test the scores for example scripts. - - :param example_script: Filename of an example script to test - :param expect: dict with expected counts of issue types - ''' - # reset scores for subsequent calls to check_example - self.b_mgr.scores = [] - self.run_example(example_script, ignore_nosec=ignore_nosec) - - result = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0} - } - - for test_scores in self.b_mgr.scores: - for score_type in test_scores: - self.assertIn(score_type, expect) - for idx, rank in enumerate(C.RANKING): - result[score_type][rank] = (test_scores[score_type][idx] / - C.RANKING_VALUES[rank]) - - self.assertDictEqual(expect, result) - - def check_metrics(self, example_script, expect): - '''A helper method to test the metrics being returned. - - :param example_script: Filename of an example script to test - :param expect: dict with expected values of metrics - ''' - self.b_mgr.metrics = metrics.Metrics() - self.b_mgr.scores = [] - self.run_example(example_script) - - # test general metrics (excludes issue counts) - m = self.b_mgr.metrics.data - for k in expect: - if k != 'issues': - self.assertEqual(expect[k], m['_totals'][k]) - # test issue counts - if 'issues' in expect: - for (criteria, default) in C.CRITERIA: - for rank in C.RANKING: - label = '{0}.{1}'.format(criteria, rank) - expected = 0 - if expect['issues'].get(criteria).get(rank): - expected = expect['issues'][criteria][rank] - self.assertEqual(expected, m['_totals'][label]) - - def test_binding(self): - '''Test the bind-to-0.0.0.0 example.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0} - } - self.check_example('binding.py', expect) - - def test_crypto_md5(self): - '''Test the `hashlib.md5` example.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 15, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 15} - } - self.check_example('crypto-md5.py', expect) - - def test_ciphers(self): - '''Test the `Crypto.Cipher` example.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 13}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 14} - } - self.check_example('ciphers.py', expect) - - def test_cipher_modes(self): - '''Test for insecure cipher modes.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1} - } - self.check_example('cipher-modes.py', expect) - - def test_eval(self): - '''Test the `eval` example.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3} - } - self.check_example('eval.py', expect) - - def test_mark_safe(self): - '''Test the `mark_safe` example.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1} - } - self.check_example('mark_safe.py', expect) - - def test_exec(self): - '''Test the `exec` example.''' - filename = 'exec-{}.py' - if six.PY2: - filename = filename.format('py2') - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, - 'HIGH': 2} - } - else: - filename = filename.format('py3') - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, - 'HIGH': 1} - } - self.check_example(filename, expect) - - def test_exec_as_root(self): - '''Test for the `run_as_root=True` keyword argument.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 5, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 5, 'HIGH': 0} - } - self.check_example('exec-as-root.py', expect) - - def test_hardcoded_passwords(self): - '''Test for hard-coded passwords.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 8, 'HIGH': 0} - } - self.check_example('hardcoded-passwords.py', expect) - - def test_hardcoded_tmp(self): - '''Test for hard-coded /tmp, /var/tmp, /dev/shm.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0} - } - self.check_example('hardcoded-tmp.py', expect) - - def test_httplib_https(self): - '''Test for `httplib.HTTPSConnection`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3} - } - self.check_example('httplib_https.py', expect) - - def test_imports_aliases(self): - '''Test the `import X as Y` syntax.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 4, 'MEDIUM': 5, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 9} - } - self.check_example('imports-aliases.py', expect) - - def test_imports_from(self): - '''Test the `from X import Y` syntax.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3} - } - self.check_example('imports-from.py', expect) - - def test_imports_function(self): - '''Test the `__import__` function.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('imports-function.py', expect) - - def test_telnet_usage(self): - '''Test for `import telnetlib` and Telnet.* calls.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('telnetlib.py', expect) - - def test_ftp_usage(self): - '''Test for `import ftplib` and FTP.* calls.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('ftplib.py', expect) - - def test_imports(self): - '''Test for dangerous imports.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('imports.py', expect) - - def test_imports_using_importlib(self): - '''Test for dangerous imports using importlib.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('imports-with-importlib.py', expect) - - def test_mktemp(self): - '''Test for `tempfile.mktemp`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 4, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4} - } - self.check_example('mktemp.py', expect) - - def test_nonsense(self): - '''Test that a syntactically invalid module is skipped.''' - self.run_example('nonsense.py') - self.assertEqual(1, len(self.b_mgr.skipped)) - - def test_okay(self): - '''Test a vulnerability-free file.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0} - } - self.check_example('okay.py', expect) - - def test_subdirectory_okay(self): - '''Test a vulnerability-free file under a subdirectory.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0} - } - self.check_example('init-py-test/subdirectory-okay.py', expect) - - def test_os_chmod(self): - '''Test setting file permissions.''' - filename = 'os-chmod-{}.py' - if six.PY2: - filename = filename.format('py2') - else: - filename = filename.format('py3') - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 8}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 9} - } - self.check_example(filename, expect) - - def test_os_exec(self): - '''Test for `os.exec*`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 8, 'HIGH': 0} - } - self.check_example('os-exec.py', expect) - - def test_os_popen(self): - '''Test for `os.popen`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 0, 'HIGH': 1}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 9} - } - self.check_example('os-popen.py', expect) - - def test_os_spawn(self): - '''Test for `os.spawn*`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 8, 'HIGH': 0} - } - self.check_example('os-spawn.py', expect) - - def test_os_startfile(self): - '''Test for `os.startfile`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0} - } - self.check_example('os-startfile.py', expect) - - def test_os_system(self): - '''Test for `os.system`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1} - } - self.check_example('os_system.py', expect) - - def test_pickle(self): - '''Test for the `pickle` module.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 6, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 8} - } - self.check_example('pickle_deserialize.py', expect) - - def test_popen_wrappers(self): - '''Test the `popen2` and `commands` modules.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 7, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7} - } - self.check_example('popen_wrappers.py', expect) - - def test_random_module(self): - '''Test for the `random` module.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 6, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 6} - } - self.check_example('random_module.py', expect) - - def test_requests_ssl_verify_disabled(self): - '''Test for the `requests` library skipping verification.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7} - } - self.check_example('requests-ssl-verify-disabled.py', expect) - - def test_skip(self): - '''Test `#nosec` and `#noqa` comments.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 5, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5} - } - self.check_example('skip.py', expect) - - def test_ignore_skip(self): - '''Test --ignore-nosec flag.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 7, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7} - } - self.check_example('skip.py', expect, ignore_nosec=True) - - def test_sql_statements(self): - '''Test for SQL injection through string building.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 14, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 6, 'HIGH': 0} - } - self.check_example('sql_statements.py', expect) - - def test_ssl_insecure_version(self): - '''Test for insecure SSL protocol versions.''' - expect = { - 'SEVERITY': {'LOW': 1, 'MEDIUM': 10, 'HIGH': 7}, - 'CONFIDENCE': {'LOW': 0, 'MEDIUM': 11, 'HIGH': 7} - } - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 10, 'HIGH': 7}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 11, 'HIGH': 7} - } - self.check_example('ssl-insecure-version.py', expect) - - def test_subprocess_shell(self): - '''Test for `subprocess.Popen` with `shell=True`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 14, 'MEDIUM': 1, 'HIGH': 3}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 0, 'HIGH': 17} - } - self.check_example('subprocess_shell.py', expect) - - def test_urlopen(self): - '''Test for dangerous URL opening.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 14, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 14} - } - self.check_example('urlopen.py', expect) - - def test_utils_shell(self): - '''Test for `utils.execute*` with `shell=True`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 5, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5} - } - self.check_example('utils-shell.py', expect) - - def test_wildcard_injection(self): - '''Test for wildcard injection in shell commands.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 10, 'MEDIUM': 0, 'HIGH': 4}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 5, 'HIGH': 9} - } - self.check_example('wildcard-injection.py', expect) - - def test_yaml(self): - '''Test for `yaml.load`.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1} - } - self.check_example('yaml_load.py', expect) - - def test_jinja2_templating(self): - '''Test jinja templating for potential XSS bugs.''' - expect = { - '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) - - def test_secret_config_option(self): - '''Test for `secret=True` in Oslo's config.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 2, 'HIGH': 0} - } - self.check_example('secret-config-option.py', expect) - - def test_mako_templating(self): - '''Test Mako templates for XSS.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3} - } - self.check_example('mako_templating.py', expect) - - def test_xml(self): - '''Test xml vulnerabilities.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 4, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5} - } - self.check_example('xml_etree_celementtree.py', expect) - - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 2, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3} - } - self.check_example('xml_expatbuilder.py', expect) - - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 1, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4} - } - self.check_example('xml_lxml.py', expect) - - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 2, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4} - } - self.check_example('xml_pulldom.py', expect) - - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1} - } - self.check_example('xml_xmlrpc.py', expect) - - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 4, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5} - } - self.check_example('xml_etree_elementtree.py', expect) - - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 1, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('xml_expatreader.py', expect) - - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 2, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4} - } - self.check_example('xml_minidom.py', expect) - - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 6, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 8} - } - self.check_example('xml_sax.py', expect) - - def test_httpoxy(self): - '''Test httpoxy vulnerability.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1} - } - self.check_example('httpoxy_cgihandler.py', expect) - self.check_example('httpoxy_twisted_script.py', expect) - self.check_example('httpoxy_twisted_directory.py', expect) - - def test_asserts(self): - '''Test catching the use of assert.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1} - } - self.check_example('assert.py', expect) - - def test_paramiko_injection(self): - '''Test paramiko command execution.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 0} - } - self.check_example('paramiko_injection.py', expect) - - def test_partial_path(self): - '''Test process spawning with partial file paths.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 11, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 11} - } - self.check_example('partial_path_process.py', expect) - - def test_try_except_continue(self): - '''Test try, except, continue detection.''' - test = next((x for x in self.b_mgr.b_ts.tests['ExceptHandler'] - if x.__name__ == 'try_except_continue')) - - test._config = {'check_typed_exception': True} - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3} - } - self.check_example('try_except_continue.py', expect) - - test._config = {'check_typed_exception': False} - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('try_except_continue.py', expect) - - def test_try_except_pass(self): - '''Test try, except pass detection.''' - test = next((x for x in self.b_mgr.b_ts.tests['ExceptHandler'] - if x.__name__ == 'try_except_pass')) - - test._config = {'check_typed_exception': True} - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3} - } - self.check_example('try_except_pass.py', expect) - - test._config = {'check_typed_exception': False} - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('try_except_pass.py', expect) - - def test_metric_gathering(self): - expect = { - 'nosec': 2, 'loc': 7, - 'issues': {'CONFIDENCE': {'HIGH': 5}, 'SEVERITY': {'LOW': 5}} - } - self.check_metrics('skip.py', expect) - expect = { - 'nosec': 0, 'loc': 4, - 'issues': {'CONFIDENCE': {'HIGH': 2}, 'SEVERITY': {'LOW': 2}} - } - self.check_metrics('imports.py', expect) - - def test_weak_cryptographic_key(self): - '''Test for weak key sizes.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 8, 'HIGH': 6}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 14} - } - self.check_example('weak_cryptographic_key_sizes.py', expect) - - def test_multiline_code(self): - '''Test issues in multiline statements return code as expected.''' - self.run_example('multiline_statement.py') - self.assertEqual(0, len(self.b_mgr.skipped)) - self.assertEqual(1, len(self.b_mgr.files_list)) - self.assertTrue(self.b_mgr.files_list[0].endswith( - 'multiline_statement.py')) - - issues = self.b_mgr.get_issue_list() - self.assertEqual(2, len(issues)) - self.assertTrue( - issues[0].fname.endswith('examples/multiline_statement.py') - ) - - self.assertEqual(1, issues[0].lineno) - self.assertEqual(list(range(1, 3)), issues[0].linerange) - self.assertIn('subprocess', issues[0].get_code()) - self.assertEqual(5, issues[1].lineno) - self.assertEqual(list(range(3, 6 + 1)), issues[1].linerange) - self.assertIn('shell=True', issues[1].get_code()) - - def test_code_line_numbers(self): - self.run_example('binding.py') - issues = self.b_mgr.get_issue_list() - - code_lines = issues[0].get_code().splitlines() - lineno = issues[0].lineno - self.assertEqual("%i " % (lineno - 1), code_lines[0][:2]) - self.assertEqual("%i " % (lineno), code_lines[1][:2]) - self.assertEqual("%i " % (lineno + 1), code_lines[2][:2]) - - def test_flask_debug_true(self): - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0} - } - self.check_example('flask_debug.py', expect) - - def test_nosec(self): - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0} - } - self.check_example('nosec.py', expect) - - def test_baseline_filter(self): - issue_text = ('A Flask app appears to be run with debug=True, which ' - 'exposes the Werkzeug debugger and allows the execution ' - 'of arbitrary code.') - json = """{ - "results": [ - { - "code": "...", - "filename": "%s/examples/flask_debug.py", - "issue_confidence": "MEDIUM", - "issue_severity": "HIGH", - "issue_text": "%s", - "line_number": 10, - "line_range": [ - 10 - ], - "test_name": "flask_debug_true", - "test_id": "B201" - } - ] - } - """ % (os.getcwd(), issue_text) - - self.b_mgr.populate_baseline(json) - self.run_example('flask_debug.py') - self.assertEqual(1, len(self.b_mgr.baseline)) - self.assertEqual({}, self.b_mgr.get_issue_list()) - - def test_blacklist_input(self): - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}, - '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) - - 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) - - def test_blacklist_pycrypto(self): - '''Test importing pycrypto module''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2} - } - self.check_example('pycrypto.py', expect) diff --git a/tests/functional/test_runtime.py b/tests/functional/test_runtime.py deleted file mode 100644 index a7f40817..00000000 --- a/tests/functional/test_runtime.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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 os -import subprocess - -import testtools - - -class RuntimeTests(testtools.TestCase): - - def _test_runtime(self, cmdlist, infile=None): - process = subprocess.Popen( - cmdlist, - stdin=infile if infile else subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - close_fds=True - ) - stdout, stderr = process.communicate() - retcode = process.poll() - return (retcode, stdout.decode('utf-8')) - - def _test_example(self, cmdlist, targets): - for t in targets: - cmdlist.append(os.path.join(os.getcwd(), 'examples', t)) - return self._test_runtime(cmdlist) - - def test_no_arguments(self): - (retcode, output) = self._test_runtime(['bandit', ]) - self.assertEqual(2, retcode) - self.assertIn("No targets found in CLI or ini files", output) - - def test_piped_input(self): - with open('examples/imports.py', 'r') as infile: - (retcode, output) = self._test_runtime(['bandit', '-'], infile) - self.assertEqual(1, retcode) - self.assertIn("Total lines of code: 4", output) - self.assertIn("Low: 2", output) - self.assertIn("High: 2", output) - self.assertIn("Files skipped (0):", output) - self.assertIn("Issue: [B403:blacklist] Consider possible", output) - self.assertIn(":2", output) - self.assertIn(":4", output) - - def test_nonexistent_config(self): - (retcode, output) = self._test_runtime([ - 'bandit', '-c', 'nonexistent.yml', 'xx.py' - ]) - self.assertEqual(2, retcode) - self.assertIn("nonexistent.yml : Could not read config file.", output) - - def test_help_arg(self): - (retcode, output) = self._test_runtime(['bandit', '-h']) - self.assertEqual(0, retcode) - self.assertIn( - "Bandit - a Python source code security analyzer", output - ) - self.assertIn("usage: bandit [-h]", output) - self.assertIn("positional arguments:", output) - self.assertIn("optional arguments:", output) - self.assertIn("tests were discovered and loaded:", output) - - def test_help_in_readme(self): - replace_list = [' ', '\t', '\n'] - (retcode, output) = self._test_runtime(['bandit', '-h']) - for i in replace_list: - output = output.replace(i, '') - output = output.replace("'", "\'") - with open('README.rst') as f: - readme = f.read() - for i in replace_list: - readme = readme.replace(i, '') - self.assertIn(output, readme) - - # test examples (use _test_example() to wrap in config location argument - def test_example_nonexistent(self): - (retcode, output) = self._test_example( - ['bandit', ], ['nonexistent.py', ] - ) - self.assertEqual(0, retcode) - self.assertIn("Files skipped (1):", output) - self.assertIn("nonexistent.py (No such file or directory", output) - - def test_example_okay(self): - (retcode, output) = self._test_example(['bandit', ], ['okay.py', ]) - self.assertEqual(0, retcode) - self.assertIn("Total lines of code: 1", output) - self.assertIn("Files skipped (0):", output) - self.assertIn("No issues identified.", output) - - def test_example_nonsense(self): - (retcode, output) = self._test_example(['bandit', ], ['nonsense.py', ]) - self.assertEqual(0, retcode) - self.assertIn("Files skipped (1):", output) - self.assertIn("nonsense.py (syntax error while parsing AST", output) - - def test_example_nonsense2(self): - (retcode, output) = self._test_example( - ['bandit', ], ['nonsense2.py', ] - ) - self.assertEqual(0, retcode) - self.assertIn( - "Exception occurred when executing tests against", output - ) - self.assertIn("Files skipped (1):", output) - self.assertIn("nonsense2.py (exception while scanning file)", output) - - def test_example_imports(self): - (retcode, output) = self._test_example(['bandit', ], ['imports.py', ]) - self.assertEqual(1, retcode) - self.assertIn("Total lines of code: 4", output) - self.assertIn("Low: 2", output) - self.assertIn("High: 2", output) - self.assertIn("Files skipped (0):", output) - 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/__init__.py b/tests/unit/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/cli/__init__.py b/tests/unit/cli/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/cli/test_baseline.py b/tests/unit/cli/test_baseline.py deleted file mode 100644 index 84f0a27b..00000000 --- a/tests/unit/cli/test_baseline.py +++ /dev/null @@ -1,298 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2015 Hewlett-Packard Enterprise -# -# 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 os -import subprocess - -import fixtures -import git -import mock -import testtools - -import bandit.cli.baseline as baseline - - -config = """ -include: - - '*.py' - - '*.pyw' - -profiles: - test: - include: - - start_process_with_a_shell - -shell_injection: - subprocess: [] - no_shell: [] - shell: - - os.system -""" - - -class BanditBaselineToolTests(testtools.TestCase): - - @classmethod - def setUpClass(cls): - # Set up prior to running test class - # read in content used for temporary file contents - with open('examples/mktemp.py') as fd: - cls.temp_file_contents = fd.read() - - def setUp(self): - # Set up prior to run each test case - super(BanditBaselineToolTests, self).setUp() - self.current_directory = os.getcwd() - - def tearDown(self): - # Tear down after running each test case - super(BanditBaselineToolTests, self).tearDown() - os.chdir(self.current_directory) - - def test_bandit_baseline(self): - # Tests running bandit via the CLI (baseline) with benign and malicious - # content - repo_directory = self.useFixture(fixtures.TempDir()).path - - # get benign and findings examples - with open('examples/okay.py') as fd: - benign_contents = fd.read() - - with open('examples/os_system.py') as fd: - malicious_contents = fd.read() - - contents = {'benign_one.py': benign_contents, - 'benign_two.py': benign_contents, - 'malicious.py': malicious_contents} - - # init git repo, change directory to it - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial commit') - os.chdir(repo_directory) - - with open('bandit.yaml', 'wt') as fd: - fd.write(config) - - # create three branches, first has only benign, second adds malicious, - # third adds benign - - branches = [{'name': 'benign1', - 'files': ['benign_one.py'], - 'expected_return': 0}, - - {'name': 'malicious', - 'files': ['benign_one.py', 'malicious.py'], - 'expected_return': 1}, - - {'name': 'benign2', - 'files': ['benign_one.py', 'malicious.py', - 'benign_two.py'], - 'expected_return': 0}] - - baseline_command = ['bandit-baseline', '-c', 'bandit.yaml', '-r', '.', - '-p', 'test'] - - for branch in branches: - branch['branch'] = git_repo.create_head(branch['name']) - git_repo.head.reference = branch['branch'] - git_repo.head.reset(working_tree=True) - - for f in branch['files']: - with open(f, 'wt') as fd: - fd.write(contents[f]) - - git_repo.index.add(branch['files']) - git_repo.index.commit(branch['name']) - - self.assertEqual(branch['expected_return'], - subprocess.call(baseline_command)) - - def test_main_non_repo(self): - # Test that bandit gracefully exits when there is no git repository - # when calling main - repo_dir = self.useFixture(fixtures.TempDir()).path - os.chdir(repo_dir) - - # assert the system exits with code 2 - self.assertRaisesRegex(SystemExit, '2', baseline.main) - - def test_main_git_command_failure(self): - # Test that bandit does not run when the Git command fails - repo_directory = self.useFixture(fixtures.TempDir()).path - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial Commit') - os.chdir(repo_directory) - - additional_content = 'additional_file.py' - with open(additional_content, 'wt') as fd: - fd.write(self.temp_file_contents) - git_repo.index.add([additional_content]) - git_repo.index.commit('Additional Content') - - with mock.patch('git.Repo.commit') as mock_git_repo_commit: - mock_git_repo_commit.side_effect = git.exc.GitCommandError( - 'commit', '') - - # assert the system exits with code 2 - self.assertRaisesRegex(SystemExit, '2', baseline.main) - - def test_main_no_parent_commit(self): - # Test that bandit exits when there is no parent commit detected when - # calling main - repo_directory = self.useFixture(fixtures.TempDir()).path - - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial Commit') - os.chdir(repo_directory) - - # assert the system exits with code 2 - self.assertRaisesRegex(SystemExit, '2', baseline.main) - - def test_main_subprocess_error(self): - # Test that bandit handles a CalledProcessError when attempting to run - # bandit baseline via a subprocess - repo_directory = self.useFixture(fixtures.TempDir()).path - - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial Commit') - os.chdir(repo_directory) - - additional_content = 'additional_file.py' - with open(additional_content, 'wt') as fd: - fd.write(self.temp_file_contents) - git_repo.index.add([additional_content]) - git_repo.index.commit('Additional Content') - - with mock.patch('subprocess.check_output') as mock_check_output: - mock_bandit_cmd = 'bandit_mock -b temp_file.txt' - mock_check_output.side_effect = ( - subprocess.CalledProcessError('3', mock_bandit_cmd) - ) - - # assert the system exits with code 3 (returned from - # CalledProcessError) - self.assertRaisesRegex(SystemExit, '3', baseline.main) - - def test_init_logger(self): - # Test whether the logger was initialized when calling init_logger - baseline.init_logger() - logger = baseline.LOG - - # verify that logger was initialized - self.assertIsNotNone(logger) - - def test_initialize_no_repo(self): - # Test that bandit does not run when there is no current git - # repository when calling initialize - repo_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(repo_directory) - - return_value = baseline.initialize() - - # assert bandit did not run due to no git repo - self.assertEqual((None, None, None), return_value) - - def test_initialize_git_command_failure(self): - # Test that bandit does not run when the Git command fails - repo_directory = self.useFixture(fixtures.TempDir()).path - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial Commit') - os.chdir(repo_directory) - - additional_content = 'additional_file.py' - with open(additional_content, 'wt') as fd: - fd.write(self.temp_file_contents) - git_repo.index.add([additional_content]) - git_repo.index.commit('Additional Content') - - with mock.patch('git.Repo') as mock_git_repo: - mock_git_repo.side_effect = git.exc.GitCommandNotFound('clone', '') - - return_value = baseline.initialize() - - # assert bandit did not run due to git command failure - self.assertEqual((None, None, None), return_value) - - def test_initialize_dirty_repo(self): - # Test that bandit does not run when the current git repository is - # 'dirty' when calling the initialize method - repo_directory = self.useFixture(fixtures.TempDir()).path - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial Commit') - os.chdir(repo_directory) - - # make the git repo 'dirty' - with open('dirty_file.py', 'wt') as fd: - fd.write(self.temp_file_contents) - git_repo.index.add(['dirty_file.py']) - - return_value = baseline.initialize() - - # assert bandit did not run due to dirty repo - self.assertEqual((None, None, None), return_value) - - @mock.patch('sys.argv', ['bandit', '-f', 'txt', 'test']) - def test_initialize_existing_report_file(self): - # Test that bandit does not run when the output file exists (and the - # provided output format does not match the default format) when - # calling the initialize method - repo_directory = self.useFixture(fixtures.TempDir()).path - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial Commit') - os.chdir(repo_directory) - - # create an existing version of output report file - existing_report = "{}.{}".format(baseline.report_basename, 'txt') - with open(existing_report, 'wt') as fd: - fd.write(self.temp_file_contents) - - return_value = baseline.initialize() - - # assert bandit did not run due to existing report file - self.assertEqual((None, None, None), return_value) - - @mock.patch('bandit.cli.baseline.bandit_args', ['-o', - 'bandit_baseline_result']) - def test_initialize_with_output_argument(self): - # Test that bandit does not run when the '-o' (output) argument is - # specified - repo_directory = self.useFixture(fixtures.TempDir()).path - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial Commit') - os.chdir(repo_directory) - - return_value = baseline.initialize() - - # assert bandit did not run due to provided -o (--ouput) argument - self.assertEqual((None, None, None), return_value) - - def test_initialize_existing_temp_file(self): - # Test that bandit does not run when the temporary output file exists - # when calling the initialize method - repo_directory = self.useFixture(fixtures.TempDir()).path - git_repo = git.Repo.init(repo_directory) - git_repo.index.commit('Initial Commit') - os.chdir(repo_directory) - - # create an existing version of temporary output file - existing_temp_file = baseline.baseline_tmp_file - with open(existing_temp_file, 'wt') as fd: - fd.write(self.temp_file_contents) - - return_value = baseline.initialize() - - # assert bandit did not run due to existing temporary report file - self.assertEqual((None, None, None), return_value) diff --git a/tests/unit/cli/test_config_generator.py b/tests/unit/cli/test_config_generator.py deleted file mode 100644 index 1431ab56..00000000 --- a/tests/unit/cli/test_config_generator.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2016 Hewlett-Packard Enterprise -# -# 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 importlib -import logging - -import mock -import testtools -import yaml - -from bandit.cli import config_generator -from bandit.core import extension_loader -from bandit.core import test_properties as test - - -def gen_config(name): - return {"test": "test data"} - - -@test.takes_config('test') -@test.checks('Str') -def _test_plugin(context, conf): - pass - - -class BanditConfigGeneratorLoggerTests(testtools.TestCase): - - def setUp(self): - super(BanditConfigGeneratorLoggerTests, self).setUp() - self.logger = logging.getLogger(config_generator.__name__) - self.original_logger_handlers = self.logger.handlers - self.original_logger_level = self.logger.level - self.logger.handlers = [] - - def tearDown(self): - super(BanditConfigGeneratorLoggerTests, self).tearDown() - self.logger.handlers = self.original_logger_handlers - self.logger.level = self.original_logger_level - - def test_init_logger(self): - # Test that a logger was properly initialized - config_generator.init_logger() - self.assertIsNotNone(self.logger) - self.assertNotEqual([], self.logger.handlers) - self.assertEqual(logging.INFO, self.logger.level) - - -class BanditConfigGeneratorTests(testtools.TestCase): - @mock.patch('sys.argv', ['bandit-config-generator']) - def test_parse_args_no_defaults(self): - # Without arguments, the generator should just show help and exit - self.assertRaises(SystemExit, config_generator.parse_args) - - @mock.patch('sys.argv', ['bandit-config-generator', '--show-defaults']) - def test_parse_args_show_defaults(self): - # Test that the config generator does show default plugin settings - return_value = config_generator.parse_args() - self.assertTrue(return_value.show_defaults) - - @mock.patch('sys.argv', ['bandit-config-generator', '--out', 'dummyfile']) - def test_parse_args_out_file(self): - # Test config generator get proper output file when specified - return_value = config_generator.parse_args() - self.assertEqual('dummyfile', return_value.output_file) - - def test_get_config_settings(self): - config = {} - for plugin in extension_loader.MANAGER.plugins: - function = plugin.plugin - if hasattr(plugin.plugin, '_takes_config'): - module = importlib.import_module(function.__module__) - config[plugin.name] = module.gen_config( - function._takes_config) - settings = config_generator.get_config_settings() - self.assertEqual(yaml.safe_dump(config, default_flow_style=False), - settings) - - @mock.patch('sys.argv', ['bandit-config-generator', '--show-defaults']) - def test_main_show_defaults(self): - # Test that the config generator does show defaults and returns 0 - with mock.patch('bandit.cli.config_generator.get_config_settings' - ) as mock_config_settings: - return_value = config_generator.main() - # The get_config_settings function should have been called - self.assertTrue(mock_config_settings.called) - self.assertEqual(0, return_value) diff --git a/tests/unit/cli/test_main.py b/tests/unit/cli/test_main.py deleted file mode 100644 index adc95cd7..00000000 --- a/tests/unit/cli/test_main.py +++ /dev/null @@ -1,289 +0,0 @@ -# Copyright 2016 IBM Corp. -# -# 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 logging -import os - -import fixtures -import mock -import testtools - -from bandit.cli import main as bandit -from bandit.core import extension_loader as ext_loader -from bandit.core import utils - -bandit_config_content = """ -include: - - '*.py' - - '*.pyw' - -profiles: - test: - include: - - start_process_with_a_shell - -shell_injection: - subprocess: - - shell: - - os.system -""" - -bandit_baseline_content = """{ - "results": [ - { - "code": "some test code", - "filename": "test_example.py", - "issue_severity": "low", - "issue_confidence": "low", - "issue_text": "test_issue", - "test_name": "some_test", - "test_id": "x", - "line_number": "n", - "line_range": "n-m" - } - ] -} -""" - - -class BanditCLIMainLoggerTests(testtools.TestCase): - - def setUp(self): - super(BanditCLIMainLoggerTests, self).setUp() - self.logger = logging.getLogger() - self.original_logger_handlers = self.logger.handlers - self.original_logger_level = self.logger.level - self.logger.handlers = [] - - def tearDown(self): - super(BanditCLIMainLoggerTests, self).tearDown() - self.logger.handlers = self.original_logger_handlers - self.logger.level = self.original_logger_level - - def test_init_logger(self): - # Test that a logger was properly initialized - bandit._init_logger(False) - - self.assertIsNotNone(self.logger) - self.assertNotEqual(self.logger.handlers, []) - self.assertEqual(logging.INFO, self.logger.level) - - def test_init_logger_debug_mode(self): - # Test that the logger's level was set at 'DEBUG' - bandit._init_logger(True) - self.assertEqual(logging.DEBUG, self.logger.level) - - -class BanditCLIMainTests(testtools.TestCase): - - def setUp(self): - super(BanditCLIMainTests, self).setUp() - self.current_directory = os.getcwd() - - def tearDown(self): - super(BanditCLIMainTests, self).tearDown() - os.chdir(self.current_directory) - - def test_get_options_from_ini_no_ini_path_no_target(self): - # Test that no config options are loaded when no ini path or target - # directory are provided - self.assertIsNone(bandit._get_options_from_ini(None, [])) - - def test_get_options_from_ini_empty_directory_no_target(self): - # Test that no config options are loaded when an empty directory is - # provided as the ini path and no target directory is provided - ini_directory = self.useFixture(fixtures.TempDir()).path - self.assertIsNone(bandit._get_options_from_ini(ini_directory, [])) - - def test_get_options_from_ini_no_ini_path_no_bandit_files(self): - # Test that no config options are loaded when no ini path is provided - # and the target directory contains no bandit config files (.bandit) - target_directory = self.useFixture(fixtures.TempDir()).path - self.assertIsNone(bandit._get_options_from_ini(None, - [target_directory])) - - def test_get_options_from_ini_no_ini_path_multi_bandit_files(self): - # Test that bandit exits when no ini path is provided and the target - # directory(s) contain multiple bandit config files (.bandit) - target_directory = self.useFixture(fixtures.TempDir()).path - second_config = 'second_config_directory' - os.mkdir(os.path.join(target_directory, second_config)) - bandit_config_one = os.path.join(target_directory, '.bandit') - bandit_config_two = os.path.join(target_directory, second_config, - '.bandit') - bandit_files = [bandit_config_one, bandit_config_two] - for bandit_file in bandit_files: - with open(bandit_file, 'wt') as fd: - fd.write(bandit_config_content) - self.assertRaisesRegex(SystemExit, '2', bandit._get_options_from_ini, - None, [target_directory]) - - def test_init_extensions(self): - # Test that an extension loader manager is returned - self.assertEqual(ext_loader.MANAGER, bandit._init_extensions()) - - def test_log_option_source_arg_val(self): - # Test that the command argument value is returned when provided - arg_val = 'file' - ini_val = 'vuln' - option_name = 'aggregate' - self.assertEqual(arg_val, bandit._log_option_source(arg_val, ini_val, - option_name)) - - def test_log_option_source_ini_value(self): - # Test that the ini value is returned when no command argument is - # provided - ini_val = 'vuln' - option_name = 'aggregate' - self.assertEqual(ini_val, bandit._log_option_source(None, ini_val, - option_name)) - - def test_log_option_source_no_values(self): - # 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)) - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) - def test_main_config_unopenable(self): - # Test that bandit exits when a config file cannot be opened - with mock.patch('bandit.core.config.__init__') as mock_bandit_config: - mock_bandit_config.side_effect = utils.ConfigError('', '') - # assert a SystemExit with code 2 - self.assertRaisesRegex(SystemExit, '2', bandit.main) - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) - def test_main_invalid_config(self): - # Test that bandit exits when a config file contains invalid YAML - # content - with mock.patch('bandit.core.config.BanditConfig.__init__' - ) as mock_bandit_config: - mock_bandit_config.side_effect = utils.ConfigError('', '') - # assert a SystemExit with code 2 - self.assertRaisesRegex(SystemExit, '2', bandit.main) - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) - def test_main_handle_ini_options(self): - # Test that bandit handles cmdline args from a bandit.yaml file - temp_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(temp_directory) - with open('bandit.yaml', 'wt') as fd: - fd.write(bandit_config_content) - with mock.patch('bandit.cli.main._get_options_from_ini' - ) as mock_get_opts: - mock_get_opts.return_value = {"exclude": "/tmp", - "skips": "skip_test", - "tests": "some_test"} - - with mock.patch('bandit.cli.main.LOG.error') as err_mock: - # SystemExit with code 2 when test not found in profile - self.assertRaisesRegex(SystemExit, '2', bandit.main) - self.assertEqual(str(err_mock.call_args[0][0]), - 'Unknown test found in profile: some_test') - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-t', 'badID', - 'test']) - def test_main_unknown_tests(self): - # Test that bandit exits when an invalid test ID is provided - temp_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(temp_directory) - with open('bandit.yaml', 'wt') as fd: - fd.write(bandit_config_content) - # assert a SystemExit with code 2 - self.assertRaisesRegex(SystemExit, '2', bandit.main) - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-s', 'badID', - 'test']) - def test_main_unknown_skip_tests(self): - # Test that bandit exits when an invalid test ID is provided to skip - temp_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(temp_directory) - with open('bandit.yaml', 'wt') as fd: - fd.write(bandit_config_content) - # assert a SystemExit with code 2 - self.assertRaisesRegex(SystemExit, '2', bandit.main) - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-p', 'bad', - 'test']) - def test_main_profile_not_found(self): - # Test that bandit exits when an invalid profile name is provided - temp_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(temp_directory) - with open('bandit.yaml', 'wt') as fd: - fd.write(bandit_config_content) - # assert a SystemExit with code 2 - with mock.patch('bandit.cli.main.LOG.error') as err_mock: - self.assertRaisesRegex(SystemExit, '2', bandit.main) - self.assertEqual( - str(err_mock.call_args[0][0]), - 'Unable to find profile (bad) in config file: bandit.yaml') - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-b', 'base.json', - 'test']) - def test_main_baseline_ioerror(self): - # Test that bandit exits when encountering an IOError while reading - # baseline data - temp_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(temp_directory) - with open('bandit.yaml', 'wt') as fd: - fd.write(bandit_config_content) - with open('base.json', 'wt') as fd: - fd.write(bandit_baseline_content) - with mock.patch('bandit.core.manager.BanditManager.populate_baseline' - ) as mock_mgr_pop_bl: - mock_mgr_pop_bl.side_effect = IOError - # assert a SystemExit with code 2 - self.assertRaisesRegex(SystemExit, '2', bandit.main) - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-b', 'base.json', - '-f', 'csv', 'test']) - def test_main_invalid_output_format(self): - # Test that bandit exits when an invalid output format is selected - temp_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(temp_directory) - with open('bandit.yaml', 'wt') as fd: - fd.write(bandit_config_content) - with open('base.json', 'wt') as fd: - fd.write(bandit_baseline_content) - # assert a SystemExit with code 2 - self.assertRaisesRegex(SystemExit, '2', bandit.main) - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test', '-o', - 'output']) - def test_main_exit_with_results(self): - # Test that bandit exits when there are results - temp_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(temp_directory) - with open('bandit.yaml', 'wt') as fd: - fd.write(bandit_config_content) - with mock.patch('bandit.core.manager.BanditManager.results_count' - ) as mock_mgr_results_ct: - mock_mgr_results_ct.return_value = 1 - # assert a SystemExit with code 1 - self.assertRaisesRegex(SystemExit, '1', bandit.main) - - @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test', '-o', - 'output']) - def test_main_exit_with_no_results(self): - # Test that bandit exits when there are no results - temp_directory = self.useFixture(fixtures.TempDir()).path - os.chdir(temp_directory) - with open('bandit.yaml', 'wt') as fd: - fd.write(bandit_config_content) - with mock.patch('bandit.core.manager.BanditManager.results_count' - ) as mock_mgr_results_ct: - mock_mgr_results_ct.return_value = 0 - # assert a SystemExit with code 0 - self.assertRaisesRegex(SystemExit, '0', bandit.main) diff --git a/tests/unit/core/__init__.py b/tests/unit/core/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/core/test_blacklisting.py b/tests/unit/core/test_blacklisting.py deleted file mode 100644 index c100bd46..00000000 --- a/tests/unit/core/test_blacklisting.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- 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.core import blacklisting - -import testtools - - -class BlacklistingTests(testtools.TestCase): - def test_report_issue(self): - data = {'level': 'HIGH', 'message': 'test {name}', 'id': 'B000'} - - issue = blacklisting.report_issue(data, 'name') - issue_dict = issue.as_dict(with_code=False) - self.assertIsInstance(issue_dict, dict) - self.assertEqual('B000', issue_dict['test_id']) - self.assertEqual('HIGH', issue_dict['issue_severity']) - self.assertEqual('HIGH', issue_dict['issue_confidence']) - self.assertEqual('test name', issue_dict['issue_text']) - - def test_report_issue_defaults(self): - data = {'message': 'test {name}'} - - issue = blacklisting.report_issue(data, 'name') - issue_dict = issue.as_dict(with_code=False) - self.assertIsInstance(issue_dict, dict) - self.assertEqual('LEGACY', issue_dict['test_id']) - self.assertEqual('MEDIUM', issue_dict['issue_severity']) - self.assertEqual('HIGH', issue_dict['issue_confidence']) - self.assertEqual('test name', issue_dict['issue_text']) diff --git a/tests/unit/core/test_config.py b/tests/unit/core/test_config.py deleted file mode 100644 index afa4cda5..00000000 --- a/tests/unit/core/test_config.py +++ /dev/null @@ -1,264 +0,0 @@ -# Copyright 2015 IBM Corp. -# -# 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 os -import tempfile -import textwrap -import uuid - -import fixtures -import mock -import testtools - -from bandit.core import config -from bandit.core import utils - - -class TempFile(fixtures.Fixture): - def __init__(self, contents=None): - super(TempFile, self).__init__() - self.contents = contents - - def setUp(self): - super(TempFile, self).setUp() - - with tempfile.NamedTemporaryFile(mode='wt', delete=False) as f: - if self.contents: - f.write(self.contents) - - self.addCleanup(os.unlink, f.name) - - self.name = f.name - - -class TestInit(testtools.TestCase): - def test_settings(self): - # Can initialize a BanditConfig. - - example_key = uuid.uuid4().hex - example_value = self.getUniqueString() - contents = '%s: %s' % (example_key, example_value) - f = self.useFixture(TempFile(contents)) - b_config = config.BanditConfig(f.name) - - # After initialization, can get settings. - self.assertEqual('*.py', b_config.get_setting('plugin_name_pattern')) - - self.assertEqual({example_key: example_value}, b_config.config) - self.assertEqual(example_value, b_config.get_option(example_key)) - - def test_file_does_not_exist(self): - # When the config file doesn't exist, ConfigFileUnopenable is raised. - - cfg_file = os.path.join(os.getcwd(), 'notafile') - self.assertRaisesRegex(utils.ConfigError, cfg_file, - config.BanditConfig, cfg_file) - - def test_yaml_invalid(self): - # When the config yaml file isn't valid, sys.exit(2) is called. - - # The following is invalid because it starts a sequence and doesn't - # end it. - invalid_yaml = '- [ something' - f = self.useFixture(TempFile(invalid_yaml)) - self.assertRaisesRegex( - utils.ConfigError, f.name, config.BanditConfig, f.name) - - -class TestGetOption(testtools.TestCase): - def setUp(self): - super(TestGetOption, self).setUp() - - self.example_key = uuid.uuid4().hex - self.example_subkey = uuid.uuid4().hex - self.example_subvalue = uuid.uuid4().hex - sample_yaml = textwrap.dedent(""" - %s: - %s: %s - """ % (self.example_key, self.example_subkey, - self.example_subvalue)) - - f = self.useFixture(TempFile(sample_yaml)) - - self.b_config = config.BanditConfig(f.name) - - def test_levels(self): - # get_option with .-separated string. - - sample_option_name = '%s.%s' % (self.example_key, self.example_subkey) - self.assertEqual(self.example_subvalue, - self.b_config.get_option(sample_option_name)) - - def test_levels_not_exist(self): - # get_option when option name doesn't exist returns None. - - sample_option_name = '%s.%s' % (uuid.uuid4().hex, uuid.uuid4().hex) - self.assertIsNone(self.b_config.get_option(sample_option_name)) - - -class TestGetSetting(testtools.TestCase): - def setUp(self): - super(TestGetSetting, self).setUp() - test_yaml = 'key: value' - f = self.useFixture(TempFile(test_yaml)) - self.b_config = config.BanditConfig(f.name) - - def test_not_exist(self): - # get_setting() when the name doesn't exist returns None - - sample_setting_name = uuid.uuid4().hex - self.assertIsNone(self.b_config.get_setting(sample_setting_name)) - - -class TestConfigCompat(testtools.TestCase): - sample_yaml = textwrap.dedent(""" - profiles: - test_1: - include: - - any_other_function_with_shell_equals_true - - assert_used - exclude: - - test_2: - include: - - blacklist_calls - - test_3: - include: - - blacklist_imports - - test_4: - exclude: - - assert_used - - test_5: - exclude: - - blacklist_calls - - blacklist_imports - - test_6: - include: - - blacklist_calls - - exclude: - - blacklist_imports - - blacklist_calls: - bad_name_sets: - - pickle: - qualnames: [pickle.loads] - message: "{func} library appears to be in use." - - blacklist_imports: - bad_import_sets: - - telnet: - imports: [telnetlib] - level: HIGH - message: "{module} is considered insecure." - """) - - def setUp(self): - super(TestConfigCompat, self).setUp() - f = self.useFixture(TempFile(self.sample_yaml)) - self.config = config.BanditConfig(f.name) - - def test_converted_include(self): - profiles = self.config.get_option('profiles') - test = profiles['test_1'] - data = {'blacklist': {}, - 'exclude': set(), - 'include': set(['B101', 'B604'])} - - self.assertEqual(data, test) - - def test_converted_exclude(self): - profiles = self.config.get_option('profiles') - test = profiles['test_4'] - - self.assertEqual(set(['B101']), test['exclude']) - - def test_converted_blacklist_call_data(self): - profiles = self.config.get_option('profiles') - test = profiles['test_2'] - data = {'Call': [{'qualnames': ['telnetlib'], - 'level': 'HIGH', - 'message': '{name} is considered insecure.', - 'name': 'telnet'}]} - - self.assertEqual(data, test['blacklist']) - - def test_converted_blacklist_import_data(self): - profiles = self.config.get_option('profiles') - test = profiles['test_3'] - data = [{'message': '{name} library appears to be in use.', - 'name': 'pickle', - 'qualnames': ['pickle.loads']}] - - self.assertEqual(data, test['blacklist']['Call']) - self.assertEqual(data, test['blacklist']['Import']) - self.assertEqual(data, test['blacklist']['ImportFrom']) - - def test_converted_blacklist_call_test(self): - profiles = self.config.get_option('profiles') - test = profiles['test_2'] - - self.assertEqual(set(['B001']), test['include']) - - def test_converted_blacklist_import_test(self): - profiles = self.config.get_option('profiles') - test = profiles['test_3'] - - self.assertEqual(set(['B001']), test['include']) - - def test_converted_exclude_blacklist(self): - profiles = self.config.get_option('profiles') - test = profiles['test_5'] - - self.assertEqual(set(['B001']), test['exclude']) - - def test_deprecation_message(self): - msg = ("Config file '%s' contains deprecated legacy config data. " - "Please consider upgrading to the new config format. The tool " - "'bandit-config-generator' can help you with this. Support for " - "legacy configs will be removed in a future bandit version.") - - with mock.patch('bandit.core.config.LOG.warning') as m: - self.config._config = {"profiles": {}} - self.config.validate('') - self.assertEqual((msg, ''), m.call_args_list[0][0]) - - def test_blacklist_error(self): - msg = (" : Config file has an include or exclude reference to legacy " - "test '%s' but no configuration data for it. Configuration " - "data is required for this test. Please consider switching to " - "the new config file format, the tool " - "'bandit-config-generator' can help you with this.") - - for name in ["blacklist_call", - "blacklist_imports", - "blacklist_imports_func"]: - - self.config._config = ( - {"profiles": {"test": {"include": [name]}}}) - try: - self.config.validate('') - except utils.ConfigError as e: - self.assertEqual(msg % name, e.message) - - def test_bad_yaml(self): - f = self.useFixture(TempFile("[]")) - try: - self.config = config.BanditConfig(f.name) - except utils.ConfigError as e: - self.assertIn("Error parsing file.", e.message) diff --git a/tests/unit/core/test_context.py b/tests/unit/core/test_context.py deleted file mode 100644 index 9e19aced..00000000 --- a/tests/unit/core/test_context.py +++ /dev/null @@ -1,266 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2015 Red Hat, Inc. -# -# 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 mock -import six -import testtools - -from bandit.core import context - - -class ContextTests(testtools.TestCase): - - def test_context_create(self): - ref_context = mock.Mock() - new_context = context.Context(context_object=ref_context) - self.assertEqual(ref_context, new_context._context) - - new_context = context.Context() - self.assertIsInstance(new_context._context, dict) - - def test_repr(self): - ref_object = dict(spam='eggs') - expected_repr = ''.format(ref_object) - new_context = context.Context(context_object=ref_object) - self.assertEqual(expected_repr, repr(new_context)) - - @mock.patch('bandit.core.context.Context._get_literal_value') - def test_call_args(self, get_literal_value): - get_literal_value.return_value = 'eggs' - ref_call = mock.Mock() - ref_call.args = [mock.Mock(attr='spam'), 'eggs'] - ref_context = dict(call=ref_call) - new_context = context.Context(context_object=ref_context) - expected_args = ['spam', 'eggs'] - self.assertListEqual(expected_args, new_context.call_args) - - def test_call_args_count(self): - ref_call = mock.Mock() - ref_call.args = ['spam', 'eggs'] - ref_context = dict(call=ref_call) - new_context = context.Context(context_object=ref_context) - self.assertEqual(len(ref_call.args), new_context.call_args_count) - - ref_context = dict(call={}) - new_context = context.Context(context_object=ref_context) - self.assertIsNone(new_context.call_args_count) - - new_context = context.Context() - self.assertIsNone(new_context.call_args_count) - - def test_call_function_name(self): - expected_string = 'spam' - ref_context = dict(name=expected_string) - new_context = context.Context(context_object=ref_context) - self.assertEqual(expected_string, new_context.call_function_name) - - new_context = context.Context() - self.assertIsNone(new_context.call_function_name) - - def test_call_function_name_qual(self): - expected_string = 'spam' - ref_context = dict(qualname=expected_string) - new_context = context.Context(context_object=ref_context) - self.assertEqual(expected_string, new_context.call_function_name_qual) - - new_context = context.Context() - self.assertIsNone(new_context.call_function_name_qual) - - @mock.patch('bandit.core.context.Context._get_literal_value') - def test_call_keywords(self, get_literal_value): - get_literal_value.return_value = 'eggs' - ref_keyword1 = mock.Mock(arg='arg1', value=mock.Mock(attr='spam')) - ref_keyword2 = mock.Mock(arg='arg2', value='eggs') - ref_call = mock.Mock() - ref_call.keywords = [ref_keyword1, ref_keyword2] - ref_context = dict(call=ref_call) - new_context = context.Context(context_object=ref_context) - expected_dict = dict(arg1='spam', arg2='eggs') - self.assertDictEqual(expected_dict, new_context.call_keywords) - - ref_context = dict(call=None) - new_context = context.Context(context_object=ref_context) - self.assertIsNone(new_context.call_keywords) - - new_context = context.Context() - self.assertIsNone(new_context.call_keywords) - - def test_node(self): - expected_node = 'spam' - ref_context = dict(node=expected_node) - new_context = context.Context(context_object=ref_context) - self.assertEqual(expected_node, new_context.node) - - new_context = context.Context() - self.assertIsNone(new_context.node) - - def test_string_val(self): - expected_string = 'spam' - ref_context = dict(str=expected_string) - new_context = context.Context(context_object=ref_context) - self.assertEqual(expected_string, new_context.string_val) - - new_context = context.Context() - self.assertIsNone(new_context.string_val) - - def test_statement(self): - expected_string = 'spam' - ref_context = dict(statement=expected_string) - new_context = context.Context(context_object=ref_context) - self.assertEqual(expected_string, new_context.statement) - - new_context = context.Context() - self.assertIsNone(new_context.statement) - - @mock.patch('bandit.core.utils.get_qual_attr') - def test_function_def_defaults_qual(self, get_qual_attr): - get_qual_attr.return_value = 'spam' - ref_node = mock.Mock(args=mock.Mock(defaults=['spam'])) - ref_context = dict(node=ref_node, import_aliases=None) - new_context = context.Context(context_object=ref_context) - self.assertListEqual(['spam'], new_context.function_def_defaults_qual) - - ref_node = mock.Mock(args=mock.Mock(defaults=[])) - ref_context = dict(node=ref_node, import_aliases=None) - new_context = context.Context(context_object=ref_context) - self.assertListEqual([], new_context.function_def_defaults_qual) - - new_context = context.Context() - self.assertListEqual([], new_context.function_def_defaults_qual) - - def test__get_literal_value(self): - new_context = context.Context() - - value = ast.Num(42) - expected = value.n - self.assertEqual(expected, new_context._get_literal_value(value)) - - value = ast.Str('spam') - expected = value.s - self.assertEqual(expected, new_context._get_literal_value(value)) - - value = ast.List([ast.Str('spam'), ast.Num(42)], ast.Load()) - expected = [ast.Str('spam').s, ast.Num(42).n] - self.assertListEqual(expected, new_context._get_literal_value(value)) - - value = ast.Tuple([ast.Str('spam'), ast.Num(42)], ast.Load()) - expected = (ast.Str('spam').s, ast.Num(42).n) - self.assertTupleEqual(expected, new_context._get_literal_value(value)) - - value = ast.Set([ast.Str('spam'), ast.Num(42)]) - expected = set([ast.Str('spam').s, ast.Num(42).n]) - self.assertSetEqual(expected, new_context._get_literal_value(value)) - - value = ast.Dict(['spam', 'eggs'], [42, 'foo']) - expected = dict(spam=42, eggs='foo') - self.assertDictEqual(expected, new_context._get_literal_value(value)) - - value = ast.Ellipsis() - self.assertIsNone(new_context._get_literal_value(value)) - - value = ast.Name('spam', ast.Load()) - expected = value.id - self.assertEqual(expected, new_context._get_literal_value(value)) - - if six.PY3: - value = ast.NameConstant(True) - expected = str(value.value) - self.assertEqual(expected, new_context._get_literal_value(value)) - - if six.PY3: - value = ast.Bytes(b'spam') - expected = value.s - self.assertEqual(expected, new_context._get_literal_value(value)) - - self.assertIsNone(new_context._get_literal_value(None)) - - @mock.patch('bandit.core.context.Context.call_keywords', - new_callable=mock.PropertyMock) - def test_check_call_arg_value(self, call_keywords): - new_context = context.Context() - call_keywords.return_value = dict(spam='eggs') - self.assertTrue(new_context.check_call_arg_value('spam', 'eggs')) - self.assertTrue(new_context.check_call_arg_value('spam', - ['spam', 'eggs'])) - self.assertFalse(new_context.check_call_arg_value('spam', 'spam')) - self.assertFalse(new_context.check_call_arg_value('spam')) - self.assertFalse(new_context.check_call_arg_value('eggs')) - - new_context = context.Context() - self.assertIsNone(new_context.check_call_arg_value(None)) - - @mock.patch('bandit.core.context.Context.node', - new_callable=mock.PropertyMock) - def test_get_lineno_for_call_arg(self, node): - expected_lineno = 42 - keyword1 = mock.Mock(arg='spam', - value=mock.Mock(lineno=expected_lineno)) - node.return_value = mock.Mock(keywords=[keyword1]) - new_context = context.Context() - actual_lineno = new_context.get_lineno_for_call_arg('spam') - self.assertEqual(expected_lineno, actual_lineno) - - new_context = context.Context() - missing_lineno = new_context.get_lineno_for_call_arg('eggs') - self.assertIsNone(missing_lineno) - - def test_get_call_arg_at_position(self): - expected_arg = 'spam' - ref_call = mock.Mock() - ref_call.args = [ast.Str(expected_arg)] - ref_context = dict(call=ref_call) - new_context = context.Context(context_object=ref_context) - self.assertEqual(expected_arg, - new_context.get_call_arg_at_position(0)) - self.assertIsNone(new_context.get_call_arg_at_position(1)) - - ref_call = mock.Mock() - ref_call.args = [] - ref_context = dict(call=ref_call) - new_context = context.Context(context_object=ref_context) - self.assertIsNone(new_context.get_call_arg_at_position(0)) - - new_context = context.Context() - self.assertIsNone(new_context.get_call_arg_at_position(0)) - - def test_is_module_being_imported(self): - ref_context = dict(module='spam') - new_context = context.Context(context_object=ref_context) - self.assertTrue(new_context.is_module_being_imported('spam')) - self.assertFalse(new_context.is_module_being_imported('eggs')) - - new_context = context.Context() - self.assertFalse(new_context.is_module_being_imported('spam')) - - def test_is_module_imported_exact(self): - ref_context = dict(imports=['spam']) - new_context = context.Context(context_object=ref_context) - self.assertTrue(new_context.is_module_imported_exact('spam')) - self.assertFalse(new_context.is_module_imported_exact('eggs')) - - new_context = context.Context() - self.assertFalse(new_context.is_module_being_imported('spam')) - - def test_is_module_imported_like(self): - ref_context = dict(imports=[['spam'], ['eggs']]) - new_context = context.Context(context_object=ref_context) - self.assertTrue(new_context.is_module_imported_like('spam')) - self.assertFalse(new_context.is_module_imported_like('bacon')) - - new_context = context.Context() - self.assertFalse(new_context.is_module_imported_like('spam')) diff --git a/tests/unit/core/test_issue.py b/tests/unit/core/test_issue.py deleted file mode 100644 index 27640786..00000000 --- a/tests/unit/core/test_issue.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2015 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 mock -import testtools - -import bandit -from bandit.core import constants -from bandit.core import issue - - -class IssueTests(testtools.TestCase): - - def test_issue_create(self): - new_issue = _get_issue_instance() - self.assertIsInstance(new_issue, issue.Issue) - - def test_issue_str(self): - test_issue = _get_issue_instance() - self.assertEqual( - ("Issue: 'Test issue' from B999:bandit_plugin: Severity: MEDIUM " - "Confidence: MEDIUM at code.py:1"), - str(test_issue) - ) - - def test_issue_as_dict(self): - test_issue = _get_issue_instance() - test_issue_dict = test_issue.as_dict(with_code=False) - self.assertIsInstance(test_issue_dict, dict) - self.assertEqual('code.py', test_issue_dict['filename']) - self.assertEqual('bandit_plugin', test_issue_dict['test_name']) - self.assertEqual('B999', test_issue_dict['test_id']) - self.assertEqual('MEDIUM', test_issue_dict['issue_severity']) - self.assertEqual('MEDIUM', test_issue_dict['issue_confidence']) - self.assertEqual('Test issue', test_issue_dict['issue_text']) - self.assertEqual(1, test_issue_dict['line_number']) - self.assertEqual([], test_issue_dict['line_range']) - - def test_issue_filter_severity(self): - levels = [bandit.LOW, bandit.MEDIUM, bandit.HIGH] - issues = [_get_issue_instance(l, bandit.HIGH) for l in levels] - - for level in levels: - rank = constants.RANKING.index(level) - for i in issues: - test = constants.RANKING.index(i.severity) - result = i.filter(level, bandit.UNDEFINED) - self.assertTrue((test >= rank) == result) - - def test_issue_filter_confidence(self): - levels = [bandit.LOW, bandit.MEDIUM, bandit.HIGH] - issues = [_get_issue_instance(bandit.HIGH, l) for l in levels] - - for level in levels: - rank = constants.RANKING.index(level) - for i in issues: - test = constants.RANKING.index(i.confidence) - result = i.filter(bandit.UNDEFINED, level) - self.assertTrue((test >= rank) == result) - - def test_matches_issue(self): - issue_a = _get_issue_instance() - - issue_b = _get_issue_instance(severity=bandit.HIGH) - - issue_c = _get_issue_instance(confidence=bandit.LOW) - - issue_d = _get_issue_instance() - issue_d.text = 'ABCD' - - issue_e = _get_issue_instance() - issue_e.fname = 'file1.py' - - issue_f = issue_a - - issue_g = _get_issue_instance() - issue_g.test = 'ZZZZ' - - issue_h = issue_a - issue_h.lineno = 12345 - - # positive tests - self.assertEqual(issue_a, issue_a) - self.assertEqual(issue_a, issue_f) - self.assertEqual(issue_f, issue_a) - - # severity doesn't match - self.assertNotEqual(issue_a, issue_b) - - # confidence doesn't match - self.assertNotEqual(issue_a, issue_c) - - # text doesn't match - self.assertNotEqual(issue_a, issue_d) - - # filename doesn't match - self.assertNotEqual(issue_a, issue_e) - - # plugin name doesn't match - self.assertNotEqual(issue_a, issue_g) - - # line number doesn't match but should pass because we don't test that - self.assertEqual(issue_a, issue_h) - - @mock.patch('linecache.getline') - def test_get_code(self, getline): - getline.return_value = b'\x08\x30' - new_issue = issue.Issue(bandit.MEDIUM, lineno=1) - - try: - new_issue.get_code() - except UnicodeDecodeError: - self.fail('Bytes not properly decoded in issue.get_code()') - - -def _get_issue_instance(severity=bandit.MEDIUM, confidence=bandit.MEDIUM): - new_issue = issue.Issue(severity, confidence, 'Test issue') - new_issue.fname = 'code.py' - new_issue.test = 'bandit_plugin' - new_issue.test_id = 'B999' - new_issue.lineno = 1 - return new_issue diff --git a/tests/unit/core/test_manager.py b/tests/unit/core/test_manager.py deleted file mode 100644 index 161b55c7..00000000 --- a/tests/unit/core/test_manager.py +++ /dev/null @@ -1,303 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2015 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 os -import sys - -import fixtures -import mock -import testtools - -from bandit.core import config -from bandit.core import constants -from bandit.core import issue -from bandit.core import manager - - -class ManagerTests(testtools.TestCase): - - def _get_issue_instance(self, sev=constants.MEDIUM, conf=constants.MEDIUM): - new_issue = issue.Issue(sev, conf, 'Test issue') - new_issue.fname = 'code.py' - new_issue.test = 'bandit_plugin' - new_issue.lineno = 1 - return new_issue - - def setUp(self): - super(ManagerTests, self).setUp() - self.profile = {} - self.profile['include'] = { - 'any_other_function_with_shell_equals_true', - 'assert_used'} - - self.config = config.BanditConfig() - self.manager = manager.BanditManager(config=self.config, - agg_type='file', - debug=False, - verbose=False) - - def test_create_manager(self): - # make sure we can create a manager - 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 - m = manager.BanditManager(config=self.config, agg_type='file', - debug=False, verbose=False, - profile=self.profile) - - 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*'])) - self.assertFalse(manager._matches_glob_list('test', ['*fes*'])) - - def test_is_file_included(self): - a = manager._is_file_included(path='a.py', included_globs=['*.py'], - excluded_path_strings='', - enforce_glob=True) - - b = manager._is_file_included(path='a.dd', included_globs=['*.py'], - excluded_path_strings='', - enforce_glob=False) - - c = manager._is_file_included(path='a.py', included_globs=['*.py'], - excluded_path_strings='a.py', - enforce_glob=True) - - d = manager._is_file_included(path='a.dd', included_globs=['*.py'], - excluded_path_strings='', - enforce_glob=True) - self.assertTrue(a) - self.assertTrue(b) - self.assertFalse(c) - self.assertFalse(d) - - @mock.patch('os.walk') - def test_get_files_from_dir(self, os_walk): - os_walk.return_value = [ - ('/', ('a'), ()), - ('/a', (), ('a.py', 'b.py', 'c.ww')) - ] - - inc, exc = manager._get_files_from_dir(files_dir='', - included_globs=['*.py'], - excluded_path_strings=None) - - 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 - baseline_data = """{ - "results": [ - { - "code": "test code", - "filename": "example_file.py", - "issue_severity": "low", - "issue_confidence": "low", - "issue_text": "test issue", - "test_name": "some_test", - "test_id": "x", - "line_number": "n", - "line_range": "n-m" - } - ] - } - """ - issue_dictionary = {"code": "test code", "filename": "example_file.py", - "issue_severity": "low", "issue_confidence": "low", - "issue_text": "test issue", "test_name": - "some_test", "test_id": "x", "line_number": "n", - "line_range": "n-m"} - baseline_items = [issue.issue_from_dict(issue_dictionary)] - self.manager.populate_baseline(baseline_data) - self.assertEqual(baseline_items, self.manager.baseline) - - @mock.patch('logging.Logger.warning') - def test_populate_baseline_invalid_json(self, mock_logger_warning): - # Test populate_baseline with invalid JSON content - baseline_data = """{"data": "bad"}""" - self.manager.populate_baseline(baseline_data) - # Default value for manager.baseline is [] - self.assertEqual([], self.manager.baseline) - self.assertTrue(mock_logger_warning.called) - - def test_results_count(self): - levels = [constants.LOW, constants.MEDIUM, constants.HIGH] - self.manager.results = ( - [issue.Issue(severity=l, confidence=l) for l in levels]) - - r = [self.manager.results_count(sev_filter=l, conf_filter=l) - for l in levels] - - self.assertEqual([3, 2, 1], r) - - def test_output_results_invalid_format(self): - # Test that output_results succeeds given an invalid format - temp_directory = self.useFixture(fixtures.TempDir()).path - lines = 5 - sev_level = constants.LOW - conf_level = constants.LOW - output_filename = os.path.join(temp_directory, "_temp_output") - output_format = "invalid" - tmp_file = open(output_filename, 'w') - self.manager.output_results(lines, sev_level, conf_level, tmp_file, - output_format) - if sys.stdout.isatty(): - self.assertFalse(os.path.isfile(output_filename)) - else: - self.assertTrue(os.path.isfile(output_filename)) - - def test_output_results_valid_format(self): - # Test that output_results succeeds given a valid format - temp_directory = self.useFixture(fixtures.TempDir()).path - lines = 5 - sev_level = constants.LOW - conf_level = constants.LOW - output_filename = os.path.join(temp_directory, "_temp_output.txt") - output_format = "txt" - tmp_file = open(output_filename, 'w') - self.manager.output_results(lines, sev_level, conf_level, tmp_file, - output_format) - self.assertTrue(os.path.isfile(output_filename)) - - @mock.patch('os.path.isdir') - 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) - - @mock.patch('os.path.isdir') - def test_discover_files_recurse_files(self, isdir): - isdir.return_value = True - 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(['files'], self.manager.files_list) - self.assertEqual(['excluded'], self.manager.excluded_files) - - @mock.patch('os.path.isdir') - def test_discover_files_exclude(self, isdir): - isdir.return_value = False - 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(['thing'], self.manager.excluded_files) - - @mock.patch('os.path.isdir') - def test_discover_files_exclude_cmdline(self, isdir): - isdir.return_value = False - with mock.patch.object(manager, '_is_file_included') as m: - self.manager.discover_files(['a', 'b', 'c'], True, - excluded_paths='a,b') - m.assert_called_with('c', ['*.py', '*.pyw'], ['a', 'b'], - enforce_glob=False) - - @mock.patch('os.path.isdir') - def test_discover_files_include(self, isdir): - isdir.return_value = False - with mock.patch.object(manager, '_is_file_included') as m: - m.return_value = True - self.manager.discover_files(['thing'], True) - 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 - temp_directory = self.useFixture(fixtures.TempDir()).path - some_file = os.path.join(temp_directory, 'some_code_file.py') - with open(some_file, 'wt') as fd: - fd.write('some_code = x + 1') - self.manager.files_list = [some_file] - with mock.patch('bandit.core.metrics.Metrics.count_issues' - ) as mock_count_issues: - mock_count_issues.side_effect = KeyboardInterrupt - # assert a SystemExit with code 2 - self.assertRaisesRegex(SystemExit, '2', self.manager.run_tests) - - def test_run_tests_ioerror(self): - # Test that a file name is skipped and added to the manager.skipped - # list when there is an IOError attempting to open/read the file - temp_directory = self.useFixture(fixtures.TempDir()).path - no_such_file = os.path.join(temp_directory, 'no_such_file.py') - self.manager.files_list = [no_such_file] - self.manager.run_tests() - # since the file name and the IOError.strerror text are added to - # manager.skipped, we convert skipped to str to find just the file name - # since IOError is not constant - self.assertIn(no_such_file, str(self.manager.skipped)) - - def test_compare_baseline(self): - issue_a = self._get_issue_instance() - issue_a.fname = 'file1.py' - - issue_b = self._get_issue_instance() - issue_b.fname = 'file2.py' - - issue_c = self._get_issue_instance(sev=constants.HIGH) - issue_c.fname = 'file1.py' - - # issue c is in results, not in baseline - self.assertEqual( - [issue_c], - manager._compare_baseline_results([issue_a, issue_b], - [issue_a, issue_b, issue_c])) - - # baseline and results are the same - self.assertEqual( - [], - manager._compare_baseline_results([issue_a, issue_b, issue_c], - [issue_a, issue_b, issue_c])) - - # results are better than baseline - self.assertEqual( - [], - manager._compare_baseline_results([issue_a, issue_b, issue_c], - [issue_a, issue_b])) - - def test_find_candidate_matches(self): - issue_a = self._get_issue_instance() - issue_b = self._get_issue_instance() - - issue_c = self._get_issue_instance() - issue_c.fname = 'file1.py' - - # issue a and b are the same, both should be returned as candidates - self.assertEqual({issue_a: [issue_a, issue_b]}, - manager._find_candidate_matches([issue_a], - [issue_a, issue_b])) - - # issue a and c are different, only a should be returned - self.assertEqual({issue_a: [issue_a]}, - manager._find_candidate_matches([issue_a], - [issue_a, issue_c])) - - # c doesn't match a, empty list should be returned - self.assertEqual({issue_a: []}, - manager._find_candidate_matches([issue_a], [issue_c])) - - # a and b match, a and b should both return a and b candidates - self.assertEqual( - {issue_a: [issue_a, issue_b], issue_b: [issue_a, issue_b]}, - manager._find_candidate_matches([issue_a, issue_b], - [issue_a, issue_b, issue_c])) diff --git a/tests/unit/core/test_meta_ast.py b/tests/unit/core/test_meta_ast.py deleted file mode 100644 index 452832d7..00000000 --- a/tests/unit/core/test_meta_ast.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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 six -import testtools - -from bandit.core import meta_ast - - -class BanditMetaAstTests(testtools.TestCase): - - def setUp(self): - super(BanditMetaAstTests, self).setUp() - self.b_meta_ast = meta_ast.BanditMetaAst() - self.node = 'fake_node' - self.parent_id = 'fake_parent_id' - self.depth = 1 - self.b_meta_ast.add_node(self.node, self.parent_id, self.depth) - self.node_id = hex(id(self.node)) - - def test_add_node(self): - expected = {'raw': self.node, - 'parent_id': self.parent_id, - 'depth': self.depth} - self.assertEqual(expected, self.b_meta_ast.nodes[self.node_id]) - - def test_str(self): - node = self.b_meta_ast.nodes[self.node_id] - expected = 'Node: %s\n\t%s\nLength: 1\n' % (self.node_id, node) - self.assertEqual(expected, six.text_type(self.b_meta_ast)) diff --git a/tests/unit/core/test_test_set.py b/tests/unit/core/test_test_set.py deleted file mode 100644 index a7ee7126..00000000 --- a/tests/unit/core/test_test_set.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright (c) 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 mock -from stevedore import extension -import testtools - -from bandit.blacklists import utils -from bandit.core import extension_loader -from bandit.core import test_properties as test -from bandit.core import test_set - - -@test.checks('Str') -@test.test_id('B000') -def test_plugin(): - 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( - 'marshal', 'B302', ['marshal.load', 'marshal.loads'], - 'Deserialization with the marshal module is possibly dangerous.' - )) - - return {'Import': sets, 'ImportFrom': sets, 'Call': sets} - - -class BanditTestSetTests(testtools.TestCase): - def _make_test_manager(self, plugin): - return extension.ExtensionManager.make_test_instance( - [extension.Extension('test_plugin', None, test_plugin, None)]) - - def setUp(self): - super(BanditTestSetTests, self).setUp() - mngr = self._make_test_manager(mock.Mock) - self.patchExtMan = mock.patch('stevedore.extension.ExtensionManager') - self.mockExtMan = self.patchExtMan.start() - self.mockExtMan.return_value = mngr - self.old_ext_man = extension_loader.MANAGER - extension_loader.MANAGER = extension_loader.Manager() - self.config = mock.MagicMock() - self.config.get_setting.return_value = None - - def tearDown(self): - self.patchExtMan.stop() - super(BanditTestSetTests, self).tearDown() - extension_loader.MANAGER = self.old_ext_man - - def test_has_defaults(self): - ts = test_set.BanditTestSet(self.config) - 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(1, len(ts.get_tests('Str'))) - - def test_profile_exclude_id(self): - profile = {'exclude': ['B000']} - ts = test_set.BanditTestSet(self.config, profile) - 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(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(1, len(ts.get_tests('Str'))) - - def test_profile_has_builtin_blacklist(self): - ts = test_set.BanditTestSet(self.config) - 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(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(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(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(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(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']} - ts = test_set.BanditTestSet(self.config, profile) - - # 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(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( - 'marshal', 'B302', ['marshal.load', 'marshal.loads'], - ('Deserialization with the marshal module is possibly ' - 'dangerous.'))] - - profile = {'include': ['B001'], 'blacklist': {'Call': data}} - - ts = test_set.BanditTestSet(self.config, profile) - blacklist = ts.get_tests('Call')[0] - - self.assertNotIn('Import', blacklist._config) - self.assertNotIn('ImportFrom', blacklist._config) - self.assertEqual(1, len(blacklist._config['Call'])) diff --git a/tests/unit/core/test_util.py b/tests/unit/core/test_util.py deleted file mode 100644 index 82b573ce..00000000 --- a/tests/unit/core/test_util.py +++ /dev/null @@ -1,305 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Copyright 2014 Hewlett-Packard Development Company, L.P. -# Copyright 2015 Nebula, Inc. -# -# 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 os -import shutil -import sys -import tempfile - -import testtools - -from bandit.core import utils as b_utils - - -def _touch(path): - '''Create an empty file at ``path``.''' - newf = open(path, 'w') - newf.close() - - -class UtilTests(testtools.TestCase): - '''This set of tests exercises bandit.core.util functions.''' - - def setUp(self): - super(UtilTests, self).setUp() - self._setup_get_module_qualname_from_path() - - def _setup_get_module_qualname_from_path(self): - '''Setup a fake directory for testing get_module_qualname_from_path(). - - Create temporary directory and then create fake .py files - within directory structure. We setup test cases for - a typical module, a path misssing a middle __init__.py, - no __init__.py anywhere in path, symlinking .py files. - ''' - - self.tempdir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, self.tempdir) - self.reltempdir = os.path.relpath(self.tempdir) - - # good/a/b/c/test_typical.py - os.makedirs(os.path.join( - self.tempdir, 'good', 'a', 'b', 'c'), 0o755) - _touch(os.path.join(self.tempdir, 'good', '__init__.py')) - _touch(os.path.join(self.tempdir, 'good', 'a', '__init__.py')) - _touch(os.path.join( - self.tempdir, 'good', 'a', 'b', '__init__.py')) - _touch(os.path.join( - self.tempdir, 'good', 'a', 'b', 'c', '__init__.py')) - _touch(os.path.join( - self.tempdir, 'good', 'a', 'b', 'c', 'test_typical.py')) - - # missingmid/a/b/c/test_missingmid.py - os.makedirs(os.path.join( - self.tempdir, 'missingmid', 'a', 'b', 'c'), 0o755) - _touch(os.path.join(self.tempdir, 'missingmid', '__init__.py')) - # no missingmid/a/__init__.py - _touch(os.path.join( - self.tempdir, 'missingmid', 'a', 'b', '__init__.py')) - _touch(os.path.join( - self.tempdir, 'missingmid', 'a', 'b', 'c', '__init__.py')) - _touch(os.path.join( - self.tempdir, 'missingmid', 'a', 'b', 'c', 'test_missingmid.py')) - - # missingend/a/b/c/test_missingend.py - os.makedirs(os.path.join( - self.tempdir, 'missingend', 'a', 'b', 'c'), 0o755) - _touch(os.path.join( - self.tempdir, 'missingend', '__init__.py')) - _touch(os.path.join( - self.tempdir, 'missingend', 'a', 'b', '__init__.py')) - # no missingend/a/b/c/__init__.py - _touch(os.path.join( - self.tempdir, 'missingend', 'a', 'b', 'c', 'test_missingend.py')) - - # syms/a/bsym/c/test_typical.py - os.makedirs(os.path.join(self.tempdir, 'syms', 'a'), 0o755) - _touch(os.path.join(self.tempdir, 'syms', '__init__.py')) - _touch(os.path.join(self.tempdir, 'syms', 'a', '__init__.py')) - os.symlink(os.path.join(self.tempdir, 'good', 'a', 'b'), - os.path.join(self.tempdir, 'syms', 'a', 'bsym')) - - def test_get_module_qualname_from_path_abs_typical(self): - '''Test get_module_qualname_from_path with typical absolute paths.''' - - name = b_utils.get_module_qualname_from_path(os.path.join( - self.tempdir, 'good', 'a', 'b', 'c', 'test_typical.py')) - self.assertEqual('good.a.b.c.test_typical', name) - - def test_get_module_qualname_from_path_with_dot(self): - '''Test get_module_qualname_from_path with a "." .''' - - name = b_utils.get_module_qualname_from_path(os.path.join( - '.', '__init__.py')) - - self.assertEqual('__init__', name) - - def test_get_module_qualname_from_path_abs_missingmid(self): - # Test get_module_qualname_from_path with missing module - # __init__.py - - name = b_utils.get_module_qualname_from_path(os.path.join( - self.tempdir, 'missingmid', 'a', 'b', 'c', - 'test_missingmid.py')) - self.assertEqual('b.c.test_missingmid', name) - - def test_get_module_qualname_from_path_abs_missingend(self): - # Test get_module_qualname_from_path with no __init__.py - # last dir''' - - name = b_utils.get_module_qualname_from_path(os.path.join( - self.tempdir, 'missingend', 'a', 'b', 'c', - 'test_missingend.py')) - self.assertEqual('test_missingend', name) - - def test_get_module_qualname_from_path_abs_syms(self): - '''Test get_module_qualname_from_path with symlink in path.''' - - name = b_utils.get_module_qualname_from_path(os.path.join( - self.tempdir, 'syms', 'a', 'bsym', 'c', 'test_typical.py')) - self.assertEqual('syms.a.bsym.c.test_typical', name) - - def test_get_module_qualname_from_path_rel_typical(self): - '''Test get_module_qualname_from_path with typical relative paths.''' - - name = b_utils.get_module_qualname_from_path(os.path.join( - self.reltempdir, 'good', 'a', 'b', 'c', 'test_typical.py')) - self.assertEqual('good.a.b.c.test_typical', name) - - def test_get_module_qualname_from_path_rel_missingmid(self): - # Test get_module_qualname_from_path with module __init__.py - # missing and relative paths - - name = b_utils.get_module_qualname_from_path(os.path.join( - self.reltempdir, 'missingmid', 'a', 'b', 'c', - 'test_missingmid.py')) - self.assertEqual('b.c.test_missingmid', name) - - def test_get_module_qualname_from_path_rel_missingend(self): - # Test get_module_qualname_from_path with __init__.py missing from - # last dir and using relative paths - - name = b_utils.get_module_qualname_from_path(os.path.join( - self.reltempdir, 'missingend', 'a', 'b', 'c', - 'test_missingend.py')) - self.assertEqual('test_missingend', name) - - def test_get_module_qualname_from_path_rel_syms(self): - '''Test get_module_qualname_from_path with symbolic relative paths.''' - - name = b_utils.get_module_qualname_from_path(os.path.join( - self.reltempdir, 'syms', 'a', 'bsym', 'c', 'test_typical.py')) - self.assertEqual('syms.a.bsym.c.test_typical', name) - - def test_get_module_qualname_from_path_sys(self): - '''Test get_module_qualname_from_path with system module paths.''' - - name = b_utils.get_module_qualname_from_path(os.__file__) - self.assertEqual('os', name) - - # This will fail because of magic for os.path. Not sure how to fix. - # name = b_utils.get_module_qualname_from_path(os.path.__file__) - # self.assertEqual(name, 'os.path') - - def test_get_module_qualname_from_path_invalid_path(self): - '''Test get_module_qualname_from_path with invalid path.''' - - name = b_utils.get_module_qualname_from_path('/a/b/c/d/e.py') - self.assertEqual('e', name) - - def test_get_module_qualname_from_path_dir(self): - '''Test get_module_qualname_from_path with dir path.''' - - self.assertRaises(b_utils.InvalidModulePath, - b_utils.get_module_qualname_from_path, '/tmp/') - - def test_namespace_path_join(self): - p = b_utils.namespace_path_join('base1.base2', 'name') - self.assertEqual('base1.base2.name', p) - - def test_namespace_path_split(self): - (head, tail) = b_utils.namespace_path_split('base1.base2.name') - self.assertEqual('base1.base2', head) - self.assertEqual('name', tail) - - def test_get_call_name1(self): - '''Gets a qualified call name.''' - tree = ast.parse('a.b.c.d(x,y)').body[0].value - name = b_utils.get_call_name(tree, {}) - self.assertEqual('a.b.c.d', name) - - def test_get_call_name2(self): - '''Gets qualified call name and resolves aliases.''' - tree = ast.parse('a.b.c.d(x,y)').body[0].value - - name = b_utils.get_call_name(tree, {'a': 'alias.x.y'}) - self.assertEqual('alias.x.y.b.c.d', name) - - name = b_utils.get_call_name(tree, {'a.b': 'alias.x.y'}) - self.assertEqual('alias.x.y.c.d', name) - - name = b_utils.get_call_name(tree, {'a.b.c.d': 'alias.x.y'}) - self.assertEqual('alias.x.y', name) - - def test_get_call_name3(self): - '''Getting name for a complex call.''' - tree = ast.parse('a.list[0](x,y)').body[0].value - name = b_utils._get_attr_qual_name(tree, {}) - self.assertEqual('', name) - # TODO(ljfisher) At best we might be able to get: - # self.assertEqual(name, 'a.list[0]') - - def test_linerange(self): - self.test_file = open("./examples/jinja2_templating.py") - self.tree = ast.parse(self.test_file.read()) - # Check linerange returns corrent number of lines - line = self.tree.body[8] - lrange = b_utils.linerange(line) - - # line 9 should be three lines long - self.assertEqual(3, len(lrange)) - - # the range should be the correct line numbers - self.assertEqual([11, 12, 13], list(lrange)) - - def test_path_for_function(self): - path = b_utils.get_path_for_function(b_utils.get_path_for_function) - self.assertEqual(path, b_utils.__file__) - - def test_path_for_function_no_file(self): - self.assertIsNone(b_utils.get_path_for_function(sys.settrace)) - - def test_path_for_function_no_module(self): - self.assertIsNone(b_utils.get_path_for_function(1)) - - def test_escaped_representation_simple(self): - res = b_utils.escaped_bytes_representation(b"ascii") - self.assertEqual(res, b"ascii") - - def test_escaped_representation_valid_not_printable(self): - res = b_utils.escaped_bytes_representation(b"\u0000") - self.assertEqual(res, b"\\x00") - - def test_escaped_representation_invalid(self): - res = b_utils.escaped_bytes_representation(b"\uffff") - self.assertEqual(res, b"\\uffff") - - def test_escaped_representation_mixed(self): - res = b_utils.escaped_bytes_representation(b"ascii\u0000\uffff") - self.assertEqual(res, b"ascii\\x00\\uffff") - - def test_deepgetattr(self): - a = type('', (), {}) - a.b = type('', (), {}) - a.b.c = type('', (), {}) - a.b.c.d = 'deep value' - a.b.c.d2 = 'deep value 2' - a.b.c.e = 'a.b.c' - self.assertEqual('deep value', b_utils.deepgetattr(a.b.c, 'd')) - self.assertEqual('deep value 2', b_utils.deepgetattr(a.b.c, 'd2')) - self.assertEqual('a.b.c', b_utils.deepgetattr(a.b.c, 'e')) - self.assertEqual('deep value', b_utils.deepgetattr(a, 'b.c.d')) - self.assertEqual('deep value 2', b_utils.deepgetattr(a, 'b.c.d2')) - self.assertRaises(AttributeError, b_utils.deepgetattr, a.b, 'z') - - def test_parse_ini_file(self): - - tests = [{'content': "[bandit]\nexclude=/abc,/def", - 'expected': {'exclude': '/abc,/def'}}, - - {'content': '[Blabla]\nsomething=something', - 'expected': None}] - - with tempfile.NamedTemporaryFile('r+') as t: - for test in tests: - f = open(t.name, 'w') - f.write(test['content']) - f.close() - - self.assertEqual(b_utils.parse_ini_file(t.name), - test['expected']) - - def test_check_ast_node_good(self): - node = b_utils.check_ast_node("Call") - self.assertEqual("Call", node) - - def test_check_ast_node_bad_node(self): - self.assertRaises(TypeError, b_utils.check_ast_node, 'Derp') - - def test_check_ast_node_bad_type(self): - self.assertRaises(TypeError, b_utils.check_ast_node, 'walk') diff --git a/tests/unit/formatters/__init__.py b/tests/unit/formatters/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/formatters/test_csv.py b/tests/unit/formatters/test_csv.py deleted file mode 100644 index 2926c33f..00000000 --- a/tests/unit/formatters/test_csv.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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 csv -import tempfile - -import six -import testtools - -import bandit -from bandit.core import config -from bandit.core import issue -from bandit.core import manager -from bandit.formatters import csv as b_csv - - -class CsvFormatterTests(testtools.TestCase): - - def setUp(self): - super(CsvFormatterTests, self).setUp() - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.context = {'filename': self.tmp_fname, - 'lineno': 4, - 'linerange': [4]} - self.check_name = 'hardcoded_bind_all_interfaces' - self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, - 'Possible binding to all interfaces.') - self.manager.out_file = self.tmp_fname - - self.issue.fname = self.context['filename'] - self.issue.lineno = self.context['lineno'] - self.issue.linerange = self.context['linerange'] - self.issue.test = self.check_name - - self.manager.results.append(self.issue) - - def test_report(self): - tmp_file = open(self.tmp_fname, 'w') - b_csv.report(self.manager, tmp_file, self.issue.severity, - self.issue.confidence) - - with open(self.tmp_fname) as f: - reader = csv.DictReader(f) - data = six.next(reader) - self.assertEqual(self.tmp_fname, data['filename']) - self.assertEqual(self.issue.severity, data['issue_severity']) - self.assertEqual(self.issue.confidence, data['issue_confidence']) - self.assertEqual(self.issue.text, data['issue_text']) - self.assertEqual(six.text_type(self.context['lineno']), - data['line_number']) - self.assertEqual(six.text_type(self.context['linerange']), - data['line_range']) - self.assertEqual(self.check_name, data['test_name']) diff --git a/tests/unit/formatters/test_html.py b/tests/unit/formatters/test_html.py deleted file mode 100644 index 928d0be0..00000000 --- a/tests/unit/formatters/test_html.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright (c) 2015 Rackspace, Inc. -# Copyright (c) 2015 Hewlett Packard Enterprise -# -# 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 collections -import tempfile - -import bs4 -import mock -import testtools - -import bandit -from bandit.core import config -from bandit.core import issue -from bandit.core import manager -from bandit.formatters import html as b_html - - -class HtmlFormatterTests(testtools.TestCase): - - def setUp(self): - super(HtmlFormatterTests, self).setUp() - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - - self.manager.out_file = self.tmp_fname - - def test_report_with_skipped(self): - self.manager.skipped = [('abc.py', 'File is bad')] - - tmp_file = open(self.tmp_fname, 'w') - b_html.report( - self.manager, tmp_file, bandit.LOW, bandit.LOW) - - with open(self.tmp_fname) as f: - soup = bs4.BeautifulSoup(f.read(), 'html.parser') - skipped = soup.find_all('div', id='skipped')[0] - - self.assertEqual(1, len(soup.find_all('div', id='skipped'))) - self.assertIn('abc.py', skipped.text) - self.assertIn('File is bad', skipped.text) - - @mock.patch('bandit.core.issue.Issue.get_code') - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_report_contents(self, get_issue_list, get_code): - self.manager.metrics.data['_totals'] = {'loc': 1000, 'nosec': 50} - - issue_a = _get_issue_instance(severity=bandit.LOW) - issue_a.fname = 'abc.py' - issue_a.test = 'AAAAAAA' - issue_a.text = 'BBBBBBB' - issue_a.confidence = 'CCCCCCC' - # don't need to test severity, it determines the color which we're - # testing separately - - issue_b = _get_issue_instance(severity=bandit.MEDIUM) - issue_c = _get_issue_instance(severity=bandit.HIGH) - - issue_x = _get_issue_instance() - get_code.return_value = 'some code' - - issue_y = _get_issue_instance() - - get_issue_list.return_value = collections.OrderedDict( - [(issue_a, [issue_x, issue_y]), - (issue_b, [issue_x]), (issue_c, [issue_y])]) - - tmp_file = open(self.tmp_fname, 'w') - b_html.report( - self.manager, tmp_file, bandit.LOW, bandit.LOW) - - with open(self.tmp_fname) as f: - soup = bs4.BeautifulSoup(f.read(), 'html.parser') - - self.assertEqual('1000', soup.find_all('span', id='loc')[0].text) - self.assertEqual('50', soup.find_all('span', id='nosec')[0].text) - - issue1 = soup.find_all('div', id='issue-0')[0] - issue2 = soup.find_all('div', id='issue-1')[0] - issue3 = soup.find_all('div', id='issue-2')[0] - - # make sure the class has been applied properly - self.assertEqual(1, len(issue1.find_all( - 'div', {'class': 'issue-sev-low'}))) - - self.assertEqual(1, len(issue2.find_all( - 'div', {'class': 'issue-sev-medium'}))) - - self.assertEqual(1, len(issue3.find_all( - 'div', {'class': 'issue-sev-high'}))) - - # issue1 has a candidates section with 2 candidates in it - self.assertEqual(1, len(issue1.find_all('div', - {'class': 'candidates'}))) - self.assertEqual(2, len(issue1.find_all('div', - {'class': 'candidate'}))) - - # issue2 doesn't have candidates - self.assertEqual(0, len(issue2.find_all('div', - {'class': 'candidates'}))) - self.assertEqual(0, len(issue2.find_all('div', - {'class': 'candidate'}))) - - # issue1 doesn't have code issue 2 and 3 do - self.assertEqual(0, len(issue1.find_all('div', {'class': 'code'}))) - self.assertEqual(1, len(issue2.find_all('div', {'class': 'code'}))) - self.assertEqual(1, len(issue3.find_all('div', {'class': 'code'}))) - - # issue2 code and issue1 first candidate have code - element1 = issue1.find_all('div', {'class': 'candidate'}) - self.assertIn('some code', element1[0].text) - element2 = issue2.find_all('div', {'class': 'code'}) - self.assertIn('some code', element2[0].text) - - # make sure correct things are being output in issues - self.assertIn('AAAAAAA:', issue1.text) - self.assertIn('BBBBBBB', issue1.text) - self.assertIn('CCCCCCC', issue1.text) - self.assertIn('abc.py', issue1.text) - - @mock.patch('bandit.core.issue.Issue.get_code') - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_escaping(self, get_issue_list, get_code): - self.manager.metrics.data['_totals'] = {'loc': 1000, 'nosec': 50} - marker = '' - - issue_a = _get_issue_instance() - issue_x = _get_issue_instance() - get_code.return_value = marker - - get_issue_list.return_value = {issue_a: [issue_x]} - - tmp_file = open(self.tmp_fname, 'w') - b_html.report( - self.manager, tmp_file, bandit.LOW, bandit.LOW) - - with open(self.tmp_fname) as f: - contents = f.read() - self.assertNotIn(marker, contents) - - -def _get_issue_instance(severity=bandit.MEDIUM, confidence=bandit.MEDIUM): - new_issue = issue.Issue(severity, confidence, 'Test issue') - new_issue.fname = 'code.py' - new_issue.test = 'bandit_plugin' - new_issue.lineno = 1 - return new_issue diff --git a/tests/unit/formatters/test_json.py b/tests/unit/formatters/test_json.py deleted file mode 100644 index 957c0473..00000000 --- a/tests/unit/formatters/test_json.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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 collections -import json -import tempfile - -import mock -import testtools - -import bandit -from bandit.core import config -from bandit.core import constants -from bandit.core import issue -from bandit.core import manager -from bandit.core import metrics -from bandit.formatters import json as b_json - - -class JsonFormatterTests(testtools.TestCase): - - def setUp(self): - super(JsonFormatterTests, self).setUp() - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.context = {'filename': self.tmp_fname, - 'lineno': 4, - 'linerange': [4]} - self.check_name = 'hardcoded_bind_all_interfaces' - self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, - 'Possible binding to all interfaces.') - - self.candidates = [issue.Issue(bandit.LOW, bandit.LOW, 'Candidate A', - lineno=1), - issue.Issue(bandit.HIGH, bandit.HIGH, 'Candiate B', - lineno=2)] - - self.manager.out_file = self.tmp_fname - - self.issue.fname = self.context['filename'] - self.issue.lineno = self.context['lineno'] - self.issue.linerange = self.context['linerange'] - self.issue.test = self.check_name - - self.manager.results.append(self.issue) - self.manager.metrics = metrics.Metrics() - - # mock up the metrics - for key in ['_totals', 'binding.py']: - self.manager.metrics.data[key] = {'loc': 4, 'nosec': 2} - for (criteria, default) in constants.CRITERIA: - for rank in constants.RANKING: - self.manager.metrics.data[key]['{0}.{1}'.format( - criteria, rank - )] = 0 - - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_report(self, get_issue_list): - self.manager.files_list = ['binding.py'] - self.manager.scores = [{'SEVERITY': [0] * len(constants.RANKING), - 'CONFIDENCE': [0] * len(constants.RANKING)}] - - get_issue_list.return_value = collections.OrderedDict( - [(self.issue, self.candidates)]) - - tmp_file = open(self.tmp_fname, 'w') - b_json.report(self.manager, tmp_file, self.issue.severity, - self.issue.confidence) - - with open(self.tmp_fname) as f: - data = json.loads(f.read()) - self.assertIsNotNone(data['generated_at']) - self.assertEqual(self.tmp_fname, data['results'][0]['filename']) - self.assertEqual(self.issue.severity, - data['results'][0]['issue_severity']) - self.assertEqual(self.issue.confidence, - data['results'][0]['issue_confidence']) - self.assertEqual(self.issue.text, data['results'][0]['issue_text']) - self.assertEqual(self.context['lineno'], - data['results'][0]['line_number']) - self.assertEqual(self.context['linerange'], - data['results'][0]['line_range']) - self.assertEqual(self.check_name, data['results'][0]['test_name']) - self.assertIn('candidates', data['results'][0]) - self.assertIn('more_info', data['results'][0]) - self.assertIsNotNone(data['results'][0]['more_info']) diff --git a/tests/unit/formatters/test_screen.py b/tests/unit/formatters/test_screen.py deleted file mode 100644 index b0ea71ef..00000000 --- a/tests/unit/formatters/test_screen.py +++ /dev/null @@ -1,213 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# Copyright (c) 2015 Hewlett Packard Enterprise -# -# 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 collections -import tempfile - -import mock -import testtools - -import bandit -from bandit.core import config -from bandit.core import issue -from bandit.core import manager -from bandit.formatters import screen - - -class ScreenFormatterTests(testtools.TestCase): - - def setUp(self): - super(ScreenFormatterTests, self).setUp() - - @mock.patch('bandit.core.issue.Issue.get_code') - def test_output_issue(self, get_code): - issue = _get_issue_instance() - get_code.return_value = 'DDDDDDD' - indent_val = 'CCCCCCC' - - def _template(_issue, _indent_val, _code, _color): - return_val = ["{}{}>> Issue: [{}:{}] {}". - format(_indent_val, _color, _issue.test_id, - _issue.test, _issue.text), - "{} Severity: {} Confidence: {}". - format(_indent_val, _issue.severity.capitalize(), - _issue.confidence.capitalize()), - "{} Location: {}:{}{}". - format(_indent_val, _issue.fname, _issue.lineno, - screen.COLOR['DEFAULT'])] - if _code: - return_val.append("{}{}".format(_indent_val, _code)) - return '\n'.join(return_val) - - issue_text = screen._output_issue_str(issue, indent_val) - expected_return = _template(issue, indent_val, 'DDDDDDD', - screen.COLOR['MEDIUM']) - self.assertEqual(expected_return, issue_text) - - issue_text = screen._output_issue_str(issue, indent_val, - show_code=False) - expected_return = _template(issue, indent_val, '', - screen.COLOR['MEDIUM']) - self.assertEqual(expected_return, issue_text) - - issue.lineno = '' - issue_text = screen._output_issue_str(issue, indent_val, - show_lineno=False) - expected_return = _template(issue, indent_val, 'DDDDDDD', - screen.COLOR['MEDIUM']) - self.assertEqual(expected_return, issue_text) - - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_no_issues(self, get_issue_list): - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.manager.out_file = self.tmp_fname - - get_issue_list.return_value = collections.OrderedDict() - with mock.patch('bandit.formatters.screen.do_print') as m: - tmp_file = open(self.tmp_fname, 'w') - screen.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, - lines=5) - self.assertIn('No issues identified.', - '\n'.join([str(a) for a in m.call_args])) - - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_report_nobaseline(self, get_issue_list): - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.manager.out_file = self.tmp_fname - - self.manager.verbose = True - self.manager.files_list = ['binding.py'] - - self.manager.scores = [{'SEVERITY': [0, 0, 0, 1], - 'CONFIDENCE': [0, 0, 0, 1]}] - - self.manager.skipped = [('abc.py', 'File is bad')] - self.manager.excluded_files = ['def.py'] - - issue_a = _get_issue_instance() - issue_b = _get_issue_instance() - - get_issue_list.return_value = [issue_a, issue_b] - - self.manager.metrics.data['_totals'] = {'loc': 1000, 'nosec': 50} - for category in ['SEVERITY', 'CONFIDENCE']: - for level in ['UNDEFINED', 'LOW', 'MEDIUM', 'HIGH']: - self.manager.metrics.data['_totals']['%s.%s' % - (category, level)] = 1 - - # Validate that we're outputting the correct issues - output_str_fn = 'bandit.formatters.screen._output_issue_str' - with mock.patch(output_str_fn) as output_str: - output_str.return_value = 'ISSUE_OUTPUT_TEXT' - - tmp_file = open(self.tmp_fname, 'w') - screen.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, - lines=5) - - calls = [mock.call(issue_a, '', lines=5), - mock.call(issue_b, '', lines=5)] - - output_str.assert_has_calls(calls, any_order=True) - - # Validate that we're outputting all of the expected fields and the - # correct values - with mock.patch('bandit.formatters.screen.do_print') as m: - tmp_file = open(self.tmp_fname, 'w') - screen.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, - lines=5) - - data = '\n'.join([str(a) for a in m.call_args[0][0]]) - - expected = 'Run started' - self.assertIn(expected, data) - - expected_items = [ - screen.header('Files in scope (1):'), - '\n\tbinding.py (score: {SEVERITY: 1, CONFIDENCE: 1})'] - - for item in expected_items: - self.assertIn(item, data) - - expected = screen.header('Files excluded (1):') + '\n\tdef.py' - self.assertIn(expected, data) - - expected = ('Total lines of code: 1000\n\tTotal lines skipped ' - '(#nosec): 50') - self.assertIn(expected, data) - - expected = ('Total issues (by severity):\n\t\tUndefined: 1\n\t\t' - 'Low: 1\n\t\tMedium: 1\n\t\tHigh: 1') - self.assertIn(expected, data) - - expected = ('Total issues (by confidence):\n\t\tUndefined: 1\n\t\t' - 'Low: 1\n\t\tMedium: 1\n\t\tHigh: 1') - self.assertIn(expected, data) - - expected = (screen.header('Files skipped (1):') + - '\n\tabc.py (File is bad)') - self.assertIn(expected, data) - - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_report_baseline(self, get_issue_list): - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.manager.out_file = self.tmp_fname - - issue_a = _get_issue_instance() - issue_b = _get_issue_instance() - - issue_x = _get_issue_instance() - issue_x.fname = 'x' - issue_y = _get_issue_instance() - issue_y.fname = 'y' - issue_z = _get_issue_instance() - issue_z.fname = 'z' - - get_issue_list.return_value = collections.OrderedDict( - [(issue_a, [issue_x]), (issue_b, [issue_y, issue_z])]) - - # Validate that we're outputting the correct issues - indent_val = ' ' * 10 - output_str_fn = 'bandit.formatters.screen._output_issue_str' - with mock.patch(output_str_fn) as output_str: - output_str.return_value = 'ISSUE_OUTPUT_TEXT' - - tmp_file = open(self.tmp_fname, 'w') - screen.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, - lines=5) - - calls = [mock.call(issue_a, '', lines=5), - mock.call(issue_b, '', show_code=False, - show_lineno=False), - mock.call(issue_y, indent_val, lines=5), - mock.call(issue_z, indent_val, lines=5)] - - output_str.assert_has_calls(calls, any_order=True) - - -def _get_issue_instance(severity=bandit.MEDIUM, confidence=bandit.MEDIUM): - new_issue = issue.Issue(severity, confidence, 'Test issue') - new_issue.fname = 'code.py' - new_issue.test = 'bandit_plugin' - new_issue.lineno = 1 - return new_issue diff --git a/tests/unit/formatters/test_text.py b/tests/unit/formatters/test_text.py deleted file mode 100644 index b849be77..00000000 --- a/tests/unit/formatters/test_text.py +++ /dev/null @@ -1,200 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# Copyright (c) 2015 Hewlett Packard Enterprise -# -# 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 collections -import tempfile - -import mock -import testtools - -import bandit -from bandit.core import config -from bandit.core import issue -from bandit.core import manager -from bandit.formatters import text as b_text - - -class TextFormatterTests(testtools.TestCase): - - def setUp(self): - super(TextFormatterTests, self).setUp() - - @mock.patch('bandit.core.issue.Issue.get_code') - def test_output_issue(self, get_code): - issue = _get_issue_instance() - get_code.return_value = 'DDDDDDD' - indent_val = 'CCCCCCC' - - def _template(_issue, _indent_val, _code): - return_val = ["{}>> Issue: [{}:{}] {}". - format(_indent_val, _issue.test_id, _issue.test, - _issue.text), - "{} Severity: {} Confidence: {}". - format(_indent_val, _issue.severity.capitalize(), - _issue.confidence.capitalize()), - "{} Location: {}:{}". - format(_indent_val, _issue.fname, _issue.lineno)] - if _code: - return_val.append("{}{}".format(_indent_val, _code)) - return '\n'.join(return_val) - - issue_text = b_text._output_issue_str(issue, indent_val) - expected_return = _template(issue, indent_val, 'DDDDDDD') - self.assertEqual(expected_return, issue_text) - - issue_text = b_text._output_issue_str(issue, indent_val, - show_code=False) - expected_return = _template(issue, indent_val, '') - self.assertEqual(expected_return, issue_text) - - issue.lineno = '' - issue_text = b_text._output_issue_str(issue, indent_val, - show_lineno=False) - expected_return = _template(issue, indent_val, 'DDDDDDD') - self.assertEqual(expected_return, issue_text) - - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_no_issues(self, get_issue_list): - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.manager.out_file = self.tmp_fname - - get_issue_list.return_value = collections.OrderedDict() - tmp_file = open(self.tmp_fname, 'w') - b_text.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, lines=5) - - with open(self.tmp_fname) as f: - data = f.read() - self.assertIn('No issues identified.', data) - - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_report_nobaseline(self, get_issue_list): - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.manager.out_file = self.tmp_fname - - self.manager.verbose = True - self.manager.files_list = ['binding.py'] - - self.manager.scores = [{'SEVERITY': [0, 0, 0, 1], - 'CONFIDENCE': [0, 0, 0, 1]}] - - self.manager.skipped = [('abc.py', 'File is bad')] - self.manager.excluded_files = ['def.py'] - - issue_a = _get_issue_instance() - issue_b = _get_issue_instance() - - get_issue_list.return_value = [issue_a, issue_b] - - self.manager.metrics.data['_totals'] = {'loc': 1000, 'nosec': 50} - for category in ['SEVERITY', 'CONFIDENCE']: - for level in ['UNDEFINED', 'LOW', 'MEDIUM', 'HIGH']: - self.manager.metrics.data['_totals']['%s.%s' % - (category, level)] = 1 - - # Validate that we're outputting the correct issues - output_str_fn = 'bandit.formatters.text._output_issue_str' - with mock.patch(output_str_fn) as output_str: - output_str.return_value = 'ISSUE_OUTPUT_TEXT' - - tmp_file = open(self.tmp_fname, 'w') - b_text.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, - lines=5) - - calls = [mock.call(issue_a, '', lines=5), - mock.call(issue_b, '', lines=5)] - - output_str.assert_has_calls(calls, any_order=True) - - # Validate that we're outputting all of the expected fields and the - # correct values - tmp_file = open(self.tmp_fname, 'w') - b_text.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, - lines=5) - with open(self.tmp_fname) as f: - data = f.read() - - expected_items = ['Run started', - 'Files in scope (1)', - 'binding.py (score: ', - "CONFIDENCE: 1", - "SEVERITY: 1", - 'Files excluded (1):', - 'def.py', - 'Undefined: 1', - 'Low: 1', - 'Medium: 1', - 'High: 1', - 'Total lines skipped ', - '(#nosec): 50', - 'Total issues (by severity)', - 'Total issues (by confidence)', - 'Files skipped (1)', - 'abc.py (File is bad)' - ] - for item in expected_items: - self.assertIn(item, data) - - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_report_baseline(self, get_issue_list): - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.manager.out_file = self.tmp_fname - - issue_a = _get_issue_instance() - issue_b = _get_issue_instance() - - issue_x = _get_issue_instance() - issue_x.fname = 'x' - issue_y = _get_issue_instance() - issue_y.fname = 'y' - issue_z = _get_issue_instance() - issue_z.fname = 'z' - - get_issue_list.return_value = collections.OrderedDict( - [(issue_a, [issue_x]), (issue_b, [issue_y, issue_z])]) - - # Validate that we're outputting the correct issues - indent_val = ' ' * 10 - output_str_fn = 'bandit.formatters.text._output_issue_str' - with mock.patch(output_str_fn) as output_str: - output_str.return_value = 'ISSUE_OUTPUT_TEXT' - - tmp_file = open(self.tmp_fname, 'w') - b_text.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, - lines=5) - - calls = [mock.call(issue_a, '', lines=5), - mock.call(issue_b, '', show_code=False, - show_lineno=False), - mock.call(issue_y, indent_val, lines=5), - mock.call(issue_z, indent_val, lines=5)] - - output_str.assert_has_calls(calls, any_order=True) - - -def _get_issue_instance(severity=bandit.MEDIUM, confidence=bandit.MEDIUM): - new_issue = issue.Issue(severity, confidence, 'Test issue') - new_issue.fname = 'code.py' - new_issue.test = 'bandit_plugin' - new_issue.lineno = 1 - return new_issue diff --git a/tests/unit/formatters/test_xml.py b/tests/unit/formatters/test_xml.py deleted file mode 100644 index 39ed009a..00000000 --- a/tests/unit/formatters/test_xml.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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 collections -import tempfile -from xml.etree import cElementTree as ET - -import testtools - -import bandit -from bandit.core import config -from bandit.core import issue -from bandit.core import manager -from bandit.formatters import xml as b_xml - - -class XmlFormatterTests(testtools.TestCase): - - def setUp(self): - super(XmlFormatterTests, self).setUp() - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.context = {'filename': self.tmp_fname, - 'lineno': 4, - 'linerange': [4]} - self.check_name = 'hardcoded_bind_all_interfaces' - self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, - 'Possible binding to all interfaces.') - self.manager.out_file = self.tmp_fname - - self.issue.fname = self.context['filename'] - self.issue.lineno = self.context['lineno'] - self.issue.linerange = self.context['linerange'] - self.issue.test = self.check_name - - self.manager.results.append(self.issue) - - def _xml_to_dict(self, t): - d = {t.tag: {} if t.attrib else None} - children = list(t) - if children: - dd = collections.defaultdict(list) - for dc in map(self._xml_to_dict, children): - 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 dd.items()}} - if 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: - if text: - d[t.tag]['#text'] = text - else: - d[t.tag] = text - return d - - def test_report(self): - tmp_file = open(self.tmp_fname, 'wb') - b_xml.report(self.manager, tmp_file, self.issue.severity, - self.issue.confidence) - - with open(self.tmp_fname) as f: - data = self._xml_to_dict(ET.XML(f.read())) - self.assertEqual(self.tmp_fname, - data['testsuite']['testcase']['@classname']) - self.assertEqual( - self.issue.text, - data['testsuite']['testcase']['error']['@message']) - self.assertEqual(self.check_name, - data['testsuite']['testcase']['@name']) diff --git a/tests/unit/formatters/test_yaml.py b/tests/unit/formatters/test_yaml.py deleted file mode 100644 index 6997d9a9..00000000 --- a/tests/unit/formatters/test_yaml.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) 2017 VMware, Inc. -# -# 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 collections -import tempfile - -import mock -import testtools -import yaml - -import bandit -from bandit.core import config -from bandit.core import constants -from bandit.core import issue -from bandit.core import manager -from bandit.core import metrics -from bandit.formatters import json as b_json - - -class YamlFormatterTests(testtools.TestCase): - - def setUp(self): - super(YamlFormatterTests, self).setUp() - conf = config.BanditConfig() - self.manager = manager.BanditManager(conf, 'file') - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.context = {'filename': self.tmp_fname, - 'lineno': 4, - 'linerange': [4]} - self.check_name = 'hardcoded_bind_all_interfaces' - self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, - 'Possible binding to all interfaces.') - - self.candidates = [issue.Issue(bandit.LOW, bandit.LOW, 'Candidate A', - lineno=1), - issue.Issue(bandit.HIGH, bandit.HIGH, 'Candiate B', - lineno=2)] - - self.manager.out_file = self.tmp_fname - - self.issue.fname = self.context['filename'] - self.issue.lineno = self.context['lineno'] - self.issue.linerange = self.context['linerange'] - self.issue.test = self.check_name - - self.manager.results.append(self.issue) - self.manager.metrics = metrics.Metrics() - - # mock up the metrics - for key in ['_totals', 'binding.py']: - self.manager.metrics.data[key] = {'loc': 4, 'nosec': 2} - for (criteria, default) in constants.CRITERIA: - for rank in constants.RANKING: - self.manager.metrics.data[key]['{0}.{1}'.format( - criteria, rank - )] = 0 - - @mock.patch('bandit.core.manager.BanditManager.get_issue_list') - def test_report(self, get_issue_list): - self.manager.files_list = ['binding.py'] - self.manager.scores = [{'SEVERITY': [0] * len(constants.RANKING), - 'CONFIDENCE': [0] * len(constants.RANKING)}] - - get_issue_list.return_value = collections.OrderedDict( - [(self.issue, self.candidates)]) - - tmp_file = open(self.tmp_fname, 'w') - b_json.report(self.manager, tmp_file, self.issue.severity, - self.issue.confidence) - - with open(self.tmp_fname) as f: - data = yaml.load(f.read()) - self.assertIsNotNone(data['generated_at']) - self.assertEqual(self.tmp_fname, data['results'][0]['filename']) - self.assertEqual(self.issue.severity, - data['results'][0]['issue_severity']) - self.assertEqual(self.issue.confidence, - data['results'][0]['issue_confidence']) - self.assertEqual(self.issue.text, data['results'][0]['issue_text']) - self.assertEqual(self.context['lineno'], - data['results'][0]['line_number']) - self.assertEqual(self.context['linerange'], - data['results'][0]['line_range']) - self.assertEqual(self.check_name, data['results'][0]['test_name']) - self.assertIn('candidates', data['results'][0]) - self.assertIn('more_info', data['results'][0]) - self.assertIsNotNone(data['results'][0]['more_info']) diff --git a/tools/openstack_coverage.py b/tools/openstack_coverage.py deleted file mode 100755 index d16b7484..00000000 --- a/tools/openstack_coverage.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 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. - -"""Tool for reporting Bandit coverage over OpenStack. - -Intended for execution against specific Jenkins and Zuul configuration files -within the openstack-infra/project-config repository. -Parses out Bandit jobs and tests as defined within these configurations. -Prints the summary of results. - -If the '-t' (test) option is provided, this tool will attempt to git clone any -project that defines a Bandit job. Once cloned, it will use tox to run the -defined Bandit job and capture logs for any failures. - -TODO: Add detection / handling of bandit.yaml for each project. -TODO: Deal with different branch definitions in the Zuul layout.yaml. -""" - -import argparse -import datetime -import os -import requests -import subprocess -import yaml - - -BASE_URL = "https://git.openstack.org/cgit/" -GIT_BASE = "https://git.openstack.org/" - -PATH_INFRA = "openstack-infra/project-config/plain/" -PATH_JENKINS = "jenkins/jobs/projects.yaml" -PATH_PROJECT_LIST = "openstack/governance/plain/reference/projects.yaml" -PATH_ZUUL = "zuul/layout.yaml" - -TITLE = "OpenStack Bandit Coverage Report -- {0} UTC".format( - datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') -) - -TEST_TYPES = ['experimental', 'check', 'gate'] - - -def get_yaml(url): - r = requests.get(url) - if r.status_code == 200: - data = yaml.load(r.content) - return(data) - raise SystemError( - "Could not obtain valid YAML from specified source ({0})" - .format(url) - ) - - -def list_projects(conf_jenkins): - data = get_yaml("{0}{1}{2}".format(BASE_URL, PATH_INFRA, conf_jenkins)) - # parse data - bandit_projects = [] - for project in data: - project_name = project['project']['name'] - project_jobs = project['project']['jobs'] - for job in project_jobs: - if type(job) == dict and 'gate-{name}-tox-{envlist}' in job: - if 'bandit' in job['gate-{name}-tox-{envlist}']['envlist']: - bandit_projects.append(project_name) - - # output results - print("Bandit jobs have been defined in the following OpenStack projects:") - for project in sorted(bandit_projects): - print(" - {0}".format(project)) - print("\n(Configuration from {0}{1}{2})\n".format( - BASE_URL, PATH_INFRA, conf_jenkins - )) - return bandit_projects - - -def coverage_zuul(conf_zuul): - data = get_yaml("{0}{1}{2}".format(BASE_URL, PATH_INFRA, conf_zuul)) - # parse data - bandit_jobs = {} - bandit_tests = {key: set() for key in TEST_TYPES} - for job in data['jobs']: - if 'bandit' in job['name']: - if job.get('voting', True) is False: - bandit_jobs[job['name']] = False - else: - bandit_jobs[job['name']] = True - for project in data['projects']: - project_name = project['name'] - for test_type in bandit_tests.keys(): - for test in project.get(test_type, []): - if str(test).endswith('bandit'): - voting = bandit_jobs.get(test, False) - bandit_tests[test_type].add((project_name, voting)) - # output results - for test_type in bandit_tests: - print( - "\n{0} tests exist for the following OpenStack projects:" - .format(test_type.capitalize()) - ) - for project in sorted(bandit_tests[test_type]): - if project[1] is False: - print(" - {0}".format(project[0])) - else: - print(" - {0} (VOTING)".format(project[0])) - print("\n(Configuration from {0}{1}{2})\n".format( - BASE_URL, PATH_INFRA, conf_zuul - )) - - -def _print_title(): - print("{0}\n{1}\n{0}\n".format( - "=" * len(TITLE), - TITLE, - "=" * len(TITLE) - )) - - -def _parse_args(): - parser = argparse.ArgumentParser() - - parser.add_argument('-t', '--test', dest='do_test', action='store_true', - help='Test upstream project Bandit gates. This will ' - 'clone each upstream project, run Bandit as ' - 'configured in the tox environment, display pass ' - 'status, and save output.') - - parser.set_defaults(do_test=False) - - return parser.parse_args() - - -def _get_repo_names(project_list): - # take a list of project names, like ['anchor', 'barbican'], get the - # corresponding repos for each. Return a dictionary with the project - # as the key and the repo as the value. - project_repos = {key: None for key in project_list} - - yaml_data = get_yaml("{0}{1}".format(BASE_URL, PATH_PROJECT_LIST)) - - for project in yaml_data: - - try: - # if one of the projects we're looking for is listed as a - # deliverable for this project, look for the first listed repo - # for that deliverable - for deliverable in yaml_data[project]['deliverables']: - - if deliverable in project_list: - # the deliverable name is the project we're looking for, - # store the listed repo name for it - project_repos[deliverable] = (yaml_data[project] - ['deliverables'] - [deliverable]['repos'][0]) - - except (KeyError, IndexError): - # improperly formatted entry, keep going - pass - - return project_repos - - -def clone_projects(project_list): - # clone all of the projects, return the directory name they are cloned in - project_locations = _get_repo_names(project_list) - - orig_dir = os.path.abspath(os.getcwd()) - - # create directory for projects - try: - dir_name = 'project-source-{}'.format(datetime.datetime.utcnow(). - strftime('%Y-%m-%d-%H-%M-%S')) - os.mkdir(dir_name) - os.chdir(dir_name) - except OSError: - print("Unable to create directory for cloning projects") - return None - - for project in project_locations: - print '=' * len(TITLE) - print("Cloning project: {} from repo {} into {}". - format(project, project_locations[project], dir_name)) - - try: - subprocess.check_call(['git', 'clone', - GIT_BASE + project_locations[project]]) - - except subprocess.CalledProcessError: - print("Unable to clone project from repo: {}". - format(project_locations[project])) - - os.chdir(orig_dir) - - return os.path.abspath(dir_name) - - -def run_bandit(source_dir): - # go through each source directory in the directory which contains source, - # run Bandit with the established tox job, save results - orig_dir = os.path.abspath(os.getcwd()) - - try: - fail_results_dir = os.path.abspath('fail_results') - os.mkdir(fail_results_dir) - except OSError: - print("Unable to make results directory") - - os.chdir(source_dir) - - run_success = {} - - for d in os.listdir(os.getcwd()): - os.chdir(d) - - print '=' * len(TITLE) - print 'Running tox Bandit in directory {}'.format(d) - - try: - subprocess.check_output(['tox', '-e', 'bandit'], - stderr=subprocess.STDOUT) - - except subprocess.CalledProcessError as exc: - run_success[d] = False - - # write log containing the process output - fail_log_path = fail_results_dir + '/' + d - with open(fail_log_path, 'w') as f: - f.write(exc.output) - print("Bandit tox failed, wrote failure log to {}". - format(fail_log_path)) - - else: - run_success[d] = True - - os.chdir(source_dir) - - os.chdir(orig_dir) - - return run_success - - -def main(): - _print_title() - - args = _parse_args() - - project_list = list_projects(PATH_JENKINS) - coverage_zuul(PATH_ZUUL) - print("=" * len(TITLE)) - - if args.do_test: - source_dir = clone_projects(project_list) - if source_dir: - results = run_bandit(source_dir) - - # output results table - print "-" * 50 - print "{:40s}{:10s}".format("Project", "Passed") - print "-" * 50 - for project in results: - print "{:40s}{:10s}".format(project, str(results[project])) - - -if __name__ == "__main__": - main() diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 83d31896..00000000 --- a/tox.ini +++ /dev/null @@ -1,105 +0,0 @@ -[tox] -minversion = 2.0 -envlist = py35,py27,pep8 -skipsdist = True - -[testenv] -usedevelop = True -install_command = pip install {opts} {packages} -setenv = - VIRTUAL_ENV={envdir} -deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} - -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt -commands = - 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] -commands = oslo_debug_helper -t tests {posargs} - -[testenv:linters] -deps = {[testenv:pep8]deps} -usedevelop = False -commands = flake8 {posargs} bandit - flake8 {posargs} tests - bandit-baseline -r bandit -ll -ii - -[testenv:pep8] -deps = {[testenv]deps} - . -usedevelop = False -commands = flake8 {posargs} bandit - flake8 {posargs} tests - {[testenv:pylint]commands} - bandit-baseline -r bandit -ll -ii - -[testenv:venv] -commands = {posargs} - -[testenv:codesec] -deps = {[testenv]deps} - . -usedevelop = False -commands = bandit-baseline -r bandit -ll -ii - -[testenv:cover] -setenv = - {[testenv]setenv} - PYTHON=coverage run --source bandit --parallel-mode -commands = - coverage erase - stestr run '{posargs}' - coverage report - -[testenv:openstack_coverage] -deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} - PyYAML>=3.1.0 - requests>=2.7.0 -commands = python tools/openstack_coverage.py - -[testenv:integration] -passenv = REPO_ROOT -whitelist_externals = bash -commands = bash scripts/integration-test.sh {posargs} - -[testenv:docs] -deps = -r{toxinidir}/doc/requirements.txt -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] -deps = -r{toxinidir}/doc/requirements.txt -commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html - -[testenv:pylint] -commands = pylint --rcfile=pylintrc bandit - -[testenv:lower-constraints] -basepython = python3 -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt - -# This environment can be used to quickly validate that all needed system -# packages required to successfully execute test targets are installed -[testenv:bindep] -# Do not install any requirements. We want this to be fast and work even if -# system dependencies are missing, since it's used to tell you what system -# dependencies are missing! This also means that bindep must be installed -# separately, outside of the requirements files. -deps = bindep -commands = bindep test