T09 · Insecure Skill Coding Practices
Note
- Location
- scripts/chat.py:24
- Finding
- Insecure Plaintext Session-State Storage<![CDATA[ ## Vulnerability Details **File Location**: `scripts/chat.py`, lines 24–42 and 331–350 **Vulnerability Type**: Plaintext sensitive state storage with unsafe default file handling **Risk Level**: Low ### Vulnerable Code ```python # Session state file path SESSION_STATE_FILE = Path(__file__).parent.parent / "state" / "session.json" def load_session_state() -> Dict[str, Any]: """Load session state""" if SESSION_STATE_FILE.exists(): try: with open(SESSION_STATE_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: pass return {} def save_session_state(state: Dict[str, Any]) -> None: """Save session state""" SESSION_STATE_FILE.parent.mkdir(parents=True, exist_ok=True) with open(SESSION_STATE_FILE, "w", encoding="utf-8") as f: json.dump(state, f, ensure_ascii=False, indent=2) ``` The state is subsequently loaded and saved as follows: ```python # Process conversation ID conversation_id = args.conversation_id if not conversation_id and not args.new_session: # Read conversation ID from state file state = load_session_state() conversation_id = state.get("conversation_id") if conversation_id: print(f"[Using saved conversation: {conversation_id}]", file=sys.stderr) # Call API result = chat( query=args.query or "", app_id=args.app_id, stream=args.stream, conversation_id=conversation_id, file_ids=file_ids, tools=tools, tool_choice=tool_choice, tool_outputs=tool_outputs, action=action, end_user_id=args.end_user_id, metadata_filter=metadata_filter, custom_metadata=custom_metadata, ) # Save conversation ID if "conversation_id" in result: save_session_state({"conversation_id": result["conversation_id"]}) ``` ### Technical Analysis The returned conversation identifier is stored in plaintext at the predictable path `state/session.json`. The code creates the directory and file wi ...[truncated 2883 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store session state in a user-specific data directory rather than inside the Skill package, such as a platform-appropriate application-state directory. 2. Create the state directory with mode `0700` and the state file with mode `0600`. 3. Open files using flags that reject symbolic links where supported, such as `os.O_NOFOLLOW`, and verify that the destination is a regular file owned by the current user. 4. Write updates atomically: - Create a temporary file securely in the same private directory. - Set its permissions to `0600`. - Write and flush the JSON. - Call `os.fsync()` when durability is required. - Replace the destination with `os.replace()`. 5. Validate loaded state before use. Require an object containing only an appropriately formatted string `conversation_id`, and reject unexpected fields or types. 6. Detect and reject state directories or files that are writable by group or other users. 7. Provide an option to disable persistence and document that conversation identifiers are retained locally. 8. If conversation identifiers are considered sensitive in the deployment model, use an operating-system credential store or another protected storage mechanism. ]]>
