Back to skill

Security audit

Course Schedule Export

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent course-timetable-to-calendar helper, but its calendar generator has a narrow input-validation weakness users should understand before importing generated files.

Reasonable to install for normal timetable exports. Use manifests from trusted sources, provide the actual timezone yourself, and inspect generated .ics files before importing if the source timetable or manifest came from someone else. The publisher should tighten ICS validation before relying on this for untrusted inputs.

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
scripts/generate_ics.py:43
Finding

ICS Property Injection Through Unvalidated Timezone and Incomplete Control-Character Escaping

Content
View full analysis

Vulnerability Details

File Location: scripts/generate_ics.py, lines 43–49, 145–147, and 193
Vulnerability Type: Calendar content injection caused by incomplete escaping and unsafe property-parameter interpolation
Risk Level: Medium

Vulnerable Code

python
def esc(text: str) -> str:
    return (
        text.replace("\\", "\\\\")
        .replace(";", r"\;")
        .replace(",", r"\,")
        .replace("\n", r"\n")
    )
python
fields = [
    "BEGIN:VEVENT",
    f"UID:{uid_for(course, start_dt)}",
    f"DTSTAMP:{stamp}",
    f"DTSTART;TZID={timezone}:{start_dt.strftime('%Y%m%dT%H%M%S')}",
    f"DTEND;TZID={timezone}:{end_dt.strftime('%Y%m%dT%H%M%S')}",
    f"SUMMARY:{esc(course['name'])}",
    f"LOCATION:{esc(course['location'])}",
    f"DESCRIPTION:{esc(chr(10).join(description_lines))}",
    "END:VEVENT",
]
python
lines = [
    "BEGIN:VCALENDAR",
    "VERSION:2.0",
    "CALSCALE:GREGORIAN",
    "METHOD:PUBLISH",
    f"X-WR-CALNAME:{esc(calendar_name)}",
    f"X-WR-TIMEZONE:{timezone}",
    *events,
    "END:VCALENDAR",
    "",
]

Technical Analysis

The ICS serializer accepts manifest-controlled text and inserts it into calendar properties. The esc() function escapes backslashes, semicolons, commas, and line-feed characters, but it does not reject or escape carriage returns or other control characters.

The timezone field is more directly exposed: it is inserted into TZID parameter positions and the X-WR-TIMEZONE property without escaping or validation. The documented schema describes this field as an IANA timezone, but the implementation does not enforce that requirement.

A JSON string can represent carriage-return and line-feed characters using escape sequences such as \r\n. After JSON parsing, these become control characters. A crafted timezone can therefore terminate the current ICS line and introduce attacker-se ...[truncated 1766 chars]

Remediation
View remediation

Remediation Suggestions

  1. Validate timezone as an IANA timezone before serialization:

    python
    from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
    
    def validate_timezone(value: str) -> str:
        if any(ord(char) < 0x20 or ord(char) == 0x7F for char in value):
            raise ValueError("Timezone contains control characters")
        try:
            ZoneInfo(value)
        except ZoneInfoNotFoundError as exc:
            raise ValueError(f"Invalid IANA timezone: {value}") from exc
        return value
    
  2. Reject carriage returns, null bytes, and other unsupported control characters in all manifest strings before generating output.

  3. Normalize line endings before text escaping. Convert \r\n and lone \r to \n, and then encode permitted newlines as the ICS literal \n.

  4. Apply stricter validation to values used in ICS property-parameter positions. Do not rely on text-property escaping for parameters such as TZID.

  5. Validate field types explicitly so that calendar_name, timezone, course names, locations, teachers, and notes must be strings.

  6. Add regression tests covering:

    • Lone \r
    • \r\n
    • Null bytes and other C0 controls
    • Semicolons, colons, commas, backslashes, and quotes
    • Attempted BEGIN:VEVENT, END:VEVENT, and BEGIN:VALARM injection
    • Invalid and valid IANA timezone identifiers
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • 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
Findings (5)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description promises a timetable-to-calendar conversion and verification tool that accepts many source formats and produces checked .ics calendars while resolving schedule-specific ambiguities. The supplied code does none of that. It is a standalone validation script for JSON manifest coverage: it parses manifest and coverage files, normalizes some course fields, matches coverage entries against courses, validates source provenance, checks for unknown source IDs, and reports uncovered courses. This is materially different from the declared primary purpose and advertised capabilities.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill instructs the agent to read reference files and run scripts that generate artifacts, which implies file read/write capability, but it does not declare any explicit tool scope or permissions boundaries. That creates an authorization ambiguity where an orchestrator may grant broader filesystem access than intended, increasing the risk of unintended file access or modification if the skill is invoked in a permissive environment.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

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

md
- a class that spans adjacent slots appears as only one shorter event

Common cause:
- reading only one source cell, or merging without checking the adjacent cell

Mitigation:
- check adjacent slot cells for same-course continuations

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · references/pitfalls.md (reported line 37)May include surrounding context.

md
- a class that spans adjacent slots appears as only one shorter event

Common cause:
- reading only one source cell, or merging without checking the adjacent cell

Mitigation:
- check adjacent slot cells for same-course continuations

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The example schema sets timezone to Asia/Shanghai, which can implicitly steer users or downstream agents toward a specific locale. The document does explain that timezone is user-specific, but the hard-coded example still presents one locale as the default without explicit opt-in or a neutral placeholder.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.