codelaw-rules-security-python 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. codelaw_rules_security_python/LICENSE +23 -0
  2. codelaw_rules_security_python/NOTICE +19 -0
  3. codelaw_rules_security_python/README.md +239 -0
  4. codelaw_rules_security_python/codelaw-pack.toml +6 -0
  5. codelaw_rules_security_python/corpus.toml +65 -0
  6. codelaw_rules_security_python/data/sources/django.json +17 -0
  7. codelaw_rules_security_python/data/sources/fastapi.json +10 -0
  8. codelaw_rules_security_python/data/sources/flask.json +14 -0
  9. codelaw_rules_security_python/presets/sec-py/recommended.toml +60 -0
  10. codelaw_rules_security_python/presets/sec-py/strict.toml +8 -0
  11. codelaw_rules_security_python/rules/sec-py/cookie-flags.cases.md +61 -0
  12. codelaw_rules_security_python/rules/sec-py/cookie-flags.md +95 -0
  13. codelaw_rules_security_python/rules/sec-py/django-settings.cases.md +61 -0
  14. codelaw_rules_security_python/rules/sec-py/django-settings.md +68 -0
  15. codelaw_rules_security_python/rules/sec-py/fastapi-cors.cases.md +31 -0
  16. codelaw_rules_security_python/rules/sec-py/fastapi-cors.md +46 -0
  17. codelaw_rules_security_python/rules/sec-py/flask-debug.cases.md +31 -0
  18. codelaw_rules_security_python/rules/sec-py/flask-debug.md +59 -0
  19. codelaw_rules_security_python/rules/sec-py/flask-settings.cases.md +51 -0
  20. codelaw_rules_security_python/rules/sec-py/flask-settings.md +84 -0
  21. codelaw_rules_security_python/rules/sec-py/hf-pin-revision.cases.md +51 -0
  22. codelaw_rules_security_python/rules/sec-py/hf-pin-revision.md +58 -0
  23. codelaw_rules_security_python/rules/sec-py/jinja-autoescape.cases.md +62 -0
  24. codelaw_rules_security_python/rules/sec-py/jinja-autoescape.md +80 -0
  25. codelaw_rules_security_python/rules/sec-py/logging-listen.cases.md +31 -0
  26. codelaw_rules_security_python/rules/sec-py/logging-listen.md +47 -0
  27. codelaw_rules_security_python/rules/sec-py/ml-unsafe-load.cases.md +61 -0
  28. codelaw_rules_security_python/rules/sec-py/ml-unsafe-load.md +68 -0
  29. codelaw_rules_security_python/rules/sec-py/no-assert-for-auth.cases.md +31 -0
  30. codelaw_rules_security_python/rules/sec-py/no-assert-for-auth.md +51 -0
  31. codelaw_rules_security_python/rules/sec-py/no-bind-all-interfaces.cases.md +41 -0
  32. codelaw_rules_security_python/rules/sec-py/no-bind-all-interfaces.md +75 -0
  33. codelaw_rules_security_python/rules/sec-py/no-cleartext-http.cases.md +41 -0
  34. codelaw_rules_security_python/rules/sec-py/no-cleartext-http.md +56 -0
  35. codelaw_rules_security_python/rules/sec-py/no-csrf-exempt.cases.md +49 -0
  36. codelaw_rules_security_python/rules/sec-py/no-csrf-exempt.md +47 -0
  37. codelaw_rules_security_python/rules/sec-py/no-dynamic-import.cases.md +35 -0
  38. codelaw_rules_security_python/rules/sec-py/no-dynamic-import.md +51 -0
  39. codelaw_rules_security_python/rules/sec-py/no-exec-eval.cases.md +51 -0
  40. codelaw_rules_security_python/rules/sec-py/no-exec-eval.md +56 -0
  41. codelaw_rules_security_python/rules/sec-py/no-format-string-injection.cases.md +35 -0
  42. codelaw_rules_security_python/rules/sec-py/no-format-string-injection.md +44 -0
  43. codelaw_rules_security_python/rules/sec-py/no-hardcoded-crypto-key.cases.md +51 -0
  44. codelaw_rules_security_python/rules/sec-py/no-hardcoded-crypto-key.md +91 -0
  45. codelaw_rules_security_python/rules/sec-py/no-hardcoded-secret.cases.md +61 -0
  46. codelaw_rules_security_python/rules/sec-py/no-hardcoded-secret.md +82 -0
  47. codelaw_rules_security_python/rules/sec-py/no-header-injection.cases.md +45 -0
  48. codelaw_rules_security_python/rules/sec-py/no-header-injection.md +55 -0
  49. codelaw_rules_security_python/rules/sec-py/no-insecure-protocol.cases.md +41 -0
  50. codelaw_rules_security_python/rules/sec-py/no-insecure-protocol.md +52 -0
  51. codelaw_rules_security_python/rules/sec-py/no-insecure-random.cases.md +41 -0
  52. codelaw_rules_security_python/rules/sec-py/no-insecure-random.md +67 -0
  53. codelaw_rules_security_python/rules/sec-py/no-insecure-tempfile.cases.md +41 -0
  54. codelaw_rules_security_python/rules/sec-py/no-insecure-tempfile.md +62 -0
  55. codelaw_rules_security_python/rules/sec-py/no-ldap-injection.cases.md +41 -0
  56. codelaw_rules_security_python/rules/sec-py/no-ldap-injection.md +51 -0
  57. codelaw_rules_security_python/rules/sec-py/no-llm-output-to-sink.cases.md +31 -0
  58. codelaw_rules_security_python/rules/sec-py/no-llm-output-to-sink.md +55 -0
  59. codelaw_rules_security_python/rules/sec-py/no-log-injection.cases.md +45 -0
  60. codelaw_rules_security_python/rules/sec-py/no-log-injection.md +52 -0
  61. codelaw_rules_security_python/rules/sec-py/no-mako.cases.md +37 -0
  62. codelaw_rules_security_python/rules/sec-py/no-mako.md +52 -0
  63. codelaw_rules_security_python/rules/sec-py/no-mark-safe.cases.md +51 -0
  64. codelaw_rules_security_python/rules/sec-py/no-mark-safe.md +54 -0
  65. codelaw_rules_security_python/rules/sec-py/no-mass-assignment.cases.md +55 -0
  66. codelaw_rules_security_python/rules/sec-py/no-mass-assignment.md +58 -0
  67. codelaw_rules_security_python/rules/sec-py/no-nosql-injection.cases.md +34 -0
  68. codelaw_rules_security_python/rules/sec-py/no-nosql-injection.md +56 -0
  69. codelaw_rules_security_python/rules/sec-py/no-open-redirect.cases.md +35 -0
  70. codelaw_rules_security_python/rules/sec-py/no-open-redirect.md +51 -0
  71. codelaw_rules_security_python/rules/sec-py/no-partial-path.cases.md +41 -0
  72. codelaw_rules_security_python/rules/sec-py/no-partial-path.md +61 -0
  73. codelaw_rules_security_python/rules/sec-py/no-path-traversal.cases.md +46 -0
  74. codelaw_rules_security_python/rules/sec-py/no-path-traversal.md +54 -0
  75. codelaw_rules_security_python/rules/sec-py/no-prompt-injection.cases.md +46 -0
  76. codelaw_rules_security_python/rules/sec-py/no-prompt-injection.md +65 -0
  77. codelaw_rules_security_python/rules/sec-py/no-pycrypto.cases.md +41 -0
  78. codelaw_rules_security_python/rules/sec-py/no-pycrypto.md +47 -0
  79. codelaw_rules_security_python/rules/sec-py/no-reflected-xss.cases.md +45 -0
  80. codelaw_rules_security_python/rules/sec-py/no-reflected-xss.md +56 -0
  81. codelaw_rules_security_python/rules/sec-py/no-regex-injection.cases.md +33 -0
  82. codelaw_rules_security_python/rules/sec-py/no-regex-injection.md +49 -0
  83. codelaw_rules_security_python/rules/sec-py/no-secret-in-log.cases.md +41 -0
  84. codelaw_rules_security_python/rules/sec-py/no-secret-in-log.md +55 -0
  85. codelaw_rules_security_python/rules/sec-py/no-shell.cases.md +55 -0
  86. codelaw_rules_security_python/rules/sec-py/no-shell.md +63 -0
  87. codelaw_rules_security_python/rules/sec-py/no-silent-except.cases.md +53 -0
  88. codelaw_rules_security_python/rules/sec-py/no-silent-except.md +54 -0
  89. codelaw_rules_security_python/rules/sec-py/no-sql-string.cases.md +91 -0
  90. codelaw_rules_security_python/rules/sec-py/no-sql-string.md +78 -0
  91. codelaw_rules_security_python/rules/sec-py/no-ssrf.cases.md +34 -0
  92. codelaw_rules_security_python/rules/sec-py/no-ssrf.md +54 -0
  93. codelaw_rules_security_python/rules/sec-py/no-stack-trace-response.cases.md +48 -0
  94. codelaw_rules_security_python/rules/sec-py/no-stack-trace-response.md +67 -0
  95. codelaw_rules_security_python/rules/sec-py/no-tls-bypass.cases.md +71 -0
  96. codelaw_rules_security_python/rules/sec-py/no-tls-bypass.md +66 -0
  97. codelaw_rules_security_python/rules/sec-py/no-trust-remote-code.cases.md +31 -0
  98. codelaw_rules_security_python/rules/sec-py/no-trust-remote-code.md +50 -0
  99. codelaw_rules_security_python/rules/sec-py/no-unsafe-deserialize.cases.md +63 -0
  100. codelaw_rules_security_python/rules/sec-py/no-unsafe-deserialize.md +51 -0
  101. codelaw_rules_security_python/rules/sec-py/no-weak-cipher.cases.md +51 -0
  102. codelaw_rules_security_python/rules/sec-py/no-weak-cipher.md +60 -0
  103. codelaw_rules_security_python/rules/sec-py/no-weak-hash.cases.md +41 -0
  104. codelaw_rules_security_python/rules/sec-py/no-weak-hash.md +54 -0
  105. codelaw_rules_security_python/rules/sec-py/no-wildcard-injection.cases.md +41 -0
  106. codelaw_rules_security_python/rules/sec-py/no-wildcard-injection.md +53 -0
  107. codelaw_rules_security_python/rules/sec-py/no-world-writable.cases.md +51 -0
  108. codelaw_rules_security_python/rules/sec-py/no-world-writable.md +57 -0
  109. codelaw_rules_security_python/rules/sec-py/no-xml-unsafe-parser.cases.md +83 -0
  110. codelaw_rules_security_python/rules/sec-py/no-xml-unsafe-parser.md +86 -0
  111. codelaw_rules_security_python/rules/sec-py/no-xmlrpc.cases.md +31 -0
  112. codelaw_rules_security_python/rules/sec-py/no-xmlrpc.md +50 -0
  113. codelaw_rules_security_python/rules/sec-py/no-xpath-injection.cases.md +41 -0
  114. codelaw_rules_security_python/rules/sec-py/no-xpath-injection.md +52 -0
  115. codelaw_rules_security_python/rules/sec-py/paramiko-host-key.cases.md +21 -0
  116. codelaw_rules_security_python/rules/sec-py/paramiko-host-key.md +44 -0
  117. codelaw_rules_security_python/rules/sec-py/password-hash-effort.cases.md +61 -0
  118. codelaw_rules_security_python/rules/sec-py/password-hash-effort.md +74 -0
  119. codelaw_rules_security_python/rules/sec-py/requests-timeout.cases.md +41 -0
  120. codelaw_rules_security_python/rules/sec-py/requests-timeout.md +63 -0
  121. codelaw_rules_security_python/rules/sec-py/tarfile-filter.cases.md +51 -0
  122. codelaw_rules_security_python/rules/sec-py/tarfile-filter.md +71 -0
  123. codelaw_rules_security_python/rules/sec-py/torch-load-weights-only.cases.md +41 -0
  124. codelaw_rules_security_python/rules/sec-py/torch-load-weights-only.md +73 -0
  125. codelaw_rules_security_python/rules/sec-py/upload-secure-filename.cases.md +41 -0
  126. codelaw_rules_security_python/rules/sec-py/upload-secure-filename.md +65 -0
  127. codelaw_rules_security_python/rules/sec-py/urlopen-scheme.cases.md +41 -0
  128. codelaw_rules_security_python/rules/sec-py/urlopen-scheme.md +54 -0
  129. codelaw_rules_security_python/rules/sec-py/weak-key-size.cases.md +51 -0
  130. codelaw_rules_security_python/rules/sec-py/weak-key-size.md +82 -0
  131. codelaw_rules_security_python/rules/sec-py/yaml-safe-load.cases.md +67 -0
  132. codelaw_rules_security_python/rules/sec-py/yaml-safe-load.md +51 -0
  133. codelaw_rules_security_python-0.3.0.dist-info/METADATA +249 -0
  134. codelaw_rules_security_python-0.3.0.dist-info/RECORD +136 -0
  135. codelaw_rules_security_python-0.3.0.dist-info/WHEEL +4 -0
  136. codelaw_rules_security_python-0.3.0.dist-info/licenses/LICENSE +23 -0
