Back to skill

Security audit

Agent Communication Hub

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent agent communication library, but its APIs allow any caller with hub access to read or change all agents' messages, events, subscriptions, sessions, and identities.

Install only if all code sharing the hub and SQLite database is trusted, or if you add an authentication/authorization layer around it. Do not treat the built-in "private" messaging as an enforced security boundary, and avoid storing sensitive payloads unless you control database access, retention, and cleanup.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/CommunicationHub.ts:51
Finding

Cross-Agent Message Disclosure and Unauthorized Acknowledgement

Content
View full analysis

Vulnerability Details

File Location: src/CommunicationHub.ts:51-114
Vulnerability Type: Missing object-level and function-level authorization
Risk Level: High

Vulnerable Code

ts
public acknowledgeMessage(messageId: string): MessageRecord {
  const record = this.getMessage(messageId);
  const acknowledgedAt = nowIso();

  this.db
    .prepare(
      `UPDATE messages
       SET status = 'acknowledged', acknowledged_at = ?
       WHERE id = ?`,
    )
    .run(acknowledgedAt, messageId);

  return this.getMessage(messageId);
}

public drainOfflineQueue(agentId: string): MessageRecord[] {
  this.sessions.getAgent(agentId);
  if (!this.sessions.isAgentOnline(agentId)) {
    return [];
  }

  const deliveredAt = nowIso();
  this.db
    .prepare(
      `UPDATE messages
       SET status = 'delivered', delivered_at = ?
       WHERE recipient_id = ? AND status = 'pending'`,
    )
    .run(deliveredAt, agentId);

  return this.listMessages({ recipientId: agentId, status: "delivered" });
}

public getPendingMessages(agentId: string): MessageRecord[] {
  return this.listMessages({ recipientId: agentId, status: "pending" });
}

