Back to skill

Security audit

undercover-is-who

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed game judge that stores local game records, with some privacy and game-integrity caveats but no evidence of malicious behavior.

Install this only for Chinese-speaking groups that are comfortable with a local SQLite record of the game. Tell players that their names and message-sender IDs are stored for identity checks, avoid exporting until the game has ended, and delete the generated database when records are no longer needed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/game_engine.py:226
Finding

Active-Game Export Discloses Secret Roles and Words

Content
View full analysis

Vulnerability Details

File Location: scripts/game_engine.py:226-231; scripts/db.py:482-519
Vulnerability Type: Missing lifecycle authorization check leading to confidential game-state disclosure
Risk Level: High

Vulnerable Code

scripts/game_engine.py:226-231:

python
def cmd_export(game_id):
    """Export the game record as Markdown."""
    md = db.export_game_record(game_id)
    if md is None:
        return {"error": f"Game not found: {game_id}"}
    return md

Relevant disclosure logic in scripts/db.py:482-519:

python
def export_game_record(game_id, base_dir=None):
    """
    Export the complete game record in Markdown format.
    """
    status = get_game_status(game_id)
    if not status:
        return None

    config = status["config"]
    config_str = f"{config['civilian']} civilians/{config['undercover']} undercover"
    if config.get("white_board", 0) > 0:
        config_str += f"/{config['white_board']} blank slate"

    lines = [
        "# Game Record",
        "",
        f"Time: {status['created_at'][:16].replace('T', ' ')}",
        f"Players: {', '.join(status['all_players'])}",
        f"Configuration: {config_str}",
        "",
        "---",
        "",
    ]

    lines.append("## Role Assignments")
    lines.append("")
    lines.append("| Player | Role | Word |")
    lines.append("|------|------|------|")
    for r in status["roles"]:
        lines.append(f"| {r['player']} | {r['role']} | {r['word']} |")
    lines.append(f"- Civilian word: “{status['civilian_word']}”")
    lines.append(f"- Undercover word: “{status['undercover_word']}”")

Technical Analysis

The export command retrieves and renders the complete game state without verifying that the game has finished. The generated record contains every player's role and assigned word, as well as both secret words.

The documented workflow calls for ...[truncated 1605 chars]

Remediation
View remediation

Remediation Suggestions

  1. Enforce the finished-state requirement inside db.export_game_record:
    python
    game = get_game_by_id(game_id)
    if not game:
        return None
    if game["status"] != "finished":
        raise PermissionError("Complete records cannot be exported before the game ends")
    
  2. Add the same check to cmd_export to provide a clear user-facing error, while retaining the database-layer check as the authoritative control.
  3. If active-game exports are required, implement a separate redacted export function that excludes:
    • Roles
    • Assigned words
    • Civilian and undercover words
    • Individual vote targets
    • Any other unrevealed game information
  4. Add automated tests proving that full export fails while status == "playing" and succeeds only after end_game.
  5. Avoid treating Skill instructions as an authorization boundary; enforce all confidentiality restrictions in code.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/vote_handler.py:47
Finding

Description and Voting Mutations Trust Caller-Supplied Player and Round Values

Content
View full analysis

Vulnerability Details

File Location: scripts/describe_handler.py:28-55; scripts/vote_handler.py:47-96
Vulnerability Type: Missing server-side identity binding and current-round validation
Risk Level: Medium

Vulnerable Code

scripts/describe_handler.py:28-55:

python
def cmd_record(game_id, player, description, round_num):
    """Record a player's description."""
    round_num = int(round_num)

    status = db.get_game_status(game_id)
    if not status:
        return {"valid": False, "error": "Game not found"}

    phase = status["phase"]
    if "description phase" not in phase:
        return {
            "valid": False,
            "error": f"The game is not currently in the description phase: {phase}",
        }

    if player not in status["alive_players"]:
        return {
            "valid": False,
            "error": f"{player} is not an alive player.",
        }

    if db.has_described(game_id, round_num, player):
        return {
            "valid": False,
            "error": f"{player} has already provided a description this round.",
        }

    db.record_description(game_id, round_num, player, description)

    progress = db.get_description_progress(game_id, round_num)

