Back to skill

Security audit

Oh My Teacher

Security checks for vulnerabilities and agentic risk

Overview

This exam-study assistant is mostly coherent, but its local state scripts can be tricked into reading or overwriting Markdown files outside their intended folder.

Review before installing if you will run the local scripts. Use the skill only in a workspace or sandbox you are comfortable letting it write to, avoid explicit --slug values from untrusted content, and inspect .oh-my-teacher state files until slug validation is fixed.

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

Warning
Location
scripts/snapshot.py:56
Finding

Path Traversal Through Unvalidated Snapshot Slug

Content
View full analysis

Vulnerability Details

File Location: scripts/snapshot.py:56-59, with write operations at scripts/snapshot.py:109-122 and scripts/snapshot.py:161-170
Vulnerability Type: Path traversal leading to arbitrary Markdown file creation or overwrite
Risk Level: Medium

Complete Code Snippet

python
def snapshot_path(workspace: Path, slug: str | None) -> Path:
    if slug:
        return snapshots_dir(workspace) / f"{slug}.md"
    return single_snapshot_path(workspace)

The resulting path is used without containment validation:

python
def cmd_save(args: argparse.Namespace) -> int:
    workspace = Path(args.workspace).resolve()
    slug = args.slug or (slugify(args.course) if args.course else None)
    path = snapshot_path(workspace, slug)
    path.parent.mkdir(parents=True, exist_ok=True)
    text = read_stdin()
    path.write_text(text, encoding="utf-8")
    if slug and args.active:
        active_path(workspace).write_text(slug, encoding="utf-8")
    write_state_json(workspace, text, slug, path)
    print(path)
    return 0

A second write-capable command uses the same unsafe path construction:

python
def cmd_set_active(args: argparse.Namespace) -> int:
    workspace = Path(args.workspace).resolve()
    if not args.slug and not args.course:
        raise SystemExit("set-active requires --slug or --course.")
    slug = args.slug or slugify(args.course)
    path = snapshot_path(workspace, slug)
    if args.require_exists and not path.exists():
        raise SystemExit(f"Snapshot not found: {path}")
    path.parent.mkdir(parents=True, exist_ok=True)
    active_path(workspace).write_text(slug, encoding="utf-8")
    print(slug)
    return 0

Technical Analysis

The --slug command-line argument is accepted directly and concatenated into a filesystem path. Unlike values produced by slugify, an explicit slug is not restricted to a safe file ...[truncated 2095 chars]

Remediation
View remediation

Remediation Suggestions

  1. Reject explicit slugs unless they match a strict allowlist, such as ^[A-Za-z0-9_-]+$ or an equivalent Unicode-aware policy.

  2. Prefer processing every supplied slug through slugify() rather than trusting --slug verbatim.

  3. Resolve both the state directory and candidate path, then verify containment before any read, directory creation, or write:

    python
    def safe_snapshot_path(workspace: Path, slug: str) -> Path:
        if not re.fullmatch(r"[\w-]+", slug, flags=re.UNICODE):
            raise SystemExit("Invalid snapshot slug.")
    
        base = snapshots_dir(workspace).resolve()
        candidate = (base / f"{slug}.md").resolve()
    
        if candidate.parent != base:
            raise SystemExit("Snapshot path escapes the state directory.")
        return candidate
    
  4. Apply the same validation to slugs read from the _active file, because persisted state may have been modified externally.

  5. Validate containment before mkdir(), not only before write_text().

  6. Add regression tests covering ../, nested traversal, absolute paths, empty values, path separators, and malicious _active contents.

  7. When possible, run the Skill with filesystem permissions restricted to its workspace.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/srs.py:89
Finding

Path Traversal Through Unvalidated SRS Slug

Content
View full analysis

Vulnerability Details

File Location: scripts/srs.py:89-92, with filesystem writes at scripts/srs.py:193-196, scripts/srs.py:270-278, and scripts/srs.py:291-315
Vulnerability Type: Path traversal leading to arbitrary Markdown file creation or overwrite
Risk Level: Medium

