47

I've got a bunch of django_mark_safe errors

>> Issue: [B703:django_mark_safe] Potential XSS on mark_safe function.
   Severity: Medium   Confidence: High
   Location: ...
   More Info: https://bandit.readthedocs.io/en/latest/plugins/b703_django_mark_safe.html
54 return mark_safe(f'<a href="{url}" target="_blank">{title}</a>')

>> Issue: [B308:blacklist] Use of mark_safe() may expose cross-site scripting vulnerabilities and should be reviewed.
   Severity: Medium   Confidence: High
   Location: ...
   More Info: https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b308-mark-safe
54 return mark_safe(f'<a href="{url}" target="_blank">{title}</a>')

And I'm curious if there is a way to skip or ignore such lines? I understand that using mark_safe could be dangerous, but what if I want to take the risk? For example this method is the only way to display custom link in Django admin, so I don't know any other option how to do it without mark_safe

JL Peyret
  • 10,917
  • 2
  • 54
  • 73
ramusus
  • 7,789
  • 5
  • 38
  • 45

5 Answers5

68

I've got an answer here:

Two ways:

  1. You can skip the B703 and B308 using the --skip argument to the command line.
  2. Or you can affix a comment # nosec on the line to skip.

https://bandit.readthedocs.io/en/latest/config.html#exclusions

vvvvv
  • 25,404
  • 19
  • 49
  • 81
ramusus
  • 7,789
  • 5
  • 38
  • 45
16

Heads up for annotating multilines with # nosec:

given:

li_without_nosec = [
    "select * from %s where 1 = 1 "
    % "foo"
]

li_nosec_at_start_works = [  # nosec - ✅ and you can put a comment
    "select * from %s where 1 = 1 "
    % "foo"
]  

# nosec - there's an enhancement request to marker above line
li_nosec_on_top_doesntwork = [  
    "select * from %s where 1 = 1 "
    % "foo"
]  

li_nosec_at_end_doesntwork = [
    "select * from %s where 1 = 1 "
    % "foo"
]  # nosec 

output:

>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Low
   Location: test.py:3
   More Info: https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html
2   li_without_nosec = [
3       "select * from %s where 1 = 1 "
4       % "foo"
5   ]

--------------------------------------------------
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Low
   Location: test.py:15
   More Info: https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html
14  li_nosec_on_top_doesntwork = [
15      "select * from %s where 1 = 1 "
16      % "foo"
17  ]

--------------------------------------------------
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Low
   Location: test.py:21
   More Info: https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html
20  li_nosec_at_end_doesntwork = [
21      "select * from %s where 1 = 1 "
22      % "foo"
23  ]  # nosec

Black

Here's hoping that black won't get involved and restructure the lines, moving the # nosec around.

so much for hope... black does move things around, just like it does with pylint directives, whenever the line length becomes too long. At which point # nosec ends up at the end.

You can either proactively break up the line and position # nosec at the first one. Or you can just wait out black and adjust if needed.

vvvvv
  • 25,404
  • 19
  • 49
  • 81
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • Suppose I have a multi-line like this: assert(10<20,"a very long multiline message with \ for better readability....") then where do I place the #nosec? – variable Jun 24 '20 at 07:23
  • @variable not sure. You could enter a new question outright, with your actual sample code, tag it bandit and link to this question (ping/DM me in a comment if you do that) and we can look for an answer. Unfortunately, until bandit allows top-of-code annotation comments, like pylint, the answer may be tightly coupled to your particular code's format so it is hard to reason about without seeing your code. Also, FWIW, if you use mypy, I think mypy `# type: ignore` are also operating with the same constraints as bandit so you could check what people are up to there. – JL Peyret Jun 24 '20 at 23:14
10

If you want to apply the rule in a local piece of your code you can do something like the following:

# this is my very basic python script

def foo():
   do_something_unsecure()  # nosec B703, B308

In that way you will skip those validations just in that line. In real work it can be probably the most appropriated way to skip some rules.

allexiusw
  • 1,533
  • 2
  • 16
  • 23
9

Just to complete the topic - in my case I had to rid of B322: input rule, and didn't wanted to write # nosec each time I found this problem in the code, or to always execute Bandit with a --skip flag.

So if you want to omit a certain rule for whole solution, you can create a .bandit file in the root of your project. Then you can write which rules should be skipped every time, for example:

[bandit]
skips: B322

And then Bandit will skip this check by default without need to give additional comments in the code.

rafaljusiak
  • 1,050
  • 1
  • 10
  • 17
1

You can config Bandit with .bandit INI file (only if it is invoked with -r option):

[bandit]
tests = B101,B102,B301

Or with pyproject.toml file:

[tool.bandit]
tests = ["B201", "B301"]
skips = ["B101", "B601"]

Or with yaml file:

skips: ['B101', 'B601']
assert_used:
  skips: ["*/test_*.py", "*/test_*.py"]

See https://bandit.readthedocs.io/en/latest/config.html

Noam Nol
  • 570
  • 4
  • 11