Back to skill

Security audit

Teams Delegate

Security checks for vulnerabilities and agentic risk

Overview

This Teams delegation skill is not clearly malicious, but it needs Review because it can monitor and send workplace messages using broad Microsoft Graph permissions with limited safeguards.

Install only if you are comfortable granting a local agent access to read and send Teams messages. Prefer draft-only operation, require explicit approval before every sent reply, reduce Graph permissions to the minimum needed, protect or regularly revoke the local token cache, and avoid unattended cron or heartbeat auto-replies unless the scope and stop conditions are tightly controlled.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.py:27
Finding

OAuth Token Cache Is Stored Without Explicit Owner-Only Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/auth.py, lines 27-29 and 41-44
Vulnerability Type: Insecure storage of reusable OAuth credentials
Risk Level: Medium

Vulnerable Code

python
def save_config(cfg):
    os.makedirs(TOKEN_DIR, exist_ok=True)
    with open(CONFIG_FILE, "w") as f: json.dump(cfg, f, indent=2)
python
def save_cache(cache):
    os.makedirs(TOKEN_DIR, exist_ok=True)
    if cache.has_state_changed:
        open(TOKEN_CACHE, "w").write(cache.serialize())

Technical Analysis

The serialized MSAL token cache can contain access tokens, refresh tokens, account identifiers, and related authentication state. The application creates ~/.teams-delegate and writes token_cache.bin without explicitly setting owner-only permissions.

Consequently, the resulting permissions depend on the user's current umask and any pre-existing directory or file permissions. In a permissively configured multi-user environment, another local account may be able to read the token cache. The implementation also writes the cache directly rather than using an atomic replacement, increasing the possibility of partial writes or interactions with an unexpectedly pre-created file.

The network transmission of bearer tokens to Microsoft authentication and Graph endpoints is necessary for the declared Teams functionality. The vulnerability is instead the insufficient protection of the reusable credentials at rest.

Attack Path

  1. The victim authenticates through the device-code flow.
  2. MSAL obtains OAuth tokens with Teams chat and other Microsoft Graph permissions.
  3. save_cache() serializes the authentication state to ~/.teams-delegate/token_cache.bin.
  4. The file inherits permissions determined by the environment rather than enforced mode 0600.
  5. A local attacker reads the cache when filesystem permissions permit it.
  6. The attacker imports or otherwise reuses the cached OA ...[truncated 770 chars]
Remediation
View remediation

Remediation Suggestions

  • Create ~/.teams-delegate with mode 0700 and verify that it is owned by the current user.
  • Create the token cache with mode 0600, independent of the ambient umask.
  • Reject symbolic links and files owned by another user before reading or writing authentication state.
  • Write updates to an owner-only temporary file in the same directory, flush and synchronize it, and atomically replace the cache.
  • Prefer an operating-system credential store or encrypted secret-storage facility instead of a plaintext serialized cache where supported.
  • Correct the documentation mismatch: the reference says token.json, while the implementation stores token_cache.bin.
  • Revoke existing sessions and require reauthentication if insecure cache permissions are detected.

Example hardening should use low-level file creation with os.open(..., os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600) or an equivalent secure credential-storage API.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/auth.py:19
Finding

Microsoft Graph Permissions Exceed the Implemented Feature Set

Content
View full analysis

Vulnerability Details

File Location: scripts/auth.py, lines 19-20
Vulnerability Type: Excessive OAuth privileges
Risk Level: Medium

Vulnerable Code

python
SCOPES = ["Chat.Read","Chat.ReadWrite","ChannelMessage.Read.All",
          "ChannelMessage.Send","Presence.Read.All","User.Read"]

These permissions are also presented as required during setup in SKILL.md, lines 20-25.

Technical Analysis

The implemented commands retrieve the current profile, list chats, read chat messages, and send chat replies. The executable code contains no operation that reads channel messages, posts to channels, or queries presence information.

