Back to skill

Security audit

agent-comm-hub

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real multi-agent hub, but its current code exposes agent messages, tasks, and approval events too broadly for a security-sensitive coordination tool.

Review this carefully before installing. Use it only in an isolated local environment with trusted agents, keep the HTTP/SSE server off untrusted networks, restrict HUB_URL to a known local or internal origin, and disable watcher/bridge/auto-execution paths unless every agent and token holder is trusted. The authorization bugs should be fixed before production use.

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/server.ts:155
Finding

Unauthenticated SSE Subscription Enables Agent Impersonation and Private Event Disclosure

Content
View full analysis

Vulnerability Details

File Location: src/server.ts, lines 155-230
Vulnerability Type: Missing authentication and object-level authorization on an agent event stream
Risk Level: Critical

Complete Code Snippet

ts
app.get("/events/:agent_id", optionalAuthMiddleware, (req: Request, res: Response) => {
  const { agent_id } = req.params;
  const authContext: AuthContext | undefined = req.auth?.agent;

  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");
  res.setHeader("X-Accel-Buffering", "no");
  res.flushHeaders();

  if (connectedCount() >= MAX_SSE_CONNECTIONS) {
    res.status(503).json({ error: "SSE connection limit reached" });
    return;
  }

  const connId = registerClient(agent_id, res);
  markAgentSseOnline(agent_id);
  incrementGauge("active_sse_connections");

  const lastEventId = req.headers["last-event-id"] as string | undefined;
  const parsedLast = lastEventId ? parseInt(lastEventId, 10) : NaN;

  if (!isNaN(parsedLast)) {
    const missed = eventLogRepo.getEventsAfter(parsedLast, agent_id);
    if (missed.length > 0) {
      for (const ev of missed) {
        if (writeStoredEvent(agent_id, ev)) {
          eventLogRepo.markDelivered(ev.id);
        }
      }
    }
  } else {
    const undelivered = eventLogRepo.getUndelivered(agent_id);
    for (const ev of undelivered) {
      if (writeStoredEvent(agent_id, ev)) {
        eventLogRepo.markDelivered(ev.id);
      }
    }

    const pending = messageRepo.pendingFor(agent_id);
    if (pending.length > 0) {
      for (const msg of pending) {
        const ok = pushToAgent(agent_id, {
          event: "new_message",
          message: msg
        });
        if (ok) {
          messageRepo.markDelivered(msg.id);
        }
      }
    }
  }

  const pendingTasks = taskRepo.listFor(agent_id, "pending");
  for (const task of pendingTasks) {
    pushToAgent(agent_id, {
      event: "task_as
...[truncated 2307 chars]
Remediation
View remediation

Remediation Suggestions

  1. Replace optionalAuthMiddleware with mandatory authentication for /events/:agent_id.
  2. Before setting SSE headers or registering the connection, enforce:
    ts
    const ctx = req.auth?.agent;
    if (!ctx) {
      res.status(401).json({ error: "Authentication required" });
      return;
    }
    if (ctx.agentId !== req.params.agent_id && ctx.role !== "admin") {
      res.status(403).json({ error: "Agent event stream access denied" });
      return;
    }
    
  3. Prefer deriving the subscription identity from the authenticated context rather than accepting it from the URL.
  4. Do not mark an agent online until identity and ownership checks succeed.
  5. Add tests covering missing tokens, invalid tokens, cross-agent tokens, administrative access, and connection replacement attempts.
  6. Consider allowing only one authenticated session or explicitly managed session set per agent, rather than silently replacing the existing connection.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/server.ts:405
Finding

REST APIs Permit Cross-Agent Task and Message Access and Modification

Content
View full analysis

Vulnerability Details

File Location: src/server.ts, lines 405-502
Vulnerability Type: Insecure direct object reference and missing per-agent authorization
Risk Level: High

Complete Code Snippet

ts
app.get("/api/tasks", authMiddleware, (req: Request, res: Response) => {
  const { agent_id, status } = req.query;
  if (!agent_id) {
    res.status(400).json({ error: "agent_id is required" });
    return;
  }

  const tasks = status
    ? taskRepo.listFor(agent_id as string, status as string)
    : taskRepo.listFor(agent_id as string, "pending");

  res.json({ tasks, count: tasks.length });
});

app.get("/api/messages", authMiddleware, (req: Request, res: Response) => {
  const { agent_id, status } = req.query;
  if (!agent_id) {
    res.status(400).json({ error: "agent_id is required" });
    return;
  }

  const queryStatus = (status as string) || "unread";
  const messages = messageRepo.listByStatus(agent_id as string, queryStatus);
  res.json({ messages, count: messages.length });
});

app.patch("/api/tasks/:id/status", authMiddleware, (req: Request, res: Response) => {
  const { status, result, progress } = req.body;
  if (!["in_progress", "completed", "failed"].includes(status)) {
    res.status(400).json({ error: `Invalid status: ${status}` });
    return;
  }

  const task = taskRepo.getById(req.params.id);
  if (!task) {
    res.status(404).json({ error: "Task not found" });
    return;
  }

  taskRepo.update(req.params.id, status, result || null, progress || 0);

  pushToAgent(task.assigned_by, {
    event: "task_updated",
    update: {
      task_id: task.id,
      status,
      result: result || null,
      progress: progress || 0,
      updated_by: "workbuddy-automation",
      timestamp: Date.now()
    }
  });

  res.json({ success: true, task_id: task.id, status });
});

app.patch("/api/messages/:id/status", authMiddleware, (req: Request, res: Response) => {
  const { status } = req.body;
  const validStatuses = ["read", "deli
...[truncated 2943 chars]
Remediation
View remediation

Remediation Suggestions

  1. For list endpoints, derive the agent ID from the authenticated context:
    ts
    const ctx = req.auth!.agent!;
    const effectiveAgentId =
      ctx.role === "admin" && typeof req.query.agent_id === "string"
        ? req.query.agent_id
        : ctx.agentId;
    
  2. Reject non-administrative requests that specify another agent's ID.
  3. Apply object-level authorization before every mutation:
    ts
    assertOwns("task", req.params.id, ctx);
    assertOwns("message", req.params.id, ctx, "recipient");
    
  4. Define stricter task semantics. Normally, only the assigned agent should update execution status; the assigning agent should not automatically receive equivalent mutation authority.
  5. Record ctx.agentId as the actual updater instead of a fixed automation identity.
  6. Return a uniform 404 or authorization error where appropriate to reduce resource-ID enumeration.
  7. Add integration tests for cross-agent reads, task mutations, message acknowledgement, administrative exceptions, and nonexistent resources.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/server.ts:974
Finding

Member-Controlled SSE Bridge Events Can Bypass Human Authorization

Content
View full analysis

Vulnerability Details

File Locations:

  • src/server.ts, lines 974-1004
  • src/tools/authorization.ts, lines 96-116
  • client-sdk/agent-client.ts, lines 342-375

Vulnerability Type: Authorization-event forgery and human-approval bypass
Risk Level: Critical

Complete Code Snippets

The internal bridge accepts arbitrary event objects from any authenticated agent:

ts
app.post("/internal/sse/push", authMiddleware, (req: Request, res: Response) => {
  const body = (req.body ?? {}) as {
    agentId?: unknown;
    event?: unknown;
    dedupId?: unknown;
    seq?: unknown;
  };

  const { agentId, event, dedupId, seq } = body;
  if (typeof agentId !== "string" || !event || typeof event !== "object") {
    res.status(400).json({
      delivered: false,
      error: "agentId(string) and event(object) required"
    });
    return;
  }

  const delivered = deliverToLocalClient(
    agentId,
    event as object,
    typeof dedupId === "string" ? dedupId : undefined,
    typeof seq === "number" ? seq : undefined
  );

  if (delivered && typeof seq === "number") {
    eventLogRepo.markDelivered(seq);
  }

  res.json({ delivered });
});

All authenticated members can list authorization requests without requester scoping:

ts
server.tool(
  "list_authorization_requests",
  "List authorization requests, optionally filtered by status.",
  {
    status: z.enum(["pending", "approved", "rejected", "expired"] as const)
      .optional()
  },
  authed(
    authContext,
    "list_authorization_requests",
    async (_ctx, { status }) => {
      const requests = authorizationService.list(
        status as AuthStatus | undefined
      );

      return {
        content: [{
          type: "text",
          text: JSON.stringify({
            requests,
            count: requests.length
          }, null, 2)
        }]
      };
    }
  )
);

The SDK trusts an incoming event to resolve the approval gate:

ts
case "authorization_resolved":
  this.complete
...[truncated 3478 chars]
Remediation
View remediation

Remediation Suggestions

  1. Do not authenticate the internal bridge with ordinary agent API tokens.
  2. Protect /internal/sse/push using a dedicated bridge credential, mutual TLS, Unix-domain socket, or a strictly loopback-bound listener.
  3. Enforce an explicit internal-service role rather than accepting all authenticated members.
  4. Reject client-supplied authorization_requested and authorization_resolved event types at the generic bridge endpoint.
  5. Generate authorization-resolution events only inside authorizationService.resolve() after an authoritative state transition.
  6. Scope list_authorization_requests to ctx.agentId for members; permit global listing only for administrators.
  7. Before resolving a pending Promise, have the SDK query an authenticated server endpoint and verify that:
    • The request exists.
    • It belongs to the current agent.
    • Its status is approved.
    • It has not expired.
  8. Bind each approval to the requesting agent, task ID, operation type, and payload digest to prevent reuse or substitution.
  9. Add tests proving that member tokens cannot call the bridge, cannot list other agents' requests, and cannot forge authorization events.
Vulnerability Patterns
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (406)

Tainted flow: 'req' from os.getenv (line 94, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

HUB_URL is taken from an environment variable and used as the destination for an authenticated-looking task-status update over HTTP without validation or transport security. In this script the function is currently unused, which lowers immediate exploitability, but if invoked in production an attacker controlling the environment or launch configuration could redirect task data/status updates to an arbitrary endpoint or exfiltrate workflow metadata.

Content

Scanner excerpt · scripts/hub_task_runner.py (reported line 100)May include surrounding context.

python
method="PATCH",
        )
        req.add_header("Content-Type", "application/json")
        with urlopen(req, timeout=10) as resp:
            return resp.status < 400
    except Exception as e:
        logger.error(f"更新任务状态失败: {e}")

Tainted flow: 'req' from os.getenv (line 91, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The watcher sends authenticated HTTP requests to a URL derived from the HUB_URL environment variable, automatically attaching the HUB_AUTH_TOKEN as a Bearer token. If an attacker can influence environment variables or deployment configuration, they can redirect requests to an attacker-controlled endpoint and exfiltrate the token, creating an SSRF-style credential leak. In this skill context, the process is a long-running agent communication daemon with task/message privileges, which makes token disclosure more dangerous than a generic local utility.

Content

Scanner excerpt · scripts/hub_watcher.py (reported line 80)May include surrounding context.

python
req = Request(url)
        if HUB_AUTH_TOKEN:
            req.add_header("Authorization", f"Bearer {HUB_AUTH_TOKEN}")
        with urlopen(req, timeout=timeout) as resp:
            return resp.read()
    except Exception as e:
        logger.warning(f"HTTP GET {url} 失败: {e}")

Tainted flow: 'req' from os.getenv (line 91, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

This PATCH path has the same trust issue: an environment-controlled HUB_URL determines the destination while the code includes HUB_AUTH_TOKEN in the Authorization header. A malicious or compromised configuration can cause authenticated status updates to be sent to an attacker-controlled server, leaking credentials and potentially task metadata. Because the daemon processes multi-agent tasks and messages automatically, this can expose sensitive orchestration data as well as the token.

Content

Scanner excerpt · scripts/hub_watcher.py (reported line 95)May include surrounding context.

python
req.add_header("Content-Type", "application/json")
        if HUB_AUTH_TOKEN:
            req.add_header("Authorization", f"Bearer {HUB_AUTH_TOKEN}")
        with urlopen(req, timeout=timeout) as resp:
            return resp.status < 400
    except Exception as e:
        logger.error(f"HTTP PATCH {url} 失败: {e}")

Tainted flow: 'req' from os.getenv (line 91, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
92% confidence
Finding

The SSE connection target is built from HUB_URL/AGENT_ID and, when HUB_AUTH_TOKEN is present, the watcher authenticates to that endpoint before entering a long-lived connection. If HUB_URL is tampered with, the daemon will initiate an authenticated connection to an attacker-controlled server, leaking the bearer token and potentially consuming spoofed events that trigger downstream file writes and automation. In this agent hub skill, that is especially risky because received events directly drive task handling and WorkBuddy trigger creation.

Content

Scanner excerpt · scripts/hub_watcher.py (reported line 288)May include surrounding context.

python
req = Request(self.url)
                if HUB_AUTH_TOKEN:
                    req.add_header("Authorization", f"Bearer {HUB_AUTH_TOKEN}")
                with urlopen(req, timeout=self.timeout) as resp:
                    logger.info("SSE 连接成功,开始监听...")
                    self._read_stream(resp)

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding

Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Content

No source excerpt is available for this finding.

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding

Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the package really performs local directory polling and file reads rather than operating as an MCP/SSE hub, then the trigger and description materially misrepresent the trust boundary. Hidden polling and local file processing can capture or act on data outside the expected communication context.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| `src/server.ts` | 服务端入口,Express + MCP/SSE 双通道 |

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| `src/tools.ts` | 全部 MCP 工具的 TypeScript 实现 |

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| `src/identity.ts` | Agent 注册、认证、4 级权限控制 |

Static analysis

Detected: suspicious.dangerous_exec, suspicious.dynamic_code_execution, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tests/integration/stdio-node-version.test.ts:272

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tests/unit/audit-worm.test.ts:156

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/redact_leaked_credentials.py:57

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test-phase2-day5.py:362

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test-phase2-gogo.py:280

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
client-sdk/adapters/host-executor.ts:45

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/sse.ts:163