Back to skill

Security audit

Sillytavern Charactecard

Security checks for vulnerabilities and agentic risk

Overview

The skill is purpose-aligned for SillyTavern character-card import/export, with some usability and robustness issues users should understand before using it on untrusted files.

Use this skill for explicit SillyTavern character-card tasks. Avoid importing unknown or very large PNG cards, and choose export paths carefully because existing files may be overwritten.

Vulnerability Patterns
  • 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
  • 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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
utils.js:58
Finding
Unbounded PNG Chunk Parsing Enables Denial of Service## Vulnerability Details **File Location**: `utils.js`, lines 58–70 **Vulnerability Type**: Improper validation of attacker-controlled PNG chunk length **Risk Level**: Medium ### Vulnerable Code ```javascript // Read chunk length (4 bytes, big-endian) if (offset + 8 > buffer.length) break; const length = buffer.readUInt32BE(offset); offset += 4; // Read chunk type (4 bytes) const type = buffer.toString('ascii', offset, offset + 4); offset += 4; // Check whether this is a text chunk if (type === 'tEXt' || type === 'iTXt') { // Read the null-terminated keyword let keywordEnd = offset; while (keywordEnd < offset + length && buffer[keywordEnd] !== 0) { keywordEnd++; } ``` ### Technical Analysis The PNG chunk length is read directly from an untrusted file. The parser does not verify that the declared `length` fits within the bytes remaining in the buffer, nor does it impose a reasonable maximum text-chunk size. For a `tEXt` or `iTXt` chunk, the keyword loop is bounded by `offset + length` but not by `buffer.length`. Once `keywordEnd` exceeds the physical buffer, `buffer[keywordEnd]` evaluates to `undefined`, which remains unequal to zero. The loop can therefore continue until it reaches the attacker-controlled declared chunk boundary. A crafted chunk length approaching `0xffffffff` may cause billions of synchronous loop iterations. Because character-card files are loaded using synchronous file access and parsed on the main Node.js thread, exploitation can block the process and deny service to other operations. The parser also advances with `offset += length + 4` without first establishing that the complete chunk data and CRC are present. The primary demonstrated security consequence is CPU exhaustion during keyword scanning. ### Attack Path 1. The attacker constructs a file with a valid eight-byte PNG signature. 2. The file contains a chunk whose type is `tEXt` or `iTXt`. 3. The chunk length field is set to a very large value, such as one near ...[truncated 968 chars]
Remediation
## Remediation Suggestions Validate every chunk before inspecting its content: 1. Calculate the chunk-data start and end with arithmetic that cannot silently exceed the buffer boundary. 2. Require the complete chunk data and four-byte CRC to be present. 3. Reject chunks whose declared size exceeds the remaining bytes. 4. Enforce a conservative maximum size for textual character-card chunks. 5. Bound keyword scanning by both the validated chunk end and `buffer.length`. 6. Require a null terminator before decoding the keyword. 7. Consider validating chunk CRC values and PNG chunk ordering. 8. Apply an overall input-file size limit before reading or parsing the file. 9. Avoid long synchronous parsing on a shared event-loop thread; use asynchronous processing or an isolated worker where practical. Example hardening pattern: ```javascript const MAX_TEXT_CHUNK_SIZE = 10 * 1024 * 1024; if (offset + 8 > buffer.length) { throw new Error('Truncated PNG chunk header'); } const length = buffer.readUInt32BE(offset); offset += 4; const type = buffer.toString('ascii', offset, offset + 4); offset += 4; if (length > MAX_TEXT_CHUNK_SIZE && (type === 'tEXt' || type === 'iTXt')) { throw new Error('PNG text chunk exceeds the permitted size'); } const chunkEnd = offset + length; const crcEnd = chunkEnd + 4; if (!Number.isSafeInteger(chunkEnd) || crcEnd > buffer.length) { throw new Error('PNG chunk length exceeds the available input'); } if (type === 'tEXt' || type === 'iTXt') { let keywordEnd = offset; while (keywordEnd < chunkEnd && buffer[keywordEnd] !== 0) { keywordEnd++; } if (keywordEnd === chunkEnd) { throw new Error('PNG text chunk has no keyword terminator'); } // Continue parsing only within [offset, chunkEnd). } ```
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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 (7)

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
SKILL.md 在功能概览中声明支持“V1/V2/V3 之间的格式转换”,并在后文兼容性与示例中描述了 V2→V3、V3→V2 的处理流程。但实际给出的核心实现 `normalizeCharacterCard` 仅检测 V2/V3 原样返回,或把 V1 转成 V2,没有任何将 V2 升级到 V3 或将 V3 降级到 V2 的代码,文档意图与实现不一致。

Vague Triggers

Medium
Confidence
95% confidence
Finding
The import trigger phrases are broad enough that ordinary user requests like '读取这个 PNG 文件' or '解析这个角色' could invoke file-reading behavior unexpectedly. In an agentic environment, overly permissive activation can cause the skill to access local files or process untrusted content when the user did not clearly intend to use this specialized capability.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The viewing triggers are vague and not scoped to already-imported character card data. This can cause the skill to activate on generic conversational prompts about a 'character,' potentially leading the agent to inspect prior context or files inappropriately and increasing the chance of unintended data handling.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The export triggers do not require a clearly identified source object or confirmation that the current data should be written to disk. In an agent workflow, ambiguous export activation can lead to unintended file creation, overwriting, or serialization of the wrong in-memory content, especially when multiple artifacts are present.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
文档明确写明如果角色卡包含 `character_book` 字段则“需要保留”,这属于对处理结果的主动承诺。然而 `normalizeCharacterCard` 在构造输出时仅复制一组固定字段,未包含 `character_book`,因此输入为 V1 或其他需规范化的对象时该字段会被丢弃,和文档说明相矛盾。

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file contains user-facing error messages and formatted output entirely in Chinese, such as returned display text and thrown error strings, with no indication that users can select another language. This can violate language/locale policy when a skill imposes a specific language without user opt-in or documented regional justification.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code writes exported character card data directly to caller-provided output paths using fs.writeFileSync, but there is no confirmation prompt, user-facing log, or warning comment indicating that existing files may be overwritten. Because these are persistent file-write operations affecting user data, the absence of any disclosure increases the risk of unintended data loss.

Static analysis

No suspicious patterns detected.