Back to skill

Security audit

xhs-note-search

Security checks for vulnerabilities and agentic risk

Overview

The skill largely does what it claims, but it needs review because it sends API credentials and analysis targets to a third-party service and automatically saves fetched data locally in plaintext.

Install only if you are comfortable sending your Xiaohongshu search terms or URLs and your Guaikei API token to the Guaikei service. Treat the generated logs as sensitive: they may contain research history, comments, profile data, and token-bearing URLs, so keep them out of source control, shared workspaces, backups, and support bundles unless reviewed and redacted.

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

T09 · Insecure Skill Coding Practices

Warning
Location
src/utils/request.js:71
Finding

API credential exposed in URL query strings

Content
View full analysis

Vulnerability Details

File Location: src/utils/request.js:71-108; credential-bearing call sites include src/api/search.js:23-26,52-60, src/api/detail.js:20-23,46-51, and src/api/post.js:20-23,46-51
Vulnerability Type: Sensitive credential in URL query parameters
Risk Level: Medium

Complete Code Snippet

javascript
async function postJson(path, params, data) {
  if (!path || typeof path !== "string") {
    throw new Error("path 必须是非空字符串");
  }
  if (!params || typeof params !== "object") {
    throw new Error("params 必须是对象");
  }
  if (!data || typeof data !== "object") {
    throw new Error("data 必须是对象");
  }
  const fullPath = `${path}?${querystring.stringify(params)}`;
  const jsonData = JSON.stringify(data);
  const options = {
    host: constants.BASE_URL,
    path: fullPath,
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Content-Length": Buffer.byteLength(jsonData),
    },
  };
  return await request(options, jsonData);
}

async function getJson(path, params) {
  if (!path || typeof path !== "string") {
    throw new Error("path 必须是非空字符串");
  }
  if (!params || typeof params !== "object") {
    throw new Error("params 必须是对象");
  }
  params._ = Date.now();

  const fullPath = `${path}?${querystring.stringify(params)}`;
  const options = {
    host: constants.BASE_URL,
    path: fullPath,
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  };
  return await request(options);
}

Credential-bearing parameters are supplied by each API module, for example:

