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

Content
View full analysis

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
View 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
Category
Not specified by scanner
Confidence
95% confidence
Finding

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

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

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

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.