Back to skill

Security audit

Boss Zhipin Scraper 2.3.1

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a legitimate BOSS Zhipin job-scraping skill, but it needs review because an optional login import can duplicate a user's main browser cookie database into a persistent scraper profile.

Install only if you are comfortable with a local scraper controlling a dedicated Chrome/Edge profile and storing job results under ~/.boss-zhipin-scraper. Prefer manual login in the dedicated browser and avoid --copy-login-state unless you understand it may copy broader browser session cookies than just BOSS Zhipin. Pin installation sources where possible, and open exported CSV files cautiously.

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/boss_cdp_raw.py:1293
Finding

Untrusted Job-Listing Fields Can Trigger Spreadsheet Formula Injection

Content
View full analysis

Vulnerability Details

File Location: scripts/boss_cdp_raw.py:607-634, 1293-1316
Vulnerability Type: CSV formula injection
Risk Level: Medium

Vulnerable Code

External job-listing fields are copied into the internal record without neutralizing spreadsheet formula prefixes:

python
return {
    "title": raw.get("jobName") or "",
    "salary": raw.get("salaryDesc") or "",
    "salary_source": "api" if raw.get("salaryDesc") else "api_empty",
    "location": "\u00b7".join([
        raw.get("cityName") or "",
        raw.get("areaDistrict") or "",
        raw.get("businessDistrict") or "",
    ]),
    "tags": " | ".join(
        t for t in (raw.get("jobExperience") or "", raw.get("jobDegree") or "")
        if t and t != "不限"
    ),
    "boss_name": raw.get("brandName") or "",
    "boss_title": raw.get("bossTitle") or "",
    "boss_active_status": map_list_boss_active_status(raw),
    "company_scale": raw.get("brandScaleName") or "",
    "company_stage": raw.get("brandStageName") or "",
    "company_industry": raw.get("brandIndustry") or "",
    "job_labels": " | ".join(raw.get("jobLabels") or []),
    "skills": " | ".join(raw.get("skills") or []),

The values are subsequently written directly to CSV:

python
def write_csv(csv_path, jobs):
    """将 jobs 列表写入 CSV 文件"""
    os.makedirs(os.path.dirname(csv_path) or ".", exist_ok=True)
    with open(csv_path, "w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=CSV_COLUMNS, extrasaction="ignore")
        writer.writeheader()
        for j in jobs:
            # 确保每列都有值
            row = {col: j.get(col, "") for col in CSV_COLUMNS}
            writer.writerow(row)
    print(f"CSV 已保存: {csv_path}")


def write_detail_csv(csv_path, details):
    """将岗位详情列表写入 CSV 文件"""
    os.makedirs(os.path.dirname(csv_path) or ".", exist_ok=True)
    with open(csv_path, "w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=DETAIL_CS
...[truncated 2656 chars]
Remediation
View remediation

Remediation Suggestions

  1. Introduce a centralized function that converts every externally sourced CSV value into a spreadsheet-safe literal before serialization.
  2. Treat cells whose first effective character is =, +, -, or @ as dangerous. Inspect after accounting for leading spaces, tabs, carriage returns, line feeds, and other control characters.
  3. Prefix dangerous values with an apostrophe or use another export strategy that reliably forces literal-text interpretation in the supported spreadsheet applications.
  4. Apply the protection to every field in both write_csv() and write_detail_csv(), rather than maintaining a partial field allowlist.
  5. Preserve the original unsanitized values in JSON exports if exact source fidelity is required, while documenting that CSV exports are spreadsheet-safe representations.
  6. Add regression tests covering values such as =WEBSERVICE(...), +1+1, -1+1, @SUM(...), and formula prefixes preceded by whitespace or control characters.
  7. Document the mitigation and advise users not to disable spreadsheet protected-view or external-content warnings.

Example hardening pattern:

python
FORMULA_PREFIXES = ("=", "+", "-", "@")

def spreadsheet_safe(value):
    if value is None:
        return ""
    text = str(value)
    effective = text.lstrip(" \t\r\n")
    if effective.startswith(FORMULA_PREFIXES):
        return "'" + text
    return text

row = {
    col: spreadsheet_safe(j.get(col, ""))
    for col in CSV_COLUMNS
}

The exact escaping convention should be tested against every spreadsheet application the project claims to support.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (24)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `scripts/boss_cdp_raw.py`:抓取主脚本

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `scripts/boss_cdp_raw.py`:抓取主脚本

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · README.md (reported line 85)May include surrounding context.

md
cd boss-zhipin-scraper

# 2. 复制到 Hermes skills 目录
mkdir -p ~/.hermes/skills/data-science/boss-zhipin-scraper/scripts
cp SKILL.md ~/.hermes/skills/data-science/boss-zhipin-scraper/
cp scripts/boss_cdp_raw.py ~/.hermes/skills/data-science/boss-zhipin-scraper/scripts/
cp scripts/job_summary.py ~/.hermes/skills/data-science/boss-zhipin-scraper/scripts/

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding

The README instructs users to run npx skills add ... without pinning a version. npx will fetch and execute the latest package version at install time, which creates a supply-chain risk if the package is compromised, ownership changes, or a malicious update is published.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding

The README recommends npx clawhub@latest install ..., which executes a remotely fetched package version that can change over time. Even though a package name is specified, using @latest still exposes users to unreviewed updates and supply-chain compromise if the package or dependency chain is hijacked.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The instruction tells users to invoke the skill with a Chinese phrase only: “安装后直接在 Hermes 对话中说"帮我搜一下 BOSS直聘 上上海的 AI Agent 岗位"。” This presents a default language constraint in the skill guidance without offering a language choice at that point, which can conflict with language/locale neutrality expectations.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · README.md (reported line 336)May include surrounding context.

md
## Star History

[![Star History Chart](https://api.star-history.com/chart?repos=eatmoreduck/boss-zhipin-scraper&type=date&legend=top-left&sealed_token=linAWksW9v7s0YEw83L89xbRzD4QWaJWxKrQHvkJBmx9xwMH8PseUKUQC9QAcRYaBFK1jBA_Mod4Vs8qH9A47spODANKwiVWieL3CxxQ3f9ZLqHYRwzTiA)](https://www.star-history.com/?type=date&repos=eatmoreduck%2Fboss-zhipin-scraper)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill instructs the agent to perform broad shell, network, file read/write, and environment-dependent actions, but it declares no explicit permission or allowed-tools scope. That mismatch weakens policy enforcement and transparency, making it easier for an agent runtime to over-grant capabilities or for users to invoke risky behavior without clear boundaries.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The documented --copy-login-state flow copies cookie-related browser state from the user's primary Chrome or Edge profile into the scraper profile. Even if limited to cookie databases and Local State, this transfers authenticated session material between trust boundaries and can expose or misuse account sessions beyond what is necessary for a job-scraping skill.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

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

bash
# 推荐:curl 一键安装
mkdir -p ~/.hermes/skills/data-science/boss-zhipin-scraper/scripts && \
curl -sL https://raw.githubusercontent.com/eatmoreduck/boss-zhipin-scraper/master/SKILL.md \
  -o ~/.hermes/skills/data-science/boss-zhipin-scraper/SKILL.md && \
curl -sL https://raw.githubusercontent.com/eatmoreduck/boss-zhipin-scraper/master/scripts/boss_cdp_raw.py \

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 372)May include surrounding context.

python
if not require_runtime_dependencies("requests", "websocket"):
            raise RuntimeError("缺少 CDP 运行依赖")
        self.cdp_port = cdp_port
        resp = requests.get(f"http://127.0.0.1:{cdp_port}/json/version", timeout=10)
        ws_url = resp.json()["webSocketDebuggerUrl"]
        self.ws = websocket.create_connection(ws_url, timeout=60)
        self.mid = 0

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 2283)May include surrounding context.

python
if not require_runtime_dependencies("requests", "websocket"):
            raise RuntimeError("缺少 CDP 运行依赖")
        self.cdp_port = cdp_port
        resp = requests.get(f"http://127.0.0.1:{cdp_port}/json/version", timeout=10)
        ws_url = resp.json()["webSocketDebuggerUrl"]
        self.ws = websocket.create_connection(ws_url, timeout=60)
        self.mid = 0

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 2369)May include surrounding context.

python
if not require_runtime_dependencies("requests", "websocket"):
            raise RuntimeError("缺少 CDP 运行依赖")
        self.cdp_port = cdp_port
        resp = requests.get(f"http://127.0.0.1:{cdp_port}/json/version", timeout=10)
        ws_url = resp.json()["webSocketDebuggerUrl"]
        self.ws = websocket.create_connection(ws_url, timeout=60)
        self.mid = 0

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script can copy Local State and browser cookie databases from the user’s primary Chrome/Edge profile into the scraper profile, effectively duplicating authenticated session material. That expands the exposure of sensitive credentials and makes account/session theft or unintended cross-use easier if the scraper profile directory is later accessed by another process or user.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The capability to import existing browser login state and cookies is security-sensitive, but the top-level skill description does not clearly warn that authenticated browser data may be copied into another profile. That weak disclosure increases the chance that a user runs the tool without understanding it will duplicate session material, which is especially risky for a scraping skill that already handles logged-in browsing.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The setup/stop helpers enumerate local browser processes and can terminate them, which gives the script host-level visibility and control beyond scraping job listings. Even though the code tries to scope actions to the isolated profile, this class of behavior is dangerous because mistakes in matching logic or environment quirks could disrupt unrelated browser sessions or be repurposed for local process interference.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 2420)May include surrounding context.

python
"Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress"
        )
        try:
            r = subprocess.run(
                ["powershell", "-NoProfile", "-Command", ps_script],
                capture_output=True, text=True, timeout=5,
            )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 2450)May include surrounding context.

