🛠 Bandit as linter on pre-commit
Salute,
Let's take a look at SAST for python today. We look at Bandit as a linter, because it is convenient to use it without analyzing and delving into business logic, because the tool does not know how to do this. It’s also useful for integrating into your Python development team.
Bandit parses the code of each project file in AST (Abstract Syntax Tree) and runs a set of rules (plugins through entry points). License type: Apache-2.0 license. Report formats: csv, custom, html, json, screen, txt, xml, yaml.
The cool thing is that if wemake-python-styleguide is used in conjunction with Bandit as an analyzer, we solve issues of denaming, shadowing of built-in functions, cognitive complexity, as well as problems with inconsistent returns, incorrect line breaks, inconsistent expressions and dealing with obvious vulnerabilities.
Teams
python -m venv .venv
pip install bandit
bandir -r ./my_python_project/ # Recursive scan
--severity-level
--confidence-level # Thresholds
--profile
-t # Select rules
-s # Skip rules
nosec # Point-by-point ignore
docker pull ghcr.io/pycqa/bandit/bandit:<tag>
cosign verify ghcr.io/pycqa/bandit/bandit:latest \ # verify image signature before use
--certificate-identity https://github.com/pycqa/bandit/.github/workflows/build-publish-image.yml@refs/tags/<version> \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Configuration file
# .bandit
exclude_dirs: ['tests', 'venv']
skips: ['B101', 'B311']
any_other_function_with_shell_equals_true:
no_shell: ['subprocess.Popen']
hardcoded_password_string:
hardcoded_password_string_re: '(?i)(password|passwd|pwd)'
Use as pre-commit
#.pre-commit-config.yaml
repos:
- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
hooks:
- id: bandit
args: ['-ll', '-ii']
CI/CD
variables:
BANDIT_SEVERITY: "medium"
BANDIT_CONFIDENCE: "medium"
BANDIT_OUTDIR: "bandit-report"
bandit_scan:
stage: security
image: ghcr.io/pycqa/bandit/bandit:<tag>
script:
- mkdir -p "$BANDIT_OUTDIR"
- bandit -r . \
--severity-level="$BANDIT_SEVERITY" \
--confidence-level="$BANDIT_CONFIDENCE" \
-f json -o "$BANDIT_OUTDIR/bandit.json"
pipeline {
agent any
options { timestamps() }
environment {
BANDIT_SEVERITY = 'medium'
BANDIT_CONFIDENCE = 'medium'
REPORT_DIR = 'bandit-report'
}
stages {
stage('Setup Python venv') {
steps {
sh '''
python3 -m venv .venv
. .venv/bin/activate
pip install --upgrade pip
pip install bandit
mkdir -p "${REPORT_DIR}"
'''
}
}
stage('Bandit Scan') {
steps {
sh '''
. .venv/bin/activate
bandit -r . --severity-level=${BANDIT_SEVERITY} --confidence-level=${BANDIT_CONFIDENCE} \
-f json -o ${REPORT_DIR}/bandit.json
bandit -r . --severity-level=${BANDIT_SEVERITY} --confidence-level=${BANDIT_CONFIDENCE} \
-f sarif -o ${REPORT_DIR}/bandit.sarif
bandit -r . --severity-level=${BANDIT_SEVERITY} --confidence-level=${BANDIT_CONFIDENCE} \
-f html -o ${REPORT_DIR}/bandit.html
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'bandit-report/**', fingerprint: true
}
unsuccessful {
script { currentBuild.result = 'UNSTABLE' }
}
}
}
Total: SAST itself provides an AST approach without code execution, where the baseline allows for gradual implementation and is expandable through plugins, but there are a large number of incorrect rules that give FP that need to be sorted out manually. The tool is lightweight, but not like a target SAST, but a very convenient solution for checks before Merge/Pull Requeste. Together with wemake-python-styleguide, we solve the problem of code structure and style within the development team.
#toolchain #sast