scripts/vote_handler.py:47-96:

python
def validate_vote(game_id, voter, target, round_num):
    """Validate a vote."""
    status = db.get_game_status(game_id)
    if not status:
        return {"valid": False, "errors": ["Game not found"]}

    alive_players = status["alive_players"]
    phase = status["phase"]
    errors = []

    if "voting phase" not in phase:
        errors.append(
            f"The game is not currently in the voting phase: {phase}"
        )
        return {"valid": False, "errors": errors}

    if voter not in alive_players:
        errors.append(f"{voter} has been eliminated and cannot vote.")
        return {"valid"
...[truncated 3811 chars]
Remediation
View remediation

Remediation Suggestions

  1. Change state-changing APIs to accept a trusted sender ID rather than a caller-selected player name:
    python
    def cmd_cast(game_id, sender_id, target, round_num):
        player = db.get_player_by_id(game_id, sender_id)
        if not player:
            return {"valid": False, "errors": ["Sender identity is not bound"]}
        voter = player["name"]
    
  2. Apply the same sender-ID resolution to description recording. Never derive the acting player solely from message text or a display name.
  3. Require the requested round to match the authoritative database state:
    python
    if round_num != status["round"]:
        return {
            "valid": False,
            "error": "The supplied round is not the current round",
        }
    
  4. Require the game status to be playing in addition to validating the current phase.
  5. Verify that the current player has an initialized description or vote record before performing an update.
  6. Check the affected-row count after UPDATE; treat zero affected rows as an error instead of reporting success.
  7. Perform sender resolution, phase validation, round validation, duplicate checking, and mutation in one database transaction to prevent inconsistent state.
  8. Add tests for unbound sender IDs, sender/name mismatches, eliminated players, stale rounds, future rounds, duplicate submissions, and missing initialized records.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (10)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
> ⚠️ 以下所有脚本路径中的 `<SKILL_DIR>` 指本技能的安装目录(即包含 `SKILL.md` 的目录)。

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest description is written as a Chinese-only operational instruction and presents the skill as activating for Chinese and English keywords while the rest of the skill content mandates Chinese-language interaction. There is no indication that users may choose another language or that the locale restriction is intentional and justified, which creates a language-policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill requires collecting and binding each player's message-sender ID for identity verification, but it does not instruct the agent to provide a privacy notice, obtain consent, define retention limits, or minimize exposure of this identifier data. Message-sender IDs are persistent identifiers, so unnecessary collection or unclear handling can create privacy and compliance risk if the IDs are stored in the SQLite database or later exposed through exports or logs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill instructs exporting the full game record and sending it to the group without any guardrails on what the export contains. Because the game database may include player names, descriptions, voting history, eliminations, reviews, and possibly bound sender IDs, sharing the complete record to all participants can disclose personal or sensitive gameplay data beyond what players reasonably expect.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code initializes and creates a persistent database file for storing full game state, including player identities, assigned roles, words, descriptions, votes, and review content. While the module docstrings describe the database technically, there is no user-facing warning, confirmation, or visible disclosure in the code about retaining potentially sensitive game data on disk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

This code file contains natural-language instructions and help text that force a specific language/locale for all users. The policy allows locale constraints only when the skill offers opt-in or clearly documents a justified regional limitation, which is not present here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This Python file’s user-facing docstring and command descriptions are entirely in Chinese, and the generated user-facing templates later in the file are also fixed to Chinese. Under the policy, forcing a specific language without user opt-in is a natural-language locale violation unless the constraint is explicitly documented and justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The template set contains many end-user messages that are all hard-coded in Chinese, and there is no option for users to choose another language. Because these strings directly control the skill’s interaction language, this is a clear locale-policy issue rather than an implementation detail.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown file presents all instructions and content in a single language, which can be a natural-language policy concern when no user opt-in or locale justification is provided. Because the file does not indicate that the skill is region-specific or that users may choose another language, it may conflict with language/locale flexibility requirements.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The file’s docstrings, comments, schema annotations, and user-facing exported Markdown content are all authored in Chinese, with no indication that users can choose another language or locale. Under the stated policy, forcing a specific language without opt-in is a natural-language policy concern.

Content

No source excerpt is available for this finding.