Back to skill

Security audit

Raindrop.io Bookmark Manager

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its Raindrop.io bookmark-management purpose, but it handles OAuth tokens and destructive account actions with too little protection or warning.

Install only if you are comfortable giving this skill access to your Raindrop account. Prefer environment variables or a short-lived test token, avoid passing secrets on the command line, check permissions on any ~/.openclaw/raindrop.env file, and be careful with delete/update/import commands because they can change or remove account data without an extra confirmation step.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/raindrop_manager.py:42
Finding

OAuth Credentials Persisted Without Restrictive File Permissions

Content
View full analysis
None: existing = load_dotenv(path) for key, value in values.items(): existing[key] = "" if value is None else str(value) lines = [f"{key}={existing[key]}" for key in sorted(existing.keys())] path.parent.mkdir(parents=True, exist_ok=True) path.write_text("\n".join(lines) + "\n", encoding="utf-8") ``` The function is called with sensitive OAuth credentials: ```python if not args.no_save: save_env_values( Path(creds["env_file"]), { "RAINDROP_CLIENT_ID": str(client_id), "RAINDROP_CLIENT_SECRET": str(client_secret), "RAINDROP_ACCESS_TOKEN": data.get("access_token"), "RAINDROP_REFRESH_TOKEN": data.get("refresh_token"), "RAINDROP_REDIRECT_URI": str(redirect_uri), }, ) ``` ### Technical Analysis OAuth client secrets, access tokens, and refresh tokens are written in plaintext to the configured environment file, which defaults to `~/.openclaw/raindrop.env`. The implementation creates the parent directory and writes the file but does not explicitly enforce owner-only permissions on either resource. The effective permissions therefore depend on the operating system, process umask, and permissions of any pre-existing file. On a multi-user system, a permissive configuration may allow another local account or process to read the credentials. A pre-existing file with insecure permissions remains insecure after `Path.write_text()` updates it. The storage is related to the declared OAuth functionality, but retaining the client secret and refresh token without permission hardenin ...[truncated 1345 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/raindrop_manager.py:340
Finding

OAuth Access and Refresh Tokens Exposed Through Standard Output

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/raindrop_manager.py:157
Finding

OAuth Authorization Flow Lacks State Binding and PKCE

Content
View full analysis
str: return OAUTH_AUTHORIZE_URL + "?" + urllib.parse.urlencode( { "client_id": client_id, "redirect_uri": redirect_uri, "response_type": "code", } ) ``` Authorization completion accepts a supplied code without binding it to a locally initiated flow: ```python data = oauth_token_request( { "grant_type": "authorization_code", "code": args.code, "client_id": client_id, "client_secret": client_secret, "redirect_uri": redirect_uri, } ) ``` ### Technical Analysis The generated OAuth authorization URL does not include an unpredictable `state` parameter, and the CLI does not retain or verify state when completing authorization. It also does not generate a PKCE verifier or include an S256 code challenge. The absence of state means the CLI cannot cryptographically associate an authorization response with the authorization attempt that it initiated. Because `auth-finish` accepts any supplied authorization code, a user or Agent may complete a mismatched or attacker-influenced flow. PKCE provides additional protection against authorization-code interception by binding the authorization code to a locally generated verifier. Its absence weakens defense in depth, particularly for desktop-style or local callback OAuth flows. The practical exploitability depends on the OAuth provider’s behavior and an attacker’s ability to influence the code supplied to `auth-finish`. The CLI does not automatically operate a callback listener, so this is lower risk than an exposed network callback that blindly consumes responses. ### ...[truncated 1441 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (19)

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 · SKILL.md (reported line 21)May include surrounding context.

md
- set `RAINDROP_ENV_FILE` to point at a local env file if you explicitly want file-based storage

Rules:
- never hardcode real client ids, secrets, access tokens, or refresh tokens into the skill
- never publish real credential values in `SKILL.md`, `references/`, or `scripts/`
- prefer environment variables over passing secrets on the command line, because shell history may retain CLI arguments

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- never publish real credential values in `SKILL.md`, `references/`, or `scripts/`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `raindrop_manager.py` — local Raindrop.io CLI for auth, collections, bookmarks, import, and export

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

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

md
- `GET /collection/{id}` — one collection
- `POST /collection` — create collection
- `PUT /collection/{id}` — update collection
- `DELETE /collection/{id}` — delete collection

Common collection fields:
- `title`

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

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

md
- `GET /raindrop/{id}` — fetch one bookmark
- `POST /raindrop` — create one bookmark
- `PUT /raindrop/{id}` — update one bookmark
- `DELETE /raindrop/{id}` — delete one bookmark

