Back to skill

Security audit

中国农历黄历吉凶查询 · Chinese Almanac, Lunar Calendar & Auspicious Days

Security checks for vulnerabilities and agentic risk

Overview

This looks like a real Chinese almanac tool, but it needs Review because its helper scripts can send passwords and tokens to an environment-selected server and print secrets to terminal output.

Install only if you trust the publisher and can control the execution environment. Do not set HUANGLI_BASE to anything except the official HTTPS API host, avoid running unattended agent login with reusable passwords, and treat terminal output from auth.py as secret because it may contain a full token or generated password.

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

Error
Location
scripts/toolkit.py:44
Finding

Bearer tokens can be transmitted to an arbitrary configurable endpoint

Content
View full analysis

Vulnerability Details

File Location: scripts/toolkit.py:44-59
Vulnerability Type: Unrestricted destination for credential-bearing requests
Risk Level: High

Vulnerable Code

python
def env():
    base = os.environ.get("HUANGLI_BASE", BASE_DEFAULT).rstrip("/")
    token = os.environ.get("HUANGLI_TOKEN", "")
    if not token:
        raise SystemExit(f"Error: HUANGLI_TOKEN is not set. Get token at: https://nongli.skill.4glz.com/dashboard")
    return base, token


def http_json(url: str, token: str, method="GET", payload=None):
    data = None
    headers = {"Authorization": f"Bearer {token}"}
    if payload is not None:
        data = json.dumps(payload).encode("utf-8")
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, method=method, data=data, headers=headers)
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())

The configurable base is subsequently used to construct credential-bearing requests:

python
def cmd_by_date(args):
    base, token = env()
    url = f"{base}/api/lunar/date/{args.date}"
    print(json.dumps(http_json(url, token), ensure_ascii=False, indent=2))

Technical Analysis

HUANGLI_BASE is accepted without validating its scheme, hostname, port, or origin. Every request made through http_json() receives the HUANGLI_TOKEN as a bearer credential.

This means that a base such as http://attacker.example would disclose the token over plaintext HTTP to an attacker-controlled server. An attacker-controlled HTTPS endpoint would also receive the token because the implementation does not restrict the destination to the declared service, api.nongli.skill.4glz.com.

