Back to skill

Security audit

命理占卜 · Chinese Fortune Telling

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent, but its local script wrappers can be made to run files outside the intended folders, so it needs review before installing.

Review or patch the bridge scripts before installation: restrict them to explicit allowlists, reject absolute paths and traversal, and avoid passing user-provided script names or @file paths. Users should also be told upfront that exact birth time, sex, birthplace, and longitude are sensitive and optional precision tradeoffs should be explained.

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

Warning
Location
scripts/run-cantian.cjs:63
Finding
Path Traversal Enables Execution of JavaScript Outside the Cantian Script Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run-cantian.cjs`, lines 63–94 **Vulnerability Type**: Unrestricted script path traversal leading to local script execution **Risk Level**: Medium ### Vulnerable Code ```js const scriptName = argv[0]; const scriptPath = path.join(CANTIAN_DIR, scriptName); if (!fs.existsSync(scriptPath)) { die(`找不到脚本: ${scriptPath}\n可用脚本:\n ` + fs.readdirSync(CANTIAN_DIR).filter((f) => f.endsWith('.ts')).join('\n '), 2); } // 拆出 --out,其余按顺序透传;@ 前缀展开为文件内容 let outFile = null; const passthrough = []; for (let i = 1; i < argv.length; i += 1) { const token = argv[i]; if (token === '--out') { outFile = argv[i + 1]; if (!outFile) die('--out 后面必须跟一个文件路径', 1); i += 1; continue; } if (token.startsWith('@')) { const source = token.slice(1); if (!fs.existsSync(source)) die(`@ 引用的文件不存在: ${source}`, 1); passthrough.push(fs.readFileSync(source, 'utf8').trim()); continue; } passthrough.push(token); } const result = spawnSync(process.execPath, [scriptPath, ...passthrough], { cwd: CANTIAN_DIR, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, }); ``` ### Technical Analysis The first command-line argument is treated as a script name and joined directly to `CANTIAN_DIR`. The code does not reject absolute paths, directory separators, or `..` traversal components. It also does not canonicalize the resulting path and confirm that it remains inside the intended Cantian directory. `path.join()` normalizes traversal components. Consequently, a value such as `../../some-directory/script.js` can resolve outside `scripts/cantian`. The only validation is `fs.existsSync()`, which verifies existence but not directory containment, file type, extension, ownership, or trust. The resolved path is subsequently passed to `process.execPath`, causing Node.js to execute the selected file. Argument-array execution prevents shell metacharacter injection, but it does not prevent selection and execu ...[truncated 1446 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace free-form script selection with an explicit allowlist: ```js const ALLOWED_SCRIPTS = new Set([ 'buildBaziFromLunar.ts', 'buildBaziFromSolar.ts', 'convertToTrueSolarTime.ts', 'getChineseCalendar.ts', 'queryFortuneRange.ts', ]); if (!ALLOWED_SCRIPTS.has(scriptName)) { die('Unsupported Cantian script', 1); } ``` 2. Resolve and verify directory containment as defense in depth: ```js const baseDir = fs.realpathSync(CANTIAN_DIR); const scriptPath = fs.realpathSync(path.resolve(baseDir, scriptName)); if ( path.dirname(scriptPath) !== baseDir || path.extname(scriptPath) !== '.ts' ) { die('Invalid Cantian script path', 1); } ``` 3. Reject absolute paths, null bytes, `..` components, and both POSIX and Windows path separators before resolution. 4. Verify that the target is a regular file rather than a directory or symbolic link. If symbolic links are unnecessary, reject them explicitly. 5. Add regression tests covering `../`, absolute paths, mixed separators, symlink escapes, and valid allowlisted basenames. 6. Continue using `spawnSync()` with an argument array and `shell: false`; this correctly avoids shell injection but must be combined with trusted executable-target selection. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run-engine.cjs:64
Finding
Path Traversal Enables Execution of JavaScript Outside the Engine Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run-engine.cjs`, lines 64–100 **Vulnerability Type**: Unrestricted script path traversal leading to local script execution **Risk Level**: Medium ### Vulnerable Code ```js const scriptName = argv[0]; const scriptPath = path.join(ENGINE_DIR, scriptName); if (!fs.existsSync(scriptPath)) { die( `找不到脚本: ${scriptPath}\n可用脚本:\n ` + fs .readdirSync(ENGINE_DIR) .filter((f) => f.endsWith('.js')) .join('\n '), 2, ); } let outFile = null; const passthrough = []; for (let i = 1; i < argv.length; i += 1) { const token = argv[i]; if (token === '--out' || token === '-o') { outFile = argv[i + 1]; if (!outFile) die('--out 后面必须跟一个文件路径', 1); i += 1; continue; } if (token.startsWith('@')) { const source = token.slice(1); if (!fs.existsSync(source)) die(`@ 引用的文件不存在: ${source}`, 1); passthrough.push(fs.readFileSync(source, 'utf8').trim()); continue; } passthrough.push(token); } const result = spawnSync(process.execPath, [scriptPath, ...passthrough], { cwd: ENGINE_DIR, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, }); ``` ### Technical Analysis The bridge accepts an unrestricted script name from `process.argv`, combines it with `ENGINE_DIR`, and checks only whether the resulting path exists. It does not enforce that the target is one of the intended engine programs or that the normalized path remains under `scripts/engine`. Directory traversal components are normalized by `path.join()`. An argument containing `../` can therefore select an existing JavaScript-compatible file elsewhere on the filesystem. That external file is then executed by Node.js through `spawnSync()`. The implementation does not invoke a shell, so ordinary shell-command injection is not the issue. The vulnerability is the failure to constrain which local program Node.js is allowed to execute. ### Attack Path 1. An attacker causes the Agent or anoth ...[truncated 1238 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only the documented engine basenames: ```js const ALLOWED_SCRIPTS = new Set([ 'bazi-analysis.js', 'jieqi.js', 'liuyao.js', 'marriage.js', 'meihua.js', 'qimen.js', 'zhuanshi.js', 'ziwei.js', ]); if (!ALLOWED_SCRIPTS.has(scriptName)) { die('Unsupported engine script', 1); } ``` 2. Resolve the base and target through `realpathSync()` and require the target’s parent directory to equal the canonical engine directory: ```js const baseDir = fs.realpathSync(ENGINE_DIR); const scriptPath = fs.realpathSync(path.resolve(baseDir, scriptName)); if ( path.dirname(scriptPath) !== baseDir || path.extname(scriptPath) !== '.js' ) { die('Invalid engine script path', 1); } ``` 3. Reject absolute paths, traversal components, path separators, and symbolic-link escapes. 4. Use `fs.statSync(scriptPath).isFile()` and, where practical, verify that the target is not a symbolic link. 5. Add automated negative tests for POSIX traversal, Windows traversal, absolute paths, nested paths, symlink escapes, and non-allowlisted files. 6. Preserve argument-array process creation with no shell. This is appropriate for preventing shell injection, but it does not replace target-path validation. ]]>
Vulnerability Patterns
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (47)

Ae1

High
Category
analysis-evasion
Content
ziFromSolar.ts, convertToTrueSolarTime.ts), pattern and useful-god analysis via scripts/engine/bazi-analysis.js, and Zi Wei Dou Shu palaces and four transformat
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ziFromSolar.ts, convertToTrueSolarTime.ts), pattern and useful-god analysis via scripts/engine/bazi-analysis.js, and Zi Wei Dou Shu palaces and four transformat
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ziFromSolar.ts, convertToTrueSolarTime.ts), pattern and useful-god analysis via scripts/engine/bazi-analysis.js, and Zi Wei Dou Shu palaces and four transformat
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ziFromSolar.ts, convertToTrueSolarTime.ts), pattern and useful-god analysis via scripts/engine/bazi-analysis.js, and Zi Wei Dou Shu palaces and four transformat
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ziFromSolar.ts, convertToTrueSolarTime.ts), pattern and useful-god analysis via scripts/engine/bazi-analysis.js, and Zi Wei Dou Shu palaces and four transformat
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**为什么必须走桥接层**:`cantian` 的 `queryFortuneRange.ts` 要求 JSON 作为单个 argv 传入,PowerShell 会破坏引号;`engine` 的脚本一律不支持 `--out`,而 Windows 下经管道传中文 stdout 会被按 GBK 解码,输出全部乱码。两个桥接
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| 核验 | 第三方报告是否可信 | 交叉校验(`verify-pillars.cjs`) | — |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| 核验 | 第三方报告是否可信 | 交叉校验(`verify-pillars.cjs`) | — |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
对立。实测两组命例:甲木日主一方(2000-06-05 06:00),`bazi-analysis.js` 判 **弱 106 分、宜取印比生扶(水木)**,`ziwei.js` 判 **偏强 316 分、宜补土、宜避木火**——**方向相反**;癸水日主一方(2000-05-05 06:00)两引擎方向一致,但分值差
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The comment states 2正1背=少阳(阳) and 2背1正=少阴(阴), but the code returns '阴' when heads===2 and '阳' when heads===1. This is a direct contradiction in the core divination logic documentation, not a mere omission, and it changes the meaning of generated readings.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
if (rule) rules.push(rule);
  }

  return rules;
}

/**
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
const dayStem = key[0];
    const mz = key.slice(1);
    if (mz === branch) {
      return rule['主用神'][0];
    }
  }
  return null;
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill instructs the agent to use Node and environment-dependent local scripts, but it does not declare any explicit tool scope, permissions, or allowed-tools boundary. That creates an authorization gap: a hosting agent may permit broader environment or filesystem access than the skill actually needs, increasing the chance of unintended code execution paths or access to local secrets through the runtime environment.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The trigger term "运势" is broad and can match generic life-advice, wellness, or emotional-support requests that are not actually asking for fortune telling. This can cause unintended invocation of a skill that requests highly sensitive birth and location data, expanding collection of personal data and steering users into deterministic occult advice when a narrower, safer skill would be more appropriate.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The phrase "问个事" is highly ambiguous and could refer to almost any request, not specifically divination. In this skill, that ambiguity matters because invocation may lead the agent to collect sensitive data or provide occult guidance in contexts where the user merely wanted ordinary advice or information, causing privacy and safety boundary failures.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The workflow explicitly requires collection of birth date, exact birth time, sex, birthplace, longitude, and optional twin/C-section details, which are highly sensitive personal data points, but the skill lacks a clear upfront user warning and consent-oriented notice. This increases privacy risk by normalizing collection of identifying and inferable data without transparency about sensitivity, retention expectations, or the option to proceed with lower-precision modes.

Natural-Language Policy Violations

Medium
Confidence
78% confidence
Finding
Line L80 states that analyses missing the provided Chinese declaration template are automatically deemed unacceptable. This effectively forces outputs to include a specific-language template with no indication that users may choose another language or locale.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
This markdown file contains only Chinese-language instructions and terminology, and there is no natural-language indication that users may request another language or that the skill is intentionally limited to a Chinese-speaking context. Under the policy rule for language or locale constraints, forcing a specific language without opt-in is a reportable issue.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
This markdown skill specifies all instructions and operational guidance only in Chinese, with no indication that the user can choose another language or locale. That creates a natural-language policy concern because it effectively forces a specific language without documented user opt-in or region-specific justification.

Natural-Language Policy Violations

Medium
Confidence
83% confidence
Finding
The entire skill guidance, templates, and required output phrasing are written only in Chinese, including mandatory disclaimer text and response patterns. There is no indication that the user may choose another language or that the locale restriction is intentionally limited to a Chinese-only context, which can violate the language/locale policy for skills that should not force a specific language without opt-in.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The script’s error messages are written only in Chinese, including validation and usage text. This creates a language/locale policy concern because the skill enforces a specific language for user interaction without opt-in or an explicit documented region-specific justification.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The unrecognized-location and insufficient-arguments messages are user-facing operational guidance, but they are only available in Chinese. For a generally usable CLI skill, forcing one language without opt-in or documented scope violates the stated natural-language locale policy criteria.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This TypeScript file emits validation errors, labels, and the final generated report entirely in Chinese, including hard-coded strings throughout the parser and renderer. Because the skill does not provide any user opt-in, language selection, or documented region-specific justification, it violates the language/locale policy criterion for natural-language policy violations.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
This JavaScript file is a code file, so SQP-3 applies. The module description, CLI usage text, and generated report content are entirely in Chinese, and the code provides no option for users to select another language or opt into this locale, which constitutes a language/locale policy issue under the rule.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The docstring at L67-L68 says the function accepts six groups of 0/1/2/3 digits with 0=少阳(阳不动) and 1=少阴(阴不动), but the implementation maps 0 to '阳' and 1 to '阴' while the earlier file header at L5 describes input as six groups of three 0/1 coin faces. These comments describe materially different input semantics from each other and from the actual parser contract, which can cause callers to provide the wrong kind of input.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/run-cantian.cjs:90

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/run-engine.cjs:96

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/verify-pillars.cjs:102