165 redaction_patterns: list[str | dict] |
None =
None,
166 max_output_bytes: int = 1_048_576,
168 func =
"Redactor.__init__"
172 for entry
in (redaction_patterns
or []):
174 if isinstance(entry, str):
175 self.
_compiled.append((re.compile(entry),
"[REDACTED]"))
176 elif isinstance(entry, dict):
177 pat = entry.get(
"pattern",
"")
178 rep = entry.get(
"replacement",
"[REDACTED]")
180 self.
_compiled.append((re.compile(pat), str(rep)))
182 _dbg(
"MCP", func, f
"invalid redaction pattern (skipped): {entry!r}", 1)
185 home = str(Path.home())
188 _dbg(
"MCP", func, f
"compiled {len(self._compiled)} custom patterns", 2)
202 """Replace lines that assign a value to a secret-named key."""
204 out_lines: list[str] = []
205 for line
in text.splitlines(keepends=
True):
206 m = _ASSIGNMENT_LINE_RE.match(line)
208 key = m.group(1).strip()
209 if any(p
in key.lower()
for p
in _SECRET_NAME_PATTERNS):
211 suffix =
"\n" if line.endswith(
"\n")
else ""
212 out_lines.append(f
"{key}=[REDACTED]{suffix}")
215 out_lines.append(line)
216 return "".join(out_lines), count
225 """Replace absolute paths with [HOME], [SYSTEM], [PROJECT] tokens.
227 Processing order matters: project root first (most specific),
228 then home, then system prefixes (most general).
231 project_str = str(project_root)
234 if project_str
in text:
235 count += text.count(project_str)
236 text = text.replace(project_str,
"[PROJECT]")
239 project_fwd = project_str.replace(
"\\",
"/")
240 if project_fwd != project_str
and project_fwd
in text:
241 count += text.count(project_fwd)
242 text = text.replace(project_fwd,
"[PROJECT]")
247 count += text.count(home_str)
248 text = text.replace(home_str,
"[HOME]")
250 home_fwd = home_str.replace(
"\\",
"/")
251 if home_fwd != home_str
and home_fwd
in text:
252 count += text.count(home_fwd)
253 text = text.replace(home_fwd,
"[HOME]")
256 if sys.platform ==
"win32":
258 for prefix
in (
"C:\\Windows\\",
"C:\\Program Files\\",
259 "C:\\Program Files (x86)\\",
"C:\\ProgramData\\"):
261 count += text.count(prefix)
262 text = text.replace(prefix,
"[SYSTEM]/")
264 for prefix
in _SYSTEM_PATH_PREFIXES_POSIX:
266 count += text.count(prefix)
267 text = text.replace(prefix,
"[SYSTEM]/")
287 def apply_all(self, text: str, project_root: Path) -> RedactorResult:
288 """Run the full five-step pipeline on *text*.
290 Never raises. Any unexpected exception is caught and the
291 original text is returned with a warning prepended.
296 Raw tool output to sanitise.
298 Absolute path to the project root (used for step 3 path
303 :class:`RedactorResult`
305 func =
"Redactor.apply_all"
306 original_size = len(text.encode(
"utf-8", errors=
"replace"))
312 total_redactions += n1
316 total_redactions += n2
320 total_redactions += n2b
324 total_redactions += n3
328 total_redactions += n4
333 except Exception
as exc:
334 _dbg(
"MCP", func, f
"SYSTEM_ERROR: redactor pipeline failed: {exc}", 1)
339 f
"redactions={total_redactions} original_bytes={original_size}",
345 redactions_applied=total_redactions,
347 original_size_bytes=original_size,