Back to skill

Security audit

Gmail-digester

Security checks for vulnerabilities and agentic risk

Overview

This Gmail digest skill mostly does what it says, but its credential handling can redirect stored email credentials to a host chosen by environment variables.

Review this before installing. Use a narrowly scoped Gmail app password, prefer environment variables over the config file, avoid setting IMAP_HOST unless you fully trust the server, and do not store the config file in a shared or synced location. Treat email content in summaries as untrusted because an email sender can put instructions in the message body.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch_unseen.py:16
Finding
Credential Redirection Through Independently Overridable IMAP Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_unseen.py`, lines 16–47 and 112–113 **Vulnerability Type**: Credential exposure through unsafe configuration-source mixing **Risk Level**: High ### Vulnerable Code ```python _env_host = os.environ.get("IMAP_HOST", "").strip() _env_port = os.environ.get("IMAP_PORT", "").strip() _env_username = os.environ.get("IMAP_USERNAME", "").strip() _env_password = os.environ.get("IMAP_PASSWORD", "").strip() _env_maxchars = os.environ.get("IMAP_MAX_BODY_CHARS", "").strip() if _env_username and _env_password: # Credentials supplied entirely via env — no config file required IMAP_HOST = _env_host or "imap.gmail.com" IMAP_PORT = int(_env_port) if _env_port else 993 USERNAME = _env_username PASSWORD = _env_password MAX_CHARS = min(int(_env_maxchars) if _env_maxchars else 2000, 2000) else: config_path_env = os.environ.get("EMAIL_CONFIG_PATH", "").strip() if config_path_env: config_path = Path(config_path_env).expanduser() else: config_path = Path.home() / ".config" / "gmail-summarize" / "config.json" cfg = json.loads(config_path.read_text()) email_cfg = cfg.get("email", {}) IMAP_HOST = _env_host or email_cfg.get("imapHost", "imap.gmail.com") IMAP_PORT = int(_env_port) if _env_port else int(email_cfg.get("imapPort", 993)) USERNAME = _env_username or email_cfg.get("imapUsername", "") PASSWORD = _env_password or email_cfg.get("imapPassword", "") ``` The resulting values are used directly for authentication: ```python client = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT) client.login(USERNAME, PASSWORD) ``` ### Technical Analysis The host, port, username, and password are resolved independently from environment variables and the fallback configuration file. Consequently, an environment-provided `IMAP_HOST` can be combined with credentials read from the user's configuration file. The script does not validate or allowlist th ...[truncated 1839 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve the endpoint and credentials atomically from a single configuration source. Do not combine an environment-selected host with credentials loaded from a file. 2. If environment credentials are incomplete, reject the configuration rather than silently filling missing values from disk. 3. For this Gmail-specific Skill, allowlist `imap.gmail.com` by default. 4. Require explicit user approval for custom IMAP providers and store the approved endpoint together with the corresponding credentials. 5. Validate the normalized hostname and reject IP literals, unexpected ports, malformed hostnames, and unapproved endpoints. 6. Consider binding credentials to an expected server identity in a secret manager rather than accepting independent generic environment variables. 7. Ensure authentication failures and exceptions never print credential values. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:44
Finding
Plaintext Credential File Is Documented Without Permission Enforcement<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 44–54 **Vulnerability Type**: Insecure storage of sensitive authentication material **Risk Level**: Medium ### Vulnerable Documentation ```markdown ### Option B — Config file Set `EMAIL_CONFIG_PATH` to point to a JSON file, or place the file at `~/.config/gmail-summarize/config.json`. The file must contain **only** the fields below (no other sensitive data should be stored in this file): ```json { "email": { "imapHost": "imap.gmail.com", "imapPort": 993, "imapUsername": "your@gmail.com", "imapPassword": "your-app-password", "maxBodyChars": 2000 } } ``` ``` The script then reads the file without validating its ownership or permissions: ```python cfg = json.loads(config_path.read_text()) email_cfg = cfg.get("email", {}) ``` ### Technical Analysis The documented fallback stores an IMAP password or app password as plaintext JSON. Neither the documentation nor the implementation requires restrictive filesystem permissions, checks file ownership, or rejects a file accessible to other local principals. Restricting the file to the listed fields does not protect the credential itself. A plaintext app password remains sensitive authentication material and may also be collected by backups, support bundles, home-directory synchronization, or other processes with filesystem access. ### Attack Path 1. A user follows the documented fallback procedure and creates the JSON configuration file containing an IMAP app password. 2. The file is created with permissive permissions due to the user's `umask`, editor behavior, copied-file permissions, or placement at a custom `EMAIL_CONFIG_PATH`. 3. Another local user or process with read access obtains the JSON file. 4. The attacker extracts `imapUsername` and `imapPassword`. 5. The attacker authenticates to the configured mailbox using the stolen credential. ### Impact Assessment The exposed credential may permit unauthorized acc ...[truncated 421 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an operating-system secret store, managed secret service, or securely injected environment credential over a plaintext JSON file. 2. If file-based credentials remain supported, require the file to be owned by the current user and have mode `0600` or stricter. 3. Add implementation checks using `stat()` and fail closed if the file is group-readable, world-readable, unexpectedly owned, or not a regular file. 4. Document secure file creation, such as setting a restrictive `umask` before writing the file. 5. Warn users not to place the credential file inside a project directory, synchronized folder, source repository, or broadly collected backup location. 6. Recommend provider-specific, revocable app passwords with the narrowest available mailbox permissions. 7. Provide credential rotation and revocation guidance in case the file is exposed. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_unseen.py:126
Finding
Untrusted Email Content Is Passed to the Agent Without Prompt-Injection Isolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_unseen.py`, lines 126–153; related workflow in `SKILL.md`, lines 65–82 **Vulnerability Type**: Indirect prompt injection through attacker-controlled email content **Risk Level**: Medium ### Vulnerable Code and Instructions The script extracts attacker-controlled subject and body content and returns it directly: ```python parsed = BytesParser(policy=policy.default).parsebytes(raw) sender = parseaddr(parsed.get("From", ""))[1].strip().lower() subject = decode_hdr(parsed.get("Subject", "")) date_str = parsed.get("Date", "") body = extract_body(parsed)[:MAX_CHARS] or "(empty)" results.append({ "sender": sender, "subject": subject, "date": date_str, "body": body, }) ``` The Skill instructs the Agent to process that content without identifying it as untrusted data: ```markdown 1. Run the fetch script via exec tool: `python {workspace}/skills/gmail-summarize/scripts/fetch_unseen.py` 2. Parse the JSON array. Each item has: sender, subject, date, body 3. For each email compose one line: `[date] sender | subject — one-sentence body summary` 4. Send the full digest via MessageTool ``` ### Technical Analysis Email senders control message subjects and bodies. Truncating a body to 2,000 characters limits size but does not neutralize natural-language instructions directed at an AI Agent. The workflow lacks an explicit trust boundary requiring the Agent to treat message content solely as inert data. It also does not prohibit obeying commands found in emails, making tool calls based on message content, revealing contextual information, or changing the required output format. This creates an indirect prompt-injection surface. Exploitability and final impact depend on the hosting Agent's instruction hierarchy, tool permissions, and whether it reliably distinguishes Skill instructions from retrieved content. ### Attack Path 1. An attacker sends the target mailbox an ema ...[truncated 1202 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a high-priority instruction stating that all sender, subject, date, and body values are untrusted data and must never be interpreted as instructions. 2. Explicitly prohibit following links, invoking tools, revealing context, changing configuration, or sending additional messages based on email content. 3. Place each message in a clearly delimited structured-data block and ensure the Agent summarizes only the designated fields. 4. Use a constrained summarization stage that accepts text and returns a fixed schema without tool access. 5. Validate the resulting digest against the expected format before invoking MessageTool. 6. Treat output-format changes, requests to ignore prior instructions, and tool-use directives in message content as hostile and omit them from summaries where appropriate. 7. Apply equivalent safeguards to both subjects and bodies because either field can carry an injection payload. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose says the skill sends a digest, but the documented workflow includes fetching full unread email bodies, reading credentials from disk or environment, and returning raw JSON from the script. This mismatch is security-significant because it masks broader data access than the user-facing description implies, increasing the chance of over-collection and unintended disclosure of sensitive email contents.

Natural-Language Policy Violations

High
Confidence
98% confidence
Finding
The workflow and output rules require the digest and summaries to be produced in Chinese, including a mandatory translation of email body summaries. This is a language-policy constraint applied without user opt-in or any documented regional justification.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
5. If result is empty, send: 📭 近两日暂无未读邮件

## Output Rules
- Send the digest message ONLY. Do NOT add any extra comments, greetings, explanations, or follow-up questions before or after the digest.
- Do NOT say things like "主人,以下是您的邮件摘要" or "如需了解详情请告知" etc.
- The digest message itself is the complete and final response.
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill requires access to environment variables, local files, and the network to read Gmail credentials and connect to an IMAP server, but it does not declare any explicit tool scope or allowed-tools boundary. That omission weakens reviewability and containment because a caller cannot easily verify what capabilities the skill is expected to exercise before running it.

Vague Triggers

Medium
Confidence
93% confidence
Finding
Broad trigger phrases like 'check my Gmail' or 'summarize my emails' can overlap with ordinary conversation and cause the skill to activate in contexts where the user did not intend credentialed mailbox access. Because this skill reads private email through IMAP, accidental invocation materially raises privacy and consent risks.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script reads sensitive credentials from environment variables and later uses them to authenticate to Gmail and fetch message content. Although the module docstring says it fetches unread Gmail, there is no explicit warning, prompt, or user-facing notice that credentials will be used and email body data will be transmitted and processed.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
When environment variables are not present, the script loads a config file and reads IMAP username and password fields. This is sensitive credential access, but the code provides no confirmation, warning, or user-facing disclosure beyond an internal comment.

Static analysis

No suspicious patterns detected.