@@ -0,0 +1,61 @@
1
+ # sec-py/django-settings — cases
2
+
3
+ ### case: DEBUG
4
+
5
+ ```py path=settings.py
6
+ DEBUG = True
7
+ ```
8
+
9
+ ```expect
10
+ 1:1 sec-py/django-settings
11
+ ```
12
+
13
+ ### case: wildcard hosts
14
+
15
+ ```py path=settings.py
16
+ ALLOWED_HOSTS = ["*"]
17
+ ```
18
+
19
+ ```expect
20
+ 1:1 sec-py/django-settings
21
+ ```
22
+
23
+ ### case: a literal key
24
+
25
+ ```py path=settings.py
26
+ SECRET_KEY = "django-insecure-abc123"
27
+ ```
28
+
29
+ ```expect
30
+ 1:1 sec-py/django-settings
31
+ ```
32
+
33
+ ### case: a key from the environment
34
+
35
+ ```py path=settings.py
36
+ SECRET_KEY = os.environ["KEY"]
37
+ ```
38
+
39
+ ```expect
40
+ (none)
41
+ ```
42
+
43
+ ### case: an insecure cookie
44
+
45
+ ```py path=settings.py
46
+ SESSION_COOKIE_SECURE = False
47
+ ```
48
+
49
+ ```expect
50
+ 1:1 sec-py/django-settings
51
+ ```
52
+
53
+ ### case: a secure cookie
54
+
55
+ ```py path=settings.py
56
+ CSRF_COOKIE_SECURE = True
57
+ ```
58
+
59
+ ```expect
60
+ (none)
61
+ ```
@@ -0,0 +1,68 @@
1
+ ---
2
+ id: sec-py/django-settings
3
+ title: 'Django settings are safe'
4
+ languages: [python]
5
+ files: ["**/settings.py", "**/settings/**/*.py", "**/*_settings.py", "**/settings_*.py"]
6
+ exclude: ["**/*dev*.py", "**/*local*.py", "**/*test*.py"]
7
+ fix: manual
8
+ fix_reason: "the production value comes from the deployment (environment variables, a secret manager)"
9
+ stability: preview
10
+ ported_from: [ruff/S105]
11
+ security: { cwe: [489, 798, 614], owasp: ["A02:2025"], severity: high }
12
+ since: 0.2.0
13
+ ---
14
+
15
+ In a settings module, `DEBUG`, `ALLOWED_HOSTS`, `SECRET_KEY` and the cookie flags are not left in their development values.
16
+
17
+ **Why:** Debug pages show settings and source (CWE-489); `ALLOWED_HOSTS = ["*"]` accepts forged Host headers; a literal `SECRET_KEY` is in the repository (CWE-798); cookies without `Secure` travel over plain HTTP (CWE-614).
18
+
19
+ ```codelaw
20
+ match:
21
+ all:
22
+ - { kind: assignment }
23
+ - any:
24
+ - all: [{ has: { field: left, regex: '^DEBUG$' } }, { has: { field: right, kind: 'true' } }]
25
+ - all:
26
+ - { has: { field: left, regex: '^ALLOWED_HOSTS$' } }
27
+ - { has: { field: right, regex: '^\[\s*[''"]\*[''"]\s*\]$' } }
28
+ - all:
29
+ - { has: { field: left, regex: '^SECRET_KEY$' } }
30
+ - has:
31
+ field: right
32
+ kind: string
33
+ regex: '^[rb]?("[^"]{4,}"|''[^'']{4,}'')$'
34
+ not: { has: { kind: interpolation } }
35
+ - all:
36
+ - has:
37
+ field: left
38
+ regex: '^(SESSION_COOKIE_SECURE|CSRF_COOKIE_SECURE|SECURE_SSL_REDIRECT|SESSION_COOKIE_HTTPONLY)$'
39
+ - { has: { field: right, kind: 'false' } }
40
+ message: 'This Django setting is unsafe outside development'
41
+ ```
42
+
43
+ ## How to fix
44
+
45
+ 1. `DEBUG = os.environ.get("DJANGO_DEBUG") == "1"`.
46
+ 2. `ALLOWED_HOSTS = ["app.example.com"]`: list the host names you serve.
47
+ 3. `SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]`, and rotate the one that was committed.
48
+ 4. Set `SESSION_COOKIE_SECURE`, `CSRF_COOKIE_SECURE` and `SECURE_SSL_REDIRECT` to `True`; run `python manage.py check --deploy`.
49
+
50
+ ## Examples
51
+
52
+ ### Python
53
+
54
+ #### Don't
55
+
56
+ ```py path=settings.py
57
+ DEBUG = True
58
+ ALLOWED_HOSTS = ["*"]
59
+ ```
60
+
61
+ #### Do
62
+
63
+ ```py path=settings.py
64
+ import os
65
+
66
+ DEBUG = os.environ.get("DJANGO_DEBUG") == "1"
67
+ ALLOWED_HOSTS = ["app.example.com"]
68
+ ```
@@ -0,0 +1,31 @@
1
+ # sec-py/fastapi-cors — cases
2
+
3
+ ### case: wildcard with credentials
4
+
5
+ ```py
6
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True)
7
+ ```
8
+
9
+ ```expect
10
+ 1:1 sec-py/fastapi-cors
11
+ ```
12
+
13
+ ### case: wildcard without credentials
14
+
15
+ ```py
16
+ app.add_middleware(CORSMiddleware, allow_origins=["*"])
17
+ ```
18
+
19
+ ```expect
20
+ (none)
21
+ ```
22
+
23
+ ### case: listed origins
24
+
25
+ ```py
26
+ app.add_middleware(CORSMiddleware, allow_origins=["https://a.example"], allow_credentials=True)
27
+ ```
28
+
29
+ ```expect
30
+ (none)
31
+ ```
@@ -0,0 +1,46 @@
1
+ ---
2
+ id: sec-py/fastapi-cors
3
+ title: 'CORS does not allow every origin with credentials'
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the list of trusted origins is the application's own"
7
+ stability: preview
8
+ security: { cwe: [942], owasp: ["A02:2025"], severity: high }
9
+ since: 0.2.0
10
+ ---
11
+
12
+ `CORSMiddleware` is not given `allow_origins=["*"]` together with `allow_credentials=True`.
13
+
14
+ **Why:** Any site can then make authenticated requests as the user and read the answers (CWE-942).
15
+
16
+ ```codelaw
17
+ match:
18
+ any:
19
+ - all:
20
+ - { kind: call }
21
+ - { has: { field: function, regex: '^(CORSMiddleware|.*\.add_middleware)$' } }
22
+ - { has: { field: arguments, regex: 'allow_origins\s*=\s*\[\s*[''"]\*[''"]\s*\]' } }
23
+ - { has: { field: arguments, regex: 'allow_credentials\s*=\s*True' } }
24
+ message: 'Every origin is allowed with credentials; list the trusted origins'
25
+ ```
26
+
27
+ ## How to fix
28
+
29
+ 1. List the origins: `allow_origins=["https://app.example.com"]`.
30
+ 2. If the API needs no cookies, set `allow_credentials=False` and keep `*`.
31
+
32
+ ## Examples
33
+
34
+ ### Python
35
+
36
+ #### Don't
37
+
38
+ ```py
39
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True)
40
+ ```
41
+
42
+ #### Do
43
+
44
+ ```py
45
+ app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.com"], allow_credentials=True)
46
+ ```
@@ -0,0 +1,31 @@
1
+ # sec-py/flask-debug — cases
2
+
3
+ ### case: debug=True
4
+
5
+ ```py
6
+ app.run(host="127.0.0.1", debug=True)
7
+ ```
8
+
9
+ ```expect
10
+ 1:1 sec-py/flask-debug
11
+ ```
12
+
13
+ ### case: debug=False
14
+
15
+ ```py
16
+ app.run(debug=False)
17
+ ```
18
+
19
+ ```expect
20
+ (none)
21
+ ```
22
+
23
+ ### case: no debug
24
+
25
+ ```py
26
+ app.run()
27
+ ```
28
+
29
+ ```expect
30
+ (none)
31
+ ```
@@ -0,0 +1,59 @@
1
+ ---
2
+ id: sec-py/flask-debug
3
+ title: 'Flask''s debugger is off'
4
+ languages: [python]
5
+ exclude: ["**/tests/**", "**/test/**", "**/test_*.py", "**/*_test.py", "**/*_tests.py", "**/tests.py", "**/conftest.py"]
6
+ fix: suggest
7
+ fix_reason: "the setting belongs to the environment, so the debug flag has to come from configuration"
8
+ stability: preview
9
+ ported_from: [bandit/B201, ruff/S201]
10
+ security: { cwe: [489], owasp: ["A02:2025"], severity: high }
11
+ since: 0.2.0
12
+ ---
13
+
14
+ `app.run(debug=True)` is not written in code.
15
+
16
+ **Why:** The debugger serves an interactive console that runs code on the server, and a stack trace shows source and settings (CWE-489).
17
+
18
+ ```codelaw
19
+ match:
20
+ kind: call
21
+ all:
22
+ - { has: { field: function, regex: '^(app|application|flask_app|self\.app)\.run$' } }
23
+ - has:
24
+ field: arguments
25
+ has:
26
+ kind: keyword_argument
27
+ all: [{ has: { field: name, regex: '^debug$' } }, { has: { field: value, kind: 'true' } }]
28
+ pattern: $KW
29
+ message: 'Flask runs with the debugger on; take `debug` from the environment'
30
+ fix:
31
+ - { remove: $KW }
32
+ ```
33
+
34
+ ## How to fix
35
+
36
+ 1. Remove `debug=True`; turn the debugger on with `flask --debug run` while developing.
37
+ 2. In production run a WSGI server (`gunicorn app:app`), not `app.run`.
38
+
39
+ ## Examples
40
+
41
+ ### Python
42
+
43
+ #### Don't
44
+
45
+ ```py
46
+ from flask import Flask
47
+
48
+ app = Flask(__name__)
49
+ app.run(debug=True)
50
+ ```
51
+
52
+ #### Do
53
+
54
+ ```py
55
+ from flask import Flask
56
+
57
+ app = Flask(__name__)
58
+ app.run()
59
+ ```
@@ -0,0 +1,51 @@
1
+ # sec-py/flask-settings — cases
2
+
3
+ ### case: a literal key
4
+
5
+ ```py
6
+ app.config["SECRET_KEY"] = "dev-secret-key"
7
+ ```
8
+
9
+ ```expect
10
+ 1:1 sec-py/flask-settings
11
+ ```
12
+
13
+ ### case: csrf off
14
+
15
+ ```py
16
+ app.config["WTF_CSRF_ENABLED"] = False
17
+ ```
18
+
19
+ ```expect
20
+ 1:1 sec-py/flask-settings
21
+ ```
22
+
23
+ ### case: update
24
+
25
+ ```py
26
+ app.config.update(SECRET_KEY="abcdef")
27
+ ```
28
+
29
+ ```expect
30
+ 1:1 sec-py/flask-settings
31
+ ```
32
+
33
+ ### case: an environment key
34
+
35
+ ```py
36
+ app.config["SECRET_KEY"] = os.environ["K"]
37
+ ```
38
+
39
+ ```expect
40
+ (none)
41
+ ```
42
+
43
+ ### case: a secure cookie
44
+
45
+ ```py
46
+ app.config["SESSION_COOKIE_SECURE"] = True
47
+ ```
48
+
49
+ ```expect
50
+ (none)
51
+ ```
@@ -0,0 +1,84 @@
1
+ ---
2
+ id: sec-py/flask-settings
3
+ title: 'Flask configuration is safe'
4
+ languages: [python]
5
+ exclude: ["**/tests/**", "**/test/**", "**/test_*.py", "**/*_test.py", "**/*_tests.py", "**/tests.py", "**/conftest.py"]
6
+ fix: manual
7
+ fix_reason: "the production values come from the deployment (environment variables, a secret manager)"
8
+ stability: preview
9
+ security: { cwe: [798, 352, 614], owasp: ["A02:2025"], severity: high }
10
+ since: 0.2.0
11
+ ---
12
+
13
+ `SECRET_KEY` is not a literal, and `WTF_CSRF_ENABLED` and `SESSION_COOKIE_SECURE` are not switched off in the app configuration.
14
+
15
+ **Why:** A literal key is in the repository (CWE-798), turning off CSRF protection lets other sites submit forms as the user (CWE-352), and a cookie without `Secure` travels over plain HTTP (CWE-614).
16
+
17
+ ```codelaw
18
+ match:
19
+ any:
20
+ - all:
21
+ - { kind: assignment }
22
+ - { has: { field: left, regex: 'config\[\s*[''"]SECRET_KEY[''"]\s*\]$' } }
23
+ - has:
24
+ field: right
25
+ kind: string
26
+ regex: '^[rb]?("[^"]{4,}"|''[^'']{4,}'')$'
27
+ not: { has: { kind: interpolation } }
28
+ - all:
29
+ - { kind: assignment }
30
+ - has:
31
+ field: left
32
+ regex: 'config\[\s*[''"](WTF_CSRF_ENABLED|WTF_CSRF_CHECK_DEFAULT|SESSION_COOKIE_SECURE|SESSION_COOKIE_HTTPONLY)[''"]\s*\]$'
33
+ - { has: { field: right, kind: 'false' } }
34
+ - kind: call
35
+ all:
36
+ - { has: { field: function, regex: '\.config\.(update|from_mapping)$' } }
37
+ - any:
38
+ - has:
39
+ field: arguments
40
+ has:
41
+ kind: keyword_argument
42
+ all:
43
+ - { has: { field: name, regex: '^SECRET_KEY$' } }
44
+ - { has: { field: value, kind: string, not: { has: { kind: interpolation } } } }
45
+ - has:
46
+ field: arguments
47
+ has:
48
+ kind: keyword_argument
49
+ all:
50
+ - { has: { field: name, regex: '^WTF_CSRF_ENABLED$' } }
51
+ - { has: { field: value, kind: 'false' } }
52
+ - has:
53
+ field: arguments
54
+ has:
55
+ kind: keyword_argument
56
+ all:
57
+ - { has: { field: name, regex: '^SESSION_COOKIE_SECURE$' } }
58
+ - { has: { field: value, kind: 'false' } }
59
+ message: 'This Flask setting is unsafe outside development'
60
+ ```
61
+
62
+ ## How to fix
63
+
64
+ 1. `app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"]`, and rotate the key that was committed.
65
+ 2. Leave `WTF_CSRF_ENABLED` on; send the token with `{{ csrf_token() }}`.
66
+ 3. Set `SESSION_COOKIE_SECURE`, `SESSION_COOKIE_HTTPONLY` and `SESSION_COOKIE_SAMESITE` to safe values.
67
+
68
+ ## Examples
69
+
70
+ ### Python
71
+
72
+ #### Don't
73
+
74
+ ```py
75
+ app.config["SECRET_KEY"] = "dev-secret-key"
76
+ ```
77
+
78
+ #### Do
79
+
80
+ ```py
81
+ import os
82
+
83
+ app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"]
84
+ ```
@@ -0,0 +1,51 @@
1
+ # sec-py/hf-pin-revision — cases
2
+
3
+ ### case: pinned
4
+
5
+ ```py
6
+ model = AutoModel.from_pretrained(name, revision=rev)
7
+ ```
8
+
9
+ ```expect
10
+ (none)
11
+ ```
12
+
13
+ ### case: unpinned
14
+
15
+ ```py
16
+ tok = AutoTokenizer.from_pretrained(name)
17
+ ```
18
+
19
+ ```expect
20
+ 1:7 sec-py/hf-pin-revision
21
+ ```
22
+
23
+ ### case: hub download
24
+
25
+ ```py
26
+ hf_hub_download(repo_id, filename)
27
+ ```
28
+
29
+ ```expect
30
+ 1:1 sec-py/hf-pin-revision
31
+ ```
32
+
33
+ ### case: dataset
34
+
35
+ ```py
36
+ load_dataset("org/data")
37
+ ```
38
+
39
+ ```expect
40
+ 1:1 sec-py/hf-pin-revision
41
+ ```
42
+
43
+ ### case: a local path is still flagged
44
+
45
+ ```py
46
+ AutoModel.from_pretrained("./model")
47
+ ```
48
+
49
+ ```expect
50
+ 1:1 sec-py/hf-pin-revision
51
+ ```
@@ -0,0 +1,58 @@
1
+ ---
2
+ id: sec-py/hf-pin-revision
3
+ title: 'Hugging Face downloads pin a revision'
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the commit hash to pin is a choice the author makes after reviewing the model"
7
+ stability: preview
8
+ ported_from: [bandit/B615]
9
+ security: { cwe: [494], owasp: ["A03:2025"], severity: medium }
10
+ since: 0.2.0
11
+ ---
12
+
13
+ `from_pretrained`, `hf_hub_download`, `snapshot_download` and `load_dataset` are called with `revision=`.
14
+
15
+ **Why:** Without a revision the code runs whatever the repository's `main` branch holds today, so a change there reaches production unreviewed (CWE-494).
16
+
17
+ ```codelaw
18
+ match:
19
+ kind: call
20
+ all:
21
+ - has:
22
+ field: function
23
+ regex: '^((.*\.)?(from_pretrained|hf_hub_download|snapshot_download)|(datasets\.)?load_dataset)$'
24
+ - not:
25
+ has:
26
+ field: arguments
27
+ has:
28
+ kind: keyword_argument
29
+ all: [{ has: { field: name, regex: '^revision$' } }]
30
+ message: 'This download follows `main`; pin `revision=` to a commit hash'
31
+ ```
32
+
33
+ ## How to fix
34
+
35
+ 1. Find the commit hash of the revision you reviewed on the model's page.
36
+ 2. Pass it: `from_pretrained(name, revision="<hash>")`.
37
+ 3. Keep the hash in one constant or setting so an upgrade is one reviewed change.
38
+
39
+ ## Examples
40
+
41
+ ### Python
42
+
43
+ #### Don't
44
+
45
+ ```py
46
+ from transformers import AutoModel
47
+
48
+ model = AutoModel.from_pretrained("org/model")
49
+ ```
50
+
51
+ #### Do
52
+
53
+ ```py
54
+ from transformers import AutoModel
55
+
56
+ MODEL_REVISION = "5c9e4d1"
57
+ model = AutoModel.from_pretrained("org/model", revision=MODEL_REVISION)
58
+ ```
@@ -0,0 +1,62 @@
1
+ # sec-py/jinja-autoescape — cases
2
+
3
+ ### case: autoescape=False
4
+
5
+ ```py
6
+ env = Environment(autoescape=False)
7
+ ```
8
+
9
+ ```expect
10
+ 1:7 sec-py/jinja-autoescape
11
+ ```
12
+
13
+ ### case: no autoescape given
14
+
15
+ ```py
16
+ env = Environment(loader=loader)
17
+ ```
18
+
19
+ ```expect
20
+ 1:7 sec-py/jinja-autoescape
21
+ ```
22
+
23
+ ### case: select_autoescape
24
+
25
+ ```py
26
+ env = Environment(autoescape=select_autoescape())
27
+ ```
28
+
29
+ ```expect
30
+ (none)
31
+ ```
32
+
33
+ ### case: SSTI
34
+
35
+ ```py
36
+ def v(request):
37
+ return render_template_string(request.args["t"])
38
+ ```
39
+
40
+ ```expect
41
+ 2:12 sec-py/jinja-autoescape
42
+ ```
43
+
44
+ ### case: from_string
45
+
46
+ ```py
47
+ env.from_string(text)
48
+ ```
49
+
50
+ ```expect
51
+ 1:1 sec-py/jinja-autoescape
52
+ ```
53
+
54
+ ### case: a literal template
55
+
56
+ ```py
57
+ render_template_string("<p>{{ x }}</p>", x=x)
58
+ ```
59
+
60
+ ```expect
61
+ (none)
62
+ ```
@@ -0,0 +1,80 @@
1
+ ---
2
+ id: sec-py/jinja-autoescape
3
+ title: 'Jinja2 keeps autoescape on'
4
+ languages: [python]
5
+ fix: suggest
6
+ fix_reason: "turning autoescape on changes the output of templates that write HTML on purpose; those need `|safe` or `Markup`"
7
+ stability: preview
8
+ ported_from: [bandit/B701, ruff/S701]
9
+ security: { cwe: [79, 1336], owasp: ["A05:2025"], severity: high }
10
+ since: 0.2.0
11
+ ---
12
+
13
+ `Environment` is not created with `autoescape=False` or without `autoescape`, and `render_template_string` / `Template` / `from_string` are not given a template that is not a literal.
14
+
15
+ **Why:** Without autoescape a value written into a page can carry a script (CWE-79); a template built from a non-literal runs template code on the server (CWE-1336).
16
+
17
+ ```codelaw
18
+ match:
19
+ any:
20
+ - kind: call
21
+ all:
22
+ - { has: { field: function, regex: '^(jinja2\.)?Environment$' } }
23
+ - has:
24
+ field: arguments
25
+ has:
26
+ kind: keyword_argument
27
+ all:
28
+ - { has: { field: name, regex: '^autoescape$' } }
29
+ - { has: { field: value, kind: 'false' } }
30
+ pattern: $KW
31
+ - all:
32
+ - { kind: call }
33
+ - { has: { field: function, regex: '^(jinja2\.)?Environment$' } }
34
+ - not:
35
+ has:
36
+ field: arguments
37
+ has: { kind: keyword_argument, has: { field: name, regex: '^(autoescape)$' } }
38
+ - { pattern: 'render_template_string($T)' }
39
+ - { pattern: 'jinja2.Template($T)' }
40
+ - { pattern: '$E.from_string($T)' }
41
+ constraints:
42
+ T: { not: { kind: string, not: { has: { kind: interpolation } } } }
43
+ E: { regex: '^(env|environment|jinja_env|jinja2\.Environment\(.*\))$' }
44
+ message: 'Jinja2 is not escaping, or the template comes from the request'
45
+ fix:
46
+ - { replace: { target: $KW, with: 'autoescape=True' } }
47
+ ```
48
+
49
+ ## How to fix
50
+
51
+ 1. Create the environment with `autoescape=True`, or `select_autoescape()`.
52
+ 2. Never use request data as the template: pass it as a value to a template file instead.
53
+
54
+ ## Examples
55
+
56
+ ### Python
57
+
58
+ #### Don't
59
+
60
+ ```py
61
+ from jinja2 import Environment
62
+
63
+ env = Environment(autoescape=False)
64
+ ```
65
+
66
+ #### Do
67
+
68
+ ```py
69
+ from jinja2 import Environment
70
+
71
+ env = Environment(autoescape=True)
72
+ ```
73
+
74
+ #### Fixed
75
+
76
+ ```py
77
+ from jinja2 import Environment
78
+
79
+ env = Environment(autoescape=True)
80
+ ```