Back to skill

Security audit

Outlook Entra

Security checks for vulnerabilities and agentic risk

Overview

This Outlook skill mostly matches its stated purpose, but it stores and refreshes Microsoft account tokens with weak safeguards and adds an hourly background job, so it needs careful review before installation.

Install only if you are comfortable granting this skill access to Outlook data and maintaining local Microsoft tokens. Before use, remove or avoid the cron job unless continuous refresh is truly needed, do not store a client secret unless required, configure strong token protection, restrict token file permissions, pin dependencies, and avoid custom OAuth or Graph URLs unless you can verify they are legitimate Microsoft endpoints.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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 (6)

T06 · System Persistence

Error
Location
README.md:113
Finding

Unnecessary Persistent Hourly Token-Refresh Job

Content
View full analysis
/dev/null | grep -v outlook_refresh; echo "55 * * * * ${SKILL_DIR}/.venv/bin/python ${SKILL_DIR}/scripts/outlook_refresh.py >> ~/.openclaw/outlook_refresh.log 2>&1" ) | crontab - ``` **Vérification :** ```bash crontab -l | grep outlook_refresh ``` ``` ### Technical Analysis The installation instructions add an hourly cron entry that survives the current Skill invocation and user session. This persistence is not required for the declared Outlook-reading functionality because `ensure_valid_token()` already refreshes an expired token on demand whenever a Graph operation is performed. The scheduled command executes an interpreter and script from a mutable Skill directory. If either the virtual environment or `outlook_refresh.py` is subsequently replaced, the modified code will execute automatically every hour under the installing user's account. The installation pipeline also removes every existing crontab line containing the text `outlook_refresh`: ```bash grep -v outlook_refresh ``` This is not scoped to an entry uniquely owned by this Skill and can unintentionally delete unrelated scheduled jobs. ### Attack Path 1. A user follows the documented installation instructions. 2. An hourly cron entry is added to the user's crontab. 3. The Skill directory, refresh script, or virtual-environment interpreter is later modified through a compromised update, another local process, or writable directory permissions. 4. Cron executes the modified component every hour without furthe ...[truncated 662 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/outlook_token.py:62
Finding

OAuth Tokens Stored Unencrypted and Without Enforced File Permissions

Content
View full analysis
dict: with open(path) as f: content = f.read().strip() fernet = _fernet() if fernet: content = fernet.decrypt(content.encode()).decode() return json.loads(content) def _write_raw(path: str, data: dict): content = json.dumps(data, indent=2) fernet = _fernet() if fernet: content = fernet.encrypt(content.encode()).decode() Path(path).parent.mkdir(parents=True, exist_ok=True) with open(path, "w") as f: f.write(content) ``` Encryption is disabled when no key is configured: ```python def _fernet() -> Optional[Fernet]: if not TOKEN_FILE_KEY or not HAS_CRYPTO: return None ``` ### Technical Analysis `TOKEN_FILE_KEY` is optional and empty by default. In that configuration, `_fernet()` returns `None`, and the complete OAuth response—including the access token and refresh token—is serialized as plaintext JSON. The file is opened using the process's default creation mode. The implementation does not explicitly create it with mode `0600`, validate the permissions of an existing token file, prevent symbolic-link replacement, or perform an atomic write. Effective permissions therefore depend on the user's umask and pre-existing filesystem state. The documentation describes the storage as “AES-GCM,” but the implementation uses the `cryptography.fernet.Fernet` format. Fernet does provide authenticated encryption, but the documentation mismatch can lead users to make incorrect security assumptions. ### Attack Path 1. The user authenticates without configuring `TOKEN_FILE_KEY`. 2. `save_token()` writes the access and refresh tokens to the configured token path as plaintext. 3. A local user, backup process, indexing tool, compromised application, or other process gains read ...[truncated 844 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/outlook_token.py:34
Finding

Unvalidated Configurable Endpoints Can Receive OAuth Credentials

Content
View full analysis
dict: token = token or ot.ensure_valid_token() url = f"{GRAPH_BASE}{path}" headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"} resp = requests.get(url, headers=headers, params=params, timeout=TIMEOUT) _handle_error(resp) return resp.json() ``` The authentication flow similarly posts the device code to `ot.TOKEN_URL`: ```python resp = req.post(ot.TOKEN_URL, data=payload_token, timeout=ot.REQUEST_TIMEOUT) ``` ### Technical Analysis The code treats `OAUTH_DEVICE_CODE_URL`, `OAUTH_TOKEN_URL`, and `MS_GRAPH_BASE_URL` as trusted without validating their scheme, hostname, port, path, or relationship to the configured tenant. A malicious or a ...[truncated 1542 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/outlook_graph.py:159
Finding

