T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/lib/engine.py:169
- Finding
- Potential Secrets Are Persisted in Plaintext Without Redaction or Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/lib/engine.py:169-178`; `scripts/lib/storage.py:11-12, 32-38` **Vulnerability Type**: Plaintext storage of potentially sensitive information **Risk Level**: Medium ### Vulnerable Code `scripts/lib/engine.py:169-178`: ```python findings.append({ "rule_id": rule["rule_id"], "title": rule["title"], "severity": rule["severity"], "matched_file": rel_path, "matched_line": line_no, "evidence": stripped[:300], "why_it_matters": rule["why_it_matters"], "recommendation": rule["recommendation"], "fix_command": rule["fix_command"] }) ``` `scripts/lib/storage.py:11-12, 32-38`: ```python MEMORY_DIR = os.path.join(get_workspace_root(), "memory", "clawguard") REPORTS_PATH = os.path.join(MEMORY_DIR, "reports.json") ``` ```python def save_report(report): data = load_reports() data["reports"][report["report_id"]] = report data["metadata"]["last_updated"] = datetime.utcnow().isoformat() with open(REPORTS_PATH, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) ``` ### Technical Analysis The scanner's rules intentionally detect credential-related material, including passwords, tokens, API keys, private keys, authorization headers, and explicit secret-exposure instructions. When a source line matches one of these rules, `scan_skill()` copies up to 300 characters from the line into the report's `evidence` field without masking credential values. `save_report()` then writes the complete report to `reports.json` in the OpenClaw workspace. The file is plaintext and is opened without explicitly enforcing a restrictive permission mode such as `0600`. Consequently, its effective permissions depend on the process umask or the permissions of a previously existing file. The implementation also has no retention limit, report deletion mechanism, or redaction step. The behavior is local and no network transmission was found. The static p ...[truncated 1677 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Redact secrets before constructing findings** - Replace credential values with fixed placeholders such as `[REDACTED]`. - Preserve only the rule identifier, file path, line number, and a sanitized excerpt. - Apply redaction for common assignment, header, URL, JSON, PEM, and environment-variable formats. 2. **Minimize retained evidence** - Do not persist complete matching lines for credential-related rules. - Consider recording only the matched pattern category and a short, sanitized context window. - Add a mode that displays sensitive evidence transiently without saving it. 3. **Enforce restrictive storage permissions** - Create `MEMORY_DIR` with mode `0700`. - Create `reports.json` with mode `0600`, for example using `os.open()` with explicit flags and permissions. - Verify permissions on existing files and refuse unsafe symbolic links before writing. 4. **Use safer writes** - Write to a securely created temporary file in the same protected directory. - Flush and atomically replace the destination. - Ensure neither the temporary file nor destination can be redirected through a symbolic-link attack. 5. **Add lifecycle controls** - Provide commands to delete individual reports and purge all stored reports. - Support configurable expiration and maximum report counts. - Document that reports may contain sensitive source excerpts. 6. **Add regression tests** - Scan representative API keys, bearer tokens, passwords, private-key material, and authorization headers. - Assert that none of the raw values appear in saved reports. - Assert that report files and directories receive the intended restrictive permissions. ]]>
