Back to skill

Security audit

CN Holiday Calendar

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its China holiday-calendar purpose, but it exposes captured API signing material and includes an unrestricted URL fetch path that should be reviewed before installation.

Review this skill before installing. Its normal holiday lookup behavior is understandable, but it should ideally validate notice URLs to official gov.cn hosts, block localhost/private-network destinations and redirects, and remove or replace the captured API signing values with an authorized access method. It does not show destructive behavior or hidden persistence, but its current network and credential handling are broader than a simple calendar checker needs.

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
scripts/query_holiday.py:235
Finding

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
str: """Fetch and extract the main body text from a gov.cn notice page.""" response = requests.get( url, headers={ "User-Agent": "Mozilla/5.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", }, timeout=20, ) response.raise_for_status() response.encoding = response.apparent_encoding or response.encoding or "utf-8" html = response.text text = _extract_div_text( html, lambda attrs: "trs_editor_view" in (attrs.get("class") or "").split(), ) if not text: text = _extract_div_text(html, lambda attrs: attrs.get("id") == "UCAP-CONTENT") if not text: raise SystemExit(f"Could not extract notice body from {url}") return text ``` The command-line interface accepts the URL directly: ```python body_parser = sub.add_parser("body", help="Fetch a notice body") body_parser.add_argument("url") ``` ```python if args.command == "body": print_body(args.url) return ``` ### Technical Analysis The `body` command passes an unrestricted, caller-supplied URL into `requests.get()`. The application does not validate: - The URL scheme - The destination hostname - The resolved IP address - The destination port - Whether the address belongs to a loopback, private, link-local, or reserved network - Redirect destinations Although the function documentation describes fetching a gov.cn notice, this restriction is not enforced in code. The HTTP client follows redirects by default, so validating only the initial URL would also be insufficient ...[truncated 2021 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/query_holiday.py:29
Finding

Hard-Coded Search API Credentials Exposed in Source Code and Documentation

Content
View full analysis
str: return md5_hex(f"{ATHENA_KEY}{ATHENA_SECRET}{timestamp_ms}TRS") ``` Consequently, a third party can generate valid signatures for current timestamps without using the skill. The documentation's description of the application key as captured from a ...[truncated 1451 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (8)

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · README.md (reported line 22)May include surrounding context.

md
- `lunar-python`

## Output rules

- Do not expose internal cache or refresh state in user-facing answers
- If a progress line is needed, use only:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill explicitly instructs the agent to read and write local cache files and to access gov.cn over the network, but it declares no permissions or allowed-tools scope. That mismatch creates an overprivileged/implicit-trust condition where an agent runtime may permit broader file and network actions than intended, and the workflow also relies on LLM-mediated selection/parsing of remote content, which increases the attack surface if tool access is not tightly constrained.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The reference explicitly instructs the skill to use a captured browser-session application key and includes static signing material (T-KEY and T-SEC) to generate authenticated requests. Embedding or relying on captured session-derived tokens/secrets in skill documentation encourages unauthorized reuse of credentials, creates a risk of secret leakage, and may violate the upstream service's access controls or terms of use.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest says the skill is for querying official gov.cn holiday notices to determine workdays, holidays, make-up workdays, and holiday names from State Council notices. This function builds extensive lunar calendar metadata, zodiac/GanZhi data, and enumerates nearly all public getters from the optional lunar library, which goes materially beyond that stated scope.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest frames the skill as a gov.cn holiday notice checker, but this evaluation path injects lunar, jieqi, solar_term, and optional expanded calendar details into ordinary date results. That behavior is not an obvious implementation detail of determining official holiday/workday status from government notices.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The module docstring states the tool is for 'gov.cn holiday notice helpers', and the implementation throughout assumes Chinese government notices and Chinese lunar-calendar concepts. For a general skill, this imposes a specific language/locale context without offering the user a choice or explicitly documenting a justified locale restriction.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/query_holiday.py (reported line 486)May include surrounding context.

python
def _call_noarg(obj: Any, name: str) -> Any:
    fn = getattr(obj, name, None)
    if callable(fn):
        try:
            return fn()

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/query_holiday.py (reported line 552)May include surrounding context.

python
if name in {"getPrev", "getNext"}:
            continue
        try:
            attr = getattr(obj, name)
        except Exception:
            continue
        if not callable(attr) or not _callable_takes_no_required_args(attr):