Skip to content

Commit 4da6936

Browse files
[flake8-bandit] Allow raw strings in suspicious-mark-safe-usage (S308) #16702 (#16770)
## Summary Stop flagging each invocation of `django.utils.safestring.mark_safe` (also available at, `django.utils.html.mark_safe`) as an error. Instead, allow string literals as valid uses for `mark_safe`. Also, update the documentation, pointing at `django.utils.html.format_html` for dynamic content generation use cases. Closes #16702 ## Test Plan I verified several possible uses, but string literals, are still flagged. --------- Co-authored-by: Micha Reiser <micha@reiser.io>
1 parent 238ec39 commit 4da6936

File tree

4 files changed

+251
-49
lines changed

4 files changed

+251
-49
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_bandit/S308.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
from django.utils.safestring import mark_safe
22

33

4-
def some_func():
5-
return mark_safe('<script>alert("evil!")</script>')
4+
def bad_func():
5+
inject = "harmful_input"
6+
mark_safe(inject)
7+
mark_safe("I will add" + inject + "to my string")
8+
mark_safe("I will add %s to my string" % inject)
9+
mark_safe("I will add {} to my string".format(inject))
10+
mark_safe(f"I will add {inject} to my string")
11+
12+
def good_func():
13+
mark_safe("I won't inject anything")
614

715

816
@mark_safe
@@ -13,8 +21,16 @@ def some_func():
1321
from django.utils.html import mark_safe
1422

1523

16-
def some_func():
17-
return mark_safe('<script>alert("evil!")</script>')
24+
def bad_func():
25+
inject = "harmful_input"
26+
mark_safe(inject)
27+
mark_safe("I will add" + inject + "to my string")
28+
mark_safe("I will add %s to my string" % inject)
29+
mark_safe("I will add {} to my string".format(inject))
30+
mark_safe(f"I will add {inject} to my string")
31+
32+
def good_func():
33+
mark_safe("I won't inject anything")
1834

1935

2036
@mark_safe

crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,21 +344,31 @@ impl Violation for SuspiciousEvalUsage {
344344
/// before rending them.
345345
///
346346
/// `django.utils.safestring.mark_safe` marks a string as safe for use in HTML
347-
/// templates, bypassing XSS protection. This is dangerous because it may allow
347+
/// templates, bypassing XSS protection. Its usage can be dangerous if the
348+
/// contents of the string are dynamically generated, because it may allow
348349
/// cross-site scripting attacks if the string is not properly escaped.
349350
///
351+
/// For dynamically generated strings, consider utilizing
352+
/// `django.utils.html.format_html`.
353+
///
350354
/// In [preview], this rule will also flag references to `django.utils.safestring.mark_safe`.
351355
///
352356
/// ## Example
353357
/// ```python
354358
/// from django.utils.safestring import mark_safe
355359
///
356-
/// content = mark_safe("<script>alert('Hello, world!')</script>") # XSS.
360+
///
361+
/// def render_username(username):
362+
/// return mark_safe(f"<i>{username}</i>") # Dangerous if username is user-provided.
357363
/// ```
358364
///
359365
/// Use instead:
360366
/// ```python
361-
/// content = "<script>alert('Hello, world!')</script>" # Safe if rendered.
367+
/// from django.utils.html import format_html
368+
///
369+
///
370+
/// def render_username(username):
371+
/// return django.utils.html.format_html("<i>{}</i>", username) # username is escaped.
362372
/// ```
363373
///
364374
/// ## References
@@ -1059,7 +1069,16 @@ fn suspicious_function(
10591069
["" | "builtins", "eval"] => SuspiciousEvalUsage.into(),
10601070

10611071
// MarkSafe
1062-
["django", "utils", "safestring" | "html", "mark_safe"] => SuspiciousMarkSafeUsage.into(),
1072+
["django", "utils", "safestring" | "html", "mark_safe"] => {
1073+
if let Some(arguments) = arguments {
1074+
if let [single] = &*arguments.args {
1075+
if single.is_string_literal_expr() {
1076+
return;
1077+
}
1078+
}
1079+
}
1080+
SuspiciousMarkSafeUsage.into()
1081+
}
10631082

10641083
// URLOpen (`Request`)
10651084
["urllib", "request", "Request"] | ["six", "moves", "urllib", "request", "Request"] => {
Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,116 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
3-
snapshot_kind: text
43
---
5-
S308.py:5:12: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
4+
S308.py:6:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
65
|
7-
4 | def some_func():
8-
5 | return mark_safe('<script>alert("evil!")</script>')
9-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
6+
4 | def bad_func():
7+
5 | inject = "harmful_input"
8+
6 | mark_safe(inject)
9+
| ^^^^^^^^^^^^^^^^^ S308
10+
7 | mark_safe("I will add" + inject + "to my string")
11+
8 | mark_safe("I will add %s to my string" % inject)
1012
|
1113

12-
S308.py:8:1: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
14+
S308.py:7:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
15+
|
16+
5 | inject = "harmful_input"
17+
6 | mark_safe(inject)
18+
7 | mark_safe("I will add" + inject + "to my string")
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
20+
8 | mark_safe("I will add %s to my string" % inject)
21+
9 | mark_safe("I will add {} to my string".format(inject))
22+
|
23+
24+
S308.py:8:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
25+
|
26+
6 | mark_safe(inject)
27+
7 | mark_safe("I will add" + inject + "to my string")
28+
8 | mark_safe("I will add %s to my string" % inject)
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
30+
9 | mark_safe("I will add {} to my string".format(inject))
31+
10 | mark_safe(f"I will add {inject} to my string")
32+
|
33+
34+
S308.py:9:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
35+
|
36+
7 | mark_safe("I will add" + inject + "to my string")
37+
8 | mark_safe("I will add %s to my string" % inject)
38+
9 | mark_safe("I will add {} to my string".format(inject))
39+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
40+
10 | mark_safe(f"I will add {inject} to my string")
41+
|
42+
43+
S308.py:10:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
44+
|
45+
8 | mark_safe("I will add %s to my string" % inject)
46+
9 | mark_safe("I will add {} to my string".format(inject))
47+
10 | mark_safe(f"I will add {inject} to my string")
48+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
49+
11 |
50+
12 | def good_func():
51+
|
52+
53+
S308.py:16:1: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
1354
|
14-
8 | @mark_safe
55+
16 | @mark_safe
1556
| ^^^^^^^^^^ S308
16-
9 | def some_func():
17-
10 | return '<script>alert("evil!")</script>'
57+
17 | def some_func():
58+
18 | return '<script>alert("evil!")</script>'
59+
|
60+
61+
S308.py:26:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
62+
|
63+
24 | def bad_func():
64+
25 | inject = "harmful_input"
65+
26 | mark_safe(inject)
66+
| ^^^^^^^^^^^^^^^^^ S308
67+
27 | mark_safe("I will add" + inject + "to my string")
68+
28 | mark_safe("I will add %s to my string" % inject)
69+
|
70+
71+
S308.py:27:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
72+
|
73+
25 | inject = "harmful_input"
74+
26 | mark_safe(inject)
75+
27 | mark_safe("I will add" + inject + "to my string")
76+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
77+
28 | mark_safe("I will add %s to my string" % inject)
78+
29 | mark_safe("I will add {} to my string".format(inject))
79+
|
80+
81+
S308.py:28:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
82+
|
83+
26 | mark_safe(inject)
84+
27 | mark_safe("I will add" + inject + "to my string")
85+
28 | mark_safe("I will add %s to my string" % inject)
86+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
87+
29 | mark_safe("I will add {} to my string".format(inject))
88+
30 | mark_safe(f"I will add {inject} to my string")
89+
|
90+
91+
S308.py:29:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
92+
|
93+
27 | mark_safe("I will add" + inject + "to my string")
94+
28 | mark_safe("I will add %s to my string" % inject)
95+
29 | mark_safe("I will add {} to my string".format(inject))
96+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
97+
30 | mark_safe(f"I will add {inject} to my string")
1898
|
1999

20-
S308.py:17:12: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
100+
S308.py:30:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
21101
|
22-
16 | def some_func():
23-
17 | return mark_safe('<script>alert("evil!")</script>')
24-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
102+
28 | mark_safe("I will add %s to my string" % inject)
103+
29 | mark_safe("I will add {} to my string".format(inject))
104+
30 | mark_safe(f"I will add {inject} to my string")
105+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
106+
31 |
107+
32 | def good_func():
25108
|
26109

27-
S308.py:20:1: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
110+
S308.py:36:1: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
28111
|
29-
20 | @mark_safe
112+
36 | @mark_safe
30113
| ^^^^^^^^^^ S308
31-
21 | def some_func():
32-
22 | return '<script>alert("evil!")</script>'
114+
37 | def some_func():
115+
38 | return '<script>alert("evil!")</script>'
33116
|
Lines changed: 108 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,132 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
33
---
4-
S308.py:5:12: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
4+
S308.py:6:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
55
|
6-
4 | def some_func():
7-
5 | return mark_safe('<script>alert("evil!")</script>')
8-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
6+
4 | def bad_func():
7+
5 | inject = "harmful_input"
8+
6 | mark_safe(inject)
9+
| ^^^^^^^^^^^^^^^^^ S308
10+
7 | mark_safe("I will add" + inject + "to my string")
11+
8 | mark_safe("I will add %s to my string" % inject)
912
|
1013

11-
S308.py:8:2: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
14+
S308.py:7:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
15+
|
16+
5 | inject = "harmful_input"
17+
6 | mark_safe(inject)
18+
7 | mark_safe("I will add" + inject + "to my string")
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
20+
8 | mark_safe("I will add %s to my string" % inject)
21+
9 | mark_safe("I will add {} to my string".format(inject))
22+
|
23+
24+
S308.py:8:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
1225
|
13-
8 | @mark_safe
14-
| ^^^^^^^^^ S308
15-
9 | def some_func():
16-
10 | return '<script>alert("evil!")</script>'
26+
6 | mark_safe(inject)
27+
7 | mark_safe("I will add" + inject + "to my string")
28+
8 | mark_safe("I will add %s to my string" % inject)
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
30+
9 | mark_safe("I will add {} to my string".format(inject))
31+
10 | mark_safe(f"I will add {inject} to my string")
32+
|
33+
34+
S308.py:9:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
35+
|
36+
7 | mark_safe("I will add" + inject + "to my string")
37+
8 | mark_safe("I will add %s to my string" % inject)
38+
9 | mark_safe("I will add {} to my string".format(inject))
39+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
40+
10 | mark_safe(f"I will add {inject} to my string")
1741
|
1842

19-
S308.py:17:12: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
43+
S308.py:10:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
2044
|
21-
16 | def some_func():
22-
17 | return mark_safe('<script>alert("evil!")</script>')
23-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
45+
8 | mark_safe("I will add %s to my string" % inject)
46+
9 | mark_safe("I will add {} to my string".format(inject))
47+
10 | mark_safe(f"I will add {inject} to my string")
48+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
49+
11 |
50+
12 | def good_func():
2451
|
2552

26-
S308.py:20:2: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
53+
S308.py:16:2: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
2754
|
28-
20 | @mark_safe
55+
16 | @mark_safe
2956
| ^^^^^^^^^ S308
30-
21 | def some_func():
31-
22 | return '<script>alert("evil!")</script>'
57+
17 | def some_func():
58+
18 | return '<script>alert("evil!")</script>'
3259
|
3360

3461
S308.py:26:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
3562
|
36-
25 | # https://github.com/astral-sh/ruff/issues/15522
37-
26 | map(mark_safe, [])
63+
24 | def bad_func():
64+
25 | inject = "harmful_input"
65+
26 | mark_safe(inject)
66+
| ^^^^^^^^^^^^^^^^^ S308
67+
27 | mark_safe("I will add" + inject + "to my string")
68+
28 | mark_safe("I will add %s to my string" % inject)
69+
|
70+
71+
S308.py:27:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
72+
|
73+
25 | inject = "harmful_input"
74+
26 | mark_safe(inject)
75+
27 | mark_safe("I will add" + inject + "to my string")
76+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
77+
28 | mark_safe("I will add %s to my string" % inject)
78+
29 | mark_safe("I will add {} to my string".format(inject))
79+
|
80+
81+
S308.py:28:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
82+
|
83+
26 | mark_safe(inject)
84+
27 | mark_safe("I will add" + inject + "to my string")
85+
28 | mark_safe("I will add %s to my string" % inject)
86+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
87+
29 | mark_safe("I will add {} to my string".format(inject))
88+
30 | mark_safe(f"I will add {inject} to my string")
89+
|
90+
91+
S308.py:29:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
92+
|
93+
27 | mark_safe("I will add" + inject + "to my string")
94+
28 | mark_safe("I will add %s to my string" % inject)
95+
29 | mark_safe("I will add {} to my string".format(inject))
96+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
97+
30 | mark_safe(f"I will add {inject} to my string")
98+
|
99+
100+
S308.py:30:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
101+
|
102+
28 | mark_safe("I will add %s to my string" % inject)
103+
29 | mark_safe("I will add {} to my string".format(inject))
104+
30 | mark_safe(f"I will add {inject} to my string")
105+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308
106+
31 |
107+
32 | def good_func():
108+
|
109+
110+
S308.py:36:2: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
111+
|
112+
36 | @mark_safe
113+
| ^^^^^^^^^ S308
114+
37 | def some_func():
115+
38 | return '<script>alert("evil!")</script>'
116+
|
117+
118+
S308.py:42:5: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
119+
|
120+
41 | # https://github.com/astral-sh/ruff/issues/15522
121+
42 | map(mark_safe, [])
38122
| ^^^^^^^^^ S308
39-
27 | foo = mark_safe
123+
43 | foo = mark_safe
40124
|
41125

42-
S308.py:27:7: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
126+
S308.py:43:7: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities
43127
|
44-
25 | # https://github.com/astral-sh/ruff/issues/15522
45-
26 | map(mark_safe, [])
46-
27 | foo = mark_safe
128+
41 | # https://github.com/astral-sh/ruff/issues/15522
129+
42 | map(mark_safe, [])
130+
43 | foo = mark_safe
47131
| ^^^^^^^^^ S308
48132
|

0 commit comments

Comments
 (0)