Nevertheless, every authentication request includes:

  • ChannelMessage.Read.All
  • ChannelMessage.Send
  • Presence.Read.All

These permissions expand the token's authority beyond the functionality currently exposed by the scripts. Chat.Read may also be redundant when Chat.ReadWrite is granted, subject to Microsoft's current permission semantics for the specific delegated endpoints.

ChannelMessage.Read.All is particularly sensitive because it can expose channel communications beyond the private chats needed by the implemented inbox workflow. Requesting unused permissions violates least privilege and increases the consequences of token theft, accidental misuse, or future vulnerable code.

Attack Path

  1. The user follows the setup instructions and grants all listed Microsoft Graph permissions.
  2. The device-code flow issues authentication state associated with these permissions.
  3. The credentials are stored in the local MSAL cache.
  4. An attacker obtains the cache through local compromise, backup exposure, malware, or another credential-access vector.
  5. The attacker uses the broader granted permissions directly against Microsoft Graph.
  6. The attacker reads channel messages, sends channel content, or queries presence information even though those ...[truncated 651 chars]
Remediation
View remediation

Remediation Suggestions

  • Reduce the default scope set to the minimum required for the implemented chat commands, likely Chat.ReadWrite and User.Read, after verifying current Microsoft Graph endpoint requirements.
  • Remove ChannelMessage.Read.All, ChannelMessage.Send, and Presence.Read.All from the default authentication flow.
  • Add channel or presence permissions only when a user explicitly enables a feature that requires them.
  • Use incremental consent so optional functionality does not increase privileges for every installation.
  • Document each requested scope, the exact command that uses it, and whether tenant administrator consent is required.
  • Reauthenticate existing users after reducing the scope set and instruct administrators to revoke unnecessary grants.
  • Add automated tests that compare requested scopes with the Graph endpoints actually used by the application.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/teams.py:129
Finding

Untrusted Teams Messages Are Passed Directly into an AI Auto-Reply Workflow

Content
View full analysis

Vulnerability Details

File Location: scripts/teams.py, lines 129-139
Vulnerability Type: Indirect prompt injection through untrusted message content
Risk Level: High

Vulnerable Code

python
def cmd_summary(token):
    uid, name = get_user_id(token)
    chats = get_chats(uid, token)
    print(f"\n=== TEAMS INBOX SUMMARY — {name} ===\n")
    for c in chats[:10]:
        preview = c.get("lastMessagePreview", {})
        sender  = preview.get("from", {}).get("user", {}).get("displayName", "?")
        content = strip_html(preview.get("body", {}).get("content", ""))
        t       = fmt_time(preview.get("createdDateTime", ""))
        imp     = preview.get("importance", "normal")
        print(f"CHAT_ID: {c['id']}")
        print(f"  Type: {c.get('chatType')} | From: {sender} | Time: {t} | Importance: {imp}")
        print(f"  Preview: {content[:120]}\n")

The associated workflow in SKILL.md, lines 40-47, directs the agent to consume this output and potentially send replies:

markdown
1. Run `summary` to get all recent conversations in one pass
2. Classify each message:
   - **Agent handles:** acknowledgements, scheduling confirmations, "got it" responses, simple status updates
   - **Needs human:** decisions, sensitive topics, anything the user explicitly wants to see, urgent/high importance from boss
3. For messages the agent handles: draft reply → run `reply`
4. For messages needing human: summarize and present to user

Technical Analysis

Teams message bodies are controlled by remote senders. The summary command labels its output as an inbox summary intended for agent review and prints sender-controlled message content without establishing a machine-enforced trust boundary between data and instructions.

strip_html() only removes strings matching an HTML-tag pattern. It does not prevent natural-language prompt injection. A malicious sender can write instru ...[truncated 1841 chars]

Remediation
View remediation

