Skip to content

Commit

Permalink
Merge pull request tylerwince#17 from shapiromatron/bandit-config
Browse files Browse the repository at this point in the history
support all options in .bandit
  • Loading branch information
tylerwince committed Aug 29, 2020
2 parents e5834e7 + c4c7b63 commit 00ba2e4
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 26 deletions.
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",
)

0 comments on commit 00ba2e4

Please sign in to comment.