Back to skill

Security audit

awam-todo

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real local to-do skill, but its web server exposes unauthenticated cross-origin read/write APIs and unsafe date-based file paths that require careful review before installation.

Install only if you are comfortable with a local to-do web service that can read/write your task files, expose local path metadata, and open local folders or an editor. Do not leave the web server running while browsing untrusted sites, do not bind it beyond 127.0.0.1, and prefer a fixed version with authentication/CSRF/origin checks and strict date/path validation.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
web/server.py:397
Finding

Unauthenticated Cross-Origin Access to Local Todo Data and Mutation APIs

Content
View full analysis

Vulnerability Details

File Location: web/server.py:397-417, 459-468, 495-824
Vulnerability Type: Missing API authentication and permissive cross-origin resource sharing
Risk Level: High

Vulnerable Code

python
def _json(self, code, obj):
    body = json.dumps(obj, ensure_ascii=False).encode("utf-8")
    self.send_response(code)
    self.send_header("Content-Type", "application/json; charset=utf-8")
    self.send_header("Content-Length", str(len(body)))
    self.send_header("Access-Control-Allow-Origin", "*")
    self.end_headers()
    self.wfile.write(body)

def _read_body(self):
    length = int(self.headers.get("Content-Length") or 0)
    if length <= 0:
        return {}
    raw = self.rfile.read(length)
    try:
        return json.loads(raw.decode("utf-8")) or {}
    except Exception:
        return {}
python
def do_POST(self):
    path = urlparse(self.path).path
    if path == "/api/todos":
        self._api_create(self._read_body())
    elif path == "/api/todos/apply-patches":
        self._api_apply_patches(self._read_body())
    elif path == "/api/workspace/open":
        self._api_workspace_open(self._read_body())
    elif path in ("/api/workspace/editor", "/api/workspace/cursor"):
        self._api_workspace_editor(self._read_body())
    else:
        self._json(404, {"error": "not found"})

Technical Analysis

The loopback HTTP service does not authenticate requests, does not validate the Origin header, and returns Access-Control-Allow-Origin: * for JSON API responses.

The body parser also accepts JSON regardless of the request's declared content type. Consequently, an attacker-controlled website can send a CORS-simple POST request with Content-Type: text/plain and a JSON-encoded body. This avoids the preflight that would ordinarily be triggered by application/json, while _read_body() still parses the request as JSO ...[truncated 2259 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the wildcard CORS header. For a same-origin local application, do not enable CORS at all.
  2. Validate Origin and Host against an exact allowlist containing only the service's expected loopback origin and port.
  3. Generate an unguessable authentication or CSRF token when the server starts and require it on every API request.
  4. Set the token through a secure initialization mechanism rather than exposing it in broadly readable API responses.
  5. Require Content-Type: application/json for JSON endpoints and reject all other media types with HTTP 415.
  6. Add explicit request authorization checks to every read and mutation endpoint, including workspace-opening endpoints.
  7. Consider rejecting browser requests with missing or unexpected Sec-Fetch-Site metadata as defense in depth.
  8. Apply request-size limits before reading request bodies.
  9. Keep the default loopback binding and warn or require explicit confirmation when the user selects a non-loopback --host.

T09 · Insecure Skill Coding Practices

Error
Location
web/server.py:559
Finding

Path Traversal Through Unvalidated Task Date

Content
View full analysis

Vulnerability Details

File Location: web/server.py:559-579, 696-806; scripts/todo.py:622-624, 780-790
Vulnerability Type: Path traversal leading to file creation or overwrite outside the storage directory
Risk Level: High

Vulnerable Code

The API accepts the date directly from the request without validating its format:

python
date = _norm(body.get("date")) or now.strftime("%Y-%m-%d")
data = todo.load_file(date)
if data["archived"]:
    data["archived"] = False
tid = todo._next_id(date)

It later writes using that value:

python
data["tasks"].append(task)
todo.save_file(date, data["archived"], data["tasks"])
todo.build_index(now)
self._json(201, _task_view(date, task, now))

The bulk patch endpoint similarly accepts an unvalidated date and eventually calls todo.save_file():

python
date = _norm(task_fields.get("date")) or now.strftime("%Y-%m-%d")
if date not in files:
    files[date] = {"archived": False, "tasks": []}
python
if not _file_content_matches(date, data["archived"], data["tasks"]):
    todo.save_file(date, data["archived"], data["tasks"])
    wrote_dates.append(date)

The path helper performs direct path concatenation:

python
def _file_path(date):
    """date is expected to be 'YYYY-MM-DD' and returns an absolute storage path."""
    return os.path.join(_storage_dir(), date + ".md")

Technical Analysis

