Back to skill

Security audit

Course TA

Security checks for vulnerabilities and agentic risk

Overview

This Course TA skill is mostly coherent, but it combines student-facing Q&A with broad Canvas admin powers, sensitive student-data access, persistent indexing/logging, and concrete unsafe file and token handling bugs that require Review before use.

Treat this as an instructor/admin tool, not a simple student Q&A bot. Install only with a least-privilege Canvas token, private staff-only log channels, explicit student notice for logging/forwarding, enrollment and discussion-reply sync disabled unless necessary, and fixes for filename containment plus Canvas URL origin validation before syncing untrusted Canvas content.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (5)

T02 · Agent Memory Poisoning

Error
Location
lib/canvas_sync.py:217
Finding

Student-Controlled Canvas Discussions Can Poison Persistent Agent Memory

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
lib/canvas_sync.py:332
Finding

Canvas-Controlled Filenames Permit Arbitrary File Writes Through Path Traversal

Content
View full analysis
max_size: skipped += 1 continue if ext in skip_ext: skipped += 1 continue if allowed_ext and ext not in allowed_ext: skipped += 1 continue # Validate download URL if not f.get("url"): skipped += 1 continue # Check if file changed (using updated_at + size as proxy) key = f"files/{f['id']}" file_hash = f"{f.get('updated_at', '')}_{size}" old_hash = state["content_hashes"].get(key) if file_hash == old_hash: continue # Download dest = self.canvas_dir / "files" / name try: self.client.download_file(f, dest) state["content_hashes"][key] = file_hash downloaded += 1 self.changes.append(f"file: {name}") except Exception as e: failed += 1 self.errors.append(f"file download '{name}': {e}") ``` ```python dest_path.parent.mkdir(parents=True, exist_ok=True) for attempt in range(BACKOFF_MAX_RETRIES + 1): try: resp = requests.get(file_url, stream=True, timeout=120) resp.raise_for_status() with open(dest_path, "wb") as f: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) return dest_path ``` ### Technical Analysis The remote Canvas `display_name` is appended directly to the local files directory. The code does not reject absolute paths, `..` components, embedded separators, or paths that resolve outside the intended directory. `download_file()` creates parent directories and opens the resulting path with `"wb"`, which truncat ...[truncated 1705 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
lib/canvas_api.py:71
Finding

Canvas Bearer Token Can Be Forwarded to Unvalidated Network Destinations

Content
View full analysis
tuple: """Load (base_url, access_token) from canvas.json. Raises FileNotFoundError if file missing, KeyError if malformed. """ path = cred_path or DEFAULT_CREDENTIALS_PATH if not path.exists(): raise FileNotFoundError(f"Canvas credentials not found at {path}") data = json.loads(path.read_text()) base_url = data["canvas_base_url"].rstrip("/") token = data["access_token"] if not base_url or not token: raise ValueError(f"Canvas credentials incomplete in {path}") return base_url, token ``` ```python self.base_url = base_url.rstrip("/") self.access_token = access_token self.write_mode = write_mode self.rate_limit_buffer = rate_limit_buffer self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {access_token}", "Accept": "application/json", }) ``` ```python if url.startswith("/"): url = self.base_url + url last_error = None for attempt in range(BACKOFF_MAX_RETRIES + 1): try: resp = self.session.request( method, url, params=params, data=data, json=json_body, stream=stream, timeout=60, ) ``` ```python url = endpoint if endpoint.startswith("http") else self.base_url + endpoint while url: resp = self._request("GET", url, params=params) data = resp.json() if isinstance(data, list): yield from data elif isinstance(data, dict): yield data else: print(f"[canvas_api] Unexpected JSON type from {url}: " f"{type(data).__name__}", file=sys.stderr) link_header = resp.headers.get("Link", "") links ...[truncated 2147 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
README.md:57
Finding

Third-Party Python Dependencies Are Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
lib/canvas_sync.py:414
Finding

Student Enrollment PII Is Collected and Stored by Default Without Restrictive Controls