Remote Attachment Names Can Escape the Download Directory

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
README.md:20
Finding

Runtime Dependencies Are Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/outlook_token.py:117
Finding

Token Revocation Command Only Deletes the Local Token Reliably

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (51)

Tainted flow: 'TOKEN_URL' from os.getenv (line 39, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/outlook_token.py (reported line 109)May include surrounding context.

python
"client_id":     CLIENT_ID,
        "refresh_token": token["refresh_token"],
    }
    resp = req.post(TOKEN_URL, data=payload, timeout=REQUEST_TIMEOUT)
    resp.raise_for_status()
    new_token = resp.json()
    new_token["expires_at"] = time.time() + new_token.get("expires_in", 3600)

Tainted flow: 'TOKEN_URL' from os.getenv (line 39, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/outlook_token.py (reported line 146)May include surrounding context.

python
"client_id":     CLIENT_ID,
        "refresh_token": token["refresh_token"],
    }
    resp = req.post(TOKEN_URL, data=payload, timeout=REQUEST_TIMEOUT)
    resp.raise_for_status()
    new_token = resp.json()
    new_token["expires_at"] = time.time() + new_token.get("expires_in", 3600)

Tainted flow: 'TOKEN_URL' from os.getenv (line 39, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The revoke_token function posts token material to TOKEN_URL, but TOKEN_URL is fully overrideable from the environment and the code performs no validation of the destination. In a hostile runtime, an attacker who can set environment variables could redirect refresh-token-bearing requests to an arbitrary server, causing credential leakage.

Content

Scanner excerpt · scripts/outlook_token.py (reported line 127)May include surrounding context.

python
"token":       token.get("refresh_token", ""),
    }
    try:
        req.post(TOKEN_URL, data=payload, timeout=REQUEST_TIMEOUT)
    except Exception:
        pass
    Path(TOKEN_FILE).unlink(missing_ok=True)

Credential Access

High
Category
Privilege Escalation
Confidence
92% confidence
Finding

The README explicitly instructs users to create a local .env file containing cloud application credentials, which creates a credential-at-rest target on disk. In the context of an email-access skill, those credentials can enable continued access to Outlook/Graph resources if the file is exposed through repo commits, filesystem compromise, backups, or overly broad permissions.

Content

Scanner excerpt · README.md (reported line 32)May include surrounding context.

uv pip install html2text requests cryptography

3. Créer et remplir la config

cp .env.example .env

Éditer .env : AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET

text

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

The specific instruction to populate .env with AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET normalizes storing a confidential secret alongside code for a read-only mail access skill. The most sensitive element is the client secret, whose compromise may allow unauthorized token acquisition depending on app configuration, making this more dangerous than mere tenant/client identifiers alone.

Content

Scanner excerpt · README.md (reported line 33)May include surrounding context.

3. Créer et remplir la config

cp .env.example .env

Éditer .env : AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET

text

## Première authentification

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Confidence
75% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · README.md (reported line 120)May include surrounding context.

de chiffrement (optionnel) | | SCOPES_DEVICE_CODE | Scopes OAuth demandés | | REQUEST_TIMEOUT | Timeout requêtes HTTP |

Cron — Refresh automatique du token

Le token expire après ~1h. Le script outlook_refresh.py le rafraîchit automatiquement.

Installation (refresh toutes les heures à HH:55) :