Remediation Suggestions

  • Treat every message body, sender name, chat title, and metadata value as untrusted external data.
  • Add explicit agent instructions stating that content retrieved from Teams must never be interpreted as system instructions, tool commands, authorization, or permission to disclose data.
  • Return structured JSON with fields marked as untrusted rather than an instruction-like plaintext block.
  • Require explicit human confirmation before every send operation, especially when a reply contains sensitive data, references another conversation, or was influenced by external text.
  • If unattended replies are essential, restrict them to deterministic, allowlisted templates and narrowly defined events rather than free-form model decisions.
  • Prevent message content from selecting chat identifiers, recipients, commands, or additional resources.
  • Enforce policy in code rather than relying exclusively on prompts: validate the target chat, cap reply behavior, block cross-chat disclosure, and log pending actions before execution.
  • Add prompt-injection tests containing adversarial message bodies and verify that they cannot trigger reads, replies, secret disclosure, or policy changes.
  • Clearly distinguish generated drafts from approved messages and default VIP, urgent, group, and channel communications to human review.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Missing User Warnings

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill description emphasizes inbox ownership and sending replies, but it does not prominently warn users that the agent may automatically send Teams messages as them. That missing disclosure undermines informed consent and increases the chance a user invokes the skill without realizing it can perform external actions in a corporate communication channel.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The auto-reply workflow describes continuous monitoring and automated reply sending, but it does not include a strong user-facing warning, explicit approval model, or guardrails around duration and scope. In a Teams environment, this can lead to unauthorized or mistaken communications, privacy exposure from ongoing monitoring, and reputational or business harm if the agent responds incorrectly.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

The content documents automatic refresh using a stored refresh token, which creates durable credential material that can be abused for ongoing access if the local token store is copied. Because the associated scopes allow reading chats, channel messages, presence, and sending messages, compromise could lead to data theft, impersonation, and lateral social engineering within Teams.

Content

Scanner excerpt · references/graph-api.md (reported line 24)May include surrounding context.

md
- User.Read — get own profile

Token is stored at ~/.teams-delegate/token.json. Run scripts/auth.py to authenticate.
Access tokens expire in 1h; script auto-refreshes using stored refresh token.

---

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger phrases are broad enough to activate on common Teams-related requests such as checking messages or replying on Teams, which can cause the skill to run in situations where the user did not clearly intend delegated message handling. In this skill’s context, that is risky because the capability includes reading inbox content and potentially sending messages on the user’s behalf.

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 · references/graph-api.md (reported line 17)May include surrounding context.

md
Required Microsoft Graph API scopes:
- Chat.Read — read 1:1 and group chat messages
- Chat.ReadWrite — send messages in chats
- ChannelMessage.Read.All — read channel messages
- ChannelMessage.Send — post to channels
- Presence.Read.All — check user/contact presence

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The documentation explicitly states that OAuth tokens are stored locally at a predictable path and that refresh tokens are retained for automatic renewal, but it gives no warning about protecting that file or using secure OS-backed secret storage. In a skill handling Microsoft Graph access to chats, channels, and presence, theft of that token file could enable unauthorized API access and persistent account/session compromise until revoked.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script persists the MSAL token cache to disk in the user's home directory without any warning, permission hardening, or encryption. Access tokens and refresh-token-related cache material are sensitive credentials; if another local user, malware, backups, or misconfigured file permissions expose this file, an attacker may reuse the cached session to access Microsoft Graph data under the victim's account.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

In app-only mode, the script falls back from /me to listing multiple users and prints their names, email/UPN, and IDs directly to the console before any explicit privacy notice or access justification. Even if intended for operator convenience, this exposes directory information and enables broad user discovery that may exceed least-privilege expectations for a messaging utility.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The script writes configuration data to ~/.teams-delegate/config.json, but there is no prior disclosure in the docstring or comments that running with --client-id will create or update a local config file. The later "Client ID saved." message confirms completion but does not serve as advance warning of the file write behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.