Back to skill

Security audit

jobwatch

Security checks for vulnerabilities and agentic risk

Overview

The skill is transparent about its job-monitoring purpose, but it needs Review because it can install persistent scheduled agent runs and process untrusted job-posting text with insufficient containment.

Install only if you are comfortable giving this skill a sensitive job-search profile and letting it run recurring monitoring. Prefer default local modes, use dedicated revocable API keys, avoid JOBWATCH_ALLOW_HOST_CREDS unless necessary, approve only the egress destinations you need, review any RSS URLs for public trusted hosts, and know how to remove the jobwatch-* cron entries before enabling live monitoring.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (3)

T06 · System Persistence

Error
Location
scripts/setup_cron.py:30
Finding

Recurring Agent Tasks Can Be Installed Without Code-Level Consent Verification

Content
View full analysis
Remediation
View remediation

T01 · Skill Instruction Hijacking

Error
Location
scripts/pipeline.py:157
Finding

Externally Controlled Job Descriptions Are Processed by a Tool-Capable Agent Without Prompt-Injection Isolation

Content
View full analysis
0`: continue immediately, do not end the turn. Process in batches: read `HOME/queue/pending_judgment.jsonl` (each line `{item, jd_text, jd_tool}`), judge each against `HOME/profile/JOB_PROFILE.md`, and append to `HOME/queue/judgments.jsonl`. ... Then run `python3 scripts/apply_judgments.py`. ``` ### Technical Analysis Job descriptions are externally controlled data obtained from ATS feeds, RSS sources, Firecrawl, or Jina. In the default `judge.mode=agent` path, the full retrieved description is stored in `pending_judgment.jsonl`. A cron-woken, tool-capable host Agent is then instructed to read that content, generate judgment records, run `apply_judgments.py`, and process the outbox. The operational instructions do not explicitly establish that every posting field and job description is untrusted data. They also do not direct the Agent to ignore commands, requests, ro ...[truncated 2558 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_jobs.py:57
Finding

Arbitrary RSS URLs Permit Ungated Requests to Internal or Unlisted Network Destinations