Although _file_path() documents that date should have the YYYY-MM-DD form, neither the direct creation endpoint nor the bulk patch endpoint enforces that requirement.

os.path.join() does not neutralize .. path components. A value such as ../../../target therefore produces a path equivalent to:

text
<storage-directory>/../../../target.md

Filesystem path resolution escapes the configured todo storage directory. save_file() then opens the resulting path in write mode, truncat ...[truncated 1992 chars]

Remediation
View remediation

Remediation Suggestions

  1. Validate every date before any filesystem operation:
    python
    def validate_date(value):
        if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", value or ""):
            raise ValueError("Invalid date")
        dt.datetime.strptime(value, "%Y-%m-%d")
        return value
    
  2. Apply this validation consistently to direct creation, bulk patch creation, CLI arguments, archive operations, and every helper that accepts a date or month.
  3. Resolve and enforce the storage boundary in _file_path():
    python
    root = os.path.realpath(_storage_dir())
    target = os.path.realpath(os.path.join(root, validated_date + ".md"))
    if os.path.commonpath([root, target]) != root:
        raise ValueError("Path escapes storage directory")
    
  4. Reject absolute paths, path separators, .., null characters, and values that do not represent valid calendar dates.
  5. Perform the containment check immediately before opening or deleting the file to centralize protection for every caller.
  6. Use atomic writes through a temporary file created inside the validated storage directory, followed by os.replace(), to reduce corruption risk.
  7. Add regression tests covering POSIX traversal, Windows separators, drive-qualified paths, absolute paths, malformed dates, and valid leap-day dates.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (32)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill description emphasizes conversational/CLI To-Do handling, but the file also specifies starting a local HTTP server, exposing REST API endpoints, auto-opening a browser, opening local directories, and launching an external editor. These are materially different capabilities that expand attack surface from local data mutation into process execution and local service exposure, and they are not surfaced prominently as security-sensitive behaviors.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill description emphasizes conversational/CLI To-Do handling, but the file also specifies starting a local HTTP server, exposing REST API endpoints, auto-opening a browser, opening local directories, and launching an external editor. These are materially different capabilities that expand attack surface from local data mutation into process execution and local service exposure, and they are not surfaced prominently as security-sensitive behaviors.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
由 `init` 自动生成)。核心命令见 `scripts/todo.py`(Python 3)。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
由 `init` 自动生成)。核心命令见 `scripts/todo.py`(Python 3)。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
由 `init` 自动生成)。核心命令见 `scripts/todo.py`(Python 3)。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
由 `init` 自动生成)。核心命令见 `scripts/todo.py`(Python 3)。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
由 `init` 自动生成)。核心命令见 `scripts/todo.py`(Python 3)。

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
改 `web/index.html` 时必须遵守:

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
82% confidence
Finding

The documented REST API exposes destructive operations such as DELETE and batch patch application that can create, update, or delete tasks and rewrite storage. If the local web service lacks strong origin validation, authentication, or anti-CSRF controls, another local webpage or process could induce unauthorized state-changing requests against the user's To-Do data.

Content

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

md
- `POST   /api/todos`               新增(重复检测命中返回 409 + duplicates,可 `force` / `update_id`)
- `PUT    /api/todos/<id>`          更新字段(含状态,带依赖/子任务守卫)
- `PATCH  /api/todos/<id>/status`   仅改状态(冲突返回 409 + conflicts,可 `force`)
- `DELETE /api/todos/<id>`          删除(同时清理其他任务对它的依赖 / 父任务引用)
- `POST   /api/todos/apply-patches` 批量应用 patch 一次性落盘(网页延迟保存入口)
  body: `{"revision": <GET /api/todos 返回的指纹>, "patches": [{"op":"create|update|status|delete", ...}]}`;
  保存前对比 revision 与当前文件指纹,不一致时把 patch 应用到最新文件(合并语义),响应含

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · web/index.html (reported line 8)May include surrounding context.

