T09 · Insecure Skill Coding Practices
- Location
scripts/gws_gmail_label_workflow.py:274- Finding
Unvalidated sender input permits Gmail search-query injection during retroactive modification
- Content
View full analysis
Vulnerability Details
File Location:
scripts/gws_gmail_label_workflow.py, lines 233-234 and 274-286
Vulnerability Type: Gmail search-query injection
Risk Level: MediumThe workflow accepts arbitrary text through repeated
--senderarguments. It strips surrounding whitespace but does not verify that each value is a single valid mailbox address:python senders = [s.strip() for s in args.sender if s and s.strip()] if not senders: raise WorkflowError("Provide at least one --sender")The unvalidated value is then interpolated directly into several Gmail search queries. The first query determines which message IDs will be modified:
python retro_applied = 0 if not args.no_retro: ids = list_all_message_ids(args.user_id, f"from:{sender}") retro_applied = batch_modify( user_id=args.user_id, ids=ids, add_label_ids=[label_id], remove_label_ids=["INBOX"] if args.remove_inbox else [], dry_run=args.dry_run, ) from_count = len(list_all_message_ids(args.user_id, f"from:{sender}")) label_count = len(list_all_message_ids(args.user_id, f"from:{sender} label:\"{args.label}\"")) inbox_count = len(list_all_message_ids(args.user_id, f"from:{sender} in:inbox"))Technical Analysis
Gmail search queries support operators, grouping, quotation, and logical expressions. Because
senderis concatenated intofrom:{sender}without validation or safe encoding, a value containing Gmail query syntax can change the meaning or scope of the resulting query.The IDs returned by the injected query are supplied to
batch_modify. Unless--keep-inboxis selected, the workflow adds the chosen label and removesINBOXfrom every returned message. This makes the injection consequential rather than merely affecting displayed search results.The later count queries are vulnerable to the same input handling and may produce misleading verification r ...[truncated 1723 chars]
- Remediation
View remediation
Remediation Suggestions
-
Validate every
--sendervalue as exactly one mailbox address before using it:- Use a standards-aware address parser.
- Require the parsed address to consume the entire input.
- Reject display names, multiple addresses, control characters, whitespace, quotes, parentheses, braces, and Gmail query operators.
- Apply a conservative length limit.
-
Construct the query through a dedicated helper that enforces the invariant that only validated addresses can reach Gmail search operations:
python import re SENDER_RE = re.compile( r"^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@" r"[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?" r"(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$" ) def validate_sender(value: str) -> str: sender = value.strip() if not SENDER_RE.fullmatch(sender): raise WorkflowError(f"Invalid sender address: {value!r}") return senderApply this function while building
senders, before filter creation or message searches. -
If advanced Gmail
from:expressions are a legitimate requirement, expose them through a separate, explicitly named option rather than overloading--sender. Advanced-query mode should:- Be disabled by default.
- Display the complete query and number of matched messages.
- Require explicit confirmation before any retroactive modification.
- Avoid default INBOX removal.
-
Add a maximum affected-message safeguard. Abort or require confirmation when the match count exceeds a configurable threshold.
-
Query and preview matching IDs before mutation, and report the planned number of affected messages. This is especially important because INBOX removal is enabled by default.
-
Add tests covering whitespace, quotes, parentheses, logical operators, multiple addresses, control characters, malformed domains, and valid international or plus-address ...[truncated 52 chars]
-
