Back to skill

Security audit

Game NPC Director

Security checks for vulnerabilities and agentic risk

Overview

The skill broadly matches NPC voice generation, but it needs review because it can automatically send generated audio to Feishu, uses local credential/helper discovery, and retains sensitive voice/transcript artifacts too loosely.

Install only if you are comfortable sending player audio, dialogue text, synthesized NPC audio, and Feishu message metadata to external services. Keep it out of shared workspaces, review the helper modules it loads from _shared and audioclaw-skills-voice-reply, use dedicated least-privilege API credentials, and avoid sticky auto-send mode unless the destination chat and session scope are explicit.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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
Findings (2)

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/send_npc_scene_to_feishu.py:35
Finding
Unverified Helper Modules Are Discovered and Executed from Ancestor Directories<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_npc_scene_to_feishu.py:35-52`; equivalent ancestor-directory imports occur in `scripts/senseaudio_asr.py:14-27`, `scripts/batch_tts_scene.py:11-24`, and `scripts/run_player_voice_npc_pipeline.py:8-21` **Vulnerability Type**: Untrusted local module discovery and execution **Risk Level**: High ### Vulnerable Code ```python def load_helper_module(skill_name: str, script_name: str, alias: str) -> Any: current = Path(__file__).resolve() for parent in current.parents: candidate = parent / skill_name / "scripts" / script_name if candidate.exists(): spec = importlib.util.spec_from_file_location(alias, candidate) if spec and spec.loader: module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module raise SystemExit(f"could not locate {skill_name}/scripts/{script_name}") feishu_sender = load_helper_module("audioclaw-skills-voice-reply", "feishu_audio_sender.py", "npc_feishu_sender") ``` The other affected scripts use the following equivalent pattern: ```python def _bootstrap_shared_senseaudio_env() -> None: current = Path(__file__).resolve() for parent in current.parents: candidate = parent / "_shared" / "senseaudio_env.py" if candidate.exists(): candidate_dir = str(candidate.parent) if candidate_dir not in sys.path: sys.path.insert(0, candidate_dir) from senseaudio_env import ensure_senseaudio_env ensure_senseaudio_env() return _bootstrap_shared_senseaudio_env() ``` ### Technical Analysis The Skill searches every ancestor directory for helper files and executes the first matching module without verifying its canonical location, ownership, permissions, package identity, signature, or cryptographic digest. The referenced helpers are not included in the audited project ...[truncated 2527 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Package all required helper modules inside the reviewed Skill or a pinned, trusted Python package. 2. Replace ancestor traversal with a single canonical import path under a trusted installation root. 3. Resolve the helper path with `Path.resolve()` and verify that it remains inside the expected trusted directory. 4. Reject helper files or parent directories writable by untrusted users. 5. Verify helper modules against a pinned SHA-256 digest or signed package manifest before loading them. 6. Avoid inserting dynamically discovered directories at the front of `sys.path`. 7. Pin dependency versions and verify package hashes during installation. 8. Fail closed when the expected helper is absent rather than searching increasingly broad filesystem locations. 9. Document and audit the exact helper package because it processes credentials and external messaging operations. 10. Run the Skill under a dedicated, minimally privileged account with access only to the required workspace and credentials. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/send_npc_scene_to_feishu.py:79
Finding
Private Audio, Transcripts, and Messaging Metadata Are Persisted with Insufficient File Protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_npc_scene_to_feishu.py:79-99,185-210`; related transcript persistence occurs in `scripts/senseaudio_asr.py:246-264` **Vulnerability Type**: Insecure storage permissions and excessive sensitive-data retention **Risk Level**: Medium ### Vulnerable Code The Feishu-ready audio is explicitly made world-readable: ```python def transcode_to_ogg(source_path: Path, target_path: Path, *, ffmpeg_exe: str) -> None: target_path.parent.mkdir(parents=True, exist_ok=True) command = [ ffmpeg_exe, "-y", "-i", str(source_path), "-vn", "-ac", "1", "-ar", "24000", "-c:a", "libopus", "-b:a", "48k", str(target_path), ] try: subprocess.run(command, check=True, capture_output=True, text=True) except subprocess.CalledProcessError as exc: stderr = (exc.stderr or "").strip() raise SystemExit(f"ffmpeg transcoding failed: {stderr or exc}") from exc os.chmod(target_path, 0o644) ``` The delivery log retains dialogue text, local paths, upload responses, send responses, the chat identifier, and session information: ```python deliveries.append( { "line_id": item.get("line_id"), "source_audio_path": str(source_path), "feishu_audio_path": str(target_path), "duration_ms": duration_ms, "label_message": send_text, "upload": upload, "send": send, "text": item.get("text", ""), } ) if args.delay_ms > 0 and index < len(selected_results): time.sleep(args.delay_ms / 1000) result = { "mode": "npc_scene_feishu_sequence_sent", "npc_name": summary.get("npc_name"), "chat_id": chat_id, "session_file": session_path, "prepared_dir": str(prepared_dir), ...[truncated 3743 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the explicit `os.chmod(target_path, 0o644)` operation. 2. Create sensitive directories with mode `0700` and audio or JSON files with mode `0600`. 3. Use secure file creation, such as `os.open()` with `O_CREAT | O_EXCL` and an explicit `0o600` mode, to avoid umask and preexisting-file ambiguity. 4. Verify permissions after file creation and reject unsafe preexisting output files. 5. Store only the fields required for delivery status. Avoid persisting complete Feishu upload and send responses. 6. Omit raw ASR responses unless diagnostic logging is explicitly enabled. 7. Redact chat IDs, session paths, message identifiers, and local filesystem paths from routine output. 8. Do not print complete transcripts or delivery responses to standard output by default. 9. Add configurable retention limits and delete intermediate audio, transcripts, and delivery records when no longer needed. 10. Keep diagnostic mode opt-in and display a warning that it may retain private speech and provider metadata. 11. Ensure the workspace is owned by a dedicated service account and is not shared with unrelated users. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (35)

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

Critical
Category
Data Flow
Content
method="POST",
        )
        try:
            with urllib.request.urlopen(request, timeout=120) as response:
                audio_bytes, trace_id, extra_info, chunk_count = parse_sse_audio(response)
                return audio_bytes, trace_id, extra_info, chunk_count, model
        except urllib.error.HTTPError as exc:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code's actual function is narrowly focused on batch text-to-speech synthesis from a manifest. It does not implement reusable NPC behavior, dialogue selection, relationship-aware generation, catchphrases, or any player audio intake/ASR. While it is related to NPC voice output, the declared description claims a broader interactive NPC voice system and specifically mentions AudioClaw, whereas the code calls SenseAudio's TTS endpoint. The relationship and event fields are only used in output filenames/metadata, not to drive behavior. This is a material description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code is a manifest builder: it loads a profile JSON, fills text templates based on relationship and event type, and outputs structured JSON containing dialogue lines and voice IDs. This partially aligns with relationship-aware dialogue, catchphrases, task briefings, and event announcements in text form. However, the declared description emphasizes reusable NPC voice behavior with player audio intake through ASR and synthesized announcements/dialogue via AudioClaw. None of that is present here. The code neither consumes audio nor invokes any ASR/TTS system; it only formats text and writes JSON files. Therefore the actual behavior is materially narrower and different from the declared voice-oriented functionality.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Most of the code aligns with the declared NPC voice pipeline: it takes player input, uses ASR for audio, generates an NPC reply based on profile and relationship/context, and synthesizes speech. However, the code also includes an additional undeclared capability to send generated scene audio to Feishu via `send_npc_scene_to_feishu.py`, which is an external messaging/integration behavior not mentioned in the description. It also explicitly supports direct text input that bypasses ASR, whereas the description emphasizes player voice intake through AudioClaw ASR. The Feishu delivery feature is the stronger mismatch because it expands the skill's capabilities beyond reusable NPC voice behavior into chat-platform distribution.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description is about in-game NPC voice behavior and dialogue features, including ASR intake and synthesized NPC speech behavior. The supplied code does not implement NPC persona logic, dialogue generation, relationship-aware responses, ASR, narration control, or reusable in-world voice behavior. Instead, it post-processes already synthesized audio and delivers it to Feishu as sequential chat audio messages, optionally with text labels. This is a materially different primary purpose and introduces undeclared external messaging/network capabilities and resource use (Feishu API, app secrets, chat IDs).

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description is for a broad NPC voice system combining dialogue behavior and both ASR/TTS features tied to AudioClaw. The supplied code only transcribes audio through SenseAudio endpoints and outputs JSON. It contains no NPC dialogue logic, no voice persona management, no relationship-aware behavior, and no text-to-speech generation. The primary purpose and external service usage are materially different from the description, so this is a clear mismatch.

Credential Access

High
Category
Privilege Escalation
Content
Practical rule:
- `scripts/batch_tts_scene.py` and `scripts/run_player_voice_npc_pipeline.py` now default to `SENSEAUDIO_API_KEY`
- If the host app injects `SENSEAUDIO_API_KEY` as a login token such as `v2.public...`, the shared bootstrap replaces it with the real `sk-...` value from `~/.audioclaw/workspace/state/senseaudio_credentials.json` before the TTS stage starts
- The ASR scripts keep their own existing defaults and are intentionally not changed here

## Resources
Confidence
98% confidence
Finding
The skill explicitly states that a login token may be replaced by reading the real `sk-...` API key from `~/.audioclaw/workspace/state/senseaudio_credentials.json`. That is credential access behavior: it allows the skill/runtime to retrieve a more privileged secret from local storage, expanding blast radius if the skill is misused or if an attacker can trigger the workflow.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
skill_dir = Path(__file__).resolve().parent
    outdir = Path(args.outdir)
    outdir.mkdir(parents=True, exist_ok=True)
    env = os.environ.copy()

    if bool(args.input_audio.strip()) == bool(args.input_text.strip()):
        raise SystemExit("Provide exactly one of --input-audio or --input-text.")
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill describes use of shell, network, file, and environment-backed operations but does not declare any explicit tool scope or permission boundaries. In an agent environment, that ambiguity can lead to over-broad execution privileges, making accidental or unauthorized external calls, file access, or command execution more likely.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill encourages transcription of player audio and automatic Feishu audio delivery without a clear warning that user content will be transmitted to external services. In a voice-interaction context, this can expose sensitive spoken content, identifiers, or private conversation data without informed consent.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
2. If the input is text, still run `scripts/run_player_voice_npc_pipeline.py --input-text ...` so the reply stays on the same voice pipeline.
3. In ongoing NPC dialogue mode, default to `--send-feishu-audio` so the generated NPC lines are sent one by one as Feishu `audio` messages.
4. Only fall back to text-first replies if the user explicitly asks for text-only output or the channel cannot play voice.
5. If the user says "直接发语音" or "一条一条发 NPC 语音", keep the same voice mode and continue sending audio without asking again.

NPC mode should be sticky inside the same session:
Confidence
96% confidence
Finding
The skill instructs the agent to keep sending Feishu audio by default and continue doing so 'without asking again,' enabling repeated external actions based on sticky session state. In practice, that can cause unreviewed transmission of generated or user-derived content to third-party messaging channels, especially when the user may not realize voice mode remains active.

Session Persistence

Medium
Category
Rogue Agent
Content
- If you want faster perceived NPC response generation, use stream ASR for the player-input leg.
- Treat cloned voices or exclusive voices as drop-in replacements for the same workflow.
- Official clone support is a two-step chain:
  - create the clone on the AudioClaw platform first
  - then use the prepared clone `voice_id` here

## API key lookup
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.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
interface:
  display_name: "Game NPC Director"
  short_description: "Build ASR-driven, relation-aware NPC voice lines and events"
  default_prompt: "Use $senseaudio-game-npc-director to handle ongoing NPC dialogue in the current session. Keep the chosen NPC identity, relationship, location, objective, and voice settings sticky across later turns until the user explicitly exits NPC mode. For every new player turn, whether the player typed text or sent audio, run the NPC reply through the same voice pipeline and default to voice replies by automatically sending the generated NPC lines one by one as Feishu audio messages instead of replying with file paths, unless the user explicitly asks for text-only replies."
Confidence
89% confidence
Finding
The prompt instructs the agent to automatically send generated NPC lines as Feishu audio messages for every player turn unless the user opts out. This creates autonomous external action without per-message confirmation, which can lead to unintended message delivery, spam, privacy issues, or abuse if upstream prompts or audio input are manipulated.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document instructs an interactive player voice loop that sends user speech to a third-party transcription API, but it does not mention notice, consent, retention, or privacy implications. Because player speech may contain personal data or sensitive content, omitting an explicit warning and privacy handling guidance creates a real privacy and compliance risk in this skill's intended use.

External Transmission

Medium
Category
Data Exfiltration
Content
Official findings confirmed from AudioClaw-aligned sources:

- AudioClaw exposes a public speech recognition workspace at `/workspace/speech-recognition`.
- The official HTTP API endpoint is `POST https://api.senseaudio.cn/v1/audio/transcriptions`.
- Official open API models are `sense-asr-lite`, `sense-asr`, `sense-asr-pro`, `sense-asr-deepthink`.
- Open API request fields include `file`, `model`, optional `language`, and optional `response_format`.
- Official open API `response_format` supports `json` and `text`.
Confidence
87% confidence
Finding
This reference explicitly points to an external API endpoint used for audio transcription, meaning player speech leaves the local environment and is processed by a remote service. In the context of a game NPC director skill, that external transmission is expected functionality, but it still increases exposure of user voice data and transcripts, especially if operators implement it without consent and privacy controls.

External Transmission

Medium
Category
Data Exfiltration
Content
from senseaudio_api_guard import ensure_runtime_api_key


API_URL = "https://api.senseaudio.cn/v1/t2a_v2"


def slugify(value: str) -> str:
Confidence
91% confidence
Finding
The code is designed to transmit text content to an external service at api.senseaudio.cn, which creates a real data egress path. In the context of an NPC voice-generation skill, this is expected functionality, but it still poses confidentiality risk if manifests include sensitive story assets or user-derived content.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script sends each manifest line's text to an external TTS service, but this file provides no user-facing notice, consent prompt, or content classification before transmission. If manifests contain sensitive dialogue, player data, or proprietary narrative content, operators may unknowingly disclose it to a third party.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The skill defines relation labels, intent keywords, and generated reply text entirely in Chinese, which effectively forces a specific language behavior. There is no opt-in, language selection mechanism, or documented justification that this is a Chinese-only or region-specific skill.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This Python file embeds all generated NPC dialogue and address terms in Chinese string literals, causing the skill to produce a specific language output by default. The file does not offer any language selection, opt-in, or justification that the skill is intentionally limited to a Chinese-language context.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_step(args: list[str], env: dict) -> None:
    completed = subprocess.run(args, env=env, check=False, capture_output=True, text=True)
    if completed.returncode != 0:
        raise SystemExit(completed.stderr.strip() or completed.stdout.strip() or f"Step failed: {' '.join(args)}")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The script includes an optional Feishu-sending stage that transmits generated artifacts outside the local pipeline, expanding the trust boundary beyond NPC voice generation. In a voice-processing skill, this creates an unexpected exfiltration path for transcripts or synthesized content, especially because the pipeline handles player-derived input and generated outputs.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code can optionally send generated audio/results to Feishu, which is an external transmission path for potentially sensitive player-derived content. In the context of an NPC voice pipeline, undisclosed outbound delivery materially increases privacy and data-handling risk because operators may not expect the skill to forward artifacts off-box.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
str(target_path),
    ]
    try:
        subprocess.run(command, check=True, capture_output=True, text=True)
    except subprocess.CalledProcessError as exc:
        stderr = (exc.stderr or "").strip()
        raise SystemExit(f"ffmpeg transcoding failed: {stderr or exc}") from exc
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This script sends chat text labels and audio content to Feishu using tenant credentials, but the file itself provides no explicit confirmation, warning, or consent gate before exfiltrating potentially sensitive dialogue and metadata to an external service. In this skill context, the data includes synthesized NPC lines and possibly session-derived chat routing, so accidental or unauthorized transmission to the wrong chat or tenant is a realistic privacy and data-leak risk.

External Transmission

Medium
Category
Data Exfiltration
Content
from senseaudio_api_guard import ensure_runtime_api_key


OPENAPI_URL = "https://api.senseaudio.cn/v1/audio/transcriptions"
PLATFORM_URL = "https://platform.senseaudio.cn/api/audio/transcriptions"
SUPPORTED_SUFFIXES = {".mp3", ".wav", ".mp4"}
OPENAPI_MAX_BYTES = 10 * 1024 * 1024
Confidence
88% confidence
Finding
The script is explicitly designed to transmit audio content to external SenseAudio endpoints, which is a real data egress behavior. In this skill context, sending player speech off-box is expected functionality, but it still becomes a security/privacy issue if operators or users are not clearly informed, if secrets are mis-scoped, or if sensitive audio is uploaded unnecessarily.

Static analysis

No suspicious patterns detected.