Complete Code Snippet

python
def srs_path(workspace: Path, slug: str | None) -> Path:
    if slug:
        return srs_dir(workspace) / f"{slug}.md"
    return single_srs_path(workspace)

The unsafe path reaches the common write function:

python
def write_rows(path: Path, rows: list[Row]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(markdown_table(rows), encoding="utf-8")

It is directly reachable through commands accepting --slug:

python
def cmd_init(args: argparse.Namespace) -> int:
    workspace = Path(args.workspace).resolve()
    path = srs_path(workspace, args.slug)
    if path.exists() and not args.force:
        raise SystemExit(f"SRS file already exists: {path}")
    write_rows(path, [])
    if args.slug and args.active:
        active_path(workspace).write_text(args.slug, encoding="utf-8")
    print(path)
    return 0

It is also reachable through update operations:

python
def cmd_update(args: argparse.Namespace) -> int:
    workspace = Path(args.workspace).resolve()
    today = parse_date(args.today)
    slug = resolve_slug(workspace, args)
    warn_multi_course(workspace, slug)
    path = srs_path(workspace, slug)
    rows = read_rows(path)
    topic = normalize_topic(args.topic)
    old = next((row for row in rows if row.topic == topic), None)

    # Preserve the topic's difficulty across updates unless explicitly overridden.
    difficulty = args.difficulty or (old.difficulty if old else "medium")
    new = updated_row(topic, args.score, today, old, difficulty=difficulty)
    rows = [row for row in rows if
...[truncated 2445 chars]
Remediation
View remediation

Remediation Suggestions

  1. Define one shared slug-validation function and use it for all direct arguments and persisted active slugs.

  2. Reject absolute paths, . and .. components, directory separators, null bytes, and values outside a strict character allowlist.

  3. Resolve and validate containment under .oh-my-teacher/srs before every read or write:

    python
    def safe_srs_path(workspace: Path, slug: str | None) -> Path:
        if slug is None:
            return single_srs_path(workspace).resolve()
        if not re.fullmatch(r"[\w-]+", slug, flags=re.UNICODE):
            raise SystemExit("Invalid SRS slug.")
    
        base = srs_dir(workspace).resolve()
        candidate = (base / f"{slug}.md").resolve()
        if candidate.parent != base:
            raise SystemExit("SRS path escapes the state directory.")
        return candidate
    
  4. Validate the contents read from _active before passing them to srs_path().

  5. Use atomic writes through a temporary file created inside the validated state directory, followed by os.replace(), to reduce corruption risk.

  6. Add tests for traversal strings, absolute paths, malicious active-state files, symlink-based escapes, and valid Unicode slugs.

  7. Consider refusing writes through symlinked state directories or checking resolved containment immediately before replacement.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Rogue AgentSelf-Modification, Session Persistence
Findings (125)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Skill-package validation, linting, repository inspection, agent/config contract enforcement, and static parsing are developer-tooling behaviors not disclosed by the educational description. That is dangerous because these behaviors can inspect internal files and metadata and may be granted access under false expectations about the skill's scope.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · references/INDEX.md (reported line 158)May include surrounding context.

md
| File | Contents |
|------|----------|
| `course-profiles.md` | Current Course Snapshot template, field rules, paper/lab/coding/oral optimization, multi-course snapshots, on-disk format |
| `environment-adaptation.md` | Host capability detection, capability-probe order, downgrade matrix, per-host output rules |
| `agent-adapter-contract.md` | Shared multi-agent adapter contract, capability tags, state/script policy, runtime prompt policy |
| `agent-optimization.md` | Agent launch protocol, optimization profiles, capability-to-behavior mapping, quality gates |
| `agent-inventory.md` | Agent capability inventory, source status, unknowns, and platform-specific notes |

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · references/environment-adaptation.md (reported line 89)May include surrounding context.

md
| Math rendering | LaTeX rendered | Use plain-text formulas and avoid display math |
| Audio/oral simulation | TTS/STT | Text-only examiner prompts and grading |

## Output Rules by Environment

### Agent Shell

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
80% confidence
Finding

Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Content

Scanner excerpt · references/question-types.md (reported line 53)May include surrounding context.

md
For `/quiz`, `/diagnose`, and high-stakes `/grade`, optionally ask the student to predict confidence (1-5) before submitting, then compare it to the actual score. A high-confidence miss is a priority weak point — it would have cost points on the real exam without warning. See `references/learning-strategies.md` → Confidence Calibration. When used, add one line to the grading output:

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
85% confidence
Finding

Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Content

Scanner excerpt · references/socratic-mode.md (reported line 7)May include surrounding context.

md
## Goal

Help the student discover the key idea by answering targeted questions. Manage difficulty and hints; do not lecture by default.

## Interaction Flow

Self-Modification

High
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Content

Scanner excerpt · scripts/srs.py (reported line 433)May include surrounding context.

python
init = sub.add_parser("init", help="Create an empty SRS table.")
    init.add_argument("--slug", help="Course slug for multi-course mode.")
    init.add_argument("--active", action="store_true", help="Set slug active.")
    init.add_argument("--force", action="store_true", help="Overwrite existing file.")
    init.set_defaults(func=cmd_init)

    active = sub.add_parser("set-active", help="Set active multi-course SRS slug.")

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/package_check.py (reported line 71)May include surrounding context.

python
def test_cli_unknown_agent_fails(self):
        script = SCRIPT_DIR / "build_runtime_prompt.py"
        env = os.environ.copy()
        env["PYTHONIOENCODING"] = "utf-8"
        result = subprocess.run(
            [sys.executable, str(script), "--root", str(ROOT_DIR), "--agent", "missing-agent"],

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests/test_build_runtime_prompt.py (reported line 57)May include surrounding context.

python
def test_cli_unknown_agent_fails(self):
        script = SCRIPT_DIR / "build_runtime_prompt.py"
        env = os.environ.copy()
        env["PYTHONIOENCODING"] = "utf-8"
        result = subprocess.run(
            [sys.executable, str(script), "--root", str(ROOT_DIR), "--agent", "missing-agent"],

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests/test_build_runtime_prompt.py (reported line 71)May include surrounding context.

python
def test_cli_unknown_agent_fails(self):
        script = SCRIPT_DIR / "build_runtime_prompt.py"
        env = os.environ.copy()
        env["PYTHONIOENCODING"] = "utf-8"
        result = subprocess.run(
            [sys.executable, str(script), "--root", str(ROOT_DIR), "--agent", "missing-agent"],

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests/test_validate_skill.py (reported line 171)May include surrounding context.

python
def test_cli_unknown_agent_fails(self):
        script = SCRIPT_DIR / "build_runtime_prompt.py"
        env = os.environ.copy()
        env["PYTHONIOENCODING"] = "utf-8"
        result = subprocess.run(
            [sys.executable, str(script), "--root", str(ROOT_DIR), "--agent", "missing-agent"],

Unvalidated Output Injection

High
Category
Output Handling
Confidence
95% confidence
Finding

Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.

Content

Scanner excerpt · scripts/tests/test_build_runtime_prompt.py (reported line 75)May include surrounding context.

python
env["PYTHONIOENCODING"] = "utf-8"
        with tempfile.TemporaryDirectory() as tmpdir:
            output = Path(tmpdir) / "prompt.md"
            result = subprocess.run(
                [sys.executable, str(script), "--root", str(ROOT_DIR), "--agent", "trae", "--output", str(output)],
                capture_output=True,
                text=True,

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests/test_srs.py (reported line 651)May include surrounding context.

python
def test_workspace_env_var_used_as_default(self):
        import os
        with tempfile.TemporaryDirectory() as tmp:
            env = dict(os.environ, OMT_WORKSPACE=tmp)
            script = SCRIPT_DIR / "srs.py"
            subprocess.run([sys.executable, str(script), "init"],
                           capture_output=True, text=True, cwd=str(SCRIPT_DIR), env=env)

Static analysis

No suspicious patterns detected.