Back to skill

Security audit

Remember The Milk

Security checks for vulnerabilities and agentic risk

Overview

This is a straightforward Remember The Milk task-management skill with disclosed credentials, network access, delete capability, and token persistence, though users should treat its saved token as sensitive.

Install only if you intend to let this skill manage a Remember The Milk account, including deleting tasks and notes. Use a dedicated RTM API key if possible, restrict ~/.rtm_token to owner-only permissions after auth, and remove the token when persistent access is no longer needed.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/rtm.py:166
Finding

Authentication Token Stored Without Enforced Restrictive Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/rtm.py, lines 166–169
Vulnerability Type: Plaintext sensitive credential stored with umask-derived permissions
Risk Level: Medium

Vulnerable Code

python
rsp = api_call("rtm.auth.getToken", shared_secret, api_key, frob=frob)
token = rsp["auth"]["token"]
username = rsp["auth"]["user"]["username"]
TOKEN_PATH.write_text(token)

Technical Analysis

The authentication token is written in plaintext to ~/.rtm_token using Path.write_text(). This operation does not explicitly enforce owner-only permissions; the resulting mode depends on the process umask. In an environment with a permissive umask, other local users may be able to read the token.

The token is especially sensitive because the authorization flow requests RTM's delete permission, its highest permission tier. Although SKILL.md warns users to apply chmod 600, documentation does not provide the same protection as enforcing secure permissions in code.

Attack Path

  1. A user runs scripts/rtm.py auth.
  2. The Skill requests an RTM token carrying delete permission.
  3. The token is saved to ~/.rtm_token with permissions determined by the current umask.
  4. If those permissions allow access by another local account, an attacker reads the token.
  5. The attacker uses the stolen token with the RTM API and associated API credentials to access or alter the linked account's data.

This path requires local read access to the token file and permissions that expose it.

Impact Assessment

Disclosure of the token can compromise the linked Remember The Milk account within the granted API scope. An attacker able to use it may read task and note data, create or modify tasks, complete tasks, and permanently delete tasks or notes. The issue does not directly grant operating-system privilege escalation, but it exposes a credential with broad application-level privileges.

Remediation
View remediation

Remediation Suggestions

  • Create the token file atomically with mode 0600, rather than relying on the process umask.
  • Open the destination with restrictive flags such as O_CREAT | O_WRONLY | O_TRUNC and an explicit 0o600 mode.
  • Prevent symlink-following where the platform supports O_NOFOLLOW, and verify that the destination is a regular file owned by the current user.
  • If replacement is required, write to a securely created file in the same directory, set mode 0600, flush it, and atomically rename it.
  • Explicitly apply chmod(0o600) after creation as defense in depth.
  • On token loading, reject files that are not owned by the current user or are readable or writable by group or others.
  • Consider using an operating-system credential store instead of a plaintext file when available.
  • Continue documenting that the token grants full RTM access, but do not rely on users to secure it manually.
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
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (7)

Tainted flow: 'req' from os.environ.get (line 56, credential/environment) → urllib.request.urlopen (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/rtm.py (reported line 58)May include surrounding context.

python
body = urllib.parse.urlencode(params).encode("utf-8")
                req = urllib.request.Request(REST_URL, data=body, method="POST")
                req.add_header("Content-Type", "application/x-www-form-urlencoded")
                with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
                    data = json.loads(resp.read().decode())
            else:
                url = REST_URL + "?" + urllib.parse.urlencode(params)

Tainted flow: 'url' from os.environ.get (line 61, credential/environment) → urllib.request.urlopen (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/rtm.py (reported line 62)May include surrounding context.

python
data = json.loads(resp.read().decode())
            else:
                url = REST_URL + "?" + urllib.parse.urlencode(params)
                with urllib.request.urlopen(url, timeout=TIMEOUT) as resp:
                    data = json.loads(resp.read().decode())

            rsp = data.get("rsp", data)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill exposes sensitive capabilities (environment variables, reading/writing a token file, and outbound network access) but does not declare an explicit tool scope such as permissions or allowed-tools. That weakens policy enforcement and reviewability, making it easier for the skill to be invoked with broader runtime powers than users or platform controls expect.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger description is broad enough to match many generic requests about tasks, todos, and reminders, which can cause over-invocation of a skill with write/delete capabilities and account credentials. In context, this increases the chance the skill activates for ambiguous everyday language and performs operations against a live RTM account when a narrower or read-only tool would have been more appropriate.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
## Security

- **Env vars:** `RTM_API_KEY` and `RTM_SHARED_SECRET` are required at runtime. Configure via OpenClaw skill env, not hardcoded.
- **Auth token:** Stored as plain text at `~/.rtm_token` after interactive auth. This file grants full access to the linked RTM account. Protect it accordingly — restrict file permissions (`chmod 600`) or remove after use if not needed persistently.
- **Network:** All API calls go to `api.rememberthemilk.com` and `www.rememberthemilk.com` only. No other outbound connections.
- **Permissions:** The auth flow requests `delete` permission (RTM's highest tier) to support task deletion. Use a dedicated API key with minimum needed scope if preferred.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/rtm.py (reported line 20)May include surrounding context.

python
import urllib.request
from pathlib import Path

REST_URL = "https://api.rememberthemilk.com/services/rest/"
AUTH_URL = "https://www.rememberthemilk.com/services/auth/"
TOKEN_PATH = Path.home() / ".rtm_token"
TIMEOUT = 15

Excessive Permissions

Low
Category
Privilege Escalation
Confidence
81% confidence
Finding

The documented auth flow requests RTM's highest 'delete' permission, granting full task deletion capability even for routine operations that may only need read or write access. If the skill is mis-invoked, compromised, or used in error, the resulting token provides unnecessarily broad authority over the user's account.

Content

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

md
- **Env vars:** `RTM_API_KEY` and `RTM_SHARED_SECRET` are required at runtime. Configure via OpenClaw skill env, not hardcoded.
- **Auth token:** Stored as plain text at `~/.rtm_token` after interactive auth. This file grants full access to the linked RTM account. Protect it accordingly — restrict file permissions (`chmod 600`) or remove after use if not needed persistently.
- **Network:** All API calls go to `api.rememberthemilk.com` and `www.rememberthemilk.com` only. No other outbound connections.
- **Permissions:** The auth flow requests `delete` permission (RTM's highest tier) to support task deletion. Use a dedicated API key with minimum needed scope if preferred.

## Commands

Static analysis

No suspicious patterns detected.