Common bookmark fields:
- `link`

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 · references/env-example.md (reported line 13)May include surrounding context.

md
- `RAINDROP_CLIENT_ID` -> your client id
- `RAINDROP_CLIENT_SECRET` -> your client secret
- `RAINDROP_ACCESS_TOKEN` -> your test or OAuth access token
- `RAINDROP_REFRESH_TOKEN` -> optional refresh token
- `RAINDROP_REDIRECT_URI` -> `http://127.0.0.1:8765/callback`

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

python
OAUTH_TOKEN_URL = "https://raindrop.io/oauth/access_token"
OPENCLAW_HOME = Path.home() / ".openclaw"
DEFAULT_ENV_FILE = OPENCLAW_HOME / "raindrop.env"
LEGACY_JSON_FILE = OPENCLAW_HOME / "raindrop-credentials.json"


class RaindropError(Exception):

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

python
merged["redirect_uri"] = "http://127.0.0.1:8765/callback"

    if require_access_token and not merged["access_token"]:
        raise RaindropError(f"No access token found. Set RAINDROP_ACCESS_TOKEN in {env_path}")
    return merged

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

python
merged["redirect_uri"] = "http://127.0.0.1:8765/callback"

    if require_access_token and not merged["access_token"]:
        raise RaindropError(f"No access token found. Set RAINDROP_ACCESS_TOKEN in {env_path}")
    return merged

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

python
merged["redirect_uri"] = "http://127.0.0.1:8765/callback"

    if require_access_token and not merged["access_token"]:
        raise RaindropError(f"No access token found. Set RAINDROP_ACCESS_TOKEN in {env_path}")
    return merged

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill documents capabilities that involve environment variables, local file reads/writes, and network access, but it does not declare any explicit tool scope or permission boundaries. That increases the chance an agent or user invokes the skill with broader-than-necessary access, making secret exposure or unintended filesystem/network actions more likely.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill advertises delete and update operations for collections and bookmarks without clearly warning that some actions are destructive or irreversible. In an agent-driven workflow, that can lead to accidental deletion or mass modification of user data if commands are run on the wrong IDs or without confirmation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This markdown file describes mutation and deletion endpoints such as creating, updating, and deleting collections and bookmarks, including DELETE /collection/{id} and DELETE /raindrop/{id}. Under the markdown-specific SQP-2 criteria, it omits any warning that these operations can modify or permanently remove user data.

Content

No source excerpt is available for this finding.

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

md
except Exception:
    certifi = None

API_BASE = "https://api.raindrop.io/rest/v1"
OAUTH_AUTHORIZE_URL = "https://raindrop.io/oauth/authorize"
OAUTH_TOKEN_URL = "https://raindrop.io/oauth/access_token"
OPENCLAW_HOME = Path.home() / ".openclaw"

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

python
except Exception:
    certifi = None

API_BASE = "https://api.raindrop.io/rest/v1"
OAUTH_AUTHORIZE_URL = "https://raindrop.io/oauth/authorize"
OAUTH_TOKEN_URL = "https://raindrop.io/oauth/access_token"
OPENCLAW_HOME = Path.home() / ".openclaw"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The OAuth completion flow persists client secret, access token, and refresh token to a local env file automatically unless --no-save is used. Storing long-lived secrets on disk without explicit consent, permission hardening, or warnings increases the chance of credential exposure through local file disclosure, backups, or accidental sharing.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The refresh flow overwrites locally stored OAuth secrets by default, again persisting sensitive credentials in plaintext on disk. This can expose valid tokens to any process or user able to read the file and may silently replace previously managed credentials without the user's awareness.

Content

No source excerpt is available for this finding.

Tainted flow: 'rows' from pathlib.Path.read_text (line 179, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/raindrop_manager.py (reported line 508)May include surrounding context.

python
out_path = Path(args.output)
    out_path.parent.mkdir(parents=True, exist_ok=True)
    if args.format == "json":
        out_path.write_text(json.dumps(rows, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
    elif args.format == "txt":
        links = [str(row.get("link") or "") for row in rows if row.get("link")]
        out_path.write_text(("\n".join(links) + "\n") if links else "", encoding="utf-8")

Tainted flow: 'links' from pathlib.Path.read_text (line 510, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/raindrop_manager.py (reported line 511)May include surrounding context.

python
out_path.write_text(json.dumps(rows, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
    elif args.format == "txt":
        links = [str(row.get("link") or "") for row in rows if row.get("link")]
        out_path.write_text(("\n".join(links) + "\n") if links else "", encoding="utf-8")
    else:
        flat = [flatten_dict(row) for row in rows]
        keys = sorted({k for row in flat for k in row.keys()})