T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/state.py:62
- Finding
- Private Memory Excerpts Persisted Without Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/analyze_patterns.py:237-246`, `scripts/analyze_patterns.py:515-517`, and `scripts/state.py:62-66` **Vulnerability Type**: Plaintext storage of sensitive memory data with process-default permissions **Risk Level**: Medium ### Vulnerable Code ```python return { "section": header, "keywords": keywords, "actions": actions, "entities": extract_entities(full_text), "has_steps": detect_steps(body), "time_hint": extract_time_hint(header, body), "day_of_week": day_of_week, "is_formalized": detect_formalized(header, body), "raw_summary": body[:500].strip(), } ``` ```python events = parse_memory_file(filepath) add_events(state, date_str, events) new_event_count += len(events) ``` ```python p.parent.mkdir(parents=True, exist_ok=True) tmp = p.with_suffix(".tmp") with open(tmp, "w") as f: json.dump(state, f, indent=2, default=str) tmp.replace(p) ``` ### Technical Analysis The analyzer reads private OpenClaw memory logs and retains up to 500 characters from each Markdown section in the `raw_summary` field. These event objects are added to the persistent `event_cache` and serialized to `state.json`. The state file and its temporary predecessor are created using ordinary `open()` calls without explicitly applying an owner-only mode such as `0600`. Their effective permissions therefore depend on the runtime environment's umask. In an environment with a permissive umask, the cached memory excerpts may be accessible to other local users or processes. The temporary file also contains the complete state while it is being written. Atomic replacement protects file integrity, but it does not provide confidentiality. Moreover, cached excerpts can remain after the corresponding source memory content has been edited or deleted. ### Attack Path 1. A memory file contains private conversation, operational, customer, or project information. 2. `parse_memory_file()` reads the file a ...[truncated 1141 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not persist raw memory excerpts unless they are strictly required. Prefer hashes, normalized keywords, counters, or other minimal derived features. 2. If summaries are required, redact credentials, tokens, email addresses, URLs containing secrets, and other sensitive patterns before storage. 3. Create the temporary state file with explicit owner-only permissions: ```python import os fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(state, f, indent=2, default=str) os.replace(tmp, p) os.chmod(p, 0o600) ``` 4. Verify the permissions of an existing state file before loading or replacing it. Refuse unsafe permissions or correct them after obtaining explicit authorization. 5. Store the state under a private directory with mode `0700`. 6. Implement configurable retention limits and purge cached records whose source files were deleted or aged out. 7. Document that `state.json` contains derived private memory data and should not be committed to source control, synchronized publicly, or included in unprotected backups. ]]>