python
return processes

    try:
        r = subprocess.run(["ps", "-axo", "pid=,command="], capture_output=True, text=True, timeout=5)
    except Exception:
        return []

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 2511)May include surrounding context.

python
cmd = ["taskkill", "/PID", str(pid), "/T"]
        if force:
            cmd.append("/F")
        subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5)
        return
    os.kill(pid, signal.SIGKILL if force else signal.SIGTERM)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 2574)May include surrounding context.

python
kwargs["creationflags"] = creationflags
    else:
        kwargs["start_new_session"] = True
    return subprocess.Popen(cmd, **kwargs)


def run_setup_chrome(cdp_port=DEFAULT_CDP_PORT, copy_login_state=False,

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This JSON file contains only Chinese-language place names, including the umbrella key "全国", with no accompanying indication that the skill is China-specific or that users can choose another language or locale. Under the natural-language policy rule, forcing a specific language/locale without opt-in can be a policy concern even in config data.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The user-facing docstring, help text, status messages, and examples are presented in Chinese throughout the file, which effectively forces a specific language for interaction. The file does not indicate any opt-in, localization setting, or alternative language path.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 55)May include surrounding context.

python
def configure_console_encoding():
    """Keep CLI diagnostics printable on Windows consoles using legacy code pages."""
    for stream_name in ("stdout", "stderr"):
        stream = getattr(sys, stream_name, None)
        reconfigure = getattr(stream, "reconfigure", None)
        if not reconfigure:
            continue

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/boss_cdp_raw.py (reported line 2870)May include surrounding context.

python
# 收集筛选条件
    filters = {}
    for key in ["scale", "stage", "salary", "experience", "degree", "industry"]:
        val = getattr(args, key)
        if val:
            filters[key] = val