Back to skill

Security audit

Hefeng Weather - 和风天气

Security checks for vulnerabilities and agentic risk

Overview

This weather skill appears purpose-aligned, but it handles API credentials in ways that need review before installation.

Install only if you trust the publisher and your configuration path. Use environment variables or --no-save where possible, set HEFENG_API_HOST only to your legitimate QWeather API domain, avoid storing raw private-key contents in the .env file, and verify ~/.config/qweather is 700 and ~/.config/qweather/.env is 600 before saving credentials.

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
scripts/qweather_api.py:39
Finding

Authentication credentials are transmitted to an unrestricted configurable host

Content
View full analysis

Vulnerability Details

File Location: scripts/qweather_api.py:39-92
Vulnerability Type: Unvalidated credential destination
Risk Level: Medium

Vulnerable Code

python
self._api_host = os.environ.get("HEFENG_API_HOST")
api_key = os.environ.get("HEFENG_API_KEY")
project_id = os.environ.get("HEFENG_PROJECT_ID")
key_id = os.environ.get("HEFENG_KEY_ID")
private_key_path = os.environ.get("HEFENG_PRIVATE_KEY_PATH")
private_key_str = os.environ.get("HEFENG_PRIVATE_KEY")

if not self._api_host:
    raise ValueError("HEFENG_API_HOST environment variable is not set")

if api_key:
    self._auth_header = {
        "X-QW-Api-Key": api_key,
        "Content-Type": "application/json"
    }
else:
    if not project_id or not key_id or (
        not private_key_path and not private_key_str
    ):
        raise ValueError("Incomplete authentication configuration")

    if private_key_path:
        with open(private_key_path, "rb") as f:
            private_key = f.read()
    else:
        private_key = private_key_str.replace(
            "\\r\\n", "\n"
        ).replace("\\n", "\n").encode()

    payload = {
        "iat": int(time.time()),
        "exp": int(time.time()) + 900,
        "sub": project_id,
    }
    headers = {"kid": key_id}

    encoded_jwt = jwt.encode(
        payload,
        private_key,
        algorithm="EdDSA",
        headers=headers
    )
    self._auth_header = {
        "Authorization": f"Bearer {encoded_jwt}"
    }

url = f"https://{self._api_host}/geo/v2/city/lookup"

response = httpx.get(
    url,
    headers=self._auth_header,
    params={"location": city}
)

Equivalent unrestricted host construction and credential transmission also occur in skill.py:155-199 and in the generic request method at scripts/qweather_api.py:126-129.

Technical Analysis

