Back to skill

Security audit

招商线索池

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent internal lead-management CLI, but it needs Review because it can use shared/local tokens for real business data and lacks strong boundaries on where sensitive requests go and when plaintext phone data is exposed.

Install only in a trusted internal environment. Do not run it with untrusted environment variables, and avoid HAWKEYE_LEAD_DOMAIN or cookie-name overrides unless the host is explicitly approved. Treat --plain-phone as sensitive PII access that may change lead state, and require explicit human approval even though the CLI does not enforce it. Tokens previously used with an untrusted launcher or overridden domain should be rotated.

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

T09 · Insecure Skill Coding Practices

Error
Location
lib/config.js:2
Finding

Unvalidated destination override can disclose authentication credentials and lead data

Content
View full analysis

Vulnerability Details

File Location: lib/config.js:2-11, lib/auth.js:19-24, lib/apiClient.js:32-42, lib/apiClient.js:68-78
Vulnerability Type: Unrestricted credential-bearing network destination
Risk Level: High

Vulnerable Code

lib/config.js:2-11:

js
export const DOMAIN =
  process.env.HAWKEYE_LEAD_DOMAIN ||
  "hawkeye-luren33.devops.sl.beta.xiaohongshu.com";

export const COOKIE_NAME =
  process.env.HAWKEYE_LEAD_COOKIE_NAME || `access-token-${DOMAIN}`;

export const BASE_URL = `https://${DOMAIN}`;

lib/auth.js:19-24:

js
function readAmbientSsoToken() {
  if (!fs.existsSync(AMBIENT_SSO_FILE)) return null;
  try {
    const data = JSON.parse(fs.readFileSync(AMBIENT_SSO_FILE, "utf8"));
    return data[`common-internal-access-token-${AMBIENT_SSO_TIER}`] || null;

lib/apiClient.js:32-42:

js
export function buildRequest(entry, bodyObj) {
  const token = getToken();
  const headers = { "Content-Type": "application/json" };
  if (token) {
    headers.Cookie = `${COOKIE_NAME}=${token}`;
  }
  return {
    method: entry.method,
    url: `${BASE_URL}${entry.path}`,
    headers,
    bodyObj,
  };
}

lib/apiClient.js:68-78:

js
const { method, url, headers } = buildRequest(entry, bodyObj);

let response;
try {
  response = await fetch(url, {
    method,
    headers,
    body: JSON.stringify(bodyObj ?? {}),
  });
} catch (err) {

Technical Analysis

The HAWKEYE_LEAD_DOMAIN environment variable controls the complete network destination without hostname validation or an allowlist. Every API request attaches either the manually configured browser token or the shared ambient SSO token as a Cookie header.

Consequently, HTTPS only protects transport to the selected host; it does not establish that the host is an authorized Hawkeye service. If an attacker or compromised launcher can influence the CLI envir ...[truncated 1869 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse the destination using the standard URL API and reject malformed values.
  2. Require the https: scheme.
  3. Enforce an exact allowlist of approved Hawkeye hosts. If suffix matching is unavoidable, use strict DNS-label boundary checks rather than substring matching.
  4. Do not attach credentials when the destination differs from the built-in approved host unless the alternate host has been explicitly configured in a trusted policy file.
  5. Never send a shared ambient SSO token to an environment-variable override.
  6. Remove the ambient-token fallback until acceptance by the intended production gateway has been verified and documented.
  7. Maintain separate credentials for test and production environments and bind each credential to an exact approved hostname.
  8. Consider rejecting HAWKEYE_LEAD_COOKIE_NAME overrides or validating them against an explicit per-host mapping.
  9. Add automated tests proving that attacker-owned hosts, lookalike suffixes, embedded ports, user-info syntax, and malformed domains are rejected.
  10. Rotate potentially exposed tokens if the CLI has previously run with an untrusted environment.

T09 · Insecure Skill Coding Practices

Warning
Location
lib/schemas.js:244
Finding

Plaintext phone retrieval and silent state mutation bypass confirmation enforcement

Content
View full analysis

Vulnerability Details

File Location: lib/schemas.js:244-266, bin/cli.js:78-83
Vulnerability Type: Missing confirmation control for sensitive data access and state-changing behavior
Risk Level: Medium

Vulnerable Code

lib/schemas.js:244-266:

js
get_lead_detail: {
  id: "get_lead_detail",
  command: "get-lead-detail",
  path: "/edith/api/seller/merchant_lead/get_lead_detail",
  method: "POST",
  mutating: false,
  verified: true,
  summary: "获取线索详情",
  notes: [
    "⚠️ 不是纯只读接口:当 source=private 且 plain_phone=true 时,(1) insite_accounts[].login_phone/pro_account_phone 和 outer_shops[].qcc_phone 会连同主手机号一起返回明文(默认脱敏);(2) 如果该线索当前是 PENDING/CONTACTED 状态,会静默把跟进状态自动推进为 TALKING。没有明确业务需要、没有征得用户同意之前不要传 plain_phone=true。",
    "not_online 字段读 LeadQueryServiceImpl.getLeadDetail 方法体确认完全没被使用,传了没有任何效果,是 IDL 里的死字段。",
    "没有权限查看该 lead_id 时,响应会伪装成业务失败 \"lead not found\",不会明确提示\"无权限\"——遇到 not found 不代表 lead_id 一定错了,也可能是没权限看这条线索所属的行业/团队。",
  ],
  requestSchema: {
    type: "object",
    properties: {
      lead_id: { type: "string", description: "线索 ID" },
      source: { type: "string", enum: ["private", "public"], description: "私海详情传 private 才可能返回明文手机号,见 notes" },
      plain_phone: { type: "boolean", default: false, description: "是否返回明文手机号,见 notes 的 PII 警示" },
      not_online: { type: "boolean", description: "IDL 死字段,服务端不读取,传了无效果" },
    },
    required: ["lead_id"],
  },

bin/cli.js:78-83:

js
if (entry.mutating && !values.confirm) {
  throw new UsageError(
    `"${entry.command}"(${entry.summary})会修改线索状态,需要显式加 --confirm(代表已获得运营人员确认)。将要发送的请求体:${JSON.stringify(body)}`
  );
}

Technical Analysis

The get-lead-detail command is classified as mutating: false, so the CLI does not require --confirm. However, its own schema documentation states that using source=private with plain_phone=true has two sensitive consequences:

  1. It returns plaintext merch ...[truncated 1925 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require --confirm whenever plain_phone is true, regardless of the command-level mutating flag.
  2. Display the exact request body and a clear warning that the operation accesses plaintext PII and may change follow-up status.
  3. Prefer splitting plaintext-phone retrieval into a distinct command explicitly marked as sensitive and mutating.
  4. Require an operation-specific confirmation value rather than accepting a generic boolean flag accidentally carried over by automation.
  5. Keep masked phone data as the default and reject plaintext retrieval unless source is explicitly private.
  6. Minimize output by redacting phone fields unless the user specifically requests that they be displayed.
  7. Prevent plaintext phone values from being written to debug logs, telemetry, command histories, or persistent Agent transcripts where possible.
  8. If the upstream API can be changed, separate detail retrieval from the state transition so a read operation never silently mutates a lead.
  9. Add tests proving that --plain-phone without confirmation is rejected and that ordinary masked detail retrieval remains non-mutating.
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (9)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
通过本目录下的零依赖本地 CLI(`bin/cli.js`,仅用 Node 内置 `fetch`)调用鹰眼后台的线索管理接口。10 个接口一一对应 10 个独立子命令,**每个子命令都自带 `--schema`(JSON Schema)和 `--help`(人类可读说明),字段/取值范围现查即可**,不需要预读本文档。

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 · lib/auth.js (reported line 7)May include surrounding context.

js
import { AMBIENT_SSO_TIER } from "./config.js";

const CRED_DIR = path.join(os.homedir(), ".hawkeye-lead-cli");
const CRED_FILE = path.join(CRED_DIR, "credentials.json");

// 公司里跑在 codewiz/seal/OpenClaw 这类 agent 环境中时,这个文件通常已经自带登录态
// (由 SSO 体系统一维护,多个内部 CLI 共用这个约定)。手动设置的 token 优先级更高,

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill exposes network and environment-dependent capabilities but does not declare an explicit tool scope or permission boundary. That makes it easier for an agent or operator to invoke outbound requests and use environment overrides without a clear least-privilege contract, increasing the chance of unintended access to internal systems or misuse of sensitive authentication context.

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 presents core user-facing instructions exclusively in Chinese, which imposes a language choice on all users. The file does not indicate that the tool is region-specific or provide any opt-in or alternative locale, matching the language-policy violation criteria.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The code reads an ambient SSO token from a shared file in the user's home directory and uses it as a fallback authentication source. That extends the skill's trust boundary beyond its own stored credentials and can cause unintended credential use or token exfiltration opportunities in agent environments where shared SSO material is present.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
53% confidence
Finding

Lines L12-L13 describe the skill as a local CLI wrapping 10 Hawkeye lead-management APIs, while L30-L38 introduce additional auth-management commands (auth set-token, auth status) as part of the exposed interface. This is not necessarily unsafe, but it does mean the documented behavior is broader than the stated '10 interfaces / 10 subcommands' framing.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

This code presents user-facing authentication errors exclusively in Chinese, and similar Chinese-only strings also appear later in the file. Under the policy, forcing a specific language without user opt-in or documented justification is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The comment at L01 says 'currently only supports one environment,' which expresses a fixed environment constraint in natural language. Under the policy rule for language/locale-style constraints, this is a documented forced choice without any user opt-in or explicit justified policy framing in this file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The package description is written entirely in Chinese and frames the skill as a Chinese-language 'merchant_lead' agent skill, with no indication that users can choose another language or locale. This can violate language/locale policy when a skill appears to assume a specific language by default without opt-in or a documented regional constraint.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.