Back to skill

Security audit

Reading Buddy

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local reading and chat app, but it needs review because its multi-user chat and room controls trust caller-supplied user IDs.

Install only if you understand this as a local prototype. Do not expose it as a shared API, WebSocket service, or multi-user platform until it has real authentication, room membership authorization, export controls, deletion confirmations, and updated dependencies.

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 (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/services/chatService.ts:10
Finding

Unauthenticated Chat Access and User Impersonation

Content
View full analysis

Vulnerability Details

File Location: src/services/chatService.ts:10-59, src/services/chatService.ts:111-118, and src/services/chatService.ts:146-169
Vulnerability Type: Missing authentication and room-level authorization
Risk Level: High

Vulnerable Code

ts
static sendMessage(
  roomId: number,
  userId: string,
  userName: string,
  content: string,
  messageType: MessageType = MessageType.TEXT
): ChatMessage {
  const db = getDatabase();
  
  const result = db.prepare(`
    INSERT INTO chat_messages (room_id, user_id, user_name, content, message_type)
    VALUES (?, ?, ?, ?, ?)
  `).run(roomId, userId, userName, content, messageType);

  const message = this.getById(result.lastInsertRowid as number)!;
  
  chatEvents.emit('message', { roomId, message });
  
  return message;
}
ts
static getRoomMessages(
  roomId: number,
  limit: number = 100,
  beforeId?: number
): ChatMessage[] {
  const db = getDatabase();
  let sql = 'SELECT * FROM chat_messages WHERE room_id = ?';
  const params: any[] = [roomId];
  
  if (beforeId) {
    sql += ' AND id < ?';
    params.push(beforeId);
  }
  
  sql += ' ORDER BY created_at DESC LIMIT ?';
  params.push(limit);
  
  const rows = db.prepare(sql).all(...params) as any[];
  return rows.map(this.mapRowToMessage).reverse();
}
ts
static deleteMessage(messageId: number, userId: string): boolean {
  const db = getDatabase();
  const message = this.getById(messageId);
  if (!message) throw new Error('Message does not exist');
  if (message.userId !== userId) throw new Error('Users may only delete their own messages');
  
  const result = db.prepare('DELETE FROM chat_messages WHERE id = ?').run(messageId);
  return result.changes > 0;
}
ts
static exportChat(roomId: number): string {
  const messages = this.getRoomMessages(roomId, 10000);
  
  let markdow
...[truncated 2712 chars]
Remediation
View remediation

Remediation Suggestions

  1. Introduce an authenticated principal derived from a verified session or signed token. Do not accept the acting user ID as an authoritative request parameter.
  2. Change service methods to receive a trusted authentication context, such as AuthContext, and derive userId from that context.
  3. Before every chat read, search, statistics, or export operation, verify that the authenticated user belongs to the requested room.
  4. Before sending a message, verify that the room exists, that the authenticated user is a member, and that the room status permits messaging.
  5. Load the display name from the registered user record instead of accepting userName from the caller.
  6. For deletion, compare the message owner with the authenticated principal rather than a supplied userId. Define explicit moderator or host permissions if privileged deletion is required.
  7. Apply pagination and bounded limits to exports and history operations to reduce denial-of-service exposure.
  8. Centralize room authorization in a reusable guard so new chat methods cannot accidentally omit membership checks.
  9. Add negative tests covering non-members, spoofed identities, unauthorized exports, and deletion attempts using another user's ID.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/services/roomService.ts:98
Finding

Spoofable Host Authorization and Unauthenticated Membership Removal

Content
View full analysis

Vulnerability Details

File Location: src/services/roomService.ts:98-103, src/services/roomService.ts:132-159, and src/services/roomService.ts:176-183
Vulnerability Type: Caller-controlled authorization identity
Risk Level: High

Vulnerable Code

ts
static leaveRoom(roomId: number, userId: string): boolean {
  const db = getDatabase();
  const result = db.prepare(`
    DELETE FROM room_members WHERE room_id = ? AND user_id = ?
  `).run(roomId, userId);
  return result.changes > 0;
}
ts
static updateStatus(roomId: number, status: RoomStatus): boolean {
  const db = getDatabase();
  const result = db.prepare(`
    UPDATE reading_rooms SET status = ? WHERE id = ?
  `).run(status, roomId);
  return result.changes > 0;
}

static startRoom(roomId: number, hostId: string): boolean {
  const db = getDatabase();
  const room = this.getById(roomId);
  if (!room) throw new Error('Reading room does not exist');
  if (room.hostId !== hostId) throw new Error('Only the host can start the reading room');
  if (room.status !== RoomStatus.PENDING) throw new Error('The reading room has already started or ended');
  
  return this.updateStatus(roomId, RoomStatus.ACTIVE);
}

static endRoom(roomId: number, hostId: string): boolean {
  const db = getDatabase();
  const room = this.getById(roomId);
  if (!room) throw new Error('Reading room does not exist');
  if (room.hostId !== hostId) throw new Error('Only the host can end the reading room');
  if (room.status === RoomStatus.ENDED) throw new Error('The reading room has ended');
  
  return this.updateStatus(roomId, RoomStatus.ENDED);
}
ts
static delete(roomId: number, hostId: string): boolean {
  const db = getDatabase();
  const room = this.getById(roomId);
  if (!room) throw new Error('Reading room does not exist');
  if (room.hostId !== hostId) throw new Error('Only the host can delete the reading
...[truncated 2237 chars]
Remediation
View remediation

Remediation Suggestions

  1. Derive the acting user from a verified authentication context rather than accepting hostId or userId as proof of identity.
  2. Replace host-ID parameters with a trusted principal, then compare room.hostId against principal.userId.
  3. Make updateStatus private or require the same centralized host-authorization check used by all state transitions.
  4. Validate allowed status transitions so callers cannot assign arbitrary or invalid room states.
  5. Modify leaveRoom so normal users can remove only their own membership. Implement a separate, explicitly authorized host or moderator removal method if needed.
  6. Require reauthorization immediately before destructive operations such as room deletion.
  7. Use transactions for room creation, membership changes, state changes, and deletion where multiple related records are affected.
  8. Enable and verify SQLite foreign-key enforcement with PRAGMA foreign_keys = ON if cascade behavior is relied upon.
  9. Add authorization tests for spoofed host IDs, direct updateStatus calls, removal of another member, and destructive actions by non-host users.
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • 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 (29)

Known Vulnerable Dependency: ws==8.19.0 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
97% confidence
Finding

The lockfile pins the application to ws 8.19.0, and the finding cites known advisories for uninitialized memory disclosure and memory-exhaustion denial of service in that version. Because ws is a runtime dependency and this appears to be a networked CLI/agent skill that may accept WebSocket connections or process untrusted WebSocket data, the vulnerable package can expose sensitive process memory or allow a remote attacker to degrade or crash availability.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: ws==8.19.0 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
80% 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.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This markdown file describes saving and exporting chat records, which can affect user privacy and data handling, but it does not include any warning or disclosure about what data is retained, who can access exports, or the sensitivity of shared content. Under the markdown-specific SQP-2 criteria, behaviors affecting user data should be accompanied by user-facing warnings.

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

读书室管理

bash
# 创建读书室
reading-buddy room create -b <bookId> -n "房间名" -u <userId>

# 列出读书室
reading-buddy room list

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The architecture document is written entirely in Chinese and does not indicate any user opt-in, alternative locale, or justification for restricting the content language. Under the policy, forcing a specific language without user choice can be a natural-language locale violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill's top-level description is presented only in Chinese, and the rest of the CLI help/output strings also indicate a Chinese-only interface. This creates a language/locale policy concern because the skill appears to force a specific language without user opt-in or any documented region-specific justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This TypeScript file contains natural-language comments and a user-visible console message exclusively in Chinese, such as the initialization status output. The policy allows locale constraints only when they are clearly documented and justified or when the user is given a language choice; neither is present here.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

This code performs a permanent database deletion of a book record, but there is no confirmation prompt, user-facing log/print, or explanatory warning comment/docstring describing the destructive effect. For code files, destructive or irreversible operations should have some visible disclosure unless clearly covered elsewhere.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

This code performs a safety-relevant data export by compiling up to 10,000 chat messages, including usernames, timestamps, and message content, into a markdown document. Although the function name indicates export behavior, there is no confirmation prompt, log/disclosure, or inline warning comment describing the privacy impact of exporting user conversation data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code performs a permanent database delete of a reading room, which is a destructive operation. The method contains no confirmation prompt, logging, or explanatory comment/docstring warning callers that the action is irreversible.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The delete method performs a destructive database operation by removing a user record, but the code provides no confirmation prompt, user-facing log/print, or explanatory warning comment beyond the method label. For a safety-relevant delete action in a code file, this lacks any disclosure that the operation is destructive.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The natural-language interface and instructions are presented only in Chinese, which can amount to a language/locale policy issue when no user opt-in, alternative language, or justification is provided. The file does not indicate that the skill is intentionally region-specific or that another language option exists.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

This markdown file is predominantly written in Chinese, but line L120 includes the English phrase "living document" without any user language choice or opt-in. That creates a minor language-policy inconsistency because the document imposes mixed-language wording rather than consistently honoring a single documented locale.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

This markdown file contains user-facing natural language exclusively in Chinese, including headings, comments, and descriptive text. Under the language/locale policy rule, forcing a specific language without user opt-in can be a policy concern when no alternative language option or scope justification is provided.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The entire skill overview is written in Chinese and does not indicate that users may choose another language or that the skill is intentionally limited to a Chinese-speaking audience. Under the policy criteria, forcing a specific language without opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The entire skill file is written in Chinese and does not indicate that other languages are supported or that language selection is optional. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest description is written entirely in Chinese, indicating a language-specific user-facing experience without any visible opt-in, alternative locale, or justification that this skill is intended only for a Chinese-speaking region. This can violate language/locale policy when users are not offered a choice or informed of the constraint.

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

json
"author": "",
  "license": "MIT",
  "dependencies": {
    "better-sqlite3": "^9.4.3",
    "commander": "^12.0.0",
    "ws": "^8.16.0"
  },

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

json
"license": "MIT",
  "dependencies": {
    "better-sqlite3": "^9.4.3",
    "commander": "^12.0.0",
    "ws": "^8.16.0"
  },
  "devDependencies": {

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

json
"dependencies": {
    "better-sqlite3": "^9.4.3",
    "commander": "^12.0.0",
    "ws": "^8.16.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.9",

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

json
"ws": "^8.16.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.9",
    "@types/node": "^20.11.0",
    "@types/ws": "^8.5.10",
    "ts-node": "^10.9.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 25)May include surrounding context.

json
},
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.9",
    "@types/node": "^20.11.0",
    "@types/ws": "^8.5.10",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"

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

json
"devDependencies": {
    "@types/better-sqlite3": "^7.6.9",
    "@types/node": "^20.11.0",
    "@types/ws": "^8.5.10",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }

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

json
"@types/better-sqlite3": "^7.6.9",
    "@types/node": "^20.11.0",
    "@types/ws": "^8.5.10",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }
}

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

json
"@types/node": "^20.11.0",
    "@types/ws": "^8.5.10",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }
}