Content
View full analysis
= 300: raise RuntimeError(f"RSS HTTP {status} for {src['url']}") root = ET.fromstring(body) ``` ### Technical Analysis The RSS source implementation accepts `src["url"]` from `config.json` and passes it directly to the shared HTTP client. It does not enforce: - HTTPS. - An approved hostname allowlist. - Publicly routable destination addresses. - Rejection of loopback, private, link-local, multicast, or reserved IP ranges. - DNS rebinding protections. - Redirect destination validation. - Per-host egress consent. This creates an SSRF-style network primitive for any party capable of influencing JobWatcher configuration or the onboarding Agent that writes it. Examples of potentially reachable targets include localhost services, private network control panels, and cloud metadata endpoints. The behavior also conflicts with the documented assertion that outbound calls are limited to listed hosts. Public ATS calls are intentionally ungated because they send only a company slug, but arbitrary RSS destinations do not share that fixed-host property. The XML parser does not execute external entities in the shown implementation, so the confirmed issue is arbitrary network destination access rather than XML external-entity expansion. ### Attack Path 1. An attacker or compromised Agent influences `config.json` and creates a source with: ```json { "kind": "rss", "url": "http://127.0.0.1:PORT/private-feed" } ``` A private address, cloud metadata address, or attacker-controlled redirect can be used instead. 2. A manual or scheduled pipeline calls `fetch_all()`. 3. `fetch_rs ...[truncated 1229 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (50)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description is for a full autonomous job-monitoring and notification system with privacy-sensitive data collection, third-party scraping/LLM usage, consent gating, and recurring automation. The supplied code does none of that. It only manages a local outbox file: listing queued messages and archiving the file after sending. This is a materially different primary purpose and lacks the core declared behaviors, resources, and triggers. While an outbox could be a supporting component in a larger system, this chunk by itself is not accurately represented by the declared description.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The skill explicitly supports reading credentials from environment variables and optionally reusing host OpenClaw and Telegram credentials. Any skill that can read secrets and also perform network operations creates a real exfiltration risk, especially because it processes untrusted external content and runs autonomously on a schedule.

Content

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

md
"telegram_chat" (the allowFrom list) — comma-separated. The legacy value 1
          (or true/yes/all) still grants all three. Exactly these three are readable and
          nothing else; each read prints a stderr line naming the credential. Without
          this variable the skill reads only the .env file you filled in yourself.
      - name: JOBWATCH_HOME
        required: false
        description: >-

Exfiltration Commands

High
Category
Prompt Injection
Confidence
90% confidence
Finding

The skill is designed to send user profile data, job descriptions, notifications, and optionally archived documents to multiple third-party services, and it can also reuse host credentials. Even with documented consent gates, this creates a substantial exfiltration surface because sensitive employment data and host-linked secrets may leave the local environment during automated runs.

Content

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

md
| **Host credentials** | the OpenClaw OpenRouter key, the `openclaw.json` Telegram bot token, and the Telegram `allowFrom` list. Only these three; the auth store is opened read-only and no other profile is read. The opt-in is **per credential** (`openrouter` / `telegram_token` / `telegram_chat`), so granting one does not grant the others. | **off** | **yes** — returns `None` / raises unless that scope is named in `JOBWATCH_ALLOW_HOST_CREDS`; each read prints a stderr warning naming the credential |
| **Scheduled task** | `setup_cron.py` appends exactly three `jobwatch-*` entries to `~/.openclaw/cron/jobs.json`, after explicit onboarding consent, writing a timestamped `.bak` of the file first and leaving pre-existing jobs untouched. `openclaw.json` and all other host configuration are never modified. | **off** | **yes** — never runs unprompted |

This skill does **not**: apply to jobs on your behalf, send messages to anyone but you, read your mail or
browser data, install packages, modify any infrastructure config beyond its own cron entries, or make
any network call to a host not listed above.

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · profile.template.md (reported line 3)May include surrounding context.

md
# JOB_PROFILE.md — 求职画像(judge 的唯一判断依据)

<!-- 复制本文件为 profile/JOB_PROFILE.md 并填写(BOOTSTRAP 入职时 agent 会带你完成)。
     真实画像已被 .gitignore 排除,不会进 git。写得越具体,判级越准。 -->

## 1. Hard Background(基本盘)

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · profile.template.md (reported line 3)May include surrounding context.

md
# JOB_PROFILE.md — 求职画像(judge 的唯一判断依据)

<!-- 复制本文件为 profile/JOB_PROFILE.md 并填写(BOOTSTRAP 入职时 agent 会带你完成)。
     真实画像已被 .gitignore 排除,不会进 git。写得越具体,判级越准。 -->

## 1. Hard Background(基本盘)

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · profile.template.md (reported line 7)May include surrounding context.

md
真实画像已被 .gitignore 排除,不会进 git。写得越具体,判级越准。 -->

## 1. Hard Background(基本盘)
- **Experience**: <!-- 年限 + 领域,如 "9 years in Backend & Cloud Infrastructure" -->
- **Current Role**: <!-- 只写职能,不必写公司名 -->
- **Core Stack**: <!-- 核心技术栈 -->
- **Differentiator**: <!-- 你与同级候选人的差异化优势 -->

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · profile.template.md (reported line 7)May include surrounding context.

md
真实画像已被 .gitignore 排除,不会进 git。写得越具体,判级越准。 -->

## 1. Hard Background(基本盘)
- **Experience**: <!-- 年限 + 领域,如 "9 years in Backend & Cloud Infrastructure" -->
- **Current Role**: <!-- 只写职能,不必写公司名 -->
- **Core Stack**: <!-- 核心技术栈 -->
- **Differentiator**: <!-- 你与同级候选人的差异化优势 -->

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/SKILL.zh.md (reported line 140)May include surrounding context.

md
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/SKILL.zh.md (reported line 213)May include surrounding context.

md
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/SKILL.zh.md (reported line 215)May include surrounding context.

md
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/SKILL.zh.md (reported line 217)May include surrounding context.

md
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/common.py (reported line 57)May include surrounding context.

python
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/common.py (reported line 267)May include surrounding context.

python
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/kb/__init__.py (reported line 7)May include surrounding context.

python
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/notify_telegram.py (reported line 28)May include surrounding context.

python
后端选择:env KB_BACKEND > config.json 的 kb.backend > 默认 local。

- local    零依赖,写 kb_local/ 目录 + index.jsonl(默认)
- twobrain 2brain 知识库(需要 .env 里的 TWOBRAIN_* key)
- aidigest 预留:对接自建 RAG(见模块 docstring)
"""
import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/common.py (reported line 58)May include surrounding context.

python
def _key(name):
    v = os.environ.get(name)
    if not v:
        raise RuntimeError(f"{name} not set in .env")
    return v

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/kb/twobrain.py (reported line 27)May include surrounding context.

python
def _key(name):
    v = os.environ.get(name)
    if not v:
        raise RuntimeError(f"{name} not set in .env")
    return v

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

md
reads became opt-in, that phrasing over-claimed the skill's default privilege.
  SKILL.md, README and the zh reference now say the token comes from your own
  `.env`, with host reuse requiring `JOBWATCH_ALLOW_HOST_CREDS=1`.
- **Cron write scope was imprecise.** Docs implied `openclaw.json` might be modified.
  `setup_cron.py` writes only `~/.openclaw/cron/jobs.json`, after a timestamped
  backup, leaving existing jobs intact. Now stated precisely in both languages.
- **Upfront privacy warning in README**, above the install and interview sections,

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The usage instructions tell the user to speak specific Chinese commands and state that the skill will then conduct an interview, while the document does not offer a language or locale choice for the interaction. This is a natural-language policy concern because it appears to prescribe a specific language by default rather than making the interaction language optional or user-selected.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill advertises extensive capabilities involving file access, environment-variable reads, network egress, and command execution, but it does not declare a restrictive tool scope such as permissions or allowed-tools. That leaves enforcement to documentation and operator discipline rather than platform controls, increasing the chance the agent can access broader capabilities than intended.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This manifest file contains multiple natural-language strings that require Chinese comprehension, including operational guidance and mode descriptions. That effectively forces a specific language for users or operators without any opt-in, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

Line L01 presents the profile file title and instructions entirely in Chinese, indicating a language expectation for the skill artifact. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless clearly justified or optional.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

该文件是 markdown,因此适用 Vague Triggers 规则。L022 将启动方式描述为对 agent 说“帮我做求职监控”,这是自然语言请求而非受限命令,文中也未给出不应触发的反例或更严格的触发范围,容易在一般职业咨询场景下与普通对话重叠。

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.