T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/recruiting_sync.py:1040
- Finding
- Sensitive Gmail and recruiting metadata stored in a plaintext state file<![CDATA[ ## Vulnerability Details **File Location**: `scripts/recruiting_sync.py:1040-1048` and `scripts/recruiting_sync.py:1455-1458` **Vulnerability Type**: Plaintext storage and excessive retention of sensitive information **Risk Level**: Medium ### Vulnerable Code ```python def build_source_payload(previous: dict[str, Any] | None, candidate: EventObservation) -> dict[str, Any]: previous_source = previous.get("source", {}) if previous else {} subjects = previous_source.get("subjects", []) thread_ids = previous_source.get("threadIds", []) return { "threadIds": merge_unique(thread_ids, candidate.source_ids), "subject": candidate.source_subjects[-1], "subjects": merge_unique(subjects, candidate.source_subjects), "sender": candidate.source_sender or previous_source.get("sender", ""), "lastSeenAt": candidate.received_at.strftime("%Y-%m-%d %H:%M"), } ``` ```python def write_state(state: dict[str, Any], output: Path) -> None: output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps(state, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") ``` ### Technical Analysis The synchronization state retains Gmail thread identifiers, original email subjects, sender information, event notes, roles, timestamps, and extracted links. The complete state is serialized as unencrypted JSON. The code does not explicitly create the file with restrictive permissions, verify the permissions of an existing file, or atomically replace it with a securely created temporary file. Consequently, actual access control depends on the process umask and permissions inherited by the configured output directory. This retention also exceeds the minimum data needed for reminder synchronization. Stable hashes or reduced event identifiers could support deduplication without retaining raw sender addresses, complete subjects, and Gmail thread identifiers. It conflicts with the Skill documentation's stated ...[truncated 1569 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Minimize persisted data: - Replace Gmail thread IDs with keyed hashes where direct identifiers are unnecessary. - Avoid storing raw sender addresses and complete subject histories. - Persist only fields required for deduplication and reminder reconciliation. 2. Create the state file with mode `0600` and its directory with mode `0700`. 3. Check existing file permissions before reading or updating the state and reject insecure configurations. 4. Write updates atomically through a securely created temporary file in the same directory, set its mode to `0600`, flush it, and then replace the destination. 5. Do not follow symbolic links when creating or replacing the state file. 6. Add a documented retention policy and prune source metadata once it is no longer required. 7. Redact query parameters or tokens from persisted links where they are not required. 8. Clearly disclose which email metadata is retained, where it is stored, and how users can delete it. ]]>
