Back to skill

Security audit

gmail-labeler

Security checks for vulnerabilities and agentic risk

Overview

This Gmail automation skill is coherent, but it handles private email, mailbox changes, external LLM review, and secrets in ways users should review carefully before installing.

Install only after you are comfortable granting Gmail read/label/archive authority and sharing selected email content with the configured LLM provider. Start in dry-run mode, disable LLM review unless explicitly needed, pin the AIN CLI, restrict logs and retention, and avoid using the provided launcher until the keyring secret is scoped only to the process that needs it.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gmail_labeler_runner.py:526
Finding

External LLM Review Discloses Email Content by Default

Content
View full analysis
dict[str, Any] | None: review = config.get('llmReview', {}) body = body_text(message)[: int(review.get('maxBodyChars', 2500))] allowed = review.get('allowedCategories', []) prompt = { 'from': message.get('from'), 'subject': message.get('subject'), 'body_excerpt': body, 'heuristic_sender_type': heuristic.sender_type, 'heuristic_category': heuristic.filter_id, 'heuristic_kind': heuristic.kind, 'heuristic_confidence': heuristic.confidence, 'allowed_categories': allowed, } system = ( 'You are an email classifier. Choose exactly one category from allowed_categories and never invent a new value. ' 'Return JSON only with keys: category, actionable, confidence, archive, star, important, notify, reason. ' 'category must be exactly one string from allowed_categories. ' 'confidence must be a number between 0 and 1. ' 'archive, star, important, notify, actionable must be booleans. ' 'If unsure, choose "none". ' 'Do not output markdown, prose, or extra keys.' ) model_chain = ...[truncated 2637 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/gmail_labeler_run.sh:9
Finding

Exported Gmail Keyring Password Is Inherited by Unrelated Child Processes

Content
View full analysis
subprocess.CompletedProcess[str]: return subprocess.run(cmd, text=True, capture_output=True, check=False) ``` ```python for model_id in model_chain: cmd = [ 'ain', 'run', json.dumps(prompt, ensure_ascii=False), '--jsonl', '--schema', str(root_dir() / 'references' / 'ain-email-review.schema.json'), '--system', system, '--model', model_id, '--timeout', str(review.get('timeoutMs', 30000)), '--max-tokens', str(review.get('maxTokens', 300)), '--temperature', str(review.get('temperature', 0.1)), ] if provider: cmd.extend(['--provider', provider]) proc = run_cmd(cmd) ``` ### Technical Analysis The launcher retrieves a production `GOG_KEYRING_PASSWORD` from Doppler and exports it into the process environment. Providing the password to `gog` is reasonably connected to unattended Gmail authentication. Exporting it for the entire Python process, however, causes every subprocess to inherit it unless an explicit environment is supplied. The runner invokes both `gog` and the unrelated third-party `ain` executable through the same unrestricted `run_cmd` function. The LLM client does not require the Gmail keyring password, so giving it access violates least privilege. The secret is not passed as a command-line argument and therefore is not directly exposed in the process argument list. The primary concern is environment inheritance by child executables and any code those executables load. ### Attack Path 1. The launcher authenticates to the production Do ...[truncated 946 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:10
Finding

Unpinned Third-Party AIN CLI Runs in a Sensitive Processing Context

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gmail_labeler_runner.py:106
Finding

Email Metadata Logs Are Created Without Enforced Owner-Only Permissions

Content
View full analysis
None: path.mkdir(parents=True, exist_ok=True) def purge_old_logs(log_dir: Path, retention_days: int) -> list[str]: removed: list[str] = [] cutoff = now_utc() - timedelta(days=retention_days) if not log_dir.exists(): return removed for child in log_dir.glob('*.jsonl'): try: mtime = datetime.fromtimestamp(child.stat().st_mtime, tz=timezone.utc) if mtime < cutoff: child.unlink() removed.append(str(child)) except FileNotFoundError: pass return removed def append_jsonl(path: Path, row: dict[str, Any]) -> None: ensure_dir(path.parent) with path.open('a', encoding='utf-8') as fh: fh.write(json.dumps(row, ensure_ascii=False) + '\n') ``` ```python plan_row = { "account": account, "messageId": message.get("id"), "from": message.get("from"), "subject": message.get("subject"), "senderType": decision.sender_type, "filterId": decision.filter_id, "kind": decision.kind, "confidence": decision.confidence, "urgent": decision.urgent, "reasons": decision.reasons[:8], "actions": actions, "notification": note, "execution": execution, "decisionSource": decision_source, "llmReview": llm_review, } all_plans.append(plan_row) append_jsonl(today_log, { "timestamp": now_utc().isoformat(), "type": "decision", **plan_row, }) ``` ### Technical Analysis The runner deliberately logs account identifiers, Gmail message IDs, sender addresses, subjects, classifications, reasons, notification routes, execution details, and LLM review output. The log directory and files are created using default `pathlib` behavior and ambient process `umask` ...[truncated 1089 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gmail_labeler_runner.py:526
Finding

Untrusted Email Content Can Influence LLM-Assisted Mailbox Mutations

Content
View full analysis
dict[str, Any] | None: review = config.get('llmReview', {}) body = body_text(message)[: int(review.get('maxBodyChars', 2500))] allowed = review.get('allowedCategories', []) prompt = { 'from': message.get('from'), 'subject': message.get('subject'), 'body_excerpt': body, 'heuristic_sender_type': heuristic.sender_type, 'heuristic_category': heuristic.filter_id, 'heuristic_kind': heuristic.kind, 'heuristic_confidence': heuristic.confidence, 'allowed_categories': allowed, } ``` ```python def decision_from_llm(message: dict[str, Any], heuristic: Decision, llm: dict[str, Any], config: dict[str, Any]) -> Decision: category = str(llm.get('category') or heuristic.filter_id) if category == 'none': return Decision(filter_id='none', kind='none', confidence=float(llm.get('confidence', heuristic.confidence)), sender_type=heuristic.sender_type, urgent=False, reasons=heuristic.reasons + [f"llm: {llm.get('reason','no category')}"], actions={}) filters = config.get('defaultFilters', []) + config.get('customFilters', []) base = next((f for f in filters if str(f.get('id')) == category), None) if not base: return heuristic actions = deepcopy(base.get('actions', {})) if bool(llm.get('archive', False)) and 'INBOX' not in actions.get('removeLabels', []): actions.setdefault('removeLabels', []).append('INBOX') if bool(llm.get('star', False)) and 'STARRED' not in actions.get('addLabels', []): actions.setdefault('addLabels', []).append('STARRED') if bool(llm.get('important', False)) and 'IMPORTANT' not ...[truncated 3719 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (14)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/gmail_labeler_run.sh (reported line 6)May include surrounding context.

sh
# Canonical Gmail labeler launcher.
# Mirrors Secretaria's working gog pattern:
# 1. fetch GOG_KEYRING_PASSWORD from Doppler
# 2. run gog/python in non-interactive mode

export GOG_KEYRING_PASSWORD="$(doppler secrets get GOG_KEYRING_PASSWORD --project openclaw --config prd --plain)"

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

The script fetches a production secret and exports it as an environment variable, which is a recognized credential-handling risk because environment variables are broadly inherited by child processes and can be exposed through diagnostics or process-inspection paths. In the context of a Gmail labeling launcher, secret access may be operationally necessary, but the implementation increases the blast radius if the runner or a child process is compromised.

Content

Scanner excerpt · scripts/gmail_labeler_run.sh (reported line 9)May include surrounding context.

sh
# 1. fetch GOG_KEYRING_PASSWORD from Doppler
# 2. run gog/python in non-interactive mode

export GOG_KEYRING_PASSWORD="$(doppler secrets get GOG_KEYRING_PASSWORD --project openclaw --config prd --plain)"

python3 /home/ubuntu/.openclaw/workspace/skills/gmail-labeler/scripts/gmail_labeler_runner.py \
  --config /home/ubuntu/.openclaw/local/gmail-labeler.config.json \

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill advertises operational runners and local logging/config overlays that imply shell execution plus file reads/writes, but it does not declare any explicit tool scope or allowed-tools boundary. In an automation that can modify Gmail state and local files, missing capability scoping increases the chance of over-privileged execution, unintended command use, or unsafe composition by downstream agents.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill clearly performs automated inbox triage and removes non-actionable messages from Inbox, but the description does not present this as a prominent user-facing warning. Users may enable or invoke the skill without appreciating that it can archive messages automatically, creating a risk of missed communications, business loss, or delayed response to important mail.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 111)May include surrounding context.

md
- `STARRED`
- `INBOX`

Create custom labels only when needed, for example:
- `Newsletter`
- `Notification`
- `Receipt`

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The config embeds Portuguese and Spanish subject/body matching phrases alongside English, indicating the skill applies language-specific behavior by default. Under the policy, locale or language behavior should offer user choice or clearly document the constraint/justification; this file provides neither.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The document directs logging of sensitive email metadata including sender, subject, message ID, reasons, and planned actions, which can expose private correspondence patterns and potentially sensitive business or personal information if logs are accessed by unauthorized users. In a Gmail automation context, this is more dangerous because the skill operates on real inbox data at scale and the guidance does not mention minimization, redaction, access controls, encryption, or user consent.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
80% confidence
Finding

The manifest describes a Gmail automation skill, but this wrapper script additionally reaches into an external secrets-management system to retrieve GOG_KEYRING_PASSWORD. Accessing Doppler and handling a separate credential source is an operational capability not stated in the skill purpose and is not obviously part of Gmail labeling behavior itself.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The launcher silently pulls a production secret and exports it into the environment without any user-facing disclosure or consent boundary. Even if used legitimately, this reduces operator awareness, increases the chance of accidental credential exposure to subprocesses, logs, or debugging tools, and makes secret use less auditable in a skill intended for automation.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/gmail_labeler_runner.py (reported line 403)May include surrounding context.

python
def run_cmd(cmd: list[str]) -> subprocess.CompletedProcess[str]:
    return subprocess.run(cmd, text=True, capture_output=True, check=False)


def fetch_messages_with_gog(account: str, config: dict[str, Any]) -> list[dict[str, Any]]:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The LLM review path sends email metadata and body excerpts to an external command/provider, including sender, subject, and message content, without any in-file consent gate, redaction step, or strong minimization beyond truncation. In the context of a Gmail triage skill, this is especially sensitive because inbox contents routinely contain private personal, business, financial, and authentication-related information that may be disclosed to third-party model services.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script persistently logs sender, subject, reasons, account identifiers, and full llmReview payloads to JSONL files under a predictable local path, creating a durable plaintext record of sensitive inbox activity. Because llmReview is logged verbatim, any body excerpts returned or stored in that object can amplify exposure, and local compromise, backup leakage, or overbroad file permissions would reveal private communications at scale.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

Line L016 states that language coverage should include English, Portuguese, and Spanish terms when defining filters. This is a natural-language locale requirement presented as a default policy, but it does not mention user preference, opt-in, or a documented regional justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The file describes automatic purging of logs and a self-improvement routine that updates local overlays, thresholds, lists, and keywords, which constitutes persistent local state modification without explicit safeguards, audit controls, or user approval. While not inherently malicious, silent runtime changes can cause unintended behavior drift, privacy issues, or loss of forensic history needed to investigate misclassifications or abuse.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.