Back to skill

Security audit

Exchange Skills

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Exchange mailbox tool, but it needs Review because it can make broad mailbox changes and its archive command may move messages into deletion folders.

Install only if you are comfortable giving this skill full access to the configured Exchange mailbox. Keep TLS verification enabled, store credentials carefully, review commands before allowing replies or batch mark-read/archive actions, and avoid using archive until the Deleted Items/trash fallback is fixed or clearly configured.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/exchange_mail.py:41
Finding

Optional TLS Certificate Verification Bypass Exposes Exchange Credentials and Mailbox Data

Content
View full analysis

Vulnerability Details

File Location: scripts/exchange_mail.py:41-45 and scripts/exchange_mail.py:82-86; documented in SKILL.md:116
Vulnerability Type: TLS certificate validation bypass
Risk Level: High

Vulnerable code:

python
# SSL verification is enabled by default for security
# If you need to disable SSL verification for corporate certificates,
# set EXCHANGE_DISABLE_SSL_VERIFY=1 environment variable
if os.environ.get('EXCHANGE_DISABLE_SSL_VERIFY') == '1':
    import urllib3
    urllib3.disable_warnings()
python
# SSL verification is enabled by default for security
# Only disable if explicitly requested via environment variable
if os.environ.get('EXCHANGE_DISABLE_SSL_VERIFY') == '1':
    BaseProtocol.HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter

The insecure option is also explicitly documented:

bash
export EXCHANGE_DISABLE_SSL_VERIFY=1  # Only if you need to disable SSL verification (not recommended)

Technical Analysis

When EXCHANGE_DISABLE_SSL_VERIFY equals 1, the script replaces the HTTP adapter used by exchangelib with NoVerifyHTTPAdapter. It also suppresses warnings that would otherwise alert the user to insecure TLS connections.

TLS encryption without certificate validation does not authenticate the Exchange server. An attacker capable of intercepting or redirecting network traffic can present an arbitrary certificate and impersonate the configured server. Because the script authenticates using an Exchange username and password and processes sensitive mailbox content, successful interception could expose credentials, messages, contacts, tasks, notes, calendar data, and mailbox mutations.