A configurable API endpoint can be legitimate for development or self-hosting, but credential-bearing production requests require explicit trust controls. Permission to query the declared almanac service does not ...[truncated 1096 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove HUANGLI_BASE overrides from production operation unless custom servers are an explicit requirement.
  2. Parse the endpoint with urllib.parse.urlsplit() and require:
    • The https scheme.
    • An exact allowlisted hostname.
    • An approved port.
    • No embedded username or password.
    • No URL fragment.
  3. Default to an exact allowlist containing only api.nongli.skill.4glz.com.
  4. If custom endpoints are necessary, require an explicit command-line opt-in separate from inherited environment configuration.
  5. Display a prominent warning and require confirmation before transmitting a token to a non-default host.
  6. Prevent authorization credentials from being forwarded across cross-origin redirects.
  7. Prefer short-lived, narrowly scoped tokens and provide prompt revocation controls.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/auth.py:29
Finding

Authentication credentials can be sent to an arbitrary or plaintext server

Content
View full analysis

Vulnerability Details

File Location: scripts/auth.py:29-35, 70-81, 145-147, 231-243
Vulnerability Type: Unrestricted authentication endpoint and sensitive-data transmission
Risk Level: High

Vulnerable Code

python
BASE = os.environ.get('HUANGLI_BASE', 'https://api.nongli.skill.4glz.com').rstrip('/')


def post_json(url, payload):
    body = json.dumps(payload).encode('utf-8')
    req = urllib.request.Request(url, data=body, method='POST', headers={'Content-Type': 'application/json'})
    with urllib.request.urlopen(req) as resp:
        return resp.getcode(), json.loads(resp.read())

Agent-mode authentication sends the username, password, and registration email to that unrestricted base:

python
def cmd_agent(action, username, email, password):
    """One-step agent mode: mode=agent, no browser, no polling."""
    start_payload = {
        'action': action,
        'username': username,
        'password': password,
        'mode': 'agent',
    }
    if action == 'register':
        start_payload['email'] = email

    try:
        code, data = post_json(f'{BASE}/api/auth/cli/device/start', start_payload)

Token verification also sends a bearer credential to the same configurable destination:

python
_, data = get_json(
    f'{BASE}/api/auth/verify',
    headers={'Authorization': f'Bearer {token}'}
)

The standard login and registration flow sends the same sensitive account data:

python
start_payload = {
    'action': action,
    'username': username,
    'password': password,
}
if action == 'register':
    start_payload['email'] = email

try:
    _, start = post_json(f'{BASE}/api/auth/cli/device/start', start_payload)

Technical Analysis

The authentication client reads HUANGLI_BASE at import time and places no trust restrictions on it. The selected endpoint receives passwords, usernames, registration email addr ...[truncated 1930 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pin authentication requests to the exact trusted HTTPS origin rather than sharing an unrestricted data API override.
  2. Validate the endpoint before any request:
    python
    from urllib.parse import urlsplit
    
    parsed = urlsplit(BASE)
    if (
        parsed.scheme != "https"
        or parsed.hostname != "api.nongli.skill.4glz.com"
        or parsed.username is not None
        or parsed.password is not None
        or parsed.fragment
    ):
        raise SystemExit("Refusing untrusted authentication endpoint")
    
  3. Maintain separate endpoint policies for authentication and ordinary almanac queries.
  4. Do not permit inherited environment variables alone to authorize sending passwords to a custom host.
  5. If private deployments must be supported, require explicit trust enrollment or certificate/public-key pinning.
  6. Disable cross-origin redirects for requests containing passwords or authorization headers.
  7. Use short-lived device codes and narrowly scoped, revocable access tokens.
  8. Avoid accepting reusable account passwords in unattended agent mode where a safer device authorization flow is available.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.py:105
Finding

Generated passwords and bearer tokens are exposed through standard output

Content
View full analysis

Vulnerability Details

File Location: scripts/auth.py:105-129, 245-251, 290-303
Vulnerability Type: Plaintext secret disclosure through process output
Risk Level: Medium

Vulnerable Code

Agent-mode registration prints the generated password in plaintext and then prints shell exports containing the complete token:

python
exports = shell_exports(token, BASE).strip()
print(f'\nAgent mode authorization successful (mode=agent, one-step).')
print(f'Username: {data.get("username")}')
if action == 'register':
    print(f'Email: {email}')
    print(f'Generated password: {password}')
    print(f'Masked password: {mask_secret(password)}')
if print_shell:
    print('\n# shell exports')
    print(exports)
else:
    print('\nRun in your current shell session:')
    print(exports)
print('\nFor persistence, store HUANGLI_TOKEN via your own secret manager.')

The exported value contains the complete access token:

python
def shell_exports(access_token, base_url):
    return (
        f"export HUANGLI_TOKEN='{shell_quote(access_token)}'\n"
        f"export HUANGLI_BASE='{shell_quote(base_url)}'\n"
    )

Standard registration also prints the complete generated password:

python
if action == 'register':
    print(f'Email: {email}')
    print(f'Generated password: {password}')
    print(f'Masked password: {mask_secret(password)}')

Successful standard authorization prints the complete token regardless of whether --print-shell was specified:

python
if print_shell:
    print('\n# shell exports')
    print(exports)
else:
    print('\nRun in your current shell session:')
    print(exports)
print('For persistence, store HUANGLI_TOKEN via your own secret manager or shell profile policy.')

Technical Analysis

The implementation writes reusable secrets directly to standard output. The later masked-password line does not protect the passwo ...[truncated 1512 chars]

Remediation
View remediation

Remediation Suggestions

  1. Do not print generated passwords by default.
  2. Make --print-shell an explicit opt-in that is required before printing the complete token; the default branch should print only safe instructions.
  3. Display a warning that secret output may be captured in logs or Agent transcripts.
  4. Prefer secure delivery through:
    • An operating-system credential store.
    • A dedicated secret manager.
    • A permission-restricted file created only with explicit consent.
    • A direct parent-process communication channel that is not logged.
  5. If a token file is supported, create it atomically with user-only permissions and never place it in the project directory.
  6. Add secret-redaction guidance and integration hooks for Agent and CI environments.
  7. Prefer short-lived, narrowly scoped tokens and make revocation readily available.
  8. If a generated password must be shown once, require an interactive confirmation and avoid printing it in non-interactive or Agent mode.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (9)

Tainted flow: 'req' from os.environ.get (line 50, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/toolkit.py (reported line 51)May include surrounding context.

python
data = json.dumps(payload).encode("utf-8")
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, method=method, data=data, headers=headers)
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The advertised purpose is a calendar/almanac utility, but the skill documentation includes account registration/login, token acquisition, credential handling, and token export behavior that are materially broader than the stated function. This mismatch can mislead users and platforms into granting trust or permissions they would not have approved if the authentication and secret-handling behavior were disclosed up front.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill declares outbound hosts and required environment variables, and the markdown also documents authentication flows, but it does not declare an explicit tool/permission scope such as allowed tools or permissions. That weakens least-privilege enforcement and makes it harder for a host agent or reviewer to understand that the skill can access secrets and make network requests.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases include very broad everyday language such as '这一天好不好' and generic calendar/date-selection terms, which can cause the skill to activate outside clearly intended contexts. Overbroad invocation increases the chance of unexpected execution, network requests, or credential-related flows when a user did not intend to use this specific skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The markdown promotes automatic username/password-based login and token retrieval without a prominent warning that credentials will be handled and transmitted to a remote service. In a skill context, silent or under-disclosed credential submission is dangerous because users may treat the skill as a simple lookup tool rather than an account-linked service that processes secrets.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes a Chinese almanac, lunar conversion, and auspicious-day query tool, but this file implements username/password login, registration, token issuance, and session verification against remote auth endpoints. Account lifecycle and auth-device flow are materially broader than the described end-user functionality of calendrical lookup and date filtering.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The module docstring states this is an authorization helper specifically for the 'Huangli' toolkit, and the file path and command examples indicate a Chinese calendar/domain-specific skill. While not inherently unsafe, the natural-language labeling and branding suggest a fixed locale/domain experience without any user-facing option to choose language or locale behavior.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/auth.py (reported line 16)May include surrounding context.

python
python3 auth.py login --print-shell

Security behavior:
    - Does not write token/env files by default.
    - Does not modify shell profile files (e.g. ~/.zshrc).
    - Prints shell exports for one-session use.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Beyond simple access to an almanac API, the code can create new accounts, generate passwords, and submit registration data to the backend. Creating user accounts is not an obvious or declared requirement of a Huangli/Nongli/Jixiong query tool based on the manifest text.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.