bash
SKILL_DIR="/home/fred-ghilini/.openclaw/workspace/skills/outlook-entra"
( crontab -l 2>/dev/null | grep -v outlook_refresh; echo "55 * * * * ${SKILL_DIR}/.venv/bin/python ${SKILL_DIR}/scripts/outlook_refresh.py >> ~/.openclaw/outlook_refresh.log 2>&1" ) | crontab -

Vérification :

bash
crontab -l | grep outlook_refresh

Si le refresh_token expire (plusieurs mois d'inactivité) → relancer le flow complet : .venv/bin/python scripts/outlook_auth.py

Chiffrement des tokens

Les tokens sont chiffrés AES-GCM si TOKEN_FILE_KEY est défini dans .env :

bash
openssl rand -base64 32

Ressources

  • [OAuth 2.0 Device Code Flow — Micros

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Confidence
75% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

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

de chiffrement (optionnel) | | SCOPES_DEVICE_CODE | Scopes OAuth demandés | | REQUEST_TIMEOUT | Timeout requêtes HTTP |

Cron — Refresh automatique du token

Le token expire après ~1h. Le script outlook_refresh.py le rafraîchit automatiquement.

Installation (refresh toutes les heures à HH:55) :

bash
SKILL_DIR="/home/fred-ghilini/.openclaw/workspace/skills/outlook-entra"
( crontab -l 2>/dev/null | grep -v outlook_refresh; echo "55 * * * * ${SKILL_DIR}/.venv/bin/python ${SKILL_DIR}/scripts/outlook_refresh.py >> ~/.openclaw/outlook_refresh.log 2>&1" ) | crontab -

Vérification :

bash
crontab -l | grep outlook_refresh

Si le refresh_token expire (plusieurs mois d'inactivité) → relancer le flow complet : .venv/bin/python scripts/outlook_auth.py

Chiffrement des tokens

Les tokens sont chiffrés AES-GCM si TOKEN_FILE_KEY est défini dans .env :

bash
openssl rand -base64 32

Ressources

  • [OAuth 2.0 Device Code Flow — Micros

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the skill includes or tests with Mail.Send despite being declared read-only, that is a significant privilege escalation from passive access to active mail operations. A misleading read-only description could cause reviewers to approve a skill that can impersonate the user for outbound messaging or abuse trusted email channels.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
88% confidence
Finding

If the skill includes or tests with Mail.Send despite being declared read-only, that is a significant privilege escalation from passive access to active mail operations. A misleading read-only description could cause reviewers to approve a skill that can impersonate the user for outbound messaging or abuse trusted email channels.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

If the skill includes or tests with Mail.Send despite being declared read-only, that is a significant privilege escalation from passive access to active mail operations. A misleading read-only description could cause reviewers to approve a skill that can impersonate the user for outbound messaging or abuse trusted email channels.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

If the skill includes or tests with Mail.Send despite being declared read-only, that is a significant privilege escalation from passive access to active mail operations. A misleading read-only description could cause reviewers to approve a skill that can impersonate the user for outbound messaging or abuse trusted email channels.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
Confidence
93% confidence
Finding

Documented profile access suggests the skill may require permissions outside the declared read-only mail/calendar/contact set. This undermines least-privilege expectations and may expose identity attributes or enable broader token grants than a reviewer intended to authorize.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The skill instructs users to place OAuth client credentials in a local .env file, which is sensitive material that can be read by other local processes, mishandled, or accidentally exposed. Because the skill also performs token acquisition and refresh, compromise of these values can enable unauthorized access or facilitate persistence against the user's Microsoft tenant.

Content

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

uv pip install html2text requests cryptography

Copier et éditer la config

cp .env.example .env

⚠️ Remplir client_id, client_secret, tenant_id dans .env

text

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The explicit instruction to populate .env with client_id, client_secret, and tenant_id encourages local storage of reusable credentials. In the context of a skill that automates token refresh and writes tokens to disk, this increases the chance of credential theft and long-lived unauthorized access if the host is compromised.

Content

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

Copier et éditer la config

cp .env.example .env

⚠️ Remplir client_id, client_secret, tenant_id dans .env

text

## Authentification (Flow)

Credential Access

High
Category
Privilege Escalation
Confidence
93% confidence
Finding

The skill explicitly documents access-token and refresh-token handling, including local storage and automatic reuse. Stored tokens are effectively bearer credentials; if exfiltrated, an attacker can access the user's Microsoft data and maintain access through refresh flows until revocation or expiry.

Content

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

md
## Notes

- Le **device code flow** (RFC 8628) : l'utilisateur authentifie via `https://microsoft.com/devicelogin`. Une seule fois.
- Les **refresh tokens** sont automatiquement utilisés quand l'access token expire.
- Si `TOKEN_FILE_KEY` est défini, les tokens sont chiffrés AES-GCM avant stockage.
- Les erreurs 401 du Graph API déclenchent un refresh automatique.

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/outlook_auth.py (reported line 44)May include surrounding context.

python
import html2text
import requests

# Load .env
for _env in [Path(__file__).parent.parent / ".env", Path(__file__).parent / ".env"]:
    if _env.exists():
        with open(_env) as f:

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/outlook_auth.py (reported line 67)May include surrounding context.

python
import html2text
import requests

# Load .env
for _env in [Path(__file__).parent.parent / ".env", Path(__file__).parent / ".env"]:
    if _env.exists():
        with open(_env) as f:

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/outlook_graph.py (reported line 28)May include surrounding context.

python
import html2text
import requests

# Load .env
for _env in [Path(__file__).parent.parent / ".env", Path(__file__).parent / ".env"]:
    if _env.exists():
        with open(_env) as f:

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The module documentation explicitly states "Lecture, écriture, refresh des tokens OAuth Microsoft," advertising token writing and refresh behavior. This conflicts with the skill manifest's stated read-only Outlook/Graph scope, because the code is not just reading data from Graph but managing and persisting OAuth credentials with write-side effects.

Content

No source excerpt is available for this finding.

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/outlook_auth.py (reported line 45)May include surrounding context.

python
# ── Config loader ───────────────────────────────────────────────────────────────

def load_env():
    env_path = Path(__file__).parent.parent / ".env"
    if env_path.exists():
        with open(env_path) as f:
            for line in f:

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/outlook_graph.py (reported line 29)May include surrounding context.

python
# ── Config loader ───────────────────────────────────────────────────────────────

def load_env():
    env_path = Path(__file__).parent.parent / ".env"
    if env_path.exists():
        with open(env_path) as f:
            for line in f:

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/outlook_token.py (reported line 22)May include surrounding context.

python
# ── Config loader ───────────────────────────────────────────────────────────────

def load_env():
    env_path = Path(__file__).parent.parent / ".env"
    if env_path.exists():
        with open(env_path) as f:
            for line in f:

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Requiring a client secret for a device code flow is atypical for a public-client user-auth pattern and encourages users to provision and store an unnecessary confidential credential in a local .env file. That increases credential exposure risk if the host, repo, logs, backups, or environment files are leaked, without clear justification from the stated read-only use case.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% confidence
Finding

The cron command establishes recurring token refresh, which is a form of session persistence because it helps maintain long-lived authenticated access without repeated user interaction. In a skill that accesses email, this persistence extends the window during which a compromised host or token store could be abused.

Content

Scanner excerpt · README.md (reported line 120)May include surrounding context.

bash
SKILL_DIR="/home/fred-ghilini/.openclaw/workspace/skills/outlook-entra"
( crontab -l 2>/dev/null | grep -v outlook_refresh; echo "55 * * * * ${SKILL_DIR}/.venv/bin/python ${SKILL_DIR}/scripts/outlook_refresh.py >> ~/.openclaw/outlook_refresh.log 2>&1" ) | crontab -

Vérification :

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% 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 · README.md (reported line 126)May include surrounding context.

Vérification :

bash
crontab -l | grep outlook_refresh

Si le refresh_token expire (plusieurs mois d'inactivité) → relancer le flow complet : .venv/bin/python scripts/outlook_auth.py

Static analysis

No suspicious patterns detected.