Back to skill

Security audit

Bulletin Tools

Security checks for vulnerabilities and agentic risk

Overview

This is a real multi-agent bulletin tool, but it needs review because its agent-wake interfaces and bulletin access controls are too broad for the authority they exercise.

Install only after reviewing whether your OpenClaw gateway token holders should be allowed to wake any agent with arbitrary task text, and whether bulletin content may contain sensitive data. Use least-privilege Discord and gateway tokens, restrict who can post or call gateway routes, avoid placing secrets or regulated data in bulletins, and treat the local bulletin database and audit logs as sensitive records.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (4)

T01 · Skill Instruction Hijacking

Error
Location
index.ts:211
Finding

Untrusted Bulletin Content Is Executed as Agent Instructions

Content
View full analysis
): string { const sections: string[] = [ `You have ${bulletins.length} pending bulletin(s) requiring your response.`, `For each bulletin below, call the \`bulletin_respond\` tool with your assessment.`, `Do nothing else — respond to all bulletins and stop.`, "", ]; for (const b of bulletins) { sections.push(`---`); sections.push(`## [${b.id}] ${b.topic}`); sections.push(``); sections.push(b.body); sections.push(``); if ((b as any).round === "critique") { sections.push(`### All Discussion Responses`); for (const r of b.responses) { const pos = (r as any).position ?? "align"; const posTag = pos === "oppose" ? " ⚠️ **[OPPOSE]**" : pos === "partial" ? ` ~ **[PARTIAL — ${((r as any).reservations ?? "").slice(0, 60)}]**` : ""; sections.push(`- **${r.agentId}**${posTag}: ${((r as any).body ?? "").slice(0, 300)}`); } } } return sections.join("\n"); } ``` The resulting prompt is passed directly to an autonomous agent: ```ts async function wakeBulletinSubscriber( agentId: string, bulletins: Array<{ id: string; topic: string; body: string; responses: any[]; resolvedSubscribers: string[] }>, label: string, ): Promise { const task = buildBulletinTaskPrompt(bulletins); const bulletinIds = bulletins.map(b => b.id); const jobLabel = `${bulletinIds.join("-")}-${agentId}-${label}`; const result = await doBulletinWake(agentId, task, jobLabel); ``` ### Technical Analysis Bulletin topics, bodies, prior responses, reservations, ...[truncated 2186 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
index.ts:868
Finding

Gateway Wake Interfaces Allow Arbitrary Prompts to Run Under a Selected Agent Identity

Content
View full analysis
{ if (!agentId || !task || !label) { return { ok: false, error: { code: "INVALID_PARAMS", message: "agentId, task, and label are required" } }; } const sessionKey = `agent:${agentId}:bulletin:${label}`; try { const result = await subagent.run({ sessionKey, message: task, idempotencyKey: `bulletin-wake-${label}-${Date.now()}`, }); auditLog(`WAKE agent=${agentId} label=${label} runId=${result.runId}`); console.log(`[bulletin-tools] Woke '${agentId}' via subagent.run: ${sessionKey}`); return { ok: true, payload: { status: "ok", runId: result.runId, sessionKey } }; } catch (err) { const msg = err instanceof Error ? err.message : String(err); console.error(`[bulletin-tools] bulletin_wake failed for '${agentId}': ${msg}`); return { ok: false, error: { code: "WAKE_FAILED", message: msg } }; } } api.registerGatewayMethod("bulletin_wake", async ({ params, respond }) => { const result = await doBulletinWake( params.agentId as string, params.task as string, params.label as string, ); if (result.ok) { respond(true, result.payload); } else { respond(false, undefined, result.error); } }); api.registerHttpRoute({ path: "/bulletin/wake", auth: "gateway", match: "exact" as const, handler: async (req: any, res: any) => { if (req.method !== "POST") { res.statusCode = 405; res.end(JSON.stringify({ ok: false, error: "method not allowed" })); return true; } const body = await new Promise((resolve) => { let data = ""; req.on("dat ...[truncated 2864 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Error
Location
index.ts:1089
Finding

Bulletin Sessions Remove Normal Bootstrap Context and Safety Guidance

Content
View full analysis
{ if (!event.sessionKey?.includes(":bulletin:")) return; // Clear all bootstrap files — the task prompt has everything the agent needs if (event.context?.bootstrapFiles) { // Keep only IDENTITY.md or SOUL.md if present (minimal agent identity) event.context.bootstrapFiles = event.context.bootstrapFiles.filter( (f: { name: string }) => f.name === "IDENTITY.md" || f.name === "SOUL.md", ); } console.log(`[bulletin-tools] Lightweight bootstrap for ${event.sessionKey} (${event.context?.bootstrapFiles?.length ?? 0} files kept)`); }, { name: "bulletin-tools.lightweight-bootstrap", description: "Strips bootstrap files for bulletin wake sessions to reduce context", }, ); ``` ### Technical Analysis The hook detects any session key containing `:bulletin:` and removes all bootstrap files except `IDENTITY.md` and `SOUL.md`. This is not limited to known session objects created from a validated bulletin record. Bootstrap files may contain project constraints, operational policies, tool-use restrictions, privacy requirements, or other instructions needed to process untrusted content safely. The plugin assumes that its generated task prompt contains everything necessary, even though that prompt includes untrusted bulletin bodies and responses. This weakens defense in depth precisely in sessions exposed to stored prompt injection. It also means that any caller capable of producing a matching session key can trigger the reduced-context behavior. ### Attack Path 1. An attacker authors a malicious bulletin or invokes the wake interface with an attacker-controlled task. 2. The plugin starts a session whose key contains `:bulletin:`. 3. The `ag ...[truncated 815 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
index.ts:801
Finding

Direct Bulletin Lookup Bypasses Subscriber Authorization

Content
View full analysis
({ agentId: r.agentId, position: r.position ?? "align", body: r.body?.slice(0, 500), reservations: r.reservations, timestamp: r.timestamp, })), critiques: (b.critiques ?? []).map((c) => ({ agentId: c.agentId, position: c.position ?? "align", body: c.body?.slice(0, 500), reservations: c.reservations, })), yourStatus: (b.responses ?? []).some((r) => r.agentId === agentId) ? "responded" : "pending", }; ``` ### Technical Analysis The requesting agent identity is derived from the tool context, but direct lookup does not verify that the agent appears in `bulletin.resolvedSubscribers`. This differs from response submission, which explicitly checks subscriber membership, and from status-filtered history, which supplies `agentId` as a database filter. Calling `formatBulletinForAgent()` does not enforce access control. It merely computes the requesting agent's response status while returning the complete bulletin body, subscriber list, responses, critiques, and res ...[truncated 1069 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (21)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented purpose presents the skill as a bulletin-board coordination tool, but the behavior includes reading local secrets, sending data to external platforms, exposing an authenticated wake endpoint, making loopback HTTP requests, writing persistent audit logs, and scheduling autonomous actions. This mismatch is dangerous because operators may install it expecting simple local coordination while unintentionally granting credential, network, and persistence capabilities with meaningful security and privacy consequences.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
93% confidence
Finding

The skill explicitly requires access to ~/.openclaw/secrets.json, indicating it can read a consolidated local secrets store in addition to service tokens. For a bulletin/coordination skill, direct access to a broad secrets file expands blast radius significantly: if the skill is compromised, misused, or overly permissive, unrelated credentials could be exposed or transmitted externally.

Content

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

md
config:
        - ~/.openclaw/mailroom/bulletin-config.json
        - ~/.openclaw/mailroom/agent-groups.json
        - ~/.openclaw/secrets.json
    primaryEnv: DISCORD_BOT_TOKEN
    install:
      - kind: node

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · CLAUDE.md (reported line 38)May include surrounding context.

md
if (_secrets) return _secrets;
  try {
    _secrets = JSON.parse(
      readFileSync(join(homedir(), ".openclaw", "secrets.json"), "utf-8"),
    ) as Record<string, string>;
  } catch {
    _secrets = {};

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

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

md
if (_secrets) return _secrets;
  try {
    _secrets = JSON.parse(
      readFileSync(join(homedir(), ".openclaw", "secrets.json"), "utf-8"),
    ) as Record<string, string>;
  } catch {
    _secrets = {};

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · index.ts (reported line 33)May include surrounding context.

ts
if (_secrets) return _secrets;
  try {
    _secrets = JSON.parse(
      readFileSync(join(homedir(), ".openclaw", "secrets.json"), "utf-8"),
    ) as Record<string, string>;
  } catch {
    _secrets = {};

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · index.ts (reported line 43)May include surrounding context.

ts
if (_secrets) return _secrets;
  try {
    _secrets = JSON.parse(
      readFileSync(join(homedir(), ".openclaw", "secrets.json"), "utf-8"),
    ) as Record<string, string>;
  } catch {
    _secrets = {};

Ssd 3

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The single-bulletin lookup path returns full bulletin content for any provided bulletinId without checking that the caller is a resolved subscriber or otherwise authorized to view it. This is a direct authorization flaw that allows an agent to enumerate or guess IDs and access bulletin contents, responses, and critiques outside its intended scope.

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

md
### Agent-initiated bulletins

The CLI (`bulletin-post`) is one way to create bulletins. But the real power is when agents post them on their own.

An agent working on a task encounters a decision it shouldn't make unilaterally. Instead of guessing or blocking, it creates a bulletin: "Here's the situation, here are the options, here's what I think — what does everyone else see?" The relevant agents respond, the critique round validates the direction, and the original agent proceeds with structured backing.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The README advertises a full audit trail with SQLite storage and audit logging but does not warn that bulletin content, agent positions, reasoning, and timestamps may contain sensitive data and be retained locally. In this skill's context, deliberation threads could easily include internal design decisions, credentials-adjacent details, or regulated business information, making undocumented retention a meaningful privacy and security risk.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
78% confidence
Finding

The README explicitly states there is no plugin-level access control beyond the subscriber list and that Discord channel visibility is the privacy boundary. In a tool handling asynchronous multi-agent discussions and audit trails, relying entirely on external channel permissions can expose sensitive deliberation data to over-broad audiences or allow improperly subscribed agents to participate without finer-grained authorization.

Content

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

md
### Private/restricted bulletins

Discord's built-in channel permissions handle access control. Create a private channel and use its ID as the `bulletinBoardChannel` in a separate config, or use `closedNotify` to route results to restricted channels.

There is no plugin-level access control beyond the subscriber list — if an agent is subscribed, it can respond. Channel-level visibility in Discord is your privacy boundary.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The documentation first states that the config schema and routing code have branches for Discord, Slack, Telegram, Signal, iMessage, and WhatsApp, including described fallback behavior for some platforms. It then immediately says 'Currently implemented and tested: Discord only,' which contradicts the earlier claim of implemented routing behavior for additional platforms rather than merely planned support.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The README describes automatic agent waking and an HTTP fallback to a Gateway endpoint without clearly warning users that posting a bulletin can trigger outbound network calls and transmit task context. In a multi-agent coordination tool, that context may include sensitive operational data, so silent network behavior increases the risk of unintended disclosure or policy violations.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill declares access to sensitive environment variables and local configuration/secrets, but it does not declare an explicit tool scope such as permissions or allowed-tools. That weakens user visibility and consent around what the skill can access, making it easier for a coordination-oriented skill to quietly rely on broader capabilities than its description suggests.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The markdown explains collaboration features but omits a clear warning that bulletin contents, agent identities, and coordination metadata may be transmitted to third-party services such as Discord and HTTP gateways. In a multi-agent system, bulletin content may include sensitive internal prompts, decisions, or operational context, so lack of disclosure materially increases the risk of unintended data exfiltration.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The code reads tokens from environment variables and ~/.openclaw/secrets.json to drive notification and gateway functionality. While token use may be functionally related to notifications, accessing local secret material inside a broadly described bulletin tool increases the blast radius if the plugin is abused or misconfigured and is not clearly minimized to only the needed credentials.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
80% confidence
Finding

The wake prompt includes raw bulletin bodies and prior responses directly in the task sent to subscriber agents. If bulletins contain sensitive, private, or regulated information, this design causes broad plaintext propagation to every subscriber and any downstream logs or model providers involved in those agent runs.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill claims to provide bulletin board discussion tools, but it also contains active orchestration logic that launches subagents and exposes gateway/HTTP wake endpoints to trigger agent execution. That is a real expansion of capability beyond passive bulletin handling, increasing attack surface and making it possible for bulletin content or callers to induce agent runs that operators may not expect from the stated purpose.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The HTTP fallback path retrieves a gateway token and uses it for a local authenticated POST to trigger agent waking, but the file provides no user-facing disclosure that this plugin performs authenticated local requests with sensitive credentials. Hidden credential use and local control-plane calls are security-relevant behaviors because they can surprise operators and obscure the true privileges exercised by the skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This markdown file states that bot tokens are resolved from environment variables and a secrets file, which indicates access to sensitive credentials. While the architecture is documented, there is no explicit warning to users that the skill reads secrets from process.env and ~/.openclaw/secrets.json, which is the kind of privacy/security-relevant behavior SQP-2 asks markdown files to disclose.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency version is specified with a caret range (^12.6.2), which allows automatic installation of newer compatible releases. This reduces build reproducibility and can unexpectedly introduce a compromised or vulnerable upstream version if the allowed range later resolves to a bad release.

Content

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

json
"license": "MIT-0",
  "type": "module",
  "dependencies": {
    "better-sqlite3": "^12.6.2"
  },
  "peerDependencies": {
    "openclaw": ">=2026.2.0"

Unverifiable Dependency: openclaw has 16 known advisory(ies) (CVE-2026-53846 (OpenClaw: Workspace .env npm_execpath could influence bundled runtime dependency); CVE-2026-32064 (OpenClaw's andbox browser noVNC observer lacked VNC authentication); CVE-2026-32006 (OpenClaw has a BlueBubbles group allowlist mismatch via DM pairing-store fallbac) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding

The peer dependency on openclaw is only constrained as >=2026.2.0, so consumers may install any later version, including releases affected by known advisories. Because the manifest does not bound or pin the acceptable version, it is not possible to verify from this file whether vulnerable versions are excluded.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
index.ts:43