Back to skill

Security audit

OpenClaw Zulip Channel Plugin

Security checks for vulnerabilities and agentic risk

Overview

This Zulip connector mostly matches its stated purpose, but it needs review because its documented sender controls appear unenforced while it can forward chat messages and mutate stored metadata.

Review before installing. Use it only where everyone who can message the Zulip bot is allowed to invoke the agent and manage topic metadata, require an HTTPS Zulip realm, avoid committing the API key, restrict the bot's stream access, rotate credentials if exposed, and update or regenerate the dependency lockfile from an approved registry.

Vulnerability Patterns
  • 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
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/channel.ts:122
Finding

Configured inbound access policies are not enforced

Content
View full analysis
{ const msg = normalizeZulipEvent(event, ownUserId); // Intercept /meta commands before AI dispatch const stripped = msg.text.replace(/<[^>]*>/g, "").trim(); if (stripped.startsWith("/meta ") || stripped === "/meta") { const streamId = event.message.stream_id; const topicName = event.message.subject; if (streamId != null && topicName) { const response = handleMetaCommand( metadataStore, { streamId, topicName }, stripped, ); const target = buildInboundTarget(msg, accountId); await sendMessageZulip(target, response, { accountId }); } return; } const target = buildInboundTarget(msg, accountId); ctx.log?.info?.(`zulip: inbound from ${msg.senderEmail} → ${target}`); if (ctx.channelRuntime) { const runtime = ctx.channelRuntime as unknown as { reply: { finalizeInboundContext: (p: Record) => unknown; dispatchReplyWithBufferedBlockDispatcher: (p: Record) => Promise; }; }; const ctxPayload = runtime.reply.finalizeInboundContext({ Body: msg.text, BodyForAgent: msg.text, RawBody: msg.text, CommandBody: msg.text, From: msg.senderEmail, To: `zulip:${accountId}`, SessionKey: target, AccountId: accountId, ChatType: msg ...[truncated 2551 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/channel.ts:125
Finding

Metadata commands bypass sender authorization

Content
View full analysis
]*>/g, "").trim(); if (stripped.startsWith("/meta ") || stripped === "/meta") { const streamId = event.message.stream_id; const topicName = event.message.subject; if (streamId != null && topicName) { const response = handleMetaCommand( metadataStore, { streamId, topicName }, stripped, ); const target = buildInboundTarget(msg, accountId); await sendMessageZulip(target, response, { accountId }); } return; } ``` ```ts export function handleMetaCommand( store: MetadataStore, ctx: CommandContext, text: string, ): string { const parsed = parseMetaCommand(text); if (isParseError(parsed)) { return `❌ ${parsed.error}`; } return executeCommand(store, ctx, parsed); } function executeCommand( store: MetadataStore, ctx: CommandContext, cmd: ParsedCommand, ): string { switch (cmd.action) { case "show": { const meta = store.get(ctx.streamId, ctx.topicName); if (!meta) { return `📋 No metadata for topic: ${ctx.topicName}`; } return formatMetadata(meta); } case "set": { const meta = store.upsert(ctx.streamId, ctx.topicName, cmd.updates); return `✅ Updated metadata\n${formatMetadata(meta)}`; } case "label": { const meta = cmd.op === "add" ? store.addLabel(ctx.streamId, ctx.topicName, cmd.tag) : store.removeLabel(ctx.streamId, ctx.topicName, cmd.tag); const verb = cmd.op === "add" ? "Added" : "Removed"; return `🏷️ ${verb} label: ${cmd.tag}\n${formatMetadata(meta)}`; } case "list": { const items = store.list(ctx.streamId, cmd.filter); if (items.length = ...[truncated 2098 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
src/client.ts:41
Finding

Unrestricted realm URL can transmit Basic-authentication credentials over plaintext HTTP

Content
View full analysis
(path: string, timeout?: number): Promise { const res = await this.rawFetch(`${this.baseUrl}${path}`, { method: "GET", headers: { Authorization: this.authHeader }, signal: AbortSignal.timeout(timeout ?? this.timeout), }); return this.parseResponse(res); } private async post( path: string, body: URLSearchParams, ): Promise { const res = await this.rawFetch(`${this.baseUrl}${path}`, { method: "POST", headers: { Authorization: this.authHeader, "Content-Type": "application/x-www-form-urlencoded", }, body, signal: AbortSignal.timeout(this.timeout), }); return this.parseResponse(res); } private async rawFetch( url: string, init: RequestInit, ): Promise { const maxRetries = 5; let attempt = 0; while (true) { const res = await fetch(url, init); if (res.status !== 429 || attempt >= maxRetries) { return res; } const retryAfter = res.headers.get("Retry-After"); const baseMs = retryAf ...[truncated 2132 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
src/channel.ts:112
Finding

Fetched bot identity is not passed to the gateway self-message filter

Content
View full analysis
{ const msg = normalizeZulipEvent(event, ownUserId); // Message processing and agent dispatch omitted here. }, onTopicRename: async (streamId, oldTopic, newTopic) => { syncPrefixToMetadata(metadataStore, streamId, oldTopic, newTopic); ctx.log?.info?.( `zulip: topic renamed: "${oldTopic}" → "${newTopic}"`, ); }, onError: (err) => { ctx.log?.info?.(`zulip: gateway error: ${String(err)}`); }, onConnected: (info) => { ctx.log?.info?.(`zulip: connected (queue=${info.queueId})`); }, log: (msg) => ctx.log?.info?.(msg), }, { abortSignal: ctx.abortSignal }, ); ``` ```ts if (event.type === "message") { const msgEvent = event; // Skip own messages if ( opts?.ownUserId != null && msgEvent.message.sender_id === opts.ownUserId ) { continue; } try { await callbacks.onMessage(msgEvent); } catch (err) { callbacks.onError?.(err); } } ``` ### Technical Analysis `channel.ts` retrieves the bot's own Zulip user ID, and `gateway.ts` contains an explicit self-message filter. However, the gateway is started with only `abortSignal`; `ownUserId` is not included in the options object. Consequently, `opts?.ownUserId` is always undefined in this integration path and the self-message filter cannot act ...[truncated 1050 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
package-lock.json:20
Finding

Lockfile retrieves the development dependency tree from a noncanonical package mirror

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says this skill is a Zulip integration plugin with messaging/threading, metadata DB, bot commands, and event queue behavior. However, the supplied code chunk contains only a TypeScript barrel export for functions/constants from secret-contract.js. Nothing in this snippet indicates Zulip connectivity, bot command handling, event processing, or metadata database functionality. Instead, it appears to expose secret/configuration-related contract utilities, which is materially different from the declared primary purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description says the skill is a Zulip channel plugin with messaging/threading, metadata DB, bot commands, and event queue processing. The supplied code does not show any Zulip integration or channel/plugin behavior. Instead, it only re-exports secret-contract utilities related to secrets and runtime configuration. That is a materially different purpose from the declared description, so this is a clear mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description presents a full OpenClaw Zulip channel plugin with substantial runtime capabilities. The supplied code chunk only contains unit tests for a probeZulip function that checks Zulip server connectivity/version and error handling. That is materially different from the declared primary purpose, and none of the listed plugin features are present in this code. While probing connectivity could be a supporting detail in a Zulip integration, this specific chunk is test code and does not match the described functionality.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description is for a full Zulip/OpenClaw integration plugin, but the provided code chunk is merely a Vitest configuration file. It configures which test files to run and the test environment, and does not implement or expose any of the declared plugin capabilities. This is a materially different primary purpose, so it should be flagged as a mismatch.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: undici==7.25.0 — 12 advisory(ies): CVE-2026-6733 (undici vulnerable to HTTP response queue poisoning via keep-alive socket reuse); CVE-2026-13697 (undici vulnerable to cross-user information disclosure and parse-time crash via ); CVE-2026-16728 (undici vulnerable to downstream response desynchronization via retry interceptor) +9 more

High
Category
Supply Chain
Confidence
94% confidence
Finding

undici 7.25.0 is associated with multiple high-severity HTTP client issues, including response desynchronization and information disclosure. Because this package appears in agent-related transitive dependencies that likely perform network I/O, the dependency is materially relevant in a chat/integration plugin ecosystem and could affect outbound HTTP interactions or shared connection handling.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: @mariozechner/pi-coding-agent==0.70.2 — 3 advisory(ies): CVE-2026-54326 (Pi Agent: Potential XSS in HTML session exports via Markdown URL sanitization by); CVE-2026-54328 (Pi Agent: Predictable temporary extension install paths allow local privilege es); CVE-2026-54327 (Pi Agent: Race condition in Pi auth.json writes could expose stored credentials)

High
Category
Supply Chain
Confidence
92% confidence
Finding

@mariozechner/pi-coding-agent 0.70.2 carries several high-severity advisories involving XSS in HTML exports, predictable temporary extension paths, and credential exposure via auth.json race conditions. Even though this looks transitive via openclaw development/runtime components, these are real issues in an agent framework context because such software commonly processes untrusted content and stores credentials locally.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: basic-ftp==5.3.0 — 1 advisory(ies): CVE-2026-44240 (basic-ftp allows a malicious FTP server to cause client-side denial of service v)

High
Category
Supply Chain
Confidence
85% confidence
Finding

basic-ftp 5.3.0 is reported vulnerable to malicious-server-triggered denial of service. This dependency is transitive through proxy/PAC tooling rather than obviously used by the plugin itself, which lowers immediate exposure, but if any runtime path allows fetching via attacker-controlled FTP URIs the issue could be triggered.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · src/client.ts (reported line 154)May include surrounding context.

ts
* 1. POST /register → get queue_id + last_event_id
 * 2. Loop: GET /events (blocking) → yield message events
 * 3. On BAD_EVENT_QUEUE_ID → re-register
 * 4. On stopAccount → DELETE /events
 */

import { ZulipClient, ZulipApiRequestError } from "./client.js";

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · src/gateway.ts (reported line 8)May include surrounding context.

ts
* 1. POST /register → get queue_id + last_event_id
 * 2. Loop: GET /events (blocking) → yield message events
 * 3. On BAD_EVENT_QUEUE_ID → re-register
 * 4. On stopAccount → DELETE /events
 */

import { ZulipClient, ZulipApiRequestError } from "./client.js";

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This markdown file describes a plugin that connects an agent to Zulip, receives direct messages, routes messages through streams/topics, and stores per-topic metadata in SQLite. The documentation does not include any warning that message content and metadata will be transmitted to a third-party chat service and persisted locally, which is a user-relevant privacy and data-handling concern.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
79% confidence
Finding

The skill clearly describes network-capable behavior by connecting to a Zulip organization, polling events, and handling bot commands, yet it does not declare any explicit tool scope such as permissions or allowed-tools. Missing capability scoping weakens trust boundaries and reviewability, making it easier for consumers to enable networked behavior without clear authorization expectations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The configuration example includes a raw Zulip API key field without any warning about secret handling, storage, or avoiding commits to source control. In practice, users frequently copy such examples into tracked config files, which can lead to credential leakage and unauthorized access to the Zulip bot account and associated organization data.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest declares the plugin id as "kagura-zulip" while the provided skill metadata identifies it as "openclaw-zulip". Identity mismatches in plugin manifests can cause the wrong component to be installed, trusted, or updated, and may let a malicious or unintended plugin masquerade as another integration in build, deployment, or review workflows.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The plugin forwards full message bodies and multiple user identifiers (email, sender ID, sender name, session/target context) into the AI runtime with no visible consent, minimization, or warning in this code path. In a chat connector skill, this can expose personal and potentially sensitive organizational content to downstream AI processing unexpectedly, increasing privacy and compliance risk.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: @anthropic-ai/sdk==0.90.0 — 1 advisory(ies): CVE-2026-41686 (Claude SDK for TypeScript has Insecure Default File Permissions in Local Filesys)

Low
Category
Supply Chain
Confidence
87% confidence
Finding

The lockfile pins @anthropic-ai/sdk to 0.90.0, and the cited issue describes insecure default file permissions for local filesystem artifacts. In a package-lock this is a real supply-chain exposure rather than an implementation bug in this repository, though the practical risk depends on whether the SDK CLI or local file-writing paths are actually used at runtime.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: @hono/node-server==1.19.14 — 1 advisory(ies): GHSA-frvp-7c67-39w9 (Node.js Adapter for Hono: Path traversal in `serve-static` on Windows via encode)

Low
Category
Supply Chain
Confidence
79% confidence
Finding

@hono/node-server 1.19.14 is flagged for a Windows path traversal issue in serve-static. This is a genuine vulnerable dependency, but in this skill context it appears only as a transitive dependency of tooling/openclaw rather than evidence that the plugin itself exposes static file serving, so exploitability is limited by deployment context.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: @protobufjs/utf8==1.1.0 — 1 advisory(ies): CVE-2026-44288 (protobufjs has overlong UTF-8 decoding)

Low
Category
Supply Chain
Confidence
73% confidence
Finding

@protobufjs/utf8 1.1.0 is flagged for overlong UTF-8 decoding, which is a real parsing weakness. In this lockfile it is only a low-impact transitive issue with unclear reachability, and there is no direct evidence from this file alone that attacker-controlled protobuf decoding is exposed by the Zulip skill.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: @vitest/mocker==3.2.4 — 1 advisory(ies): CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock)

Low
Category
Supply Chain
Confidence
76% confidence
Finding

@vitest/mocker 3.2.4 has a reported path traversal/arbitrary file read issue, but it is a dev/test dependency only. This is still a real vulnerable component in the supply chain, though risk is primarily limited to test or local developer environments rather than the deployed Zulip integration.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: body-parser==2.2.2 — 1 advisory(ies): CVE-2026-12590 (body-parser vulnerable to denial of service when invalid limit value silently di)

Low
Category
Supply Chain
Confidence
78% confidence
Finding

body-parser 2.2.2 is flagged for a denial-of-service condition involving invalid limit handling. This is a genuine issue in the dependency, but within this skill file it is transitive and there is no evidence that this package directly exposes Express request parsing in the Zulip plugin itself, so the practical risk is modest.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 17)May include surrounding context.

json
},
  "devDependencies": {
    "openclaw": "2026.4.24",
    "typescript": "^6.0.3",
    "vitest": "^3.1.1"
  },
  "scripts": {

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 18)May include surrounding context.

json
"devDependencies": {
    "openclaw": "2026.4.24",
    "typescript": "^6.0.3",
    "vitest": "^3.1.1"
  },
  "scripts": {
    "test": "vitest run",

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The manifest describes a Zulip channel plugin with topic threading, metadata DB, bot commands, and event queue processing, so metadata support is in scope. However, this file concretely creates and persists a SQLite database under the user's home directory, which is a material write-to-disk behavior not reflected in the channel/plugin metadata blurb here and could matter to operators evaluating what the skill does.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The remove method deletes records from the database, which is a destructive operation. In this file, the deletion has no confirmation prompt, user-facing log/print, or explanatory comment/docstring warning about the action.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The handleRename method performs a persistent update to stored metadata and records the rename in history, affecting existing data. This file provides no confirmation, user-facing logging, or explanatory comment/docstring disclosing that stored topic names will be changed.

Content

No source excerpt is available for this finding.