Back to skill

Security audit

OpenClaw Offer Radar

Security checks for vulnerabilities and agentic risk

Overview

The skill broadly matches its recruiting-reminder purpose, but it includes under-disclosed abilities to read sensitive mail-derived data and change or delete Apple Reminders.

Review before installing. Use only with a dedicated Apple Reminders list, run the scan first without --sync-reminders, avoid clear-list or sync-plan --clear, and do not add heartbeat automation until you have verified the output. Treat the local state file as sensitive because it may contain recruiting email metadata and private links.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/recruiting_sync.py:1040
Finding
Sensitive Gmail and recruiting metadata stored in a plaintext state file<![CDATA[ ## Vulnerability Details **File Location**: `scripts/recruiting_sync.py:1040-1048` and `scripts/recruiting_sync.py:1455-1458` **Vulnerability Type**: Plaintext storage and excessive retention of sensitive information **Risk Level**: Medium ### Vulnerable Code ```python def build_source_payload(previous: dict[str, Any] | None, candidate: EventObservation) -> dict[str, Any]: previous_source = previous.get("source", {}) if previous else {} subjects = previous_source.get("subjects", []) thread_ids = previous_source.get("threadIds", []) return { "threadIds": merge_unique(thread_ids, candidate.source_ids), "subject": candidate.source_subjects[-1], "subjects": merge_unique(subjects, candidate.source_subjects), "sender": candidate.source_sender or previous_source.get("sender", ""), "lastSeenAt": candidate.received_at.strftime("%Y-%m-%d %H:%M"), } ``` ```python def write_state(state: dict[str, Any], output: Path) -> None: output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps(state, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") ``` ### Technical Analysis The synchronization state retains Gmail thread identifiers, original email subjects, sender information, event notes, roles, timestamps, and extracted links. The complete state is serialized as unencrypted JSON. The code does not explicitly create the file with restrictive permissions, verify the permissions of an existing file, or atomically replace it with a securely created temporary file. Consequently, actual access control depends on the process umask and permissions inherited by the configured output directory. This retention also exceeds the minimum data needed for reminder synchronization. Stable hashes or reduced event identifiers could support deduplication without retaining raw sender addresses, complete subjects, and Gmail thread identifiers. It conflicts with the Skill documentation's stated ...[truncated 1569 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Minimize persisted data: - Replace Gmail thread IDs with keyed hashes where direct identifiers are unnecessary. - Avoid storing raw sender addresses and complete subject histories. - Persist only fields required for deduplication and reminder reconciliation. 2. Create the state file with mode `0600` and its directory with mode `0700`. 3. Check existing file permissions before reading or updating the state and reject insecure configurations. 4. Write updates atomically through a securely created temporary file in the same directory, set its mode to `0600`, flush it, and then replace the destination. 5. Do not follow symbolic links when creating or replacing the state file. 6. Add a documented retention policy and prune source metadata once it is no longer required. 7. Redact query parameters or tokens from persisted links where they are not required. 8. Clearly disclose which email metadata is retained, where it is stored, and how users can delete it. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/apple_reminders_bridge.py:266
Finding
Bulk reminder deletion is available without confirmation or ownership checks<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apple_reminders_bridge.py:266-282`, `scripts/apple_reminders_bridge.py:311-321`, and `scripts/apple_reminders_bridge.py:389-395` **Vulnerability Type**: Unguarded destructive operation **Risk Level**: Medium ### Vulnerable Code ```python def clear_list(args: argparse.Namespace) -> int: ensure_list(args.list, args.account) escaped_list = escape(args.list) script = [ 'tell application "Reminders"', f'tell list "{escaped_list}"', "set itemCount to count every reminder", "repeat while (count every reminder) > 0", "delete reminder 1", "end repeat", "return itemCount", "end tell", "end tell", ] proc = run_applescript(script) output = (proc.stdout or proc.stderr).strip() if output: print(output) return proc.returncode ``` ```python def sync_plan(args: argparse.Namespace) -> int: with open(args.file, "r", encoding="utf-8") as fh: plan = json.load(fh) list_name = args.list or plan.get("list", "OpenClaw") account_name = args.account or plan.get("account", "iCloud") if args.clear: clear_code = clear_list(argparse.Namespace(list=list_name, account=account_name)) if clear_code != 0: return clear_code ``` ```python clear = sub.add_parser("clear-list") clear.add_argument("--list", default="OpenClaw") clear.add_argument("--account", default="iCloud") clear.set_defaults(func=clear_list) sync = sub.add_parser("sync-plan") sync.add_argument("--file", required=True) sync.add_argument("--list") sync.add_argument("--account") sync.add_argument("--clear", action="store_true") ``` ### Technical Analysis The bridge exposes two paths that can delete every reminder in a selected list: - The direct `clear-list` subcommand - The `sync-plan --clear` option Deletion occurs immediately and repeatedly until the list is empty. There is no interactive confirmatio ...[truncated 2086 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `clear-list` and `sync-plan --clear` if they are not required for normal operation. 2. If bulk clearing must remain: - Require an explicit confirmation token containing the exact account and list name. - Display a deletion plan and require a separate confirmation step. - Support a mandatory dry-run before destructive execution. 3. Restrict bulk operations to a dedicated Skill-owned list and reject arbitrary list names by default. 4. Tag reminders created by the Skill with a stable ownership identifier and delete only reminders carrying that identifier. 5. Do not infer authorization to delete all list contents merely because the Skill can access the list. 6. Add a maximum deletion count and abort when the threshold is exceeded unless a separate override is supplied. 7. Create an export or backup before bulk deletion where supported. 8. Log the selected account, list, affected reminder identifiers, and deletion count without exposing reminder contents. 9. Require explicit user approval before an AI agent or scheduled automation can invoke any destructive subcommand. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared behavior is narrowly framed as converting recruiting emails into reminders, but the implementation reportedly can delete reminders, clear whole lists, enumerate all reminders, and bulk-sync arbitrary JSON while lacking the advertised Gmail parsing safeguards. This mismatch is dangerous because users may authorize the skill under false assumptions, enabling destructive or overbroad actions unrelated to the stated purpose.

Missing User Warnings

High
Confidence
99% confidence
Finding
The bulk clear loop deletes all reminders in the target list without any warning or interactive confirmation. Given the skill context, this is especially risky because the feature is not essential to the advertised behavior and could wipe a user's reminder list in a single invocation.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The badge text '中文优先' explicitly signals a language preference, and the README later reinforces that the skill is prepared for Chinese users. Under the policy, forcing or presuming a specific language without user opt-in is a natural-language policy concern.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes a local Python script and explicitly depends on Mail and Reminders access, yet it declares no tool or permission scope. That creates hidden capability risk: a caller cannot tell up front that the skill may execute shell commands, read email content, and write to system reminders, which weakens review and consent boundaries.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill directs scanning Gmail candidates and reading message bodies through Apple Mail without an explicit privacy warning or consent notice. Because email bodies can contain sensitive personal, employment, and account information, silent collection or processing increases privacy and data-handling risk.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The instruction '标题必须是中文' forces a specific language for output. This is a natural-language locale policy issue because the skill does not provide opt-in, fallback behavior, or a user choice for language.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The manifest describes reading Gmail recruiting emails and synchronizing results to Apple Reminders, but it does not clearly warn the user that the skill will access email contents and create or modify reminder data. Because both sources can contain sensitive personal and employment information, lack of explicit notice and consent increases the risk of privacy violations and unintended data modification.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The short description and default prompt specify converting content to Chinese and syncing reminders in a Chinese format, but there is no indication that the user can choose another language or that the Chinese-only behavior is a documented regional requirement. This creates a natural-language locale policy concern because the skill appears to impose a language preference by default.

Vague Triggers

Medium
Confidence
87% confidence
Finding
The default prompt is broad enough to encourage scanning a user's recent Gmail messages without clear bounds such as sender scope, label scope, time range enforcement, or explicit confirmation before acting. In a skill that reads email and creates reminders, this can lead to over-collection of sensitive mailbox content and unintended processing of unrelated personal messages.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = ["osascript"]
    for line in lines:
        cmd.extend(["-e", line])
    return subprocess.run(
        cmd,
        capture_output=True,
        text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script exposes update, delete, list, and bulk sync/clear capabilities that go beyond the stated purpose of converting recruitment emails into reminder entries. In an agent-skill context, this expands the blast radius: a compromised or over-permissive caller can manipulate arbitrary reminders rather than only creating the expected recruitment-related items.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The delete operation removes a reminder immediately based only on an ID and list name, with no user confirmation, no ownership check, and no verification that the reminder was created by this skill. In an agent environment, this can silently erase user data if the wrong ID is supplied or if the skill is abused.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
`clear_list` deletes every reminder in the specified list, and `sync_plan` can trigger it with `--clear`. For a skill whose purpose is merely to turn recruiting emails into reminders, a bulk-wipe primitive is unnecessarily dangerous and can cause major data loss if invoked on the wrong list or by an unintended caller.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_json(cmd: list[str]) -> Any:
    proc = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_text(cmd: list[str], timeout: int | None = None) -> str:
    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_json_shell(command: str) -> Any:
    proc = subprocess.run(
        ["zsh", "-lc", command],
        capture_output=True,
        text=True,
Confidence
98% confidence
Finding
This code executes a constructed command string through `zsh -lc`, which reintroduces shell parsing and command injection risk. Although `account`, `query`, and numeric parameters are partly shell-quoted, the pattern remains dangerous because any future unquoted interpolation or unexpected input reaching `command` can lead to arbitrary command execution in the user's environment.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_bridge(cmd: list[str]) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        [sys.executable, str(REMINDERS_SCRIPT), *cmd],
        capture_output=True,
        text=True,
Confidence
97% confidence
Finding
This subprocess call executes a Python script whose path is taken from `OFFER_RADAR_REMINDERS_SCRIPT`. If an attacker can influence the environment or skill configuration, they can redirect execution to arbitrary local code, leading to code execution with the user's privileges whenever reminder sync runs.

Tainted flow: 'REMINDERS_SCRIPT' from os.environ.get (line 23, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def run_bridge(cmd: list[str]) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        [sys.executable, str(REMINDERS_SCRIPT), *cmd],
        capture_output=True,
        text=True,
Confidence
99% confidence
Finding
`REMINDERS_SCRIPT` is derived from an environment variable and then executed as Python code. Environment variables are a weak trust boundary in automation contexts; if a parent process, wrapper, or malicious local configuration sets this variable, the skill will run attacker-controlled code during reminder operations.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
When `--sync-reminders` is used, the script can create, update, and delete Apple Reminders entries based on parsed email content and state reconciliation logic, with no interactive confirmation. In this skill context, that is risky because parsing mistakes, maliciously crafted emails, or state mismatches can silently alter the user's personal reminders and remove items they rely on.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The script persists recruiting-derived state locally, including metadata such as subjects, senders, thread IDs, timing, links, and review items. In the context of job-search email processing, this creates a privacy and confidentiality risk because sensitive application activity and interview details are stored on disk without any visible consent, minimization, or protection controls in this code.

Missing User Warnings

Low
Confidence
93% confidence
Finding
This markdown file documents a behavior that affects user data and system state: writing reminders and creating an iCloud-backed list. Although the steps mention the behavior, they do not provide a clear cautionary warning about modifying the user's reminders data before the sync command is run.

Missing User Warnings

Low
Confidence
85% confidence
Finding
The sync flow writes data into Apple Reminders but does not clearly warn the user that system reminder lists will be modified. Even if the write is expected, missing disclosure can cause unintended persistence, clutter, or overwriting/duplication in personal task lists.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
This file contains user-facing natural-language text in both English and Simplified Chinese, including the main subtitle and feature labels. Because the skill does not indicate that Chinese is optional or region-specific, it may violate a language/locale policy requiring explicit user choice or documented justification.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The script generates user-visible reminder titles, notes, labels, and filtering behavior in Chinese, such as 面试, 截止时间, and 中文标题 policy values. Because the file provides no user opt-in or locale selection, it effectively forces a specific language for outputs, which is a natural-language policy concern.

Static analysis

No suspicious patterns detected.