public listMessages(query: MessageQuery = {}): MessageRecord[] {
  const conditions: string[] = [];
  const values: Array<string | number> = [];

  if (query.senderId) {
    conditions.push("sender_id = ?");
    values.push(query.senderId);
  }
  if (query.recipientId) {
    conditions.push("recipient_id = ?");
    values.push(query.recipientId);
  }
  if (query.status) {
    conditions.push("status = ?");
    values.push(query.status);
  }
  if (query.kind) {
    conditions.push("kind = ?");
    values.push(query.kind);
  }

  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
  const limit = query.limit ?? 100;
  const rows = this.db
    .prepare(`SELECT * FROM messages ${w
...[truncated 2414 chars]
Remediation
View remediation

Remediation Suggestions

  1. Introduce an authenticated caller context for every public operation:
    ts
    interface CallerContext {
      agentId: string;
      roles: string[];
    }
    
  2. Derive the requesting agent from a validated session or unforgeable capability rather than accepting it solely as an input parameter.
  3. Restrict message reads to records where the authenticated caller is the sender or recipient.
  4. Permit global message history only through a separately protected administrator or auditor API.
  5. Before acknowledgement, retrieve the message and verify that its recipientId equals the authenticated caller's agent ID.
  6. Restrict queue inspection and draining to the authenticated recipient.
  7. Apply state-transition checks so only delivered messages can be acknowledged and only pending messages can be delivered.
  8. Add negative authorization tests covering cross-agent reads, queue draining, and acknowledgement attempts.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/SessionManager.ts:7
Finding

Agent Impersonation and Unauthorized Presence Manipulation

Content
View full analysis

Vulnerability Details

File Location: src/SessionManager.ts:7-78; src/CommunicationHub.ts:22-46; src/CommunicationHub.ts:121-159
Vulnerability Type: Missing identity authentication and authorization
Risk Level: High

Vulnerable Code

ts
public registerAgent(input: AgentRegistrationInput): AgentRecord {
  const existing = this.db
    .prepare("SELECT * FROM agents WHERE id = ?")
    .get(input.id) as Record<string, string> | undefined;

  const timestamp = nowIso();
  const metadata = input.metadata ?? {};
  const name = input.name ?? input.id;

  if (existing) {
    this.db
      .prepare(
        `UPDATE agents
         SET name = ?, metadata_json = ?, updated_at = ?, last_seen_at = ?
         WHERE id = ?`,
      )
      .run(name, toJson(metadata), timestamp, timestamp, input.id);
  } else {
    this.db
      .prepare(
        `INSERT INTO agents (id, name, status, metadata_json, last_seen_at, created_at, updated_at)
         VALUES (?, ?, 'offline', ?, ?, ?, ?)`,
      )
      .run(input.id, name, toJson(metadata), timestamp, timestamp, timestamp);
  }

  return this.getAgent(input.id);
}

public connectAgent(input: SessionConnectInput): SessionRecord {
  this.ensureAgentExists(input.agentId);
  const sessionId = createId("session");
  const timestamp = nowIso();

  this.db
    .prepare(
      `INSERT INTO sessions (id, agent_id, status, connected_at, disconnected_at, metadata_json)
       VALUES (?, ?, 'connected', ?, NULL, ?)`,
    )
    .run(sessionId, input.agentId, timestamp, toJson(input.metadata ?? {}));

  this.db
    .prepare(
      `UPDATE agents
       SET status = 'online', last_seen_at = ?, updated_at = ?
       WHERE id = ?`,
    )
    .run(timestamp, timestamp, input.agentId);

  return this.getSession(sessionId);
}

public disconnectAgent(agentId: string): void {
  this.ensureAgentExists(agentId);
  const ti
...[truncated 3771 chars]
Remediation
View remediation

Remediation Suggestions

  1. Bind each agent identity to authenticated credentials, a validated session token, or an unforgeable capability during initial registration.
  2. Separate initial registration from profile updates. Reject duplicate IDs by default and require identity-owner or administrator authorization for updates.
  3. Return an authenticated session handle from connectAgent() and require it for all identity-scoped operations.
  4. Derive senderId from the validated caller context; do not accept it as a freely selectable message parameter.
  5. Require ownership verification before connecting or disconnecting an agent.
  6. If administrators may manage other agents, implement explicit role checks and audit those actions.
  7. Prevent one disconnect request from terminating sessions that do not belong to the authenticated session unless the caller has administrative privileges.
  8. Add tests proving that one agent cannot update, connect, disconnect, or send messages as another agent.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/EventBus.ts:26
Finding

Unauthorized Event History Disclosure and Subscription Deletion

Content
View full analysis

Vulnerability Details

File Location: src/EventBus.ts:26-28; src/EventBus.ts:66-114
Vulnerability Type: Missing subscription ownership and event-access authorization
Risk Level: High

Vulnerable Code

ts
public unsubscribe(subscriptionId: string): void {
  this.db.prepare("DELETE FROM event_subscriptions WHERE id = ?").run(subscriptionId);
}

public replay(options: EventReplayOptions = {}): EventRecord[] {
  const conditions: string[] = [];
  const values: Array<string | number> = [];

  if (options.type) {
    conditions.push("type = ?");
    values.push(options.type);
  }
  if (options.sourceAgentId) {
    conditions.push("source_agent_id = ?");
    values.push(options.sourceAgentId);
  }
  if (options.since) {
    conditions.push("created_at >= ?");
    values.push(options.since);
  }

  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
  const limit = options.limit ?? 100;
  const sql = `SELECT * FROM events ${where} ORDER BY created_at DESC LIMIT ?`;
  const rows = this.db.prepare(sql).all(...values, limit) as Array<Record<string, string | null>>;

  return rows.map((row) => ({
    id: row.id as string,
    type: row.type as string,
    sourceAgentId: row.source_agent_id as string | null,
    payload: fromJson(row.payload_json as string),
    metadata: fromJson(row.metadata_json as string),
    createdAt: row.created_at as string,
  }));
}

public listSubscriptions(agentId?: string): EventSubscription[] {
  const rows = agentId
    ? (this.db
        .prepare("SELECT * FROM event_subscriptions WHERE agent_id = ? ORDER BY created_at ASC")
        .all(agentId) as Array<Record<string, string>>)
    : (this.db.prepare("SELECT * FROM event_subscriptions ORDER BY created_at ASC").all() as Array<Record<string, string>>);

  return rows.map((row) => ({
    id: row.id,

...[truncated 2184 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require an authenticated caller context for subscription creation, listing, deletion, publication, and replay.
  2. Remove arbitrary agentId selection from ordinary callers and derive the subscriber identity from the authenticated context.
  3. Before deleting a subscription, retrieve it and verify that its agent_id belongs to the caller. Permit cross-agent deletion only for an explicitly authorized administrator.
  4. Scope listSubscriptions() to the current agent by default; expose global enumeration only through a protected administrative API.
  5. Restrict event replay to event types the authenticated agent is permitted to receive, or implement explicit per-event access-control policies.
  6. Consider storing recipient or access-policy information with persisted events so replay authorization can be enforced consistently.
  7. Return a not-found or generic authorization error for unauthorized subscription IDs to reduce enumeration.
  8. Add tests for cross-agent subscription enumeration, deletion, and unauthorized event replay.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (10)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill explicitly provides SQLite-backed persistence, offline queues, audit history, and JSON storage of message and event payloads, but it does not warn users that communication content and history will be retained. This can lead operators to use the skill for sensitive agent-to-agent traffic without understanding the privacy, retention, and compliance implications, increasing the chance of accidental exposure or improper handling of stored data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The publish method writes event payload and metadata into persistent storage, which may include user or system data. In this code file there is no confirmation prompt, logging, or explanatory comment/docstring disclosing that event contents are stored.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

This markdown file describes features that store agent messages, session history, and offline queues durably in SQLite, which can affect user or system data privacy. Under the markdown-specific warning rule, the description should disclose that message contents and metadata may be persisted for audit, replay, and delayed delivery.

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 29)May include surrounding context.

json
],
  "license": "MIT",
  "dependencies": {
    "better-sqlite3": "^11.10.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.13",

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 32)May include surrounding context.

json
"better-sqlite3": "^11.10.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.13",
    "@types/node": "^24.0.0",
    "tsx": "^4.20.5",
    "typescript": "^5.8.2",

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 33)May include surrounding context.

json
},
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.13",
    "@types/node": "^24.0.0",
    "tsx": "^4.20.5",
    "typescript": "^5.8.2",
    "vitest": "^3.0.8"

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 34)May include surrounding context.

json
"devDependencies": {
    "@types/better-sqlite3": "^7.6.13",
    "@types/node": "^24.0.0",
    "tsx": "^4.20.5",
    "typescript": "^5.8.2",
    "vitest": "^3.0.8"
  }

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 35)May include surrounding context.

json
"@types/better-sqlite3": "^7.6.13",
    "@types/node": "^24.0.0",
    "tsx": "^4.20.5",
    "typescript": "^5.8.2",
    "vitest": "^3.0.8"
  }
}

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 36)May include surrounding context.

json
"@types/node": "^24.0.0",
    "tsx": "^4.20.5",
    "typescript": "^5.8.2",
    "vitest": "^3.0.8"
  }
}

Unverifiable Dependency: vitest has 3 known advisory(ies) (CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock); CVE-2025-24964 (Vitest allows Remote Code Execution when accessing a malicious website while Vit)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.