codetool-shell 0.1.1__py3-none-win_amd64.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.
- codetool_shell/__init__.py +11 -0
- codetool_shell/api.py +59 -0
- codetool_shell/bin/windows-x86_64/codetool-shell-rust.exe +0 -0
- codetool_shell/filters/__init__.py +14 -0
- codetool_shell/filters/build_compiler/__init__.py +7 -0
- codetool_shell/filters/build_compiler/detector.py +412 -0
- codetool_shell/filters/build_compiler/reducer.py +166 -0
- codetool_shell/filters/build_compiler/summary.py +617 -0
- codetool_shell/filters/ci_job_log/__init__.py +7 -0
- codetool_shell/filters/ci_job_log/detector.py +64 -0
- codetool_shell/filters/ci_job_log/reducer.py +99 -0
- codetool_shell/filters/ci_job_log/summary.py +243 -0
- codetool_shell/filters/diff/__init__.py +7 -0
- codetool_shell/filters/diff/detector.py +136 -0
- codetool_shell/filters/diff/reducer.py +308 -0
- codetool_shell/filters/generic_log/__init__.py +7 -0
- codetool_shell/filters/generic_log/detector.py +175 -0
- codetool_shell/filters/generic_log/reducer.py +99 -0
- codetool_shell/filters/generic_log/summary.py +161 -0
- codetool_shell/filters/git.py +514 -0
- codetool_shell/filters/html_cleanup/__init__.py +7 -0
- codetool_shell/filters/html_cleanup/detector.py +136 -0
- codetool_shell/filters/html_cleanup/reducer.py +27 -0
- codetool_shell/filters/html_cleanup/summary.py +422 -0
- codetool_shell/filters/json_payload/__init__.py +7 -0
- codetool_shell/filters/json_payload/detector.py +62 -0
- codetool_shell/filters/json_payload/reducer.py +81 -0
- codetool_shell/filters/json_payload/summary.py +233 -0
- codetool_shell/filters/listing/__init__.py +7 -0
- codetool_shell/filters/listing/detector.py +294 -0
- codetool_shell/filters/listing/reducer.py +30 -0
- codetool_shell/filters/log_template/__init__.py +7 -0
- codetool_shell/filters/log_template/constants.py +76 -0
- codetool_shell/filters/log_template/detector.py +331 -0
- codetool_shell/filters/log_template/reducer.py +78 -0
- codetool_shell/filters/log_template/template.py +280 -0
- codetool_shell/filters/log_template/types.py +21 -0
- codetool_shell/filters/opaque_payload/__init__.py +7 -0
- codetool_shell/filters/opaque_payload/detector.py +563 -0
- codetool_shell/filters/opaque_payload/reducer.py +142 -0
- codetool_shell/filters/opaque_payload/summary.py +61 -0
- codetool_shell/filters/package_manager/__init__.py +7 -0
- codetool_shell/filters/package_manager/detector.py +220 -0
- codetool_shell/filters/package_manager/reducer.py +110 -0
- codetool_shell/filters/package_manager/summary.py +172 -0
- codetool_shell/filters/pipeline.py +65 -0
- codetool_shell/filters/rg.py +250 -0
- codetool_shell/filters/system_output/__init__.py +7 -0
- codetool_shell/filters/system_output/detector.py +600 -0
- codetool_shell/filters/system_output/reducer.py +331 -0
- codetool_shell/filters/system_output/summary.py +164 -0
- codetool_shell/filters/table/__init__.py +7 -0
- codetool_shell/filters/table/detector.py +244 -0
- codetool_shell/filters/table/reducer.py +57 -0
- codetool_shell/filters/table/summary.py +37 -0
- codetool_shell/filters/test_runner/__init__.py +7 -0
- codetool_shell/filters/test_runner/ansi.py +80 -0
- codetool_shell/filters/test_runner/detector.py +409 -0
- codetool_shell/filters/test_runner/reducer.py +288 -0
- codetool_shell/filters/test_runner/summary.py +449 -0
- codetool_shell/filters/text.py +38 -0
- codetool_shell/filters/traceback/__init__.py +7 -0
- codetool_shell/filters/traceback/detector.py +209 -0
- codetool_shell/filters/traceback/reducer.py +141 -0
- codetool_shell/filters/traceback/summary.py +122 -0
- codetool_shell/filters/tree.py +59 -0
- codetool_shell/py.typed +0 -0
- codetool_shell/python_backend.py +38 -0
- codetool_shell/rust_backend.py +254 -0
- codetool_shell-0.1.1.dist-info/METADATA +152 -0
- codetool_shell-0.1.1.dist-info/RECORD +72 -0
- codetool_shell-0.1.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Reduce high-confidence system command outputs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..text import join_preserving_final_newline, score, split_preserving_final_newline
|
|
6
|
+
from .detector import (
|
|
7
|
+
DfOutput,
|
|
8
|
+
DuOutput,
|
|
9
|
+
EnvOutput,
|
|
10
|
+
PingOutput,
|
|
11
|
+
PsOutput,
|
|
12
|
+
StatBlock,
|
|
13
|
+
SystemctlStatusOutput,
|
|
14
|
+
WcOutput,
|
|
15
|
+
parse_df_output,
|
|
16
|
+
parse_du_output,
|
|
17
|
+
parse_env_output,
|
|
18
|
+
parse_ping_output,
|
|
19
|
+
parse_ps_output,
|
|
20
|
+
parse_stat_blocks,
|
|
21
|
+
parse_systemctl_status_output,
|
|
22
|
+
parse_wc_output,
|
|
23
|
+
)
|
|
24
|
+
from .summary import (
|
|
25
|
+
SAFE_ENV_KEYS,
|
|
26
|
+
format_env_var,
|
|
27
|
+
is_opaque_value,
|
|
28
|
+
is_sensitive_key,
|
|
29
|
+
omitted_line,
|
|
30
|
+
redact_sensitive_text,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_HEAD_ROWS = 2
|
|
34
|
+
_TAIL_ROWS = 2
|
|
35
|
+
_DF_HIGH_USE_THRESHOLD = 85
|
|
36
|
+
_PS_HIGH_CPU_THRESHOLD = 10.0
|
|
37
|
+
_PS_HIGH_MEM_THRESHOLD = 5.0
|
|
38
|
+
_TOP_DU_ROWS = 5
|
|
39
|
+
_PING_EDGE_REPLIES = 2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def compress_system_output(text: str) -> str:
|
|
43
|
+
"""Compress clear system command outputs, otherwise return ``text``."""
|
|
44
|
+
|
|
45
|
+
lines, final_newline = split_preserving_final_newline(text)
|
|
46
|
+
if not lines:
|
|
47
|
+
return text
|
|
48
|
+
|
|
49
|
+
for reducer in (
|
|
50
|
+
_reduce_env,
|
|
51
|
+
_reduce_df,
|
|
52
|
+
_reduce_ps,
|
|
53
|
+
_reduce_ping,
|
|
54
|
+
_reduce_systemctl,
|
|
55
|
+
_reduce_du,
|
|
56
|
+
_reduce_wc,
|
|
57
|
+
_reduce_stat,
|
|
58
|
+
):
|
|
59
|
+
candidate_lines = reducer(lines)
|
|
60
|
+
if candidate_lines is None:
|
|
61
|
+
continue
|
|
62
|
+
candidate = join_preserving_final_newline(candidate_lines, final_newline)
|
|
63
|
+
if score(candidate) < score(text):
|
|
64
|
+
return candidate
|
|
65
|
+
return text
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _reduce_env(lines: list[str]) -> list[str] | None:
|
|
69
|
+
parsed = parse_env_output(lines)
|
|
70
|
+
if parsed is None:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
vars_by_key = {var.key: var.value for var in parsed.vars}
|
|
74
|
+
selected = [
|
|
75
|
+
(key, vars_by_key[key]) for key in SAFE_ENV_KEYS if key in vars_by_key
|
|
76
|
+
]
|
|
77
|
+
if not selected:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
redacted = sum(
|
|
81
|
+
1
|
|
82
|
+
for var in parsed.vars
|
|
83
|
+
if is_sensitive_key(var.key) or (var.key != "PATH" and is_opaque_value(var.value))
|
|
84
|
+
)
|
|
85
|
+
omitted = len(parsed.vars) - len(selected)
|
|
86
|
+
output = [
|
|
87
|
+
f"system env: vars={len(parsed.vars)} redacted={redacted} omitted={omitted}"
|
|
88
|
+
]
|
|
89
|
+
output.extend(format_env_var(key, value) for key, value in selected)
|
|
90
|
+
if omitted > 0:
|
|
91
|
+
output.append(omitted_line(omitted, "env", "var"))
|
|
92
|
+
return output
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _reduce_df(lines: list[str]) -> list[str] | None:
|
|
96
|
+
parsed = parse_df_output(lines)
|
|
97
|
+
if parsed is None:
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
high_indices = [
|
|
101
|
+
index
|
|
102
|
+
for index, row in enumerate(parsed.rows)
|
|
103
|
+
if row.usage >= _DF_HIGH_USE_THRESHOLD
|
|
104
|
+
]
|
|
105
|
+
important_indices = [
|
|
106
|
+
index for index, row in enumerate(parsed.rows) if _is_important_mount(row.mount)
|
|
107
|
+
]
|
|
108
|
+
selected = _edge_indices(len(parsed.rows))
|
|
109
|
+
selected.extend(high_indices)
|
|
110
|
+
selected.extend(important_indices)
|
|
111
|
+
|
|
112
|
+
output = [
|
|
113
|
+
f"df: filesystems={len(parsed.rows)} high_use={len(high_indices)}",
|
|
114
|
+
parsed.header,
|
|
115
|
+
]
|
|
116
|
+
output.extend(
|
|
117
|
+
_selected_rows(
|
|
118
|
+
len(parsed.rows),
|
|
119
|
+
selected,
|
|
120
|
+
lambda index: parsed.rows[index].line,
|
|
121
|
+
"df",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
return output
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _reduce_du(lines: list[str]) -> list[str] | None:
|
|
128
|
+
parsed = parse_du_output(lines)
|
|
129
|
+
if parsed is None:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
selected = _edge_indices(len(parsed.rows))
|
|
133
|
+
selected.extend(
|
|
134
|
+
index
|
|
135
|
+
for index, _row in sorted(
|
|
136
|
+
enumerate(parsed.rows),
|
|
137
|
+
key=lambda item: item[1].size,
|
|
138
|
+
reverse=True,
|
|
139
|
+
)[:_TOP_DU_ROWS]
|
|
140
|
+
)
|
|
141
|
+
selected.extend(index for index, row in enumerate(parsed.rows) if row.is_total)
|
|
142
|
+
|
|
143
|
+
output = [f"du: entries={len(parsed.rows)}"]
|
|
144
|
+
output.extend(
|
|
145
|
+
_selected_rows(
|
|
146
|
+
len(parsed.rows),
|
|
147
|
+
selected,
|
|
148
|
+
lambda index: parsed.rows[index].line,
|
|
149
|
+
"du",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
return output
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _reduce_ps(lines: list[str]) -> list[str] | None:
|
|
156
|
+
parsed = parse_ps_output(lines)
|
|
157
|
+
if parsed is None:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
high_indices = [
|
|
161
|
+
index
|
|
162
|
+
for index, row in enumerate(parsed.rows)
|
|
163
|
+
if (row.cpu is not None and row.cpu >= _PS_HIGH_CPU_THRESHOLD)
|
|
164
|
+
or (row.mem is not None and row.mem >= _PS_HIGH_MEM_THRESHOLD)
|
|
165
|
+
]
|
|
166
|
+
interesting_indices = [
|
|
167
|
+
index
|
|
168
|
+
for index, row in enumerate(parsed.rows)
|
|
169
|
+
if _is_interesting_command(row.command)
|
|
170
|
+
]
|
|
171
|
+
selected = _edge_indices(len(parsed.rows))
|
|
172
|
+
selected.extend(high_indices)
|
|
173
|
+
selected.extend(interesting_indices)
|
|
174
|
+
|
|
175
|
+
output = [
|
|
176
|
+
f"ps: processes={len(parsed.rows)} high={len(high_indices)} interesting={len(interesting_indices)}",
|
|
177
|
+
parsed.header,
|
|
178
|
+
]
|
|
179
|
+
output.extend(
|
|
180
|
+
_selected_rows(
|
|
181
|
+
len(parsed.rows),
|
|
182
|
+
selected,
|
|
183
|
+
lambda index: redact_sensitive_text(parsed.rows[index].line),
|
|
184
|
+
"ps",
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
return output
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _reduce_ping(lines: list[str]) -> list[str] | None:
|
|
191
|
+
parsed = parse_ping_output(lines)
|
|
192
|
+
if parsed is None:
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
selected = [0]
|
|
196
|
+
selected.extend(parsed.reply_indices[:_PING_EDGE_REPLIES])
|
|
197
|
+
selected.extend(parsed.reply_indices[-_PING_EDGE_REPLIES:])
|
|
198
|
+
selected.extend(parsed.event_indices)
|
|
199
|
+
selected.extend(parsed.summary_indices)
|
|
200
|
+
|
|
201
|
+
output = [
|
|
202
|
+
f"ping: replies={len(parsed.reply_indices)} events={len(parsed.event_indices)}"
|
|
203
|
+
]
|
|
204
|
+
output.extend(
|
|
205
|
+
_selected_rows(
|
|
206
|
+
len(parsed.lines),
|
|
207
|
+
selected,
|
|
208
|
+
lambda index: parsed.lines[index],
|
|
209
|
+
"ping",
|
|
210
|
+
noun="line",
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
return output
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _reduce_systemctl(lines: list[str]) -> list[str] | None:
|
|
217
|
+
parsed = parse_systemctl_status_output(lines)
|
|
218
|
+
if parsed is None:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
selected = [*parsed.core_indices, *parsed.alert_indices]
|
|
222
|
+
output = [
|
|
223
|
+
f"systemctl status: lines={len(parsed.lines)} alerts={len(parsed.alert_indices)}"
|
|
224
|
+
]
|
|
225
|
+
output.extend(
|
|
226
|
+
_selected_rows(
|
|
227
|
+
len(parsed.lines),
|
|
228
|
+
selected,
|
|
229
|
+
lambda index: redact_sensitive_text(parsed.lines[index]),
|
|
230
|
+
"status",
|
|
231
|
+
noun="line",
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
return output
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _reduce_wc(lines: list[str]) -> list[str] | None:
|
|
238
|
+
parsed = parse_wc_output(lines)
|
|
239
|
+
if parsed is None:
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
file_indices = [index for index, row in enumerate(parsed.rows) if not row.is_total]
|
|
243
|
+
selected = file_indices[:3] + file_indices[-3:] + [parsed.total_index]
|
|
244
|
+
output = [f"wc: files={len(file_indices)}"]
|
|
245
|
+
output.extend(
|
|
246
|
+
_selected_rows(
|
|
247
|
+
len(parsed.rows),
|
|
248
|
+
selected,
|
|
249
|
+
lambda index: parsed.rows[index].line,
|
|
250
|
+
"wc",
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
return output
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _reduce_stat(lines: list[str]) -> list[str] | None:
|
|
257
|
+
blocks = parse_stat_blocks(lines)
|
|
258
|
+
if blocks is None:
|
|
259
|
+
return None
|
|
260
|
+
output = [f"stat: files={len(blocks)}"]
|
|
261
|
+
output.extend(_format_stat_block(block) for block in blocks)
|
|
262
|
+
return output
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _selected_rows(
|
|
266
|
+
total: int,
|
|
267
|
+
selected: list[int],
|
|
268
|
+
line_for_index,
|
|
269
|
+
label: str,
|
|
270
|
+
*,
|
|
271
|
+
noun: str = "row",
|
|
272
|
+
) -> list[str]:
|
|
273
|
+
output: list[str] = []
|
|
274
|
+
selected_sorted = sorted({index for index in selected if 0 <= index < total})
|
|
275
|
+
cursor = 0
|
|
276
|
+
for index in selected_sorted:
|
|
277
|
+
omitted = index - cursor
|
|
278
|
+
if omitted > 0:
|
|
279
|
+
output.append(omitted_line(omitted, label, noun))
|
|
280
|
+
output.append(line_for_index(index))
|
|
281
|
+
cursor = index + 1
|
|
282
|
+
if cursor < total:
|
|
283
|
+
output.append(omitted_line(total - cursor, label, noun))
|
|
284
|
+
return output
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _edge_indices(total: int) -> list[int]:
|
|
288
|
+
return [
|
|
289
|
+
*range(min(_HEAD_ROWS, total)),
|
|
290
|
+
*range(max(_HEAD_ROWS, total - _TAIL_ROWS), total),
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _is_important_mount(mount: str) -> bool:
|
|
295
|
+
return mount in {"/", "/home", "/var", "/tmp", "/boot", "/data"} or mount.startswith(
|
|
296
|
+
("/var/", "/boot/", "/data/")
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _is_interesting_command(command: str) -> bool:
|
|
301
|
+
lower = command.lower()
|
|
302
|
+
return any(
|
|
303
|
+
marker in lower
|
|
304
|
+
for marker in (
|
|
305
|
+
"python",
|
|
306
|
+
"pytest",
|
|
307
|
+
"cargo",
|
|
308
|
+
"rustc",
|
|
309
|
+
"node",
|
|
310
|
+
"npm",
|
|
311
|
+
"java",
|
|
312
|
+
"gradle",
|
|
313
|
+
"mvn",
|
|
314
|
+
"postgres",
|
|
315
|
+
"redis",
|
|
316
|
+
"nginx",
|
|
317
|
+
"gunicorn",
|
|
318
|
+
"uvicorn",
|
|
319
|
+
"docker",
|
|
320
|
+
"kubectl",
|
|
321
|
+
"systemd",
|
|
322
|
+
"sshd",
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _format_stat_block(block: StatBlock) -> str:
|
|
328
|
+
return (
|
|
329
|
+
f"{block.file} | size={block.size} | mode={block.mode} | "
|
|
330
|
+
f"owner={block.owner} | group={block.group} | modify={block.modify}"
|
|
331
|
+
)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Formatting and redaction helpers for system-shellion."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
SAFE_ENV_KEYS: tuple[str, ...] = (
|
|
8
|
+
"USER",
|
|
9
|
+
"LOGNAME",
|
|
10
|
+
"HOME",
|
|
11
|
+
"PWD",
|
|
12
|
+
"SHELL",
|
|
13
|
+
"TERM",
|
|
14
|
+
"LANG",
|
|
15
|
+
"LC_ALL",
|
|
16
|
+
"VIRTUAL_ENV",
|
|
17
|
+
"CONDA_DEFAULT_ENV",
|
|
18
|
+
"PYTHON_VERSION",
|
|
19
|
+
"PATH",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_SENSITIVE_KEY_PARTS = (
|
|
23
|
+
"secret",
|
|
24
|
+
"token",
|
|
25
|
+
"password",
|
|
26
|
+
"passwd",
|
|
27
|
+
"credential",
|
|
28
|
+
"private",
|
|
29
|
+
"api_key",
|
|
30
|
+
"apikey",
|
|
31
|
+
"access_key",
|
|
32
|
+
"session_key",
|
|
33
|
+
"auth",
|
|
34
|
+
"bearer",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def omitted_line(count: int, label: str, noun: str = "row") -> str:
|
|
39
|
+
plural = "" if count == 1 else "s"
|
|
40
|
+
return f"… {count} {label} {noun}{plural} omitted"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_sensitive_key(key: str) -> bool:
|
|
44
|
+
lower = key.lower().replace("-", "_")
|
|
45
|
+
return (
|
|
46
|
+
lower == "key"
|
|
47
|
+
or lower.endswith("_key")
|
|
48
|
+
or any(part in lower for part in _SENSITIVE_KEY_PARTS)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_opaque_value(value: str) -> bool:
|
|
53
|
+
if len(value) < 64:
|
|
54
|
+
return False
|
|
55
|
+
compact = value.strip()
|
|
56
|
+
if "://" in compact:
|
|
57
|
+
return True
|
|
58
|
+
allowed = sum(
|
|
59
|
+
1 for char in compact if char.isalnum() or char in "+/=_-.:"
|
|
60
|
+
)
|
|
61
|
+
return allowed * 100 >= len(compact) * 95 and not any(
|
|
62
|
+
char.isspace() for char in compact
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def format_env_var(key: str, value: str) -> str:
|
|
67
|
+
if key == "PATH":
|
|
68
|
+
return f"{key}={_format_path_value(value)}"
|
|
69
|
+
if is_sensitive_key(key) or is_opaque_value(value):
|
|
70
|
+
return f"{key}=[redacted]"
|
|
71
|
+
return f"{key}={value}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def redact_sensitive_text(text: str) -> str:
|
|
75
|
+
"""Redact obvious secret-bearing argv/env tokens without exposing values."""
|
|
76
|
+
|
|
77
|
+
tokens = text.split()
|
|
78
|
+
if not tokens:
|
|
79
|
+
return text
|
|
80
|
+
|
|
81
|
+
output: list[str] = []
|
|
82
|
+
redact_next = False
|
|
83
|
+
changed = False
|
|
84
|
+
for token in tokens:
|
|
85
|
+
if redact_next:
|
|
86
|
+
output.append("[redacted]")
|
|
87
|
+
redact_next = False
|
|
88
|
+
changed = True
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
assignment = _sensitive_assignment_prefix(token)
|
|
92
|
+
if assignment is not None:
|
|
93
|
+
key_prefix, value = assignment
|
|
94
|
+
output.append(f"{key_prefix}=[redacted]")
|
|
95
|
+
if value.lower() in {"bearer", "basic"}:
|
|
96
|
+
redact_next = True
|
|
97
|
+
changed = True
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
if token.lower() == "bearer":
|
|
101
|
+
output.append(token)
|
|
102
|
+
redact_next = True
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
if token.startswith("--"):
|
|
106
|
+
if "=" in token:
|
|
107
|
+
option, _value = token.split("=", 1)
|
|
108
|
+
if is_sensitive_key(option.lstrip("-")):
|
|
109
|
+
output.append(f"{option}=[redacted]")
|
|
110
|
+
changed = True
|
|
111
|
+
continue
|
|
112
|
+
elif is_sensitive_key(token.lstrip("-")):
|
|
113
|
+
output.append(token)
|
|
114
|
+
redact_next = True
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
output.append(token)
|
|
118
|
+
|
|
119
|
+
if not changed:
|
|
120
|
+
return text
|
|
121
|
+
return " ".join(output)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _format_path_value(value: str) -> str:
|
|
125
|
+
separator = (
|
|
126
|
+
";"
|
|
127
|
+
if ";" in value and re.search(r"(?:^|;)[A-Za-z]:[\\/]", value)
|
|
128
|
+
else ";" if value.count(";") > value.count(":") else ":"
|
|
129
|
+
)
|
|
130
|
+
entries = [entry for entry in value.split(separator) if entry]
|
|
131
|
+
if not entries:
|
|
132
|
+
return "[]"
|
|
133
|
+
if len(entries) == 1:
|
|
134
|
+
return entries[0]
|
|
135
|
+
return (
|
|
136
|
+
f"[path entries={len(entries)} first={entries[0]} last={entries[-1]}]"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _sensitive_assignment_prefix(token: str) -> tuple[str, str] | None:
|
|
141
|
+
if "=" not in token:
|
|
142
|
+
return None
|
|
143
|
+
parts = token.split("=")
|
|
144
|
+
for index in range(len(parts) - 1):
|
|
145
|
+
key = parts[index].rsplit(",", 1)[-1]
|
|
146
|
+
if key.startswith("--"):
|
|
147
|
+
key = key.lstrip("-")
|
|
148
|
+
value = "=".join(parts[index + 1 :])
|
|
149
|
+
if not value:
|
|
150
|
+
continue
|
|
151
|
+
if _is_assignment_key(key) and (
|
|
152
|
+
is_sensitive_key(key) or _is_sensitive_url_assignment(key, value)
|
|
153
|
+
):
|
|
154
|
+
prefix = "=".join(parts[: index + 1])
|
|
155
|
+
return prefix, value
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _is_assignment_key(key: str) -> bool:
|
|
160
|
+
return bool(key) and key.replace("_", "").replace("-", "").isalnum()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _is_sensitive_url_assignment(key: str, value: str) -> bool:
|
|
164
|
+
return key.lower().endswith("_url") and "://" in value
|