T09 · Insecure Skill Coding Practices
- Location
secret_scanner.py:524- Finding
JSON Reports Expose Unredacted Source Lines Containing Secrets
- Content
View full analysis
Vulnerability Details
File Location:
secret_scanner.py, lines 524–531 and 625
Vulnerability Type: Sensitive information disclosure through incomplete output redaction
Risk Level: HighVulnerable Code
python # Mask the secret for display masked = mask_secret(matched_text) line_preview = line.strip()[:120] finding = SecretFinding( pattern_name=pattern_def["name"], severity=pattern_def["severity"], file_path=rel_path, line_number=i, line_preview=line_preview,python 'findings': [asdict(f) for f in report.findings],Technical Analysis
The scanner masks
matched_textbefore assigning it tosecret_preview, but separately stores the first 120 characters of the original source line inline_preview. This source line is not redacted.The JSON formatter serializes the entire
SecretFindingobject usingasdict(f). Consequently,line_previewis included in JSON output and can contain the complete secret. For example, if an API key appears near the beginning of a line, both a maskedsecret_previewand an unmaskedline_previewwill be emitted.This behavior contradicts the documented claim that secret masking makes reports safe to share. The Markdown formatter does not currently print
line_preview, but JSON output generated with--jsonor--json --outputexposes it.Attack Path
- A scanned source or configuration file contains a credential within the first 120 characters of a line.
- A user or CI job invokes the scanner with
--json. - The scanner detects the credential and masks the regex match for
secret_preview. - The scanner also stores the original, unredacted source line in
line_preview. asdict(f)serializes that field into the JSON report.- The report is printed to CI logs, retained as an artifact, or shared with another party.
- Anyone with access to that output can recover the exposed credential.
...[truncated 554 chars]
- Remediation
View remediation
Remediation Suggestions
- Do not store an unredacted source line in
SecretFinding. - Redact every regex match from the source line before assigning
line_preview. - Prefer constructing an explicit JSON object rather than serializing every dataclass field with
asdict(). - Exclude
line_previewfrom all externally visible output unless it has been safely sanitized. - Consider replacing the entire value portion of credential assignments rather than only the exact regex match.
- Add regression tests that place known sentinel secrets at different positions in a line and assert that the raw values never occur in Markdown, JSON, standard output, or saved report files.
- Treat existing JSON reports as potentially sensitive and remove them from public logs or artifacts where appropriate.
A safer implementation would redact matches before storing the preview:
python redacted_line = line for compiled in self._compiled_patterns: redacted_line = compiled["_regex"].sub("[REDACTED]", redacted_line) line_preview = redacted_line.strip()[:120]The JSON formatter should also explicitly select safe fields instead of serializing the complete dataclass.
- Do not store an unredacted source line in
