Back to skill

Security audit

Indeed Monitor

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly performs Indeed lead scraping, but it needs Review because it can use a local Chrome profile and append scraped lead data to a hard-coded personal file with under-disclosed scope.

Review before installing. Use an isolated browser profile rather than a personal Chrome profile, avoid --save unless you are comfortable appending to the hard-coded master lead file, and treat all saved lead text as untrusted scraped content until the output path and Markdown sanitization are fixed.

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
monitor.py:277
Finding
Unsanitized Indeed Content Persisted into the Markdown Lead Database<![CDATA[ ## Vulnerability Details **File Location**: `monitor.py:277-281` **Vulnerability Type**: Persistent Markdown content injection **Risk Level**: Medium ### Vulnerable Code ```python with open(LEADS_FILE, "a") as f: f.write(f"\n\n---\n\n## 📋 Indeed Scan — {timestamp}\n\n") f.write("| Company | Title | Location | Pain Signal |\n") f.write("|---------|-------|----------|-------------|\n") for lead in all_leads: signals = ", ".join(lead.get("pain_signals", [])) f.write(f"| {lead['company']} | {lead['title']} | {lead['location']} | {signals} |\n") ``` ### Technical Analysis The values written as `company`, `title`, and `location` are derived from public Indeed content. The application writes these untrusted values directly into `MASTER_LEAD_LIST.md` without escaping Markdown metacharacters, filtering HTML, removing control characters, or applying a restrictive character and length policy. An attacker who can publish or influence an Indeed job listing can place characters such as pipes, brackets, links, image syntax, or HTML in a parsed field. Pipe characters can create additional table cells or rows, while Markdown and HTML constructs can introduce attacker-controlled links or rendered content. If the generated lead database is subsequently included in an AI agent's context, instruction-like text from a listing could also act as persistent indirect prompt-injection content. That consequence depends on a downstream system treating the Markdown file as trusted instructions rather than untrusted data; this script itself does not execute the injected text. ### Attack Path 1. An attacker publishes or modifies an Indeed job listing containing crafted Markdown, HTML, table delimiters, or instruction-like text in a field that the parser may identify as a company name, job title, or location. 2. The user runs `monitor.py --save`. 3. The script retrieves and parses the attacker-influenced listing. 4. The crafted value reaches ...[truncated 1089 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every scraped field as untrusted data. 2. Escape Markdown table delimiters and formatting characters before writing values. At minimum: - Replace `\` with `\\`. - Replace `|` with `\|`. - Remove carriage returns and replace newlines with spaces. - Encode or remove HTML delimiters such as `<` and `>`. - Neutralize Markdown link and image syntax where rendered links are unnecessary. 3. Enforce maximum lengths and an allowlist of expected characters for company names, titles, and locations. 4. Prefer a structured storage format such as JSON or properly generated CSV. If Markdown is required, generate it from validated structured data using a safe serialization function. 5. Label scraped text as untrusted external content when supplying it to an AI system. Keep data fields separate from system or task instructions and instruct the downstream agent never to follow instructions contained in lead records. 6. Consider rejecting records containing control characters, embedded URLs, multiline content, or instruction-like payloads. For example: ```python def escape_markdown_cell(value, max_length=200): value = str(value or "") value = value.replace("\r", " ").replace("\n", " ") value = value.replace("\\", "\\\\").replace("|", "\\|") value = value.replace("<", "&lt;").replace(">", "&gt;") return value[:max_length] for lead in all_leads: company = escape_markdown_cell(lead["company"]) title = escape_markdown_cell(lead["title"]) location = escape_markdown_cell(lead["location"]) signals = escape_markdown_cell(", ".join(lead.get("pain_signals", []))) f.write(f"| {company} | {title} | {location} | {signals} |\n") ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
monitor_browser.py:190
Finding
Unsanitized Browser-Scraped Content Persisted into the Markdown Lead Database<![CDATA[ ## Vulnerability Details **File Location**: `monitor_browser.py:190-194` **Vulnerability Type**: Persistent Markdown content injection **Risk Level**: Medium ### Vulnerable Code ```python with open(LEADS_FILE, "a") as f: f.write(f"\n\n---\n\n## 📋 Indeed Scan (Browser) — {timestamp}\n\n") f.write("| Company | Title | Location | Signals |\n") f.write("|---------|-------|----------|---------|\n") for lead in all_leads: signals = ", ".join(lead.get("signals", [])) f.write(f"| {lead['company']} | {lead['title']} | {lead['location']} | {signals} |\n") ``` ### Technical Analysis The browser implementation obtains job data from an Indeed accessibility snapshot and stores parsed company names, titles, and locations directly in a Markdown table. Although parsing constrains some values by expected formatting, it does not provide contextual Markdown output encoding. Consequently, characters accepted by the parser can alter the generated table or introduce attacker-controlled Markdown or HTML. Because the data is appended to a persistent lead file, the injected content remains after the monitoring process exits. Instruction-like listing content could also become an indirect prompt-injection vector if the lead file is later provided to an AI agent as trusted context. This is a downstream, conditional risk; the browser monitor itself neither interprets those instructions nor directly executes code from the listing. ### Attack Path 1. An attacker creates an Indeed listing with crafted content in a title or company-related field that remains compatible with the snapshot parser. 2. The user activates the Chrome relay and runs `monitor_browser.py --save`. 3. The OpenClaw browser relay navigates to Indeed and returns a snapshot containing the crafted listing. 4. `parse_snapshot` extracts the attacker-controlled field. 5. The save loop inserts that value into `MASTER_LEAD_LIST.md` without Markdown escaping. 6. The injected content ...[truncated 780 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply contextual Markdown encoding to every browser-scraped value before persistence. 2. Remove newlines and control characters and escape table separators, backslashes, brackets, and HTML delimiters. 3. Define strict maximum lengths and expected-character policies for each field. 4. Store the original records in JSON or another structured format and produce Markdown only as a sanitized presentation artifact. 5. Do not place scraped text in an AI system's instruction channel. Pass it as clearly marked untrusted data and prohibit the agent from acting on embedded directives. 6. Add tests containing pipes, multiline values, Markdown links, image syntax, HTML, and instruction-like strings to verify that none can escape their intended table cells. A shared sanitizer should be used by both monitor implementations: ```python def escape_markdown_cell(value, max_length=200): value = str(value or "") value = value.replace("\r", " ").replace("\n", " ") value = value.replace("\\", "\\\\").replace("|", "\\|") value = value.replace("<", "&lt;").replace(">", "&gt;") return value[:max_length] for lead in all_leads: company = escape_markdown_cell(lead["company"]) title = escape_markdown_cell(lead["title"]) location = escape_markdown_cell(lead["location"]) signals = escape_markdown_cell(", ".join(lead.get("signals", []))) f.write(f"| {company} | {title} | {location} | {signals} |\n") ``` ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill claims to monitor target areas and output enriched lead information, but the documented behavior includes undeclared writes to a specific local path, lacks the promised phone enrichment, and only generates simplistic signals over hardcoded searches. In a lead-generation skill, this context makes the discrepancy more concerning because it affects data provenance, user expectations, and hidden persistence of potentially sensitive business intelligence.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill claims to monitor target areas and output enriched lead information, but the documented behavior includes undeclared writes to a specific local path, lacks the promised phone enrichment, and only generates simplistic signals over hardcoded searches. In a lead-generation skill, this context makes the discrepancy more concerning because it affects data provenance, user expectations, and hidden persistence of potentially sensitive business intelligence.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill advertises executable commands that use network access, shell execution, and optional file writes, but the manifest declares no explicit tool scope or permissions. This is dangerous because users and enforcement systems cannot accurately constrain what the skill is allowed to do, increasing the chance of unintended data access, filesystem modification, or unreviewed external communications.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The top-level docstring asserts that each posting is a confirmed lead. In practice, the code merely scrapes search results, filters names, and assigns heuristic scores based on title and company-name keywords, which does not substantiate a confirmed-lead claim.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The manifest says the skill outputs an enriched lead list with company name, phone when available, location, and pain point summary. In the implementation, parsing and saved output never attempt to collect phone numbers or perform any enrichment beyond simple title/company/location extraction and heuristic scoring, so the actual behavior is materially narrower than described.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def fetch(url):
    r = subprocess.run(
        ["python3", SCRAPLING_SCRIPT, "web", url],
        capture_output=True, text=True, timeout=30
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
When --save is used, the script persists scraped lead data to a fixed master file path with no validation, no preview/consent beyond the flag, and no controls around data classification or retention. In this skill context, the data is business lead information rather than high-sensitivity secrets, which lowers severity, but silent persistence to a shared internal file can still create privacy, governance, and unauthorized-disclosure risk if the file is broadly accessible or synced.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def openclaw_snapshot(target_id):
    """Get aria snapshot of current browser tab."""
    r = subprocess.run(
        ["node", "-e", f"""
const {{OpenClawBrowser}} = require('/opt/homebrew/lib/node_modules/openclaw/dist/tools/browser.js');
// Just use curl to the local API
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The code invokes external local tooling through subprocess to reach browser-related functionality that is broader than simple lead monitoring. In an agent environment, undeclared access to local executables and relay components expands capability scope and can be abused to interact with host resources beyond the user's expectations.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import time
    url = f"https://www.indeed.com/jobs?q={query.replace(' ', '+')}&l={location.replace(' ', '+').replace(',', '%2C')}&sort=date&fromage=7"
    
    subprocess.run(
        ["openclaw", "browser", "navigate", "--url", url, "--profile", "chrome"],
        capture_output=True, text=True, timeout=20
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
This skill can drive a local Chrome profile and capture snapshots, giving it visibility into browser state and page content beyond the narrow task of lead collection. That is especially risky in agent contexts because a real user profile may contain authenticated sessions, personal data, or unrelated tabs, so browser-control capability materially increases privacy and lateral-access risk.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code performs browser automation and page snapshotting without an explicit user warning or consent prompt, despite interacting with a local browser profile. In practice, this can surprise users and expose page contents or authenticated context to the skill without informed approval.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
)
    time.sleep(2)
    
    s = subprocess.run(
        ["openclaw", "browser", "snapshot", "--profile", "chrome", "--compact"],
        capture_output=True, text=True, timeout=20
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The skill appends data to a fixed local path outside its apparent declared output boundary, which can modify user data stores without clear disclosure or runtime confirmation. In an agent setting, undeclared file writes are dangerous because they create persistence side effects and can contaminate sensitive local notes or operational files.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Appending to a hard-coded local master file creates persistent changes to user data without a prior warning about the exact file being modified. In an agent ecosystem, silent writes are dangerous because they can overwrite, pollute, or leak information into files the user did not expect the skill to touch.

Description-Behavior Mismatch

Low
Confidence
88% confidence
Finding
The manifest scope is limited to receptionist, customer service representative, and front desk roles. The parser additionally treats office manager, call center, and administrative assistant titles as targets, expanding the monitored role set beyond the stated purpose.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
The natural-language signal `Bilingual needed → Sofia angle` operationalizes language-related targeting in a fixed way without offering any user choice or documented justification. This can be interpreted as enforcing a language/locale-related preference in the skill's behavior rather than leaving such criteria to explicit user opt-in.

Static analysis

No suspicious patterns detected.