T09 · Insecure Skill Coding Practices
- Location
scripts/newsletter-digest.py:746- Finding
Entire Gmail Threads May Be Disclosed Beyond the Selected Message Scope
- Content
View full analysis
Vulnerability Details
File Location:
scripts/newsletter-digest.py:746-757, 788-791
Related Instruction:SKILL.md:79
Vulnerability Type: Excessive email retrieval and unintended disclosure to an external summarization provider
Risk Level: MediumComplete Code Snippet
python def fetch_message_body(message: dict[str, Any], account: str | None) -> str: message_id = str(message.get("id") or "") thread_id = str(message.get("threadId") or "") if not thread_id: return "" result = run_command( ["gog", "gmail", "thread", "get", thread_id, *build_account_args(account), "--full"], check=False, ) body = result.stdout.strip() if result.returncode == 0 else "" if body and not looks_like_placeholder_body(body): return body try: fallback = fetch_best_message_body(message_id, account) except RuntimeError: fallback = "" return fallback or bodypython body = fetch_message_body(message, account) if not body: body = "[Could not fetch content]" temp_file = temp_dir / f"newsletter-{message_id}.html" temp_file.write_text(body) log(f"[{index}/{len(messages)}] Summarizing...") outcome = summarize_text(temp_file, model, prompt)The behavior is also explicitly required by the Skill instructions:
markdown - Use `gog gmail thread get --full` first for body extraction.Technical Analysis
The Gmail search identifies individual messages matching configured newsletter labels or sender addresses and limits the search with
newer_than:1d. However, body extraction does not initially retrieve only the matched message. Instead, it invokesgog gmail thread get <threadId> --fulland treats the command's complete output as the content to summarize.A Gmail thread can contain content outside the intended selection boundary, including:
- Older messages that do not satisfy
newer_than:1d - User replies and private annotations
- Forwarded messages ...[truncated 2687 chars]
- Older messages that do not satisfy
- Remediation
View remediation
Remediation Suggestions
-
Replace full-thread retrieval with message-specific retrieval:
python ["gog", "gmail", "get", message_id, *build_account_args(account), "--json"]Extract only the MIME body belonging to the exact message ID returned by the search.
-
Make the existing
fetch_best_message_body()message-specific path the primary retrieval mechanism rather than a fallback. -
If thread retrieval is operationally unavoidable:
- Parse the thread into individual messages.
- Select only the message whose ID equals the search result's message ID.
- Reapply the one-day boundary before processing.
- Reject messages that do not independently satisfy the configured sender or label scope.
- Remove quoted history and forwarded-message sections before summarization.
-
Apply content minimization before invoking
summarize:- Strip unnecessary headers, tracking data, signatures, and quoted replies.
- Submit only the newsletter body required to produce the digest.
- Avoid including unrelated thread metadata.
-
Clearly disclose that newsletter content is sent to the configured summarization provider and obtain informed user consent before external processing.
-
Add regression tests using a thread containing an old sensitive message and a recent matching newsletter message. Verify that only the recent matched message reaches the summarizer.
-
Retain temporary-directory cleanup, but create files with explicitly restrictive permissions where portability permits and ensure cleanup also occurs when subprocess execution fails.
-