Attack Path

  1. The user enables the documented EXCHANGE_DISABLE_SSL_VERIFY=1 setting, commonly to work around an internal or self-signed certificate.
  2. An attacker gains a network interception position or manipulates DNS, routing, or proxy configur ...[truncated 822 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove the certificate-verification bypass and always use a validating TLS adapter.
  • Support a configurable corporate certificate authority bundle instead of disabling validation.
  • Allow users to provide a CA file through a setting such as EXCHANGE_CA_BUNDLE, and validate both the certificate chain and server hostname.
  • Consider certificate or public-key pinning for high-security deployments.
  • If an insecure diagnostic mode must remain, prevent it from using production credentials, require an explicit command-line confirmation, and emit a prominent warning on every request.
  • Do not suppress TLS warnings globally.
  • Update SKILL.md to document installation of the corporate CA certificate rather than recommending certificate validation bypass.

T08 · Insecure Dependencies

Warning
Location
scripts/exchange_mail.py:75
Finding

Unpinned Third-Party Dependency Installation Creates Supply-Chain Risk

Content
View full analysis

Vulnerability Details

File Location: scripts/exchange_mail.py:75-81
Vulnerability Type: Unpinned dependency installation guidance
Risk Level: Medium

Vulnerable code:

python
try:
    from exchangelib import Credentials, Account, Configuration, DELEGATE
    from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter
except ImportError:
    print("Error: exchangelib not installed.", file=sys.stderr)
    print("Run: pip install exchangelib", file=sys.stderr)
    sys.exit(1)

Technical Analysis

The script instructs users to install exchangelib without specifying a reviewed version, integrity hash, dependency lockfile, or trusted package-index configuration. The effective code loaded by the Skill can therefore change over time even though the project itself has not changed.

Python package installation may execute package build logic, while subsequent imports execute package initialization code. A compromised upstream release, compromised package-index account, or unsafe future transitive dependency could consequently execute code with the privileges of the user running the installation or Skill.

No evidence shows that the current exchangelib package is malicious. The vulnerability is the unsafe and non-reproducible dependency acquisition process.

Attack Path

  1. The script is run in an environment where exchangelib is unavailable.
  2. The user follows the displayed pip install exchangelib instruction.
  3. The package index supplies the latest available release and unconstrained transitive dependencies.
  4. An upstream account, release, build artifact, or transitive dependency has been compromised.
  5. Malicious code executes during installation or when the dependency is imported.
  6. That code can access the invoking user's files, environment, and Exchange credentials.

Impact Assessment

Malicious dependency code would run with the operating-system privileges o ...[truncated 346 chars]

Remediation
View remediation

Remediation Suggestions

  • Add a dependency manifest containing a reviewed and exact exchangelib version.

  • Generate and commit cryptographic hashes for the package and every transitive dependency.

  • Direct users to install with a reproducible command such as:

    bash
    python3 -m pip install --require-hashes -r requirements.txt
    
  • Use an isolated virtual environment rather than installing into the system Python environment.

  • Periodically review pinned versions for security updates, then update pins and hashes through a controlled process.

  • In sensitive deployments, use an authenticated internal package mirror containing reviewed artifacts.

  • Avoid dynamically installing dependencies from within the Skill.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/exchange_mail.py:143
Finding

Collision-Prone Short Email Identifiers Authorize Destructive Mailbox Operations

Content
View full analysis

Vulnerability Details

File Location: scripts/exchange_mail.py:143-147, with identifier resolution at scripts/exchange_mail.py:214-230 and mutation usage at scripts/exchange_mail.py:347-390 and scripts/exchange_mail.py:433-444
Vulnerability Type: Weak and ambiguous object identifier
Risk Level: Medium

Vulnerable code:

python
def generate_email_id(item) -> str:
    """Generate stable 8-character ID from email."""
    id_source = (item.message_id or item.id or str(item.datetime_received))
    return hashlib.md5(id_source.encode()).hexdigest()[:8]
python
def find_email_by_id(email_id: str):
    """Find email item by short ID."""
    global _emails_cache

    if email_id in _emails_cache:
        return _emails_cache[email_id]

    # Search in recent emails
    account = get_account()
    start_date = datetime.now(timezone.utc) - timedelta(days=7)

    for item in account.inbox.filter(
        datetime_received__gte=start_date
    ).order_by('-datetime_received')[:100]:
        item_id = generate_email_id(item)
        _emails_cache[item_id] = item
        if item_id == email_id:
            return item

    return None

The resolved object is used for state-changing operations, for example:

python
elif args.target:
    item = find_email_by_id(args.target)
    if not item:
        print(f"❌ Email with ID {args.target} not found")
        return

    item.is_read = True
    item.save(update_fields=['is_read'])
    print(f"✅ Email {args.target} marked as read")
python
elif args.target:
    item = find_email_by_id(args.target)
    if not item:
        print(f"❌ Email with ID {args.target} not found")
        return

    try:
        item.move(archive_folder)
        print(f"✅ Email {args.target} archived")
    except Exception as e:
        print(f"❌ Archive error: {e}")

Technical Analysis

The identif ...[truncated 1964 chars]

Remediation
View remediation

Remediation Suggestions

  • Use the full opaque Exchange item ID for internal object resolution.
  • If human-friendly handles are necessary, generate at least 128 bits of cryptographically random data and maintain an explicit handle-to-item mapping.
  • Detect collisions when inserting handles and refuse to proceed if an identifier maps to more than one message.
  • Before any state-changing operation, retrieve the item by its immutable Exchange identifier and verify expected metadata such as sender, subject, and received time.
  • Display the selected sender and subject and require confirmation for replies or destructive mailbox operations.
  • Do not derive security-sensitive handles from sender-controlled Message-ID values.
  • Clear or update stale cache entries after moving messages.

other

Warning
Location
scripts/exchange_mail.py:401
Finding

Archive Command Can Silently Move Messages to Deleted Items

Content
View full analysis

Vulnerability Details

File Location: scripts/exchange_mail.py:401-419
Vulnerability Type: Unintended destructive mailbox operation
Risk Level: Medium

Vulnerable code:

python
def cmd_archive(args):
    """Archive email(s) - move to Archive folder."""
    account = get_account()

    # Find archive folder
    from exchangelib import Folder

    archive_folder = None
    for folder in account.root.walk():
        if folder.name.lower() in ['archive', 'архив', 'deleted items', 'удаленные']:
            archive_folder = folder
            break

    if not archive_folder:
        try:
            archive_folder = account.trash
        except:
            archive_folder = Folder(parent=account.inbox.parent, name='Archive')
            archive_folder.save()
            print("📁 Created Archive folder")

Technical Analysis

The command is documented and presented as an archive operation, but the destination selection explicitly treats folders named Deleted Items and удаленные as archive folders. If no matching folder is found, it also selects account.trash before attempting to create an Archive folder.

Folder traversal order is not used to prioritize a canonical archive folder. Consequently, Deleted Items may be selected even when the user intends non-destructive archival. Messages moved there may be automatically purged by mailbox retention or cleanup policies.

The issue is particularly consequential for archive --external, archive --internal, and archive --all, because these modes can move many messages without interactive confirmation.

Attack Path

  1. The configured mailbox lacks a recognized folder named Archive or Архив, or the root traversal encounters Deleted Items first.
  2. The user invokes the documented archive command.
  3. The script selects Deleted Items or account.trash as archive_folder.
  4. A single message or as many as 50 batch-s ...[truncated 676 chars]
Remediation
View remediation

Remediation Suggestions

  • Resolve only the canonical Exchange Archive folder or a folder explicitly configured by the user.
  • Remove Deleted Items, localized deleted-item names, and account.trash from archive destination fallback logic.
  • If no Archive folder exists, either create a dedicated Archive folder or abort with a clear error.
  • Verify the destination folder type or distinguished folder ID rather than relying only on localized display names.
  • Require an explicit confirmation before batch archive operations, showing the destination and number of affected messages.
  • Add a dry-run option that lists the messages and destination without moving anything.
  • Log each moved item's immutable Exchange ID and original folder to support recovery and auditing.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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 (11)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/exchange_mail.py (reported line 48)May include surrounding context.

python
import urllib3
    urllib3.disable_warnings()

# Load .env file from script directory
def load_env_file():
    """Load environment variables from .env file in script directory."""
    import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/exchange_mail.py (reported line 50)May include surrounding context.

python
import urllib3
    urllib3.disable_warnings()

# Load .env file from script directory
def load_env_file():
    """Load environment variables from .env file in script directory."""
    import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/exchange_mail.py (reported line 53)May include surrounding context.

python
"""Load environment variables from .env file in script directory."""
    import os
    script_dir = os.path.dirname(os.path.abspath(__file__))
    env_path = os.path.join(script_dir, '.env')
    if os.path.exists(env_path):
        with open(env_path, 'r') as f:
            for line in f:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill declares access to sensitive Exchange credentials and file-based configuration but does not define any explicit tool scope or permission boundary. In an agent environment, missing scope makes it easier for the skill to be invoked with broader-than-expected capabilities, increasing the risk of unauthorized mailbox access or secret exposure.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The trigger list contains broad phrases like 'calendar', 'schedule', 'contacts', 'tasks', and 'notes', which can match many routine user requests. That can cause unintended activation of a high-privilege skill that can read or modify mailbox data, leading to accidental data access or destructive actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill advertises capabilities to reply to emails, mark messages as read, and archive mail without warning that these actions modify user state and may be irreversible or user-visible. In this context, the skill has direct access to business communications, so accidental activation could send unwanted messages or alter evidence and workflow-critical mailbox state.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code supports destructive mailbox changes such as archive --external and archive --all, which move multiple messages and may even fall back to Deleted Items, but it performs the action immediately once invoked. Although success is printed afterward, there is no pre-action confirmation prompt or cautionary warning in the CLI help/docstring about the batch-destructive behavior.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The archive-folder selection logic treats 'Deleted Items' and trash as acceptable archive destinations. A user invoking archive may therefore unintentionally move mail into a deletion workflow, causing data loss or retention-policy bypass rather than safe archival. In an email-management skill, this is more dangerous because batch operations can affect many messages quickly.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The notes retrieval logic scans the Inbox and classifies matching messages as notes based on folder name or subject text. This can surface ordinary emails through the notes command, causing unintended disclosure of mailbox contents and confusion about data boundaries. In this skill context, cross-surface access is sensitive because the tool already handles private email data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The documentation instructs users to place Exchange server address, username, and password in environment variables and notes that a .env file may be loaded, but it does not include a clear warning about secret handling. This increases the chance that credentials are stored insecurely, committed to source control, or exposed through logs or shared environments.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The calendar display uses the fixed string 全天 for all-day events, which imposes a specific locale in user-facing output. There is no language selection, locale detection, or documentation justifying a region-specific output requirement.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.