Back to skill

Security audit

OfferCatcher

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to match its recruiting-email reminder purpose, but it uses risky installation patterns and broad email access that users should review before installing.

Install only through ClawHub or a pinned, verified release, not the documented curl-to-bash path. Before use, configure a dedicated recruiting mailbox/account, keep days and max_results small, understand whether your OpenClaw LLM is local or remote, and avoid invoking the helper clear-list or custom --output options unless you fully intend those effects.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T03 · Remote Payload Retrieval and Execution

Error
Location
README.md:57
Finding
Mutable Remote Installation Script Is Executed Directly by a Shell<![CDATA[ ## Vulnerability Details **File Location**: `README.md:57-60`, `README.md:89-92`, `README_CN.md:57-60`, `README_CN.md:89-92`, and `install.sh:1-3` **Vulnerability Type**: Remote payload retrieval and immediate execution **Risk Level**: Critical ### Vulnerable Code ```bash # Install curl -sSL https://raw.githubusercontent.com/NissonCX/offercatcher/main/install.sh | bash ``` The same installation pattern is repeated in the English and Chinese documentation. The installer itself advertises the same command: ```bash #!/bin/bash # OfferCatcher one-click installation script # Usage: curl -sSL https://raw.githubusercontent.com/NissonCX/offercatcher/main/install.sh | bash ``` ### Technical Analysis The installation instructions download a shell script from the mutable `main` branch of a remote GitHub repository and pass the response directly to `bash`. The downloaded content is not: - Pinned to an immutable commit or release. - Authenticated using a release signature. - Checked against a published cryptographic hash. - Saved locally for inspection before execution. - Restricted to the behavior of the version reviewed during this audit. The bundled version of `install.sh` performs ordinary repository installation and configuration. However, that does not make the documented command safe: the effective script executed by a future user is whatever the remote `main` branch returns at installation time. This behavior is not necessary for the Skill's email-to-reminder functionality. A package-manager installation, versioned archive, or locally reviewed installer would provide the same functionality without directly executing mutable network content. ### Attack Path 1. An attacker compromises the GitHub account, repository, branch, maintainer credentials, or another component capable of modifying the remote installer. 2. The attacker replaces `install.sh` on the `main` branch with a malicious shell payload. 3. A user follows the documented one-line insta ...[truncated 1098 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all `curl | bash` installation instructions. 2. Publish immutable, versioned release archives. 3. Provide a SHA-256 checksum and a detached cryptographic signature for each release. 4. Instruct users to download, verify, inspect, and then execute the installer: ```bash curl --fail --show-error --location \ --output offercatcher-install.sh \ https://example.invalid/releases/v0.1.0/install.sh printf '%s %s\n' '<EXPECTED_SHA256>' offercatcher-install.sh | shasum -a 256 --check less offercatcher-install.sh bash offercatcher-install.sh ``` 5. Prefer the documented ClawHub installation mechanism if it verifies package identity and integrity. 6. Pin documentation to a specific release rather than the mutable `main` branch. 7. Protect release publication with signed tags, branch protection, mandatory review, and multi-factor authentication. ]]>

T08 · Insecure Dependencies

Error
Location
install.sh:16
Finding
Installer Clones and Updates Executable Skill Code from an Unpinned Branch<![CDATA[ ## Vulnerability Details **File Location**: `install.sh:16-26` **Vulnerability Type**: Unpinned executable dependency and unsafe update mechanism **Risk Level**: High ### Vulnerable Code ```bash # 2. Clone or update the skill if [ -d "$SKILL_PATH" ]; then echo "Existing installation detected; updating..." cd "$SKILL_PATH" git pull -q else echo "Cloning skill to $SKILL_PATH..." git clone -q https://github.com/NissonCX/offercatcher.git "$SKILL_PATH" fi ``` ### Technical Analysis The installer treats the latest state of the remote repository as the installed dependency. Neither `git clone` nor `git pull` is pinned to an audited commit, signed release, or immutable tag. The installed repository contains executable Python and shell scripts with access to Apple Mail, Apple Reminders, the user's home directory, and the surrounding OpenClaw execution context. Consequently, silently updating those files creates a high-value software supply-chain path. The use of GitHub is not inherently unsafe, and no malicious dependency was identified in the bundled snapshot. The issue is that the installer does not guarantee that future installed code matches the audited snapshot. An existing local installation can also be changed without presenting or reviewing the incoming diff. ### Attack Path 1. An attacker gains the ability to push to the upstream repository or compromise its release process. 2. The attacker modifies an executable file such as `scripts/recruiting_sync.py`, `scripts/apple_reminders_bridge.py`, or the Skill instructions. 3. A user runs the installer on a new or existing installation. 4. `git clone` or `git pull` retrieves the modified branch head. 5. OpenClaw or the user later invokes the replaced Skill code. 6. The attacker's code executes with the user's permissions and any application permissions granted to the invoking process. ### Impact Assessment The direct impact is replacement of locally executed Skill code. A comp ...[truncated 510 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Install only from a specific signed release or immutable commit. 2. Resolve and verify the expected commit before checking out executable files: ```bash git clone --no-checkout https://github.com/NissonCX/offercatcher.git "$SKILL_PATH" cd "$SKILL_PATH" git checkout --detach '<AUDITED_COMMIT_SHA>' test "$(git rev-parse HEAD)" = '<AUDITED_COMMIT_SHA>' ``` 3. Prefer signed tags and verify them with `git tag -v` or `git verify-commit`. 4. Do not run `git pull` as an implicit update mechanism. 5. Present available updates and their diffs to the user, then require explicit approval. 6. Publish checksums for all executable files or the complete release archive. 7. Add repository branch protection, mandatory reviews, signed release automation, and restricted maintainer access. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/recruiting_sync.py:534
Finding
Scanner Collects Unrelated Recent Email Bodies Before Establishing Recruiting Relevance<![CDATA[ ## Vulnerability Details **File Location**: `scripts/recruiting_sync.py:301-344`, `scripts/recruiting_sync.py:367-404`, and `scripts/recruiting_sync.py:534-553` **Vulnerability Type**: Excessive sensitive-data collection and violation of least privilege **Risk Level**: High ### Vulnerable Code The message-listing logic selects recent messages from the configured mailbox without first establishing that they are recruiting-related: ```python script_lines = [ 'tell application "Mail"', f'set acc to account "{escaped_account}"', f'set mbx to mailbox "{escaped_mailbox}" of acc', 'set output to ""', 'set cutoff to (current date) - (' + str(days) + ' * days)', 'set matchCount to 0', 'repeat with m in messages of mbx', f'if matchCount is greater than or equal to {max_results * 3} then exit repeat', 'set msgDate to date received of m', 'if msgDate > cutoff then', 'set msgId to (id of m) as string', 'set subj to subject of m as string', 'set sndr to sender of m as string', 'set ts to (date received of m) as string', ] ``` Bodies are then fetched for every selected message, subject only to a character limit: ```python script = [ f"with timeout of {MAIL_TIMEOUT_SECONDS * 2} seconds", 'tell application "Mail"', f'set targetIds to {{{ids_list}}}', f'set acc to account "{escaped_account}"', f'set mbx to mailbox "{escaped_mailbox}" of acc', 'set results to ""', 'repeat with m in messages of mbx', 'set msgId to id of m as string', 'if targetIds contains msgId then', 'set c to content of m', f'if (length of c) > {MAIL_BODY_LIMIT} then set c to text 1 thru {MAIL_BODY_LIMIT} of c', 'set results to results & msgId & tab & c & character id 0', 'end if', 'end repeat', 'return results', 'end tell', 'end timeout', ] ``` The complete selected content is emitted as JSON for LLM parsing: ```python messages = list_recent_mail_messages( args.days, ...[truncated 3043 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require users to select a dedicated recruiting mailbox or folder by default. 2. Perform a metadata-only first pass using sender and subject fields. 3. Fetch bodies only for messages explicitly classified as likely recruiting correspondence. 4. Prefer an on-device classifier for the relevance decision. 5. Add an interactive confirmation listing candidate senders and subjects before reading bodies. 6. Reduce body excerpts to the minimum portion needed and retrieve additional text only when necessary. 7. Redact common sensitive values before model processing, including one-time codes, reset links, tokens, and financial identifiers. 8. Clearly disclose whether the configured LLM is local or remote, including its retention policy. 9. Add configurable sender/domain allowlists and message-selection controls. 10. Set strict positive upper bounds for `days` and `max_results` at argument parsing time. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/recruiting_sync.py:416
Finding
Caller-Controlled State Output Can Overwrite Files Outside the Documented State Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/recruiting_sync.py:416-461`, `scripts/recruiting_sync.py:628-660`, and `scripts/manual_event.py:61-76`, `scripts/manual_event.py:151-181` **Vulnerability Type**: Insufficient output-path restriction and arbitrary user-writable file overwrite **Risk Level**: Medium ### Vulnerable Code The main scanner's validation function permits approved roots, but its fallback rejects only selected path patterns and then accepts all other resolved locations: ```python def validate_path_in_home(path: Path) -> Path: resolved = path.expanduser().resolve() home = Path.home().resolve() allowed_roots = [ home, Path("/tmp").resolve(), Path(os.environ.get("TMPDIR", "/tmp")).resolve(), ] for root in allowed_roots: try: resolved.relative_to(root) return resolved except ValueError: continue original_str = str(path) if ( ".." in original_str or original_str.startswith("/etc") or original_str.startswith("/var") ): raise SystemExit( f"Path {path} may pose a security risk; refusing access" ) return resolved ``` The accepted location is subsequently overwritten with JSON state: ```python def write_state(state: dict[str, Any], path: Path) -> None: validated_path = validate_path_in_home(path) validated_path.parent.mkdir(parents=True, exist_ok=True) validated_path.write_text( json.dumps(state, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) ``` The path is caller-controlled through `--output`: ```python parser.add_argument( "--output", default=str(STATE_PATH), help="State file path", ) ``` The auxiliary `manual_event.py` implementation has no equivalent path validation: ```python def write_state(path: Path, state: dict) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text( ...[truncated 2360 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the permissive fallback and reject every path outside a narrowly defined state directory. 2. Use one canonical state root, such as: ```python STATE_ROOT = ( Path.home() / ".openclaw" / "workspace" / "memory" ).resolve() def validate_state_path(path: Path) -> Path: resolved = path.expanduser().resolve(strict=False) try: resolved.relative_to(STATE_ROOT) except ValueError as exc: raise SystemExit( f"Output must remain under {STATE_ROOT}" ) from exc return resolved ``` 3. Apply the same validation in `manual_event.py`. 4. If custom state paths are not required, remove the `--output` option entirely. 5. Reject symbolic links for the destination and relevant parent components. 6. Write atomically by creating a temporary file in the approved directory, setting restrictive permissions, flushing it, and replacing the final state file. 7. Set state-file permissions to user-only access, such as mode `0600`. 8. Validate the existing state schema before rewriting it and fail closed on unexpected content. ]]>
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
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (56)

Chaining Abuse

High
Category
Tool Misuse
Content
```bash
# Install
curl -sSL https://raw.githubusercontent.com/NissonCX/offercatcher/main/install.sh | bash

# Configure
echo 'mail_account: "Gmail"' >> ~/.openclaw/offercatcher.yaml
Confidence
94% confidence
Finding
The shell pipeline into bash is a dangerous command-chaining pattern because it converts downloaded content directly into execution without validation, making abuse trivial if upstream content changes. In context, the skill is intended for end users on macOS and likely runs with the user's permissions, so exploitation could lead to code execution, persistence, and access to mail/reminder data.

Chaining Abuse

High
Category
Tool Misuse
Content
### Option 2: One-Line Install

```bash
curl -sSL https://raw.githubusercontent.com/NissonCX/offercatcher/main/install.sh | bash
```

### Option 3: Manual Install
Confidence
94% confidence
Finding
This repeated pipe-to-bash command presents the same chaining-abuse risk and normalizes an unsafe installation practice for users. Because this skill later processes email and writes reminders, any compromise at install time can subvert the entire workflow and silently harvest sensitive personal communications.

Chaining Abuse

High
Category
Tool Misuse
Content
```bash
# 安装
curl -sSL https://raw.githubusercontent.com/NissonCX/offercatcher/main/install.sh | bash

# 配置
echo 'mail_account: "Gmail"' >> ~/.openclaw/offercatcher.yaml
Confidence
97% confidence
Finding
The '| bash' chain turns a remote content fetch directly into shell execution with no inspection boundary. In the context of a mailbox-scanning macOS automation skill, compromise of the fetched script could give an attacker code execution on a host with access to Mail, Reminders, local files, and possibly iCloud-synced data.

Chaining Abuse

High
Category
Tool Misuse
Content
### 方式二:一键安装

```bash
curl -sSL https://raw.githubusercontent.com/NissonCX/offercatcher/main/install.sh | bash
```

### 方式三:手动安装
Confidence
97% confidence
Finding
This direct command chain repeats the same arbitrary-code-execution risk in the dedicated installation section, making it especially likely to be followed. Because the skill is designed to access sensitive mailbox content and create reminders, successful exploitation could expose personal communications and manipulate user data beyond initial installation.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill claims AI parsing and sync behavior, but the detected behavior suggests manual event/reminder creation with local state persistence and no evidence of email ingestion. This can mislead users into granting trust to a workflow that behaves differently and stores undeclared local tracking data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill claims AI parsing and sync behavior, but the detected behavior suggests manual event/reminder creation with local state persistence and no evidence of email ingestion. This can mislead users into granting trust to a workflow that behaves differently and stores undeclared local tracking data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill claims AI parsing and sync behavior, but the detected behavior suggests manual event/reminder creation with local state persistence and no evidence of email ingestion. This can mislead users into granting trust to a workflow that behaves differently and stores undeclared local tracking data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill claims AI parsing and sync behavior, but the detected behavior suggests manual event/reminder creation with local state persistence and no evidence of email ingestion. This can mislead users into granting trust to a workflow that behaves differently and stores undeclared local tracking data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill claims AI parsing and sync behavior, but the detected behavior suggests manual event/reminder creation with local state persistence and no evidence of email ingestion. This can mislead users into granting trust to a workflow that behaves differently and stores undeclared local tracking data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill claims AI parsing and sync behavior, but the detected behavior suggests manual event/reminder creation with local state persistence and no evidence of email ingestion. This can mislead users into granting trust to a workflow that behaves differently and stores undeclared local tracking data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The skill claims AI parsing and sync behavior, but the detected behavior suggests manual event/reminder creation with local state persistence and no evidence of email ingestion. This can mislead users into granting trust to a workflow that behaves differently and stores undeclared local tracking data.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
- notes: Additional info
```

## Output Rules

- Reminder title: Company + Event type (e.g., "Google Interview", "Meta Coding Test")
- Include: Time, role, link in notes
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Hidden Instructions

High
Category
Prompt Injection
Content
<svg width="800" height="280" viewBox="0 0 800 280" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <!-- 超炫渐变 - 主标题 -->
    <linearGradient id="megaGrad" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" stop-color="#FF0080">
        <animate attributeName="stop-color" values="#FF0080;#7928CA;#FF0080" dur="4s" repeatCount="indefinite"/>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Chaining Abuse

High
Category
Tool Misuse
Content
#!/bin/bash
# OfferCatcher 一键安装脚本
# 用法: curl -sSL https://raw.githubusercontent.com/NissonCX/offercatcher/main/install.sh | bash

set -e
Confidence
96% confidence
Finding
Using '| bash' creates an unsafe execution chain where unreviewed network content is executed immediately in a shell. In the context of an install script for a skill that also clones and updates code from a remote repository, this increases the blast radius because compromise of the upstream content can lead to arbitrary command execution on the user's machine during installation.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The clear-list command deletes every reminder in the specified list in a loop, creating an immediate destructive primitive. Because the skill is supposed to sync recruiting emails, giving it a bulk-wipe capability is unnecessarily dangerous; if invoked accidentally or abusively, it can erase important user data at scale.

Missing User Warnings

High
Confidence
98% confidence
Finding
The bulk clear-list operation deletes all reminders in a list with no warning, confirmation, or ownership validation. In this skill context, that is especially risky because a recruiting-email helper has no clear need to destroy unrelated reminders, so the feature materially increases the chance of catastrophic user-data loss.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation states that raw Apple Mail content is scanned and then parsed by an LLM, but it does not explicitly warn that emails may contain sensitive personal, employment, or account data. In this skill context, that omission is security-relevant because users may unknowingly expose private mailbox contents to an AI processing pipeline or external component without informed consent.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The examples show both dry-run and live apply commands, but do not clearly emphasize that the non-dry-run command will create or modify Apple Reminders data. In a workflow driven by LLM-parsed events, unclear documentation increases the chance of unintended writes, duplicate reminders, or persistence of malformed data derived from email content.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README promotes automatic scanning of email contents and LLM-based parsing but does not warn users that potentially sensitive recruiting emails may be transmitted to, or processed by, an AI system. In this skill’s context, emails can contain personal data, interview links, contact details, and assessment information, so omission of a privacy/data-handling warning can lead to unsafe deployment and unintended disclosure.

Session Persistence

Medium
Category
Rogue Agent
Content
### Step 2: Configure

Create `~/.openclaw/offercatcher.yaml`:

```yaml
mail_account: "Gmail"    # Your Apple Mail account name
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.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The README describes automatic mailbox scanning and AI parsing but does not clearly warn users that email contents may be sent to an LLM or otherwise exposed to external AI processing. Recruiting emails commonly contain sensitive personal data, interview links, deadlines, and employer information, so missing disclosure materially increases privacy and consent risk.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill advertises shell, file, and environment-capable behavior but declares no explicit tool scope or permissions boundary. That creates an authorization gap where an agent may execute sensitive local operations without clear user consent or sandboxing expectations.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill handles private email and explicitly routes email bodies into an LLM parsing step, yet it provides no user warning about scanning sensitive mailbox contents or sharing them with a model. In a recruiting context, emails may contain personal identifiers, interview schedules, links, and confidential hiring materials, making the omission materially risky.

Ssd 3

Medium
Confidence
93% confidence
Finding
The parsing prompt asks the LLM to process full email bodies and return structured fields plus free-form notes, without constraints to suppress sensitive information. That increases the chance of reproducing confidential email content, assessment links, personal data, or secrets in model outputs or downstream reminder notes.

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.

Static analysis

No suspicious patterns detected.