HEFENG_API_HOST is used directly to construct the destin ...[truncated 2404 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse the configured value as a hostname rather than interpolating an unrestricted string.
  2. Reject values containing a URL scheme, user information, path, query, fragment, unexpected port, control characters, or an IP literal.
  3. Maintain an administrator-controlled allowlist of authorized QWeather hosts and explicitly enrolled custom account domains.
  4. Require a separate confirmation or trusted configuration mechanism before adding a custom domain to the allowlist.
  5. Use a preconfigured httpx.Client with follow_redirects=False, explicit TLS verification, and bounded connection/read timeouts.
  6. If redirects must be supported, verify every redirect destination before forwarding authentication headers.
  7. Apply the same validation in skill.py, scripts/qweather_api.py, and scripts/configure.py so no entry point can bypass it.
  8. Document that changing the host changes the party receiving authentication credentials.

T09 · Insecure Skill Coding Practices

Warning
Location
skill.py:108
Finding

The module configuration API persists credentials with umask-dependent permissions

Content
View full analysis

Vulnerability Details

File Location: skill.py:108-127
Vulnerability Type: Insecure local credential storage
Risk Level: Medium

Vulnerable Code

python
# Save to file
if save_to_file:
    config_dir = os.path.expanduser("~/.config/qweather")
    os.makedirs(config_dir, exist_ok=True)
    config_file = os.path.join(config_dir, ".env")

    lines = [f"HEFENG_API_HOST={api_host}"]
    if api_key:
        lines.append(f"HEFENG_API_KEY={api_key}")
    if project_id:
        lines.append(f"HEFENG_PROJECT_ID={project_id}")
    if key_id:
        lines.append(f"HEFENG_KEY_ID={key_id}")
    if private_key_path:
        lines.append(
            f"HEFENG_PRIVATE_KEY_PATH={private_key_path}"
        )
    if private_key:
        lines.append(f"HEFENG_PRIVATE_KEY={private_key}")

    try:
        with open(config_file, "w") as f:
            f.write("\n".join(lines))
        logger.info(f"Configuration saved to: {config_file}")
    except Exception as e:
        return {
            "success": False,
            "message": f"Failed to save configuration file: {e}"
        }

The configure() function declares save_to_file: bool = True, so persistence is enabled by default.

Technical Analysis

The module-level configuration function writes API keys and potentially complete private-key material to ~/.config/qweather/.env. It creates the directory without an explicit restrictive mode and opens the credential file using the ordinary open(..., "w") interface.

Effective permissions therefore depend on the process umask and any permissions already assigned to the directory or file. Under a permissive umask, the resulting credential file may be readable by the local group or by other users. Existing permissive permissions are also preserved when the file is truncated and rewritten.

Credential persistence is related to the declared authentication functionality, but s ...[truncated 1415 chars]

Remediation
View remediation

Remediation Suggestions

  1. Change save_to_file to False by default and require explicit consent for credential persistence.
  2. Create the directory with mode 0700 and explicitly repair the mode of an existing directory with os.chmod.
  3. Create the file atomically with os.open using O_CREAT | O_WRONLY | O_TRUNC and mode 0600.
  4. Apply os.fchmod(fd, 0o600) after opening so existing files are also corrected.
  5. Use O_NOFOLLOW where supported, then verify with fstat that the target is a regular file owned by the current user.
  6. Write to a securely created temporary file in the same directory, flush and fsync it, and atomically replace the destination.
  7. Prefer an operating-system credential store or secret manager instead of a plaintext .env file.
  8. Do not persist raw private-key contents unless explicitly required; prefer a protected key-file path with independently verified owner-only permissions.
  9. Avoid returning or logging credential values and provide clear rotation guidance if insecure storage is detected.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/configure.py:70
Finding

The configuration CLI does not correct permissions on existing credential paths

Content
View full analysis

Vulnerability Details

File Location: scripts/configure.py:70-90
Vulnerability Type: Incomplete credential-file permission enforcement
Risk Level: Low

Vulnerable Code

python
if not args.no_save:
    config_dir = os.path.expanduser("~/.config/qweather")
    os.makedirs(config_dir, exist_ok=True, mode=0o700)
    config_file = os.path.join(config_dir, ".env")

    lines = [f"HEFENG_API_HOST={args.api_host}"]
    if args.api_key:
        lines.append(f"HEFENG_API_KEY={args.api_key}")
    if args.project_id:
        lines.append(f"HEFENG_PROJECT_ID={args.project_id}")
    if args.key_id:
        lines.append(f"HEFENG_KEY_ID={args.key_id}")
    if args.private_key_path:
        lines.append(
            f"HEFENG_PRIVATE_KEY_PATH={args.private_key_path}"
        )
    if args.private_key:
        lines.append(f"HEFENG_PRIVATE_KEY={args.private_key}")

    try:
        # Create the file with owner-only permissions
        fd = os.open(
            config_file,
            os.O_CREAT | os.O_WRONLY | os.O_TRUNC,
            0o600
        )
        with os.fdopen(fd, "w") as f:
            f.write("\n".join(lines))

Technical Analysis

The CLI requests secure creation modes, but these modes only apply when the directory or file is newly created:

  • os.makedirs(..., mode=0o700, exist_ok=True) does not change the mode of an existing directory.
  • The 0o600 argument to os.open only controls the mode of a newly created file. O_TRUNC preserves the permissions of an existing file.

Consequently, an existing group-readable or world-readable .env file remains permissive after the CLI rewrites it. The script subsequently reports that the file permission was set to 600, which can give users a false sense of security.

The implementation also does not explicitly reject symbolic links or verify ownership and file type before truncating the destination.

Attack

...[truncated 1316 chars]

Remediation
View remediation

Remediation Suggestions

  1. Run os.chmod(config_dir, 0o700) after os.makedirs and verify that the directory is owned by the current user.
  2. Open the file with O_NOFOLLOW where available.
  3. Immediately apply os.fchmod(fd, 0o600) so existing files receive the required permissions.
  4. Use os.fstat to verify that the opened object is a regular file owned by the current user.
  5. Reject configuration directories that are symlinks or writable by group/other users.
  6. Use atomic replacement with a securely created owner-only temporary file.
  7. Check the final permissions before reporting success; do not claim mode 0600 unless verification succeeds.
  8. Warn the user and refuse to save credentials when ownership or permission repair fails.
Vulnerability Patterns
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (76)

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
96% confidence
Finding

_api_host is sourced from environment variables or the configure() function and used directly to build outbound URLs. Because authenticated headers containing the API key or JWT bearer token are sent to that host, an attacker who can influence HEFENG_API_HOST can redirect requests to an arbitrary server and capture credentials or proxy all weather queries, which is effectively SSRF plus credential exfiltration.

Content

Scanner excerpt · skill.py (reported line 199)May include surrounding context.

python
url = f"https://{_api_host}/geo/v2/city/lookup"

    try:
        response = httpx.get(url, headers=_auth_header, params={"location": city})

        if response.status_code != 200:
            logger.error(f"查询城市位置失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
96% confidence
Finding

The request URL is built from the untrusted _api_host value and then fetched with _auth_header attached. If api_host is maliciously set, the function will send authorization material to an attacker-controlled endpoint, exposing the weather API key or JWT and enabling unauthorized API use or traffic inspection.

Content

Scanner excerpt · skill.py (reported line 242)May include surrounding context.

python
url = f"https://{_api_host}/v7/weather/{days}?location={location_id}"

    try:
        response = httpx.get(url=url, headers=_auth_header)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取天气数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
96% confidence
Finding

This function sends authenticated requests to a URL whose host is derived from external configuration. In a skill context, that makes the tool capable of silently beaconing secrets to an attacker-controlled server whenever a user asks for weather data, even though the skill's declared purpose is only data lookup.

Content

Scanner excerpt · skill.py (reported line 278)May include surrounding context.

python
params = {"location": loc_value, "lang": lang, "unit": unit}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取实况天气数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
96% confidence
Finding

The hourly weather query performs an outbound call to a host fully controlled by HEFENG_API_HOST while including credentials in headers. This can be exploited to exfiltrate API credentials and turn the skill into an SSRF primitive for reaching arbitrary HTTPS targets chosen through configuration.

Content

Scanner excerpt · skill.py (reported line 320)May include surrounding context.

python
params = {"location": loc_value, "lang": lang, "unit": unit}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取逐小时天气数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Historical weather retrieval loops over dates and repeatedly sends authenticated requests to the configured host. If that host is malicious, the loop amplifies leakage and remote interaction by issuing multiple credential-bearing requests automatically.

Content

Scanner excerpt · skill.py (reported line 371)May include surrounding context.

python
params = {"location": location_id, "date": target_date, "lang": lang, "unit": unit}

        try:
            response = httpx.get(url, headers=_auth_header, params=params)
            if response.status_code == 200:
                results[target_date] = response.json()
            else:

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
96% confidence
Finding

The air-quality request uses an attacker-influenced host and sends authentication headers to it. If configuration is poisoned, an attacker gains visibility into request metadata and credentials and can return crafted responses that the calling agent may trust as legitimate weather information.

Content

Scanner excerpt · skill.py (reported line 401)May include surrounding context.

python
params = {"lang": "zh"}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取空气质量数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Historical air-quality queries also repeatedly call a host taken from environment/config with auth headers. This is exploitable for bulk credential capture and repeated user-query disclosure, making the finding more than a harmless configuration flexibility issue.

Content

Scanner excerpt · skill.py (reported line 439)May include surrounding context.

python
params = {"location": location_id, "date": target_date, "lang": lang}

        try:
            response = httpx.get(url, headers=_auth_header, params=params)
            if response.status_code == 200:
                results[target_date] = response.json()
            else:

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The hourly air-quality forecast endpoint is requested using a URL derived from mutable environment/config input. Since authorization is attached, compromise of api_host results in credential leakage and misuse of the skill as a general outbound connector inconsistent with its limited business purpose.

Content

Scanner excerpt · skill.py (reported line 485)May include surrounding context.

python
params = {"hours": hours, "lang": lang}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取空气质量小时预报数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

An unvalidated host from configuration is used to retrieve daily air-quality forecasts with auth headers. This expands the blast radius of a simple config change into full credential exfiltration and attacker-controlled responses across the skill's network-facing features.

Content

Scanner excerpt · skill.py (reported line 529)May include surrounding context.

python
params = {"days": days, "lang": lang}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取空气质量每日预报数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The station lookup function trusts _api_host for outbound requests and attaches sensitive auth headers. A malicious host can harvest credentials and feed arbitrary JSON back to the skill, which is dangerous because users expect passive weather lookups rather than remote endpoint selection.

Content

Scanner excerpt · skill.py (reported line 554)May include surrounding context.

python
params = {"lang": lang}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取空气质量监测站数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Life-index queries are sent to a host loaded from the environment, with credentials attached. This creates a direct path from local configuration poisoning to unauthorized outbound connections and secret disclosure, which is materially risky in an agent skill that may run with user secrets available.

Content

Scanner excerpt · skill.py (reported line 589)May include surrounding context.

python
params = {"location": location_id, "type": index_types, "lang": "zh"}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取生活指数数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The warning query embeds location in the URL but still relies on the same untrusted _api_host. If an attacker controls that host, the function leaks credentials and allows spoofed alert data to be returned, potentially misleading downstream consumers about safety-relevant weather warnings.

Content

Scanner excerpt · skill.py (reported line 615)May include surrounding context.

python
url = f"https://{_api_host}/v7/warning/now?location={location_id}&lang=zh"

    try:
        response = httpx.get(url, headers=_auth_header)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取预警数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The astronomy API call sends authorization to a host chosen through environment/config state. This is dangerous because a non-obvious configuration injection can turn a benign astronomy lookup into a credential leak and arbitrary remote call path.

Content

Scanner excerpt · skill.py (reported line 669)May include surrounding context.

python
params = {"location": loc_value, "date": date, "lang": lang}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取太阳天文数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The moon-data function uses the tainted host in an authenticated outbound request. A hostile configuration can therefore capture bearer tokens/API keys and return falsified weather data, which violates user expectations for a read-only weather skill.

Content

Scanner excerpt · skill.py (reported line 723)May include surrounding context.

python
params = {"location": loc_value, "date": date, "lang": lang}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取月亮天文数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Minutely precipitation requests use a URL built from _api_host without trust validation. Because the same auth header is reused globally, any redirection of host configuration exposes secrets and permits attacker-controlled responses across the skill's core functionality.

Content

Scanner excerpt · skill.py (reported line 753)May include surrounding context.

python
params = {"location": loc_value, "lang": lang}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取分钟级降水数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Grid-weather real-time requests are sent to an environment-controlled host with authorization attached. In practice, this makes the skill an authenticated HTTP client to arbitrary destinations, enabling SSRF-style misuse and secret exfiltration well beyond the weather-query use case.

Content

Scanner excerpt · skill.py (reported line 800)May include surrounding context.

python
params = {"location": formatted_loc, "lang": lang, "unit": unit}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取格点实时天气数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Daily grid-weather queries trust a configurable host and send secrets to it. This broadens a weather skill into a credential-bearing network pivot if local configuration or environment variables are influenced by another component or attacker.

Content

Scanner excerpt · skill.py (reported line 852)May include surrounding context.

python
params = {"location": formatted_loc, "lang": lang, "unit": unit}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取格点每日天气预报失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Hourly grid-weather requests use the same tainted host pattern, so a poisoned configuration sends authenticated traffic to attacker infrastructure. The repetition across many functions increases exploitability because any normal skill use will trigger the leak.

Content

Scanner excerpt · skill.py (reported line 904)May include surrounding context.

python
params = {"location": formatted_loc, "lang": lang, "unit": unit}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取格点逐小时天气预报失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Top-city lookup performs an authenticated GET to a host controlled by configuration. This is a true vulnerability because the issue is not the request parameters but the untrusted destination receiving sensitive headers and influencing returned content.

Content

Scanner excerpt · skill.py (reported line 933)May include surrounding context.

python
params = {"number": str(number), "type": city_type, "lang": lang}

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"获取热门城市数据失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

POI search sends credentials to a destination derived from _api_host, which can be attacker-controlled. In a location-oriented skill this is especially sensitive because the request may also expose user location/POI interests to a malicious endpoint in addition to API secrets.

Content

Scanner excerpt · skill.py (reported line 1018)May include surrounding context.

python
params["city"] = city_location_id

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"POI搜索失败 - 状态码: {response.status_code}")

Tainted flow: 'url' from os.environ.get (line 435, credential/environment) → httpx.get (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

Range POI search uses the tainted host for authenticated network access, which can leak both credentials and fine-grained location data. The contextual risk is higher here because users may provide precise coordinates, making arbitrary host redirection a privacy and security issue.

Content

Scanner excerpt · skill.py (reported line 1095)May include surrounding context.

python
params["city"] = city_location_id

    try:
        response = httpx.get(url, headers=_auth_header, params=params)
        if response.status_code == 200:
            return response.json()
        logger.error(f"POI范围搜索失败 - 状态码: {response.status_code}")

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

This mismatch is more security-relevant because it highlights extra behaviors beyond simple weather lookup: credential intake, private-key support, secret persistence, and broader geodata operations. Undisclosed handling of authentication material and filesystem writes increases the chance that users or orchestrators grant trust or privileges under a narrower mental model than the skill actually requires.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

This mismatch is more security-relevant because it highlights extra behaviors beyond simple weather lookup: credential intake, private-key support, secret persistence, and broader geodata operations. Undisclosed handling of authentication material and filesystem writes increases the chance that users or orchestrators grant trust or privileges under a narrower mental model than the skill actually requires.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

This mismatch is more security-relevant because it highlights extra behaviors beyond simple weather lookup: credential intake, private-key support, secret persistence, and broader geodata operations. Undisclosed handling of authentication material and filesystem writes increases the chance that users or orchestrators grant trust or privileges under a narrower mental model than the skill actually requires.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This mismatch is more security-relevant because it highlights extra behaviors beyond simple weather lookup: credential intake, private-key support, secret persistence, and broader geodata operations. Undisclosed handling of authentication material and filesystem writes increases the chance that users or orchestrators grant trust or privileges under a narrower mental model than the skill actually requires.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.