Content
View full analysis
int: """Sync enrollment roster (JSON only, NOT indexed in memory for privacy).""" print(f" Syncing enrollments...", end=" ", flush=True) try: enrollments = self.client.get_enrollments(self.canvas_id) except CanvasAPIError as e: self.errors.append(f"enrollments: {e}") print(f"ERROR: {e}") return 0 # Build roster (sanitized — just names, roles, no emails) roster = [] for e in enrollments: user = e.get("user", {}) roster.append({ "user_id": e.get("user_id"), "name": user.get("name", user.get("sortable_name", "?")), "role": e.get("type", "?"), "state": e.get("enrollment_state", "?"), }) key = "enrollments" content = json.dumps(roster, indent=2) new_hash = _content_hash(content) old_hash = state["content_hashes"].get(key) changed = 0 if new_hash != old_hash: roster_path = self.canvas_dir / "enrollments" / "roster.json" roster_path.parent.mkdir(parents=True, exist_ok=True) roster_path.write_text(content + "\n") state["content_hashes"][key] = new_hash changed = 1 self.changes. ...[truncated 1848 chars]
Remediation
View remediation
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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (42)

Context-Inappropriate Capability

Critical
Category
Not specified by scanner
Confidence
99% confidence
Finding

The broadcast command escalates from a read-oriented teaching assistant to a write-capable cross-course announcement tool by instantiating a write-enabled Canvas client and posting to every active course. If exposed through the agent, an attacker or confused user could send unauthorized announcements across multiple classes, causing large-scale integrity, trust, and operational harm.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README documents capabilities far beyond a narrow Discord course Q&A assistant, including broad Canvas synchronization, admin tooling, and content management. This scope expansion increases the attack surface and suggests the skill may access or manipulate sensitive academic data in ways not justified by the stated purpose, creating a dangerous mismatch between expected and actual behavior.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

Advertising grades, rosters, enrollments, and cross-section broadcast capabilities introduces access to highly sensitive educational records and mass-communication functions that are not necessary for ordinary course-question answering. If exposed through the agent or misconfigured, these features could enable FERPA/privacy violations, unauthorized disclosure, or abuse of instructor-level powers.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 62)May include surrounding context.

bash
  pip3 install requests beautifulsoup4
  • A Canvas LMS API token (generate from Canvas → Account → Settings → New Access Token)

1. Install the skill

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Interaction logging and audit-trail creation are additional behaviors not highlighted in the user-facing description. While logging may be legitimate, undisclosed storage and secondary use of user questions/answers is a privacy and trust issue, especially in educational settings.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Interaction logging and audit-trail creation are additional behaviors not highlighted in the user-facing description. While logging may be legitimate, undisclosed storage and secondary use of user questions/answers is a privacy and trust issue, especially in educational settings.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Interaction logging and audit-trail creation are additional behaviors not highlighted in the user-facing description. While logging may be legitimate, undisclosed storage and secondary use of user questions/answers is a privacy and trust issue, especially in educational settings.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Interaction logging and audit-trail creation are additional behaviors not highlighted in the user-facing description. While logging may be legitimate, undisclosed storage and secondary use of user questions/answers is a privacy and trust issue, especially in educational settings.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Interaction logging and audit-trail creation are additional behaviors not highlighted in the user-facing description. While logging may be legitimate, undisclosed storage and secondary use of user questions/answers is a privacy and trust issue, especially in educational settings.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Interaction logging and audit-trail creation are additional behaviors not highlighted in the user-facing description. While logging may be legitimate, undisclosed storage and secondary use of user questions/answers is a privacy and trust issue, especially in educational settings.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest presents a local-materials Discord TA, but the skill also performs direct Canvas queries and write operations. This underdisclosure matters because networked data access and remote writes carry materially higher risk than passive local RAG lookups.

Content

No source excerpt is available for this finding.

Exfiltration Commands

High
Category
Prompt Injection
Confidence
90% confidence
Finding

The skill includes an instruction to send messages to the professor containing student identity and request summaries. Outbound messaging is a classic exfiltration primitive; in this context it is especially sensitive because the skill also handles educational records and private student communications.

Content

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

md
**If DM succeeds:**
- Confirm to the student: "✅ I've forwarded your message to Prof. <PROFESSOR_NAME>!"

**If DM fails** (error like "Unknown Channel", "Cannot send messages to this user", etc.):

