Back to skill

Security audit

agent-notify

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent voice-notification skill, but it runs a persistent monitor that reads recent agent conversations and can send prompt snippets, host metadata, and an auth code to a configured WebSocket endpoint, including plaintext endpoints.

Install only if you are comfortable with a background process reading recent agent session files and sending task labels to your own notification backend. Prefer a trusted wss:// endpoint, use a revocable auth code, avoid sensitive prompts while it is running, check `notify.py status`, and stop it with `notify.py stop` when you no longer need monitoring.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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)

T09 · Insecure Skill Coding Practices

Error
Location
notify.py:269
Finding

Transcript-Derived Data and Authentication Credentials May Be Sent over Plaintext WebSocket

Content
View full analysis

Vulnerability Details

File Location: notify.py:269-285, notify.py:467-481; SKILL.md:31-32
Vulnerability Type: Plaintext transmission of sensitive information to an unrestricted endpoint
Risk Level: High

Vulnerable Code

python
ws = await asyncio.wait_for(
    websockets.connect(cfg["url"], proxy=cfg.get("proxy")), timeout
)

hello = {
    "mac": cfg["device_id"],
    "code": cfg["auth_code"],
    "agent": cfg.get("agent", "unknown"),
    "host": os.uname().nodename,
    "v": 1,
}
await ws.send(json.dumps(hello))

Transcript-derived task information is subsequently included in the outbound event:

python
return {
    "type": "task_status",
    "agent": agent,
    "task": task,
    "status": status,
    "time": dt.isoformat(timespec="seconds"),
    "detail": detail,
    "text": TEMPLATE.format(
        agent=AGENT_CN.get(agent, agent),
        task=task,
        status=STATUS_CN.get(status, status),
        time=_spoken_time(dt),
    ),
}

The documented setup example permits a plaintext endpoint:

bash
python3 <SKILL_DIR>/notify.py setup \
  --url ws://192.168.1.10:9004/agent/notify \
  --device-id aa:bb:cc:dd:ee:ff \
  --auth-code XXXX

Technical Analysis

The application accepts an arbitrary WebSocket URL and does not require the encrypted wss:// scheme. During connection establishment, it sends the authorization code, device identifier, detected agent name, and local hostname. Later status frames contain task text extracted from agent conversation records.

When ws:// is used, WebSocket traffic has no transport-layer confidentiality or integrity. A network-positioned attacker can observe or modify authentication and notification frames. The unrestricted destination also means that a user who is induced to configure an attacker-controlled endpoint will disclose the same data directly to that endpoint.

Certificat ...[truncated 1357 chars]

Remediation
View remediation

Remediation Suggestions

  • Require wss:// for all non-loopback destinations and reject plaintext remote endpoints.
  • Retain normal TLS certificate and hostname verification.
  • Optionally support explicit certificate or public-key pinning for private deployments.
  • Display the destination and a preview of the information that will be transmitted before enabling monitoring.
  • Use narrowly scoped, short-lived, revocable credentials rather than reusable authorization codes.
  • Bind credentials to the intended device on the server and apply replay protection.
  • Redact common secret formats and sensitive identifiers from task text before transmission.
  • Provide a mode that transmits only a user-supplied task label and status, without conversation-derived text or hostname metadata.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
notify.py:205
Finding

Monitoring Scope Includes All Recent Session Transcripts for the Selected Agent

Content
View full analysis

Vulnerability Details

File Location: notify.py:205-213, notify.py:395-436
Vulnerability Type: Overbroad access to agent conversation records
Risk Level: High

Vulnerable Code

python
ADAPTERS = {
    "claude-code": {"glob": "~/.claude/projects/*/*.jsonl", "parse": _parse_claude},
    "hermes": {"glob": "~/.hermes/sessions/session_*.json", "parse": _parse_hermes},
    "codex": {"glob": "~/.codex/sessions/**/*.jsonl", "parse": _parse_jsonl_generic},
    "openclaw": {"glob": "~/.openclaw/sessions/**/*.json*", "parse": _parse_jsonl_generic},
}

The scan applies the selected glob recursively and parses every recently modified matching file:

python
for path in globmod.glob(pattern, recursive=True):
    try:
        mtime = os.path.getmtime(path)
    except OSError:
        continue
    if now - mtime > ACTIVE_WINDOW:
        continue

    state, task = (parse(path) if parse else ("unknown", ""))
    idle = now - mtime
    key = os.path.basename(path)
    seen = prev.get(key) or {}

Technical Analysis