html
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#7c5cff">
<title>Awam Todo 看板</title>
<!-- 约束:无外部库、资源全内联、断网可用(不引用任何 CDN / 外链字体 / 远程图片) -->
<style>
  :root{
    /* 冷白底 + 紫色渐变主色 + 语义三态(绿成功 / 红逾期 / 琥珀容错) */

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · web/index.html (reported line 8)May include surrounding context.

html
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#7c5cff">
<title>Awam Todo 看板</title>
<!-- 约束:无外部库、资源全内联、断网可用(不引用任何 CDN / 外链字体 / 远程图片) -->
<style>
  :root{
    /* 冷白底 + 紫色渐变主色 + 语义三态(绿成功 / 红逾期 / 琥珀容错) */

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · web/server.py (reported line 25)May include surrounding context.

python
POST   /api/todos                       新增任务(重复检测命中时返回 409 + duplicates)
  PUT    /api/todos/<id>                  更新任务字段(含状态,带依赖/子任务守卫)
  PATCH  /api/todos/<id>/status           仅改状态(冲突返回 409 + conflicts)
  DELETE /api/todos/<id>                  删除任务(同时清理其他任务对它的引用)
  POST   /api/todos/apply-patches         批量应用 patch 并一次性落盘(网页端延迟保存入口)
  POST   /api/workspace/open              用系统文件管理器打开目录
  POST   /api/workspace/editor            用 env.json 配置的编辑器打开目录

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

These handlers let HTTP clients trigger os.startfile / open / xdg-open / editor execution on the local machine. In context, that is dangerous because the server is a local web service with no authentication, so any site or local process that can reach it may coerce the host into opening applications or sensitive workspaces, expanding impact beyond simple todo management.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The server sets Access-Control-Allow-Origin: * on JSON responses while exposing mutable endpoints and local app-launching features. That makes browser-based cross-origin interaction far easier and, combined with the lack of auth/CSRF controls, enables malicious web pages to read from and potentially drive the local service, including todo modifications and workspace/editor opening.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill declares broad capabilities including shell, file read/write, environment access, and network-facing behavior, but does not constrain them with an explicit tool scope such as permissions or allowed-tools. This creates an overprivileged execution surface: if the skill is auto-invoked, it can perform filesystem changes, launch processes, and expose a local server without a machine-readable least-privilege boundary.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

文件整体以中文规定技能描述、字段名、状态值、注释常量说明及示例交互,且未说明可按用户偏好切换语言或接受其他语言输出。按照规则,若技能在自然语言层面强制特定语言而没有用户选择或明确的地域性正当说明,应视为策略违规。

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases are extremely broad and include common everyday language such as '记一下' and '帮我记', with no clear exclusion conditions. In an agent environment, this can cause unintended auto-activation and lead to writes, state changes, launches, or server actions when the user did not intend to invoke a privileged To-Do skill.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/selftest.py (reported line 20)May include surrounding context.

python
def run(tmp, *args, timeout=60):
    """跑一条 CLI 命令。带超时保护:卡住的一律当成失败,不让自测整体挂死。"""
    try:
        r = subprocess.run([PY, os.path.join(tmp, "scripts", "todo.py")] + list(args),
                           capture_output=True, text=True, encoding="utf-8", errors="replace",
                           timeout=timeout)
        return r.returncode, (r.stdout or "").strip(), (r.stderr or "").strip()

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The natural-language interface, usage examples, help text, and task field conventions are all presented in Chinese, effectively forcing a specific language for interaction. The file does not offer user opt-in for language/locale selection or explain that the skill is intentionally region-specific.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Environment detection collects and persists host metadata including hostname, cwd, OS details, and Python version into env.json without an explicit warning or consent step. That data can reveal sensitive filesystem layout and host identity, and may later be exposed through the env command or accidental file sharing.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
88% confidence
Finding

The script launches an external program via subprocess.Popen using editor.path and the task workspace as arguments. Although it avoids shell=True, it still executes a user-configurable binary and opens a user-controlled path, so a local attacker or compromised configuration could cause arbitrary program execution when the work command is used.

Content

Scanner excerpt · scripts/todo.py (reported line 518)May include surrounding context.

python
kwargs["creationflags"] = (getattr(subprocess, "DETACHED_PROCESS", 0)
                                  | getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0))
    try:
        subprocess.Popen(_launch_argv(cmd, target), **kwargs)
    except Exception as e:  # noqa: BLE001
        return False, "启动编辑器失败:%s" % e
    return True, "已用编辑器打开"

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This HTML file declares lang="zh-CN", and the visible interface text throughout the page is exclusively Chinese. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is explicitly documented and justified.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

技能清单虽然提到可配置编辑器路径,但核心目的仍是输入、管理和保存待办事项。本文件在任务卡片中直接暴露“打开目录”和“用编辑器打开”入口,并在后续代码中调用后端接口触发本地工作空间打开,这属于对本地文件系统/外部程序的操作能力,而不只是待办数据管理界面本身。

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

openWorkspace() 向后端发送 /api/workspace/open 和 /api/workspace/editor 请求,请求语义表明会打开本地目录或启动编辑器。这种能力会跨出待办记录与状态维护的上下文,触发宿主环境中的外部程序或文件浏览动作,属于需要额外正当性的能力。

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The file's natural-language documentation, CLI help text, API descriptions, and many response messages are written only in Chinese, with no indication that the user can choose another language. This creates a locale/language policy issue because the skill imposes a fixed language rather than offering user choice or documenting a justified region-specific constraint.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/smoke-web.js:177