45 lines
1.7 KiB
GDScript
45 lines
1.7 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
## Secret scrubber — redacts credentials from log/error output before
|
|
## returning it through the MCP transport. Applied to debugger_get_log
|
|
## and editor_get_console; never applied to
|
|
## script_read (source code should not be mutated).
|
|
|
|
# Three high-precision patterns that cover real secrets with minimal
|
|
# false-positives. The broad hex pattern ([A-Fa-f0-9]{32,}) is
|
|
# deliberately omitted — false-positives on Godot UIDs, asset hashes,
|
|
# and import checksums outweigh the marginal secret-detection gain.
|
|
# Add patterns here only when a concrete leak is found.
|
|
const PATTERNS: Array[String] = [
|
|
"(?i)(api[_-]?key|token|secret|password)\\s*[:=]\\s*[\\w\\.\\-]+",
|
|
"sk-[A-Za-z0-9]{20,}",
|
|
"(?i)bearer\\s+[A-Za-z0-9\\.\\-_]+",
|
|
]
|
|
|
|
|
|
## Compiled once at script load.
|
|
static var _compiled_patterns: Array[RegEx] = _compile_patterns()
|
|
|
|
static func _compile_patterns() -> Array[RegEx]:
|
|
var out: Array[RegEx] = []
|
|
for pattern in PATTERNS:
|
|
var regex := RegEx.new()
|
|
regex.compile(pattern)
|
|
out.append(regex)
|
|
return out
|
|
|
|
|
|
## Scrub secrets from text. Returns {text: String, redaction_count: int}.
|
|
## When redaction_count > 0 and source is non-empty, emits a push_warning
|
|
## so developers can correlate [REDACTED] appearances with the scrubber.
|
|
static func scrub(text: String, source: String = "") -> Dictionary:
|
|
var out := text
|
|
var redaction_count := 0
|
|
for regex in _compiled_patterns:
|
|
var matches := regex.search_all(out)
|
|
redaction_count += matches.size()
|
|
out = regex.sub(out, "[REDACTED]", true)
|
|
if redaction_count > 0 and not source.is_empty():
|
|
push_warning("[Scrubber] %d redactions applied to %s" % [redaction_count, source])
|
|
return {"text": out, "redaction_count": redaction_count}
|