Discord bots cannot initiate DMs with users unless:
- The user has DMs enabled for server members, AND

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The file introduces a full Canvas LMS API client, including network access to an external platform, credential loading, file downloads, and broad course-data retrieval. That materially exceeds the stated skill scope of answering questions from local workspace course materials, increasing attack surface and creating a path to access live institutional data if the skill or surrounding agent is misused.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The client exposes methods to retrieve sensitive LMS data such as enrollments and submissions, which can contain student identities, participation details, and academic records. Those capabilities are not justified by the described teaching-assistant purpose and create privacy and compliance risk if invoked by an agent, prompt injection, or an overbroad workflow.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The file implements a broad Canvas course-management dashboard with deadline aggregation, submission tracking, grade overview, roster access, and cross-section broadcasting, which materially exceeds the declared Discord course-Q&A/RAG teaching-assistant scope. In an agent environment, this kind of hidden scope expansion is dangerous because it grants access to operational and student-data functions that users would not reasonably expect from the advertised skill.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

These commands expose roster, submission status, engagement, and grade statistics that are unrelated to answering student questions from course materials and may reveal educational records or participation data. In the stated skill context, this unnecessarily expands access to sensitive student information and enables privacy-invasive workflows without any apparent authorization boundary or role checks.

Content

No source excerpt is available for this finding.

Instruction Override

High
Category
Prompt Injection
Confidence
70% confidence
Finding

This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Content

Scanner excerpt · references/setup-guide.md (reported line 81)May include surrounding context.

text

To find channel IDs:
1. In Discord: **Settings → Advanced → enable Developer Mode**
2. Right-click the channel → **Copy Channel ID**
3. Prefix with `channel:` in the config

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The documentation claims the assistant refuses grade inquiries while also exposing grade-related dashboard capabilities, creating a policy/implementation contradiction. Such inconsistencies are risky because operators may assume grades are inaccessible when the underlying tooling still permits retrieval, increasing the chance of accidental disclosure or prompt-mediated misuse.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
84% confidence
Finding

Periodic background sync, audit logging, submission tracking, and other persistent operational features indicate the skill maintains ongoing state and performs recurring actions beyond a single user request. In an agent context, persistent state and unattended syncing can magnify the impact of misconfiguration, stale authorization, or unauthorized data retention, especially when reads are described as unrestricted.

Content

Scanner excerpt · README.md (reported line 15)May include surrounding context.

md
- **Admin commands** — Inline content editing, Canvas sync triggers, submission tracking
- **Dashboard tools** — Deadlines, grades, engagement, roster, cross-section broadcast
- **Audit logging** — JSONL logs of all interactions
- **Write gating** — All Canvas write operations require explicit user confirmation; reads are unrestricted
- **Incremental sync** — SHA-256 change detection, only writes files that actually differ
- **Cron scheduling** — Periodic background sync (configurable interval)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill declares broad operational behavior including file reads/writes, subprocess execution, and external Canvas access, but does not declare an explicit tool allowlist or permissions boundary. In a skill that handles credentials, logs, configuration files, and messaging, missing scope restrictions increases the blast radius if the skill is misrouted, prompt-injected, or modified later.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The manifest presents a local-materials Discord TA, but the skill also performs direct Canvas queries and write operations. This underdisclosure matters because networked data access and remote writes carry materially higher risk than passive local RAG lookups.

Content

No source excerpt is available for this finding.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill grants unrestricted tool access without appropriate constraints. An agent with unfettered tool access can perform arbitrary actions including file modification, network requests, and code execution.

Content

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

md
- **Only** users in `admin_users` can trigger edits — all others get: "Sorry, only the course administrator can modify course information."
- **Only** files in `editable_files` can be modified — never modify SKILL.md, openclaw.json, or any system file.
- **Never** execute arbitrary code from admin messages — only update the listed markdown files.
- All Canvas write operations (POST/PUT) require explicit user confirmation — read operations are unrestricted.
- Log every edit with `log_interaction.py` using `status=admin_edit`.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The logging instructions direct the assistant to store excerpts of user questions and responses and potentially broadcast summaries externally. Plain-language capture of student content increases privacy risk, especially if messages contain grades, personal circumstances, or accidental sensitive disclosures.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill can repost summaries of student questions to a Discord log channel without notifying the student in advance. This creates a hidden secondary disclosure path for educational conversations, potentially exposing sensitive questions, circumstances, or identifiers to a broader audience than the student intended.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

Forwarding student requests to the professor via Discord DM is an outbound messaging capability outside the stated teaching-assistant/RAG purpose. That creates an exfiltration channel for user content and identifiers, and expands the consequences of prompt injection or social engineering.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.