-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathgenerate_docs.py
More file actions
212 lines (183 loc) · 10.6 KB
/
generate_docs.py
File metadata and controls
212 lines (183 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import argparse
from dataclasses import dataclass
from codemodder.registry import load_registered_codemods
from pathlib import Path
@dataclass
class DocMetadata:
"""Codemod specific metadata only for documentation"""
importance: str
guidance_explained: str
need_sarif: str = "No"
# codemod-specific metadata that's used only for docs, not for codemod API
METADATA = {
"django-debug-flag-on": DocMetadata(
importance="Medium",
guidance_explained="Django's `DEBUG` flag may be overridden somewhere else or the runtime settings file may be set with the `DJANGO_SETTINGS_MODULE` environment variable. This means that the `DEBUG` flag may intentionally be left on as a development aid.",
),
"django-session-cookie-secure-off": DocMetadata(
importance="Medium",
guidance_explained="Django's `SESSION_COOKIE_SECURE` flag may be overridden somewhere else or the runtime settings file may be set with the `DJANGO_SETTINGS_MODULE` environment variable. This means that the flag may intentionally be left off or missing. Also some applications may still want to support pure http. This is often the case for legacy apps.",
),
"enable-jinja2-autoescape": DocMetadata(
importance="High",
guidance_explained="This codemod protects your applications against XSS attacks. We believe using the default behavior is unsafe.",
),
"fix-mutable-params": DocMetadata(
importance="Medium",
guidance_explained="We believe that this codemod fixes an unsafe practice and that the changes themselves are safe and reliable.",
),
"harden-pyyaml": DocMetadata(
importance="Medium",
guidance_explained="This codemod replaces any unsafe loaders with the `SafeLoader`, which is already the recommended replacement suggested in `yaml` documentation. We believe this replacement is safe and should not result in any issues.",
),
"harden-ruamel": DocMetadata(
importance="Medium",
guidance_explained="This codemod replaces any unsafe `typ` argument with `typ='safe'`, which makes safety explicit and is one of the recommended uses suggested in `ruamel` documentation. We believe this replacement is safe and should not result in any issues.",
),
"https-connection": DocMetadata(
importance="High",
guidance_explained="Support for HTTPS is widespread which, save in some legacy applications, makes this change safe.",
),
"jwt-decode-verify": DocMetadata(
importance="High",
guidance_explained="This codemod ensures your code uses all available validations when calling `jwt.decode`. We believe this replacement is safe and should not result in any issues.",
),
"limit-readline": DocMetadata(
importance="Medium",
guidance_explained="This codemod sets a maximum of 5MB allowed per line read by default. It is unlikely but possible that your code may receive lines that are greater than 5MB _and_ you'd still be interested in reading them, so there is some nominal risk of exceptional cases.",
),
"safe-lxml-parser-defaults": DocMetadata(
importance="High",
guidance_explained="We believe this change is safe, effective, and protects your code against very serious security attacks.",
),
"safe-lxml-parsing": DocMetadata(
importance="High",
guidance_explained="We believe this change is safe, effective, and protects your code against very serious security attacks.",
),
"order-imports": DocMetadata(
importance="Low",
guidance_explained="TODO SKIP FOR NOW",
),
"sandbox-process-creation": DocMetadata(
importance="High",
guidance_explained="We believe this change is safe and effective. The behavior of sandboxing `subprocess.run` and `subprocess.call` calls will only throw `SecurityException` if they see behavior involved in malicious code execution, which is extremely unlikely to happen in normal operation.",
),
"remove-unnecessary-f-str": DocMetadata(
importance="Low",
guidance_explained="We believe this codemod is safe and will not cause any issues.",
),
"unused-imports": DocMetadata(
importance="Low",
guidance_explained="We believe this codemod is safe and will not cause any issues. It is important to note that importing modules may have side-effects that alter the behavior, even if unused, but we believe those cases are rare enough to be safe.",
),
"requests-verify": DocMetadata(
importance="High",
guidance_explained="There may be times when setting `verify=False` is useful for testing though we discourage it. \nYou may also decide to set `verify=/path/to/ca/bundle`. This codemod will not attempt to modify the `verify` value if you do set it to a path.",
),
"secure-flask-cookie": DocMetadata(
importance="Medium",
guidance_explained="Our change provides the most secure way to create cookies in Flask. However, it's possible you have configured your Flask application configurations to use secure cookies. In these cases, using the default parameters for `set_cookie` is safe.",
),
"secure-random": DocMetadata(
importance="High",
guidance_explained="While most of the functions in the `random` module aren't cryptographically secure, there are still valid use cases for `random.random()` such as for simulations or games.",
),
"secure-tempfile": DocMetadata(
importance="High",
guidance_explained="We believe this codemod is safe and will cause no unexpected errors.",
),
"upgrade-sslcontext-minimum-version": DocMetadata(
importance="High",
guidance_explained="This codemod updates the minimum supported version of TLS. Since this is an important security fix and since all modern servers offer TLSv1.2, we believe this change can be safely merged without review.",
),
"upgrade-sslcontext-tls": DocMetadata(
importance="High",
guidance_explained="This codemod updates the minimum supported version of TLS. Since this is an important security fix and since all modern servers offer TLSv1.2, we believe this change can be safely merged without review.",
),
"url-sandbox": DocMetadata(
importance="High",
guidance_explained="""By default, the protection only weaves in 2 checks, which we believe will not cause any issues with the vast majority of code:
1. The given URL must be HTTP/HTTPS.
2. The given URL must not point to a "well-known infrastructure target", which includes things like AWS Metadata Service endpoints, and internal routers (e.g., 192.168.1.1) which are common targets of attacks.
However, on rare occasions an application may use a URL protocol like "file://" or "ftp://" in backend or middleware code.
If you want to allow those protocols, change the incoming PR to look more like this and get the best security possible:
```diff
-resp = requests.get(url)
+resp = safe_requests.get.get(url, allowed_protocols=("ftp",))
```""",
),
"use-defusedxml": DocMetadata(
importance="High",
guidance_explained="We believe this change is safe and effective and guards against serious XML vulnerabilities. You should review this code before merging to make sure the dependency has been properly added to your project.",
),
"use-walrus-if": DocMetadata(
importance="Low",
guidance_explained="We believe that using the walrus operator is an improvement in terms of clarity and readability. However, this change is only compatible with codebases that support Python 3.8 and later, so it requires quick validation before merging.",
),
"bad-lock-with-statement": DocMetadata(
importance="Low",
guidance_explained="We believe this replacement is safe and should not result in any issues.",
),
"sql-parameterization": DocMetadata(
importance="High",
guidance_explained="Python has a wealth of database drivers that all use the same `dbapi2` interface detailed in [PEP249](https://peps.python.org/pep-0249/). Different drivers may require different string tokens used for parameterization, and Python's dynamic typing makes it quite hard, and sometimes impossible, to detect which driver is being used just by looking at the code.",
),
"use-generator": DocMetadata(
importance="Low",
guidance_explained="We believe this replacement is safe and leads to better performance.",
),
"secure-flask-session-configuration": DocMetadata(
importance="Medium",
guidance_explained="Our change fixes explicitly insecure session configuration for a Flask application. However, there may be valid cases to use these insecure configurations, such as for testing or backward compatibility.",
),
"fix-file-resource-leak": DocMetadata(
importance="High",
guidance_explained="We believe this change is safe and will only close file resources that are not referenced outside of the with statement block.",
),
}
def generate_docs(codemod):
try:
codemod_data = METADATA[codemod.name]
except KeyError as exc:
raise KeyError(f"Must add {codemod.name} to METADATA") from exc
formatted_references = [
f"* [{ref['description']}]({ref['url']})" for ref in codemod.references
]
markdown_references = "\n".join(formatted_references) or "N/A"
output = f"""---
title: {codemod.summary}
sidebar_position: 1
---
## {codemod.id}
| Importance | Review Guidance | Requires SARIF Tool |
|------------|----------------------------|---------------------|
| {codemod_data.importance} | {codemod.review_guidance} | {codemod_data.need_sarif} |
{codemod.description}
If you have feedback on this codemod, [please let us know](mailto:feedback@pixee.ai)!
## F.A.Q.
### Why is this codemod marked as {codemod.review_guidance}?
{codemod_data.guidance_explained}
## Codemod Settings
N/A
## References
{markdown_references}
"""
return output
SKIP_DOCS = ["order-imports", "unused-imports"]
def main():
parser = argparse.ArgumentParser(
description="Generate public docs for registered codemods."
)
parser.add_argument(
"directory", type=str, help="directory path where to create doc files"
)
argv = parser.parse_args()
parent_dir = Path(argv.directory)
registry = load_registered_codemods()
for codemod in registry.codemods:
if codemod.name in SKIP_DOCS:
continue
doc = generate_docs(codemod)
codemod_doc_name = f"{codemod.id.replace(':', '_').replace('/', '_')}.md"
with open(parent_dir / codemod_doc_name, "w", encoding="utf-8") as f:
f.write(doc)