javascript
return await postJson(
  "/api/xiaohongshu/note-search/keyword",
  { _: Date.now(), token: token },
  { keyword, type, sort, time, limit },
);
javascript
const res = await getJson("/api/xiaohongshu/note-search/info", {
  _: Date.now(),
  token: token,
  keyword,
 
...[truncated 1800 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove the token from all query parameter objects.

  • Send the credential in an authorization header, preferably:

    javascript
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json"
    }
    
  • If the upstream API cannot accept an authorization header, place the token in the HTTPS request body and ensure bodies are excluded from logs.

  • Configure the API server, reverse proxies, monitoring products, and web application firewalls to redact existing token query parameters.

  • Rotate all tokens that may already have been captured in request logs.

  • Use short-lived, revocable, least-privilege credentials where supported.

  • Avoid echoing request URLs in diagnostics and add automated tests asserting that generated paths never contain token=.

T09 · Insecure Skill Coding Practices

Warning
Location
src/utils/log.js:24
Finding

Sensitive API results and access-bearing URLs are stored in plaintext files

Content
View full analysis

Vulnerability Details

File Location: src/utils/log.js:24-35; callers include src/xiaohongshu/search-cli.js:196-199, src/xiaohongshu/detail-cli.js:151-154, and src/xiaohongshu/post-cli.js:153-156
Vulnerability Type: Insecure storage of sensitive data
Risk Level: Medium

Complete Code Snippet

javascript
const outputFilename = path.join(
  path.dirname(__filename),
  "..",
  "..",
  "logs",
  safeFilename,
);

try {
  await fs.promises.mkdir(path.dirname(outputFilename), { recursive: true });
  await fs.promises.writeFile(outputFilename, content);
  utils.printSuccess(`  → 已保存到 ${outputFilename}`);
} catch (error) {
  utils.printError(`日志写入失败: ${error.message}`);
}

A representative caller serializes the entire request metadata and returned dataset:

javascript
const finalOutput = {
  status: "success",
  error_code: "OK",
  message: "评论任务完成",
  timestamp: new Date().toLocaleString(),
  request: {
    command: "detail",
    url: url,
    limit: limit,
  },
  skill_metadata: {
    skill_version: constants.VERSION,
    runtime_version: process.versions.node,
    execution_time: Date.now() - startTime,
  },
  results: detailTask,
};

await log.taskWrite(
  `${startTime}_${validator.url2Name(url)}_detail.json`,
  JSON.stringify(finalOutput, null, 2),
);

Returned URLs can explicitly include access-bearing xsec_token values:

javascript
res.data.url =
  "https://www.xiaohongshu.com/explore/" +
  res.data.id +
  "?xsec_token=" +
  res.data.xsec_token;

Technical Analysis

Every successful task is automatically serialized into a persistent JSON file under the project-level logs/ directory. The stored content can include:

  • User-supplied note or profile URLs containing xsec_token.
  • Returned note and profile URLs containing xsec_token.
  • Search keywords and task parameters.
  • Complete note, profile ...[truncated 2002 chars]
Remediation
View remediation

Remediation Suggestions

  • Make result persistence opt-in rather than automatic.

  • Redact query parameters named xsec_token, token, and other credential-like fields recursively before printing or writing results.

  • Do not store complete response objects unless explicitly requested.

  • Create the log directory and files with owner-only permissions:

    javascript
    await fs.promises.mkdir(logDirectory, {
      recursive: true,
      mode: 0o700
    });
    
    await fs.promises.writeFile(outputFilename, redactedContent, {
      mode: 0o600,
      flag: "wx"
    });
    
  • Define a retention period and securely delete expired files.

  • Add logs/ to .gitignore and exclude it from default backup and support-bundle workflows.

  • Consider encryption at rest when datasets must persist.

  • Warn users before storing comments, profile information, or token-bearing URLs.

  • Review and remove existing log files, and rotate any credentials found in them.

T09 · Insecure Skill Coding Practices

Warning
Location
src/validate/url.js:3
Finding

Substring-based URL validation permits off-domain URLs to be submitted to the remote retrieval service

Content
View full analysis

Vulnerability Details

File Location: src/validate/url.js:3-45; downstream submission occurs in src/api/detail.js:17-24 and src/api/post.js:17-24
Vulnerability Type: Improper URL validation and allowlisting
Risk Level: Medium

Complete Code Snippet

javascript
function normalizeUrl(url) {
  if (typeof url !== "string" || url.trim() === "") {
    utils.printError(`小红书链接不能为空`);
    return false;
  }
  url = url.trim();
  url = url.replace("http://", "https://");
  if (url.indexOf("https://") !== 0) {
    utils.printError(`小红书链接必须以 https:// 开头`);
    return false;
  }
  if (url.indexOf(" ") !== -1) {
    utils.printError(`小红书链接不能包含空格`);
    return false;
  }
  return true;
}

function isNoteUrl(url) {
  if (!normalizeUrl(url)) return false;
  if (url.indexOf("https://www.xiaohongshu.com/explore/") !== -1) {
    return true;
  } else if (url.indexOf("https://xhslink.com/m/") !== -1) {
    return true;
  } else if (url.indexOf("https://xhslink.cn/m/") !== -1) {
    return true;
  } else {
    return false;
  }
}

function isProfileUrl(url) {
  if (!normalizeUrl(url)) return false;
  if (url.indexOf("https://www.xiaohongshu.com/user/profile/") !== -1) {
    return true;
  } else if (url.indexOf("https://xhslink.com/m/") !== -1) {
    return true;
  } else if (url.indexOf("https://xhslink.cn/m/") !== -1) {
    return true;
  } else {
    return false;
  }
}

The accepted value is then forwarded unchanged:

javascript
return await postJson(
  "/api/xiaohongshu/detail/url",
  { _: Date.now(), token: token },
  { url: url, limit: limit },
);

Technical Analysis

The validator searches the complete input for an approved URL substring instead of parsing the URL and comparing its actual hostname and pathname. An attacker-controlled URL can therefore pass validation by placing an approved URL inside its query string, fragm ...[truncated 2241 chars]

Remediation
View remediation

Remediation Suggestions

  • Parse input with the WHATWG URL class and compare canonical fields rather than using substring searches.
  • Require the protocol to be exactly https:.
  • Require the hostname to exactly match one of:
    • www.xiaohongshu.com
    • xhslink.com
    • xhslink.cn
  • Validate path prefixes independently:
    • /explore/ for note URLs.
    • /user/profile/ for profile URLs.
    • /m/ for supported short links.
  • Reject embedded credentials, unexpected ports, backslashes, malformed percent encoding, and control characters.
  • Return and use the canonical parsed URL instead of continuing to use the original string.
  • On the remote service, repeat the validation after every redirect and block loopback, link-local, private, reserved, and metadata-service address ranges.
  • Resolve DNS immediately before connection and protect against DNS rebinding.
  • Add regression tests for URLs containing approved text in query strings, fragments, usernames, and attacker-controlled subdomains.

A safer local validation pattern is:

javascript
function parseAllowedUrl(input, expectedType) {
  let parsed;
  try {
    parsed = new URL(input.trim());
  } catch {
    return null;
  }

  if (parsed.protocol !== "https:") return null;
  if (parsed.username || parsed.password || parsed.port) return null;

  const host = parsed.hostname.toLowerCase();

  if (host === "www.xiaohongshu.com") {
    const prefix =
      expectedType === "note" ? "/explore/" : "/user/profile/";
    if (!parsed.pathname.startsWith(prefix)) return null;
  } else if (host === "xhslink.com" || host === "xhslink.cn") {
    if (!parsed.pathname.startsWith("/m/")) return null;
  } else {
    return null;
  }

  return parsed.href;
}
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (40)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The code is narrowly focused on keyword-based note search via two endpoints: creating a search task and querying search results. It enriches returned search items with note/profile URLs, but there is no functionality here for retrieving a specific note's details, loading comments, or listing a blogger's/public creator's works. Since the declared description presents those as available capabilities, while this code chunk only covers search, the description overstates the implemented behavior for this supplied code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

该代码片段的实际功能仅是通用 CLI 参数处理与帮助文本生成,属于底层命令行输入解析组件。它没有表现出任何与小红书平台交互、网络请求、数据抓取、内容查询、评论读取或运营分析相关的行为。根据评估标准,虽然这可能是更大系统中的支持性实现,但就当前提供的代码片段而言,其主要行为与声明的技能目的存在明显不一致,因此应判定为描述与行为不匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

声明描述的是面向小红书公开内容搜索与数据抓取分析的业务能力,但提供的代码仅实现了本地日志写入功能,没有任何与小红书数据搜索、笔记详情获取、评论查看、博主作品抓取或分析相关的逻辑。虽然日志功能可能是某个更大系统的辅助模块,但就该代码片段本身来看,其实际行为与声明用途不一致,且涉及未声明的本地文件系统写入能力。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Based on the supplied code chunk alone, there is a clear description-to-behavior mismatch. The description promises a data acquisition/analysis tool for Xiaohongshu public content, but the code only prints a banner and colored log messages to stderr. Logging helpers can be a supporting component in a larger tool, but this chunk by itself does not implement any of the declared core capabilities or interact with Xiaohongshu data/resources.

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 41)May include surrounding context.

md
- "分析这条小红书笔记评论区的主要观点和负面反馈: node src/xiaohongshu/detail-cli.js --url 'https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy' --limit 100"

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- "分析这条小红书笔记评论区的主要观点和负面反馈: node src/xiaohongshu/detail-cli.js --url 'https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy' --limit 100"

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- "分析这条小红书笔记评论区的主要观点和负面反馈: node src/xiaohongshu/detail-cli.js --url 'https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy' --limit 100"

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- "看这个小红书博主最近 20 条作品都在发什么: node src/xiaohongshu/post-cli.js --url 'https://www.xiaohongshu.com/user/profile/xxx?xsec_token=yyy' --limit 20"

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- "看这个小红书博主最近 20 条作品都在发什么: node src/xiaohongshu/post-cli.js --url 'https://www.xiaohongshu.com/user/profile/xxx?xsec_token=yyy' --limit 20"

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- "看这个小红书博主最近 20 条作品都在发什么: node src/xiaohongshu/post-cli.js --url 'https://www.xiaohongshu.com/user/profile/xxx?xsec_token=yyy' --limit 20"

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest description, examples, headings, and operational instructions are entirely in Chinese, and the file does not indicate that another language is supported or that Chinese is a user-selected option. This creates a locale/language policy concern because the skill effectively requires a specific language without explicit opt-in or justification as a region-specific constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

This code's natural-language comments and API-facing descriptions are exclusively in Chinese, which can constitute a language policy violation when no user opt-in or locale justification is provided. The stated policy requires flagging skills that force a specific language without offering choice.

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 emits user-facing status and remediation text only in Chinese, including the warning and follow-up instructions. This enforces a specific language choice without offering any locale selection or fallback, which matches the language/locale policy violation category.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code contains hard-coded Chinese user-facing error messages and success output, which imposes a specific language on users without any opt-in or indication that the skill is intentionally limited to a Chinese-speaking context. The policy explicitly flags language or locale constraints when they are forced rather than offered as a choice or justified as region-specific.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Multiple user-visible error strings are hard-coded in Chinese, such as '请求失败', '响应解析失败', and validation errors, with no indication that the skill is region-specific or that users can opt into the locale. This is a natural-language policy concern because it imposes a language choice without documented justification or configurability.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The postJson helper serializes an arbitrary data object and sends it over the network via HTTPS, but this file contains no comment, log, prompt, or other disclosure indicating that user or system data may be transmitted. For code files, network calls that transmit data should have some visible warning unless clearly documented elsewhere or obviously inherent to the skill’s purpose, which is not established in this file alone.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code emits validation errors only in Chinese via string literals such as '搜索关键词不能为空' and similar messages. That imposes a specific language on users without offering a locale choice, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The manifest describes searching public notes, viewing note details/comments, and listing a blogger's public works for analytics use cases. This file additionally persists the full result set to disk via log.taskWrite, which is not an obvious requirement of simply returning note details/comments and expands the skill's capability into local data retention.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The tool sends a user-supplied URL together with an API token to an external service through post.createPostTask/getPostTask, but the execution flow does not clearly disclose this transmission at the time it occurs. Users may reasonably believe the CLI performs local processing of public URLs, so undisclosed external transmission creates a privacy and trust risk and could leak targets of analysis to the service operator.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The code writes the full task output, including requested profile URL and returned results, to a local JSON file without explicit consent or an inline warning at the point of persistence. This can expose scraped content, metadata, and potentially sensitive operational data to other local users, backups, or later unintended reuse, especially because filenames are derived from user input.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The calls to search.createSearchTask and search.getSearchTask transmit the user's keyword and related parameters over the network, which falls under network/HTTP operations that may send user data. This file includes status logs and a token requirement note, but it does not clearly warn users that their search terms will be sent to an external service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Writing search results to disk without an explicit warning creates a privacy surprise and may leave recoverable artifacts on shared machines, CI runners, or agent host environments. Because the file name also embeds the search keyword, even the presence of the file can reveal potentially sensitive user intent or competitive research topics.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The package description and keywords are entirely in Chinese, which imposes a specific language context in the skill metadata without indicating any user choice or multilingual support. Under the policy, language-specific natural-language content can be a violation when it forces a locale without opt-in or justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The README presents all user-facing instructions, warnings, and usage guidance only in Chinese. Under the policy, forcing a specific language without giving users a choice can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.