The implementation does not bind monitoring to a session explicitly selected by the user. Instead, it enumerates all matching session files for the configured agent and processes every file modified within the active six-hour window.

The transcript parsers read each selected file and retain the latest recognized user text as the task label. Status events then transmit this label to the configured server. Consequently, enabling the daemon once may cover unrelated conversations that happen to be active under the same agent account.

This violates least-privilege principles because task notification only requires status for an enrolled task or session, not general access to all recent transcripts.

Attack Path

  1. A user enables the notification daemon for one intended task.
  2. The configured adapter expands a wildcard covering all recent sessions for that agent. ...[truncated 1039 chars]
Remediation
View remediation

Remediation Suggestions

  • Require explicit per-session enrollment and store the canonical path or session identifier selected by the user.
  • Prefer a local event hook or IPC mechanism that receives only a status and user-defined task label.
  • Do not derive notification text from full conversation transcripts by default.
  • Allow users to provide a separate, non-sensitive notification label.
  • Apply secret detection and redaction before constructing outbound events.
  • Show the exact monitored session list in the status command.
  • Add allowlist and denylist controls for projects and session paths.
  • Add tests proving that unrelated concurrent sessions are neither parsed nor transmitted.

T06 · System Persistence

Error
Location
notify.py:548
Finding

Detached Monitoring Daemon Continues Across Agent Sessions

Content
View full analysis

Vulnerability Details

File Location: notify.py:548-560; SKILL.md:58-59
Vulnerability Type: Cross-session background persistence
Risk Level: High

Vulnerable Code

python
os.makedirs(DIR, exist_ok=True)
log = open(LOGFILE, "a", buffering=1)
p = subprocess.Popen(
    [sys.executable, os.path.abspath(__file__), "_daemon"],
    stdout=log, stderr=log, stdin=subprocess.DEVNULL,
    start_new_session=True,
)
with open(PIDFILE, "w") as f:
    f.write(str(p.pid))
time.sleep(1.5)

Technical Analysis

The process is deliberately launched with start_new_session=True, disconnected standard input, and log-file redirection. It therefore survives termination of the terminal or agent interaction that launched it. The background process continues to scan newly modified conversation files, send notifications, and reconnect after network interruptions until explicitly stopped or otherwise terminated.

The persistence is documented and does not install an operating-system startup service, scheduled task, or boot-time autostart entry. Nevertheless, it creates a cross-session monitoring process whose ongoing data access may no longer be visible in the interaction that initiated it.

Attack Path

  1. The user or agent runs the start subcommand.
  2. The script creates a new detached process session and writes its PID to a file.
  3. The original terminal or agent session exits.
  4. The detached daemon remains active under the user's account.
  5. It continues scanning newly modified transcript files and sending inferred status events.
  6. Monitoring continues until stop is run, the process exits, or the host is restarted.

Impact Assessment

The daemon retains the same file and network privileges as the invoking user. It does not elevate privileges and does not demonstrate persistence across a system reboot. Its practical scope is continued access to supported agent transcript directories an ...[truncated 204 chars]

Remediation
View remediation

Remediation Suggestions

  • Make foreground, session-bound execution the default.
  • Require explicit, informed confirmation before launching a detached monitor.
  • Add an automatic expiration time and require renewal for continued monitoring.
  • Restrict the daemon to explicitly enrolled session identifiers.
  • Display a persistent and easily discoverable indicator while monitoring is active.
  • Record the start time, scope, destination, and expiry in the status output.
  • Ensure stale PID files are handled safely and verify process identity before sending termination signals.
  • Provide shutdown hooks tied to the initiating agent session where supported.

T08 · Insecure Dependencies

Warning
Location
SKILL.md:183
Finding

Dependency Installation Instruction Does Not Pin or Verify the Package

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:183-185
Vulnerability Type: Unpinned and unverifiable third-party dependency installation
Risk Level: Medium

Vulnerable Code

bash
pip install websockets

Technical Analysis

The documentation instructs users to install the websockets package without a version constraint, lockfile, artifact hash, approved index, or isolated environment. The installed artifact can therefore vary according to installation time, package-index configuration, dependency resolution, and future releases.

This does not prove that the named package is malicious. The issue is that the instruction provides no reproducible or cryptographically verified dependency baseline. A compromised package release, unsafe package mirror, or maliciously configured package index could introduce code that executes during installation or when imported by notify.py.

Attack Path

  1. The required module is unavailable on the user's system.
  2. The user follows the documented pip install websockets instruction.
  3. Pip resolves the package from the user's configured index without a required version or hash.
  4. A compromised index or release supplies an altered distribution.
  5. Package-controlled installation behavior or imported runtime code executes with the user's privileges.
  6. The malicious dependency can access the same files, credentials, and network resources available to the notification process.

Impact Assessment

Exploitation would execute dependency-controlled code with the privileges of the user running pip or the application. The scope may include user-owned files, agent transcripts, notification credentials, and outbound network access.

If a user runs the installation command with elevated privileges, the impact could increase substantially; however, the project does not instruct users to use elevated privileges.

Remediation
View remediation

Remediation Suggestions

  • Pin a reviewed package version, such as an exact websockets release.
  • Maintain a lockfile with transitive dependency versions.
  • Require package hashes using a hash-locked requirements file.
  • Document the trusted package index explicitly.
  • Install dependencies in a dedicated virtual environment.
  • Add automated dependency vulnerability and integrity scanning.
  • Review and deliberately update dependency pins instead of accepting uncontrolled future releases.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (12)

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · notify.py (reported line 688)May include surrounding context.

python
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    time.sleep(1.2)
    try:
        env = dict(os.environ)
        tmp_home = tempfile.mkdtemp(prefix="agent-notify-selftest-")
        env["HOME"] = tmp_home
        me = os.path.abspath(__file__)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill clearly instructs the agent to run shell commands, inspect environment variables, write persistent configuration under the user's home directory, and start a long-lived daemon, yet it declares no explicit tool scope or permission boundaries. This creates a permission-transparency problem: a user or host platform may invoke the skill without realizing it needs shell, file write, and environment access, increasing the chance of unintended execution or over-privileged use.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger list contains broad natural-language phrases such as '干完告诉我', '语音播报', and '播报状态' that could match ordinary conversation rather than an intentional request to enable a background-monitoring skill. Because this skill can configure endpoints, persist credentials, and launch a daemon, accidental activation is more dangerous here than in a harmless read-only skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Although the body later mentions reading session files and that the daemon survives agent exit, the skill description and activation surface do not prominently warn the user up front that it continuously monitors local session records and keeps running in the background. That lack of conspicuous disclosure undermines informed consent for behavior that affects privacy and persistence, especially because it monitors multiple agents via shared config.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The module docstring and protocol example specify Chinese narration text, and later code constructs all spoken notifications in Chinese. Because this file is code, these natural-language strings count for policy review, and there is no user opt-in or language selection mechanism documented here.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill derives the spoken task name from session/user content and later transmits it to the backend and a voice device. That can leak sensitive prompts, secrets, filenames, customer data, or internal project details to another system and aloud in the physical environment, especially because the feature is designed to auto-broadcast without a per-message consent check.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The TEMPLATE, STATUS_CN, AGENT_CN, and _spoken_time logic generate notifications exclusively in Chinese with Chinese time phrasing such as “上午/下午/晚上”. This enforces a specific language/locale without presenting a choice or opt-in, which matches the language-policy violation category.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The event payload explicitly embeds user-derived task text into a server-bound 'text' field intended for direct TTS playback. This creates a concrete confidentiality risk because potentially sensitive session content is both transmitted off-host and audibly disclosed to anyone near the device, and the backend is instructed to speak it verbatim.

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 · notify.py (reported line 552)May include surrounding context.

python
return 0
    os.makedirs(DIR, exist_ok=True)
    log = open(LOGFILE, "a", buffering=1)
    p = subprocess.Popen(
        [sys.executable, os.path.abspath(__file__), "_daemon"],
        stdout=log, stderr=log, stdin=subprocess.DEVNULL,
        start_new_session=True,   # 脱离终端,agent 退出后它还活着

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 · notify.py (reported line 684)May include surrounding context.

python
srv_py = os.path.join(tempfile.gettempdir(), "agent_notify_fake_server.py")
    with open(srv_py, "w") as f:
        f.write(code)
    srv = subprocess.Popen([sys.executable, srv_py, str(port)],
                           stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    time.sleep(1.2)
    try:

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 · notify.py (reported line 695)May include surrounding context.

python
url = f"ws://127.0.0.1:{port}/agent/notify"

        def run(*a):
            return subprocess.run([sys.executable, me, *a], env=env,
                                  capture_output=True, text=True)

        print("── 1. 错误授权码应当被拒")

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code's natural-language documentation and assertions are written entirely in Chinese, and the tests explicitly require generated text to be converted into spoken Chinese phrasing. That indicates a language-specific behavior without any visible user choice or opt-in, which matches the locale/language policy concern.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
test_scan.py:11