Set screen formatter colours based on terminal BG
Two-stage background detection: $COLORFGBG followed by OSC11. Fallback to dark if colour can't be detected using the methods above. Can be overriden by $BANDIT_LIGHT_BG environment variable.
Resolves: #1271
Bright BG
Dark BG
Ping @ericwb , @lukehinds , @sigmavirus24
Fix the broken CI then someone may spend the time reviewing this
@sigmavirus24 I take you meant to say "please fix your contribution", not "fix our broken CI" :) I've made the (only) file I have edited to pass the pre-commit hooks (black, imports, etc)
Running tests as per CONTRIBUTING.md:
tox run -e pep8
tox run -e py37
tox run -e docs
tox run -e cover
From the list above:
-
tox run -e pep8implodes spectacularly. -
tox run -e py37does not execute due to unavailable interpreter (3.7 is EOL for a number of years) -
tox run -e docssucceeds with a single warning. -
tox run -e coverfails with reassuring totals:
======
Totals
======
Ran: 272 tests in 1.5158 sec.
- Passed: 272
- Skipped: 0
- Expected Fail: 0
- Unexpected Success: 0
- Failed: 0
Sum of execute time for each test: 4.2907 sec.
[...]
cover: commands[2]> coverage report
No data to report.
cover: exit 1 (0.05 seconds) /home/user/code/bandit> coverage report pid=1823815
cover: FAIL code 1 (3.53=setup[1.04]+cmd[0.04,2.40,0.05] seconds)
evaluation failed :( (3.60 seconds)
@sigmavirus24 I take you meant to say "please fix your contribution", not "fix our broken CI" :) I've made the (only) file I have edited to pass the pre-commit hooks (black, imports, etc)
Running tests as per CONTRIBUTING.md:
tox run -e pep8 tox run -e py37 tox run -e docs tox run -e coverFrom the list above:
tox run -e pep8implodes spectacularly.tox run -e py37does not execute due to unavailable interpreter (3.7 is EOL for a number of years)tox run -e docssucceeds with a single warning.tox run -e coverfails with reassuring totals:====== Totals ====== Ran: 272 tests in 1.5158 sec. - Passed: 272 - Skipped: 0 - Expected Fail: 0 - Unexpected Success: 0 - Failed: 0 Sum of execute time for each test: 4.2907 sec. [...] cover: commands[2]> coverage report No data to report. cover: exit 1 (0.05 seconds) /home/user/code/bandit> coverage report pid=1823815 cover: FAIL code 1 (3.53=setup[1.04]+cmd[0.04,2.40,0.05] seconds) evaluation failed :( (3.60 seconds)
https://github.com/PyCQA/bandit/actions/runs/15929218056/job/44934249349#step:5:42
That's failing due to your change. If you're unwilling to contribute in good faith and instead are going to be hyper-literal, I'm not sure it's worth my spending more time helping you land this
@sigmavirus24 , I have no clue what are you talking about.
How else should someone follow the file called CONTRBUTING.md if not literally?
Here is what I see on my side, zero error details:
I have tested the patch in foot and XFce terminal; it works as designed.
I have ticked the box "allow edits by maintainers" (which you are). You are very welcome to do further changes that will satisfy the CI. If GitHub machinery can be changed in a way that allows me to see these errors as well - I am equally happy to take a look at them myself.
You say pep8 "implodes spectacularly" but you don't share the implosion or try to fix it.
pep8: commands[0]> flake8 bandit
bandit/formatters/screen.py:160:50: E203 whitespace before ':'
rgb_part = response[rgb_start + 4 :]
^
pep8: exit 1 (0.91 seconds) /home/runner/work/bandit/bandit> flake8 bandit pid=2532
pep8: commands[1]> flake8 tests
pep8: commands[2]> pylint --rcfile=pylintrc bandit
Traceback (most recent call last):
File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/astpeephole.py", line 17, in <module>
_TYPES = (_ast.Str, _ast.Bytes)
AttributeError: module '_ast' has no attribute 'Str'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/bandit/bandit/.tox/pep8/bin/pylint", line 8, in <module>
sys.exit(run_pylint())
File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/pylint/__init__.py", line 15, in run_pylint
from pylint.lint import Run
File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/pylint/lint.py", line 64, in <module>
import astroid
File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/__init__.py", line 67, in <module>
from astroid.builder import parse, extract_node
File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/builder.py", line 26, in <module>
from astroid import rebuilder
File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/rebuilder.py", line 17, in <module>
from astroid import astpeephole
File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/astpeephole.py", line 19, in <module>
_TYPES = (_ast.Str, )
AttributeError: module '_ast' has no attribute 'Str'
Is what is in the logs. I don't know what's wrong with your browser configuration that you cannot see the logs but I can always see the logs on any project I contribute to regardless of whether I'm a maintainer or not.
tox -e py39 gives you a hint, as to why pep8 is "imploding spectacularly":
py39: commands[1]> stestr run
=========================
Failures during discovery
=========================
--- import errors ---
Failed to import test module: tests.unit.formatters.test_screen
Traceback (most recent call last):
File "/opt/hostedtoolcache/Python/3.9.23/x64/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
module = self._get_module_from_name(name)
File "/opt/hostedtoolcache/Python/3.9.23/x64/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
__import__(name)
File "/home/runner/work/bandit/bandit/tests/unit/formatters/test_screen.py", line 16, in <module>
from bandit.formatters import screen
File "/home/runner/work/bandit/bandit/bandit/formatters/screen.py", line 70, in <module>
def term_detect_bg() -> bool | None:
TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'
================================================================================
The above traceback was encountered during test discovery which imports all the found test modules in the specified test_path.
If you see 3.9 is failing here you can run locally tox -e py39. tox -e py37 would fail similarly because you're using syntax only introduced in 3.10. Instead of
def term_detect_bg() -> bool | None:
You can use
import typing as t
# ...
def term_detect_bg() -> t.Optional[bool]:
I'm uninterested in merging this as it doesn't scratch any itch I have so I will not fix this for you.
tox run -e pep8
$ tox run -e pep8
.pkg: _optional_hooks> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_sdist> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_wheel> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_editable> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: build_wheel> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: build_sdist> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
pep8: install_package> pip install --force-reinstall --no-deps /home/user/code/bandit/.tox/.tmp/package/14/bandit-0.0.1.dev1461.tar.gz
pep8: commands[0]> flake8 bandit
pep8: commands[1]> flake8 tests
pep8: commands[2]> pylint --rcfile=pylintrc bandit
/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/pylint/__pkginfo__.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import parse_version
Traceback (most recent call last):
File "/home/user/code/bandit/.tox/pep8/bin/pylint", line 8, in <module>
sys.exit(run_pylint())
~~~~~~~~~~^^
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/pylint/__init__.py", line 15, in run_pylint
from pylint.lint import Run
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/pylint/lint.py", line 64, in <module>
import astroid
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/__init__.py", line 57, in <module>
from astroid.nodes import *
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/nodes.py", line 18, in <module>
from astroid.node_classes import (
...<15 lines>...
)
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/node_classes.py", line 25, in <module>
from astroid import bases
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/bases.py", line 25, in <module>
MANAGER = manager.AstroidManager()
^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/lazy_object_proxy/simple.py", line 131, in __getattr__
return getattr(self.__wrapped__, name)
^^^^^^^^^^^^^^^^
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/lazy_object_proxy/utils.py", line 58, in __get__
value = obj.__dict__[self.func.__name__] = self.func(obj)
~~~~~~~~~^^^^^
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/lazy_object_proxy/simple.py", line 81, in __wrapped__
return factory()
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/util.py", line 24, in <lambda>
lambda: importlib.import_module('.' + module_name, 'astroid'))
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/importlib/__init__.py", line 88, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/manager.py", line 21, in <module>
from astroid.interpreter._import import spec
File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/interpreter/_import/spec.py", line 6, in <module>
import imp
ModuleNotFoundError: No module named 'imp'
pep8: exit 1 (0.08 seconds) /home/user/code/bandit> pylint --rcfile=pylintrc bandit pid=2080145
pep8: command failed but is marked ignore outcome so handling it as success
pep8: commands[3]> bandit-baseline -r bandit -ll -ii
[ INFO ] No output format specified, using terminal
[ INFO ] Got current commit: [6d9ffff9a036a32bd9ab3cd8476029937b5b3b2a term_autocolours]
[ INFO ] Got parent commit: [e16f98f79c7c05352c69c3bfaa901636f834d4d0 remotes/origin/term_autocolours]
[ INFO ] Getting Bandit baseline results
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[manager] WARNING Test in comment: _lines is not a test name or id, ignoring
[manager] WARNING Test in comment: is is not a test name or id, ignoring
[manager] WARNING Test in comment: a is not a test name or id, ignoring
[manager] WARNING Test in comment: dict is not a test name or id, ignoring
[manager] WARNING Test in comment: of is not a test name or id, ignoring
[manager] WARNING Test in comment: line is not a test name or id, ignoring
[manager] WARNING Test in comment: number is not a test name or id, ignoring
[manager] WARNING Test in comment: set is not a test name or id, ignoring
[manager] WARNING Test in comment: of is not a test name or id, ignoring
[manager] WARNING Test in comment: tests is not a test name or id, ignoring
[manager] WARNING Test in comment: to is not a test name or id, ignoring
[manager] WARNING Test in comment: ignore is not a test name or id, ignoring
[manager] WARNING Test in comment: tkelsey is not a test name or id, ignoring
[manager] WARNING Test in comment: catching is not a test name or id, ignoring
[manager] WARNING Test in comment: expected is not a test name or id, ignoring
[manager] WARNING Test in comment: exception is not a test name or id, ignoring
[tester] WARNING nosec encountered (B108), but no failed test on line 61
[json] INFO JSON output written to file: /home/user/.cache/TMPDIR/tmpqzr_qjzm/_bandit_baseline_run.json_
[ INFO ] Comparing Bandit results to baseline
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.13.3
[manager] WARNING Test in comment: _lines is not a test name or id, ignoring
[manager] WARNING Test in comment: is is not a test name or id, ignoring
[manager] WARNING Test in comment: a is not a test name or id, ignoring
[manager] WARNING Test in comment: dict is not a test name or id, ignoring
[manager] WARNING Test in comment: of is not a test name or id, ignoring
[manager] WARNING Test in comment: line is not a test name or id, ignoring
[manager] WARNING Test in comment: number is not a test name or id, ignoring
[manager] WARNING Test in comment: set is not a test name or id, ignoring
[manager] WARNING Test in comment: of is not a test name or id, ignoring
[manager] WARNING Test in comment: tests is not a test name or id, ignoring
[manager] WARNING Test in comment: to is not a test name or id, ignoring
[manager] WARNING Test in comment: ignore is not a test name or id, ignoring
[manager] WARNING Test in comment: tkelsey is not a test name or id, ignoring
[manager] WARNING Test in comment: catching is not a test name or id, ignoring
[manager] WARNING Test in comment: expected is not a test name or id, ignoring
[manager] WARNING Test in comment: exception is not a test name or id, ignoring
[tester] WARNING nosec encountered (B108), but no failed test on line 61
Working... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Run started:2025-06-28 14:35:36.512120
Test results:
No issues identified.
Code scanned:
Total lines of code: 8656
Total lines skipped (#nosec): 0
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 10
Run metrics:
Total issues (by severity):
Undefined: 0
Low: 3
Medium: 0
High: 1
Total issues (by confidence):
Undefined: 0
Low: 0
Medium: 1
High: 3
Files skipped (0):
^[]11;rgb:ffff/ffff/ffff^[\ pep8: OK (4.29=setup[2.77]+cmd[0.11,0.13,0.08,1.20] seconds)
congratulations :) (4.35 seconds)
$
/ffff/ffff^[\
tox run -e pep8 from Python 3.9 / Debian 11 container
$ tox run -e pep8
pep8: install_deps> pip install . -r /app/requirements.txt -r /app/test-requirements.txt
Processing /app
Installing build dependencies ... done
Getting requirements to build wheel ... done
Installing backend dependencies ... done
Preparing metadata (pyproject.toml) ... error
error: subprocess-exited-with-error
× Preparing metadata (pyproject.toml) did not run successfully.
│ exit code: 1
╰─> [18 lines of output]
/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/util.py:75: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Error parsing
Traceback (most recent call last):
File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/core.py", line 105, in pbr
attrs = util.cfg_to_args(path, dist.script_args)
File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/util.py", line 272, in cfg_to_args
pbr.hooks.setup_hook(config)
File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/hooks/__init__.py", line 25, in setup_hook
metadata_config.run()
File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/hooks/base.py", line 27, in run
self.hook()
File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/hooks/metadata.py", line 25, in hook
self.config['version'] = packaging.get_version(
File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/packaging.py", line 866, in get_version
raise Exception("Versioning for this project requires either an sdist"
Exception: Versioning for this project requires either an sdist tarball, or access to an upstream git repository. It's also possible that there is a mismatch between the package name in setup.cfg and the argument given to pbr.version.VersionInfo. Project name bandit was given, but was not able to be found.
error in setup command: Error parsing /app/setup.cfg: Exception: Versioning for this project requires either an sdist tarball, or access to an upstream git repository. It's also possible that there is a mismatch between the package name in setup.cfg and the argument given to pbr.version.VersionInfo. Project name bandit was given, but was not able to be found.
[end of output]
note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed
× Encountered error while generating package metadata.
╰─> See above for output.
note: This is an issue with the package mentioned above, not pip.
hint: See above for details.
pep8: exit 1 (1.67 seconds) /app> pip install . -r /app/requirements.txt -r /app/test-requirements.txt pid=435
pep8: FAIL code 1 (1.68 seconds)
evaluation failed :( (1.74 seconds)
The stestr run shown in your listing attempts to import modules not present in test-requirements.txt, so it will fail even if code is Python3.9-compliant:
$ stestr run
=========================
Failures during discovery
=========================
--- import errors ---
Failed to import test module: tests.unit.cli.test_baseline
Traceback (most recent call last):
File "/usr/local/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
module = self._get_module_from_name(name)
File "/usr/local/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
__import__(name)
File "/app/tests/unit/cli/test_baseline.py", line 10, in <module>
import git
ModuleNotFoundError: No module named 'git'
Failed to import test module: tests.unit.formatters.test_sarif
Traceback (most recent call last):
File "/usr/local/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
module = self._get_module_from_name(name)
File "/usr/local/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
__import__(name)
File "/app/tests/unit/formatters/test_sarif.py", line 15, in <module>
from bandit.formatters import sarif
File "/app/bandit/formatters/sarif.py", line 134, in <module>
import sarif_om as om
ModuleNotFoundError: No module named 'sarif_om'
================================================================================
The above traceback was encountered during test discovery which imports all the found test modules in the specified test_path.
Anyway, with the latest changes the code should work with Python 3.9-3.13.
@ericwb , I'd be happy to, however I am not certain where and how this would work. My first take was along the following lines:
def term_serve_colourscheme() -> Dict[str, Style]:
light = term_detect_bg()
return {
"DEFAULT": Style(),
"HEADER": Style(color="blue", bold=True) if light else Style(color="cyan", bold=True),
"LOW": Style(color="green", bold=True) if light else Style(color="green", bold=True),
"MEDIUM": Style(color="magenta", bold=True) if light else Style(color="yellow", bold=True),
"HIGH": Style(color="red", bold=True) if light else Style(color="red", bold=True),
}
...but then we will need to change the rest of the code that relies on COLOR being a dict of the particular format with ANSI escapes. Which will result in a much larger refactor.
In regards to the colour detection itself - I have not found anything of use in the documentation, certainly not a "please tell me if it's a dark terminal" utility.
Thoughts?
I don't think security tooling should leave people behind when vendors are still supporting the language version.
Noted. I am still keen to get this merged, 3.9 test was the only one failing. Any pointers? Happy to discuss in IRC / Discord / whatever chat app is the latest flavour of the month.