Back to skill

Security audit

Hogwarts RPG

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Hogwarts RPG, but it needs review because its instructions and helper scripts create real risks around shell execution, persistent memory, path handling, and overbroad role-control language.

Review before installing. The RPG behavior is mostly disclosed, but the current package should be treated cautiously until the moderation command avoids shell interpolation, save IDs are path-contained, persistent world memory is clearly treated as untrusted data, and the description accurately discloses the full seven-year content scope.

Vulnerability Patterns
  • 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
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:254
Finding

Shell Command Injection Through the Mandatory Moderation Workflow

Content
View full analysis
" | python3 {baseDir}/scripts/moderation.py check - ``` The accompanying workflow requires the agent to replace the placeholder with generated narrative text before executing the command. ### Technical Analysis The generated narrative is influenced by player input and is inserted into a double-quoted shell argument. Double quotes do not prevent shell command substitution. Constructs such as `$(command)` and backticks are evaluated by the shell before `echo` sends the resulting text to the moderation process. Moderation therefore occurs too late to prevent exploitation: the shell interprets command-substitution syntax before `moderation.py` receives the text. Escaping ordinary quotation marks alone would also be insufficient unless all shell metacharacters were handled correctly. Although the underlying Python moderation CLI safely supports standard input, the Skill instructs the agent to construct the standard input through an unsafe shell command. ### Attack Path 1. A player supplies text containing a shell payload, such as an instruction designed to make the narrative reproduce `$(attacker_command)`. 2. The language model incorporates the payload into its generated narrative. 3. The agent replaces the placeholder in the documented `echo` command with that narrative. 4. The host shell parses the resulting command. 5. Command substitution executes before `moderation.py` starts processing the narrative. 6. The output of the injected command is passed to moderation, concealing the fact that execution has already occurred. ### Impact Assessment Successful exploitation permits arbitrary command execution with the operating-system privileges of the process running the agent or Skill. Depending on those privilege ...[truncated 510 chars]
Remediation
View remediation

T02 · Agent Memory Poisoning

Error
Location
scripts/world_memory.py:78
Finding

Persistent Prompt Injection Through User-Controlled World Memory

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/game_engine.py:98
Finding

Path Traversal in Save-State Read, Write, and Delete Operations

Content
View full analysis
Remediation
View remediation

T01 · Skill Instruction Hijacking

Warning
Location
SKILL.md:49
Finding

Skill Text Improperly Claims Highest Instruction Priority

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (41)

Tainted flow: 'req' from os.environ.get (line 104, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/moderation.py (reported line 114)May include surrounding context.

python
method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            data = json.loads(resp.read().decode("utf-8"))
        result = data.get("results", [{}])[0]
        flagged = bool(result.get("flagged", False))

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Persistent storage of player-created world facts, memory inspection/deletion interfaces, and context injection mechanisms indicate broader data collection and administrative access than the description suggests. In a child-focused roleplay setting, storing freeform user-created content can capture personal or sensitive information even if the prompt says not to ask for it.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Persistent storage of player-created world facts, memory inspection/deletion interfaces, and context injection mechanisms indicate broader data collection and administrative access than the description suggests. In a child-focused roleplay setting, storing freeform user-created content can capture personal or sensitive information even if the prompt says not to ask for it.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

Persistent storage of player-created world facts, memory inspection/deletion interfaces, and context injection mechanisms indicate broader data collection and administrative access than the description suggests. In a child-focused roleplay setting, storing freeform user-created content can capture personal or sensitive information even if the prompt says not to ask for it.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Persistent storage of player-created world facts, memory inspection/deletion interfaces, and context injection mechanisms indicate broader data collection and administrative access than the description suggests. In a child-focused roleplay setting, storing freeform user-created content can capture personal or sensitive information even if the prompt says not to ask for it.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

Persistent storage of player-created world facts, memory inspection/deletion interfaces, and context injection mechanisms indicate broader data collection and administrative access than the description suggests. In a child-focused roleplay setting, storing freeform user-created content can capture personal or sensitive information even if the prompt says not to ask for it.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill invokes shell commands, reads and writes files, accesses environment variables, and may call external moderation backends, yet it declares no explicit tool scope or permission boundaries. That creates an unnecessarily broad execution surface and makes unintended or unsafe tool use harder to constrain or audit.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest description is entirely in German and defines German trigger words, indicating the skill is intended to activate and operate in German only. The file does not offer a language choice or document a justified region-specific constraint, which makes this a natural-language locale policy issue.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The user-facing narrative guidance, prompts, and quoted example messages are all prescribed in German, and the skill instructs the agent to speak in that mode throughout the adventure. There is no instruction to ask the user for preferred language or to allow switching, so the skill effectively forces a specific locale.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The manifest says the canonical plot of 'Stein der Weisen' is the red thread in the background and frames the experience around the player's own character in Harry's year. This file defines chapters, books_covered, characters, and scenes spanning years 1 through 7, including plots from 'Kammer des Schreckens' through 'Heiligtuemer des Todes', which is materially broader than the stated scope.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The natural-language descriptions in the metadata are explicitly written in German and define the narrative setting and tone without any indication that language is configurable or user-selected. Under the policy, forcing a specific language without opt-in is a locale/language policy concern.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest describes a player in Harry Potter's year with the canonical plot of 'Stein der Weisen' as the red thread in the background. This file includes locations explicitly tied to years 2 through 7, such as the Chamber of Secrets, Hogsmeade, the Order headquarters, the Department of Mysteries, and the Battle of Hogwarts, which materially broadens the narrative scope beyond the stated premise.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill metadata presented to users describes a year-1, age-12 Hogwarts adventure centered on 'Stein der Weisen', but the file actually defines content coverage across all seven school years and all major Harry Potter books. This mismatch can mislead users, reviewers, and any policy gating that relies on declared scope, causing under-review of later, darker, or more mature material.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The manifest describes a Hogwarts adventure in the player's year with the canonical plot of 'Stein der Weisen' as the background red thread. Beginning at y2_s01 and continuing through y7_s19, the file contains complete additional arcs for Chamber of Secrets, Prisoner of Azkaban, Goblet of Fire, Order of the Phoenix, Half-Blood Prince, and Deathly Hallows, which materially exceeds the described narrative scope.

Content

No source excerpt is available for this finding.

Tainted flow: 'INDEX_FILE' from os.environ.get (line 10, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/book_manager.py (reported line 29)May include surrounding context.

python
def save_index(index):
    with open(INDEX_FILE, "w", encoding="utf-8") as f:
        json.dump(index, f, ensure_ascii=False, indent=2)

Tainted flow: 'book_dir' from os.environ.get (line 46, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/book_manager.py (reported line 51)May include surrounding context.

python
os.makedirs(os.path.join(book_dir, "chunks"), exist_ok=True)

        # meta.json schreiben
        with open(os.path.join(book_dir, "meta.json"), "w", encoding="utf-8") as f:
            json.dump(book["meta"], f, ensure_ascii=False, indent=2)

        # characters.json schreiben

Tainted flow: 'book_dir' from os.environ.get (line 46, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/book_manager.py (reported line 55)May include surrounding context.

python
json.dump(book["meta"], f, ensure_ascii=False, indent=2)

        # characters.json schreiben
        with open(os.path.join(book_dir, "characters.json"), "w", encoding="utf-8") as f:
            json.dump(book["characters"], f, ensure_ascii=False, indent=2)

        # plot_graph.json schreiben

Tainted flow: 'book_dir' from os.environ.get (line 46, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/book_manager.py (reported line 59)May include surrounding context.

python
json.dump(book["characters"], f, ensure_ascii=False, indent=2)

        # plot_graph.json schreiben
        with open(os.path.join(book_dir, "plot_graph.json"), "w", encoding="utf-8") as f:
            json.dump(book["plot_graph"], f, ensure_ascii=False, indent=2)

        index["books"].append({

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The module-level natural-language description and all user-facing CLI strings are written exclusively in German, indicating a fixed language experience. There is no indication that users can opt into another language or that the locale restriction is required for a region-specific purpose.

Content

No source excerpt is available for this finding.

Tainted flow: 'path' from os.environ.get (line 617, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
88% confidence
Finding

The save path is derived from STATE_DIR/SAVES_DIR, which can be influenced via environment variables, and the code writes to that path without constraining it to a trusted base directory. In addition, save_id is concatenated directly into the filename, so if an attacker can invoke the script with a crafted save_id and control the environment, they may overwrite arbitrary files accessible to the process via path traversal or redirection to attacker-chosen directories.

Content

Scanner excerpt · scripts/game_engine.py (reported line 110)May include surrounding context.

python
def write_save(save_id, data):
    data["updated_at"] = datetime.now().isoformat()
    path = os.path.join(SAVES_DIR, f"{save_id}.json")
    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The delete_save command permanently removes a save file with os.remove immediately after checking existence. Although the function has a docstring and prints after deletion, there is no user confirmation prompt or explicit pre-action warning for this irreversible operation in the code path.

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 user-facing natural-language strings exclusively in German, including the module description and command-line help/output. That enforces a specific language without any opt-in, fallback, or justification that the skill is intended only for German-speaking users, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

Tainted flow: 'path' from os.environ.get (line 62, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/memory_system.py (reported line 44)May include surrounding context.

python
def save_memory(mem):
    mem["updated_at"] = datetime.now().isoformat()
    path = os.path.join(MEMORY_DIR, "player_memory.json")
    with open(path, "w", encoding="utf-8") as f:
        json.dump(mem, f, ensure_ascii=False, indent=2)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This file embeds natural-language documentation, CLI usage text, and operational messages exclusively in German, including the module docstring and user-visible command help. Under the policy, forcing a specific language without offering a choice or documenting a justified locale constraint is a natural-language policy violation.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/moderation.py (reported line 54)May include surrounding context.

python
OPENAI_MOD_KEY = os.environ.get("OPENAI_MOD_KEY") or os.environ.get("OPENAI_API_KEY")
FAIL_CLOSED = os.environ.get("MODERATION_FAIL_CLOSED", "1") == "1"
OPENAI_MOD_MODEL = os.environ.get("OPENAI_MOD_MODEL", "omni-moderation-latest")
OPENAI_MOD_URL = "https://api.openai.com/v1/moderations"
# Same-Auth-Moderation über OpenClaw (nutzt dasselbe GPT-OAuth wie der Spielleiter)
OPENCLAW_BIN = os.environ.get("OPENCLAW_BIN", "openclaw")
OPENCLAW_MOD_MODEL = os.environ.get("OPENCLAW_MOD_MODEL", "openai/gpt-5.5")