Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support all options in .bandit #17

Merged
merged 1 commit into from
Aug 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ Automated security testing built right into your workflow!

You already use flake8 to lint all your code for errors, ensure docstrings are formatted correctly, sort your imports correctly, and much more... so why not ensure you are writing secure code while you're at it? If you already have flake8 installed all it takes is `pip install flake8-bandit`.

## Configuration

To include or exclude tests, use the standard `.bandit` configuration file. An example valid `.bandit` config file:

```text
[bandit]
exclude = /frontend,/scripts,/tests,/venv
tests: B101
```

In this case, we've specified to ignore a number of paths, and to only test for B101.

**Note:** flake8-bugbear uses bandit default prefix 'B' so this plugin replaces the 'B' with an 'S' for Security. For more information, see https://github.com/PyCQA/flake8-bugbear/issues/37

## How's it work?

We use the [bandit](https://github.com/PyCQA/bandit) package from [PyCQA](http://meta.pycqa.org/en/latest/) for all the security testing.
100 changes: 75 additions & 25 deletions flake8_bandit.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,78 @@
"""Implementation of bandit security testing in Flake8."""
import ast
import configparser
import sys
from functools import lru_cache
from pathlib import Path
from typing import Dict, NamedTuple, Set

import pycodestyle
from flake8.options.config import ConfigFileFinder
from flake8 import utils as stdin_utils

from bandit.core.config import BanditConfig
from bandit.core.meta_ast import BanditMetaAst
from bandit.core.metrics import Metrics
from bandit.core.node_visitor import BanditNodeVisitor
from bandit.core.test_set import BanditTestSet

try:
import configparser
except ImportError:
import ConfigParser as configparser

try:
from flake8.engine import pep8 as stdin_utils
except ImportError:
from flake8 import utils as stdin_utils
__version__ = "2.1.2"


__version__ = "2.1.2"
class Flake8BanditConfig(NamedTuple):
profile: Dict
target_paths: Set
excluded_paths: Set

@classmethod
@lru_cache(maxsize=32)
def from_config_file(cls) -> "Flake8BanditConfig":
# set defaults
profile = {}
target_paths = set()
excluded_paths = set()

# populate config from `.bandit` configuration file
ini_file = ConfigFileFinder("bandit", None, None).local_config_files()
config = configparser.ConfigParser()
try:
config.read(ini_file)
bandit_config = {k: v for k, v in config.items("bandit")}

# test-set profile
if bandit_config.get("skips"):
profile["exclude"] = (
bandit_config.get("skips").replace("S", "B").split(",")
)
if bandit_config.get("tests"):
profile["include"] = (
bandit_config.get("tests").replace("S", "B").split(",")
)

# file include/exclude
if bandit_config.get("targets"):
paths = bandit_config.get("targets").split(",")
for path in paths:
# convert absolute to relative
if path.startswith("/"):
path = "." + path
target_paths.add(Path(path))

if bandit_config.get("exclude"):
paths = bandit_config.get("exclude").split(",")
for path in paths:
# convert absolute to relative
if path.startswith("/"):
path = "." + path
excluded_paths.add(Path(path))

except (configparser.Error, KeyError, TypeError) as e:
profile = {}
if str(e) != "No section: 'bandit'":
sys.stderr.write(f"Unable to parse config file: {e}")

return cls(profile, target_paths, excluded_paths)


class BanditTester(object):
Expand All @@ -41,25 +92,24 @@ def __init__(self, tree, filename, lines):
self.lines = lines

def _check_source(self):
ini_file = ConfigFileFinder("bandit", None, None).local_config_files()
config = configparser.ConfigParser()
try:
config.read(ini_file)
profile = {k: v.replace("S", "B") for k, v in config.items("bandit")}
if profile.get("skips"):
profile["exclude"] = profile.get("skips").split(",")
if profile.get("tests"):
profile["include"] = profile.get("tests").split(",")
except (configparser.Error, KeyError, TypeError) as e:
if str(e) != "No section: 'bandit'":
import sys
err = "Unable to parse config file: %s\n" % e
sys.stderr.write(err)
profile = {}
config = Flake8BanditConfig.from_config_file()

# potentially exit early if bandit config tells us to
filepath = Path(self.filename)
filepaths = set(filepath.parents)
filepaths.add(filepath)
if (
config.excluded_paths and config.excluded_paths.intersection(filepaths)
) or (
config.target_paths
and len(config.target_paths.intersection(filepaths)) == 0
):
return []

bnv = BanditNodeVisitor(
self.filename,
BanditMetaAst(),
BanditTestSet(BanditConfig(), profile=profile),
BanditTestSet(BanditConfig(), profile=config.profile),
False,
[],
Metrics(),
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ def run(self):
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"Topic :: Security",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
],
# $ setup.py publish support.
cmdclass={"upload": UploadCommand},
python_requires=">=3.6",
)