T09 · Insecure Skill Coding Practices
- Location
opencode_acp_client.py:317- Finding
Persisted Session Conversations Are Stored in Plaintext Without Restrictive Permissions
- Content
View full analysis
Vulnerability Details
File Location:
opencode_acp_client.py, lines 317-336 and 448-463
Vulnerability Type: Plaintext storage of potentially sensitive conversation data with permissions determined by the process umask
Risk Level: MediumVulnerable Code
python def ensure_session_store_dir() -> None: """Create local session storage directory if missing.""" SESSION_STORE_DIR.mkdir(parents=True, exist_ok=True) def load_persisted_session(session_name: str) -> Optional[dict]: """Load persisted session metadata from disk.""" path = session_file_path(session_name) if not path.exists(): return None with path.open("r", encoding="utf-8") as fp: return json.load(fp) def save_persisted_session(session_name: str, data: dict) -> None: """Persist session metadata to disk.""" ensure_session_store_dir() path = session_file_path(session_name) with path.open("w", encoding="utf-8") as fp: json.dump(data, fp, ensure_ascii=False, indent=2)The sensitive data written through this function includes complete user prompts and assistant responses:
python history = data.get("history", []) history.append({"role": "user", "content": args.task, "ts": int(time.time())}) assistant_text = result.get("assistantText", "") if assistant_text: history.append({"role": "assistant", "content": assistant_text, "ts": int(time.time())}) data["history"] = history data["projectDir"] = project_dir data["updatedAt"] = int(time.time()) save_persisted_session(session_name, data)Technical Analysis
Session mode persistently stores the complete conversation history in JSON files under the skill-local
.acp_sessions/directory. The directory is created without an explicit mode, and session files are opened with the ordinary"w"mode without enforcing owner-only permissions. Consequently, their effective permissions depend on th ...[truncated 2060 chars]- Remediation
View remediation
Remediation Suggestions
- Create
.acp_sessionswith owner-only permissions (0700) and verify or correct permissions when the directory already exists. - Create each session file with owner-only permissions (
0600) rather than relying on the process umask. - Write updates atomically to a securely created temporary file in the same directory, set its mode to
0600, flush and synchronize it, and then replace the destination. - Refuse to follow symlinks and validate that both the storage directory and destination are owned by the expected user.
- Add
.acp_sessions/to.gitignoreand exclude it from packaging and backup workflows unless explicitly required. - Document that prompts must not contain credentials or other secrets and provide a mode that disables persistence.
- Implement a retention policy and a command to securely remove old session histories.
- If session histories must be stored on shared systems, encrypt them using credentials held outside the project directory.
Example Unix hardening:
python def ensure_session_store_dir() -> None: SESSION_STORE_DIR.mkdir(mode=0o700, parents=True, exist_ok=True) os.chmod(SESSION_STORE_DIR, 0o700) def save_persisted_session(session_name: str, data: dict) -> None: ensure_session_store_dir() path = session_file_path(session_name) flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(path, flags, 0o600) try: with os.fdopen(fd, "w", encoding="utf-8") as fp: json.dump(data, fp, ensure_ascii=False, indent=2) except Exception: os.close(fd) raise- Create
