Back to skill

Security audit

article-link-skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent but needs Review because it sends an Import Token and article links to a remote API while disabling TLS certificate and hostname verification.

Review this before installing. The skill appears to perform its advertised article-link workflow, but do not use it with a real Import Token until TLS verification is restored, API_BASE is restricted to the intended HTTPS host, and token handling is tightened. Treat any configured token as exposed if you have already used this version on an untrusted network.

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/article_link.py:57
Finding

TLS Certificate and Hostname Verification Are Disabled

Content
View full analysis

Vulnerability Details

File Location: scripts/article_link.py:57-69
Vulnerability Type: Improper TLS certificate validation
Risk Level: High

Vulnerable Code

python
ctxs: List[ssl.SSLContext] = []
try:
    c1 = ssl.create_default_context()
    c1.check_hostname = False
    c1.verify_mode = ssl.CERT_NONE
    ctxs.append(c1)
except Exception:
    pass
try:
    c2 = ssl._create_unverified_context()  # noqa: SLF001
    ctxs.append(c2)
except Exception:
    pass

The unverified contexts are subsequently used for every HTTPS connection:

python
opener = urllib_request.build_opener(
    urllib_request.ProxyHandler({}),
    urllib_request.HTTPSHandler(context=ctx),
)

Authenticated requests transmit the Import Token through the resulting connection:

python
if effective_token:
    headers["X-Import-Token"] = effective_token

Technical Analysis

The client explicitly disables both certificate-chain validation and hostname verification. The first context changes verify_mode to ssl.CERT_NONE, while the second is created through Python's private _create_unverified_context() function.

There is no secure context attempted before these contexts. Therefore, this is not a narrowly scoped compatibility fallback: all HTTPS requests accept certificates issued by unknown authorities, self-signed certificates, expired certificates, and certificates issued for unrelated hostnames.

Although the traffic remains encrypted, the client does not authenticate the remote server. An attacker able to intercept or redirect network traffic can terminate TLS using an arbitrary certificate and impersonate the configured API.

Attack Path

  1. A user configures a valid Import Token and invokes an authenticated command such as submit, quota, status, jobs, or article.
  2. An attacker gains a network interception position, controls a gateway or DNS response, or otherwise redirects traffic intended for the API.
  3. The attacker presents a ...[truncated 958 chars]
Remediation
View remediation

Remediation Suggestions

Use a standard verified TLS context and remove all unverified fallback contexts:

python
_SSL_CONTEXT = ssl.create_default_context()

Create the HTTPS handler only with that verified context:

python
opener = urllib_request.build_opener(
    urllib_request.ProxyHandler({}),
    urllib_request.HTTPSHandler(context=_SSL_CONTEXT),
)

Do not disable certificate validation in response to network, proxy, EOF, or compatibility errors. Certificate failures should terminate the request and produce a clear error.

Additional hardening should include:

  1. Require a supported Python and OpenSSL version rather than weakening TLS.
  2. Use the operating system's trusted certificate store or an explicitly configured CA bundle.
  3. Distinguish transient connection failures from certificate-validation failures.
  4. Never retry a certificate failure using a less secure SSL context.
  5. Consider certificate or public-key pinning if the API's operational model can safely support rotation.
  6. Add automated tests confirming that self-signed, expired, and hostname-mismatched certificates are rejected.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/article_link.py:47
Finding

Authentication Token Can Be Sent to an Unvalidated API Destination

Content
View full analysis

Vulnerability Details

File Location: scripts/article_link.py:47
Vulnerability Type: Unvalidated security-sensitive endpoint configuration
Risk Level: Medium

Vulnerable Code

The API destination can be supplied through an environment variable or local configuration without validation:

python
API_BASE = os.environ.get("API_BASE") or _CONFIG.get("api_base") or "https://pick-read.vip/api"

The destination is directly combined with an API path:

python
url = f"{API_BASE.rstrip('/')}{path}"
effective_token = token or IMPORT_TOKEN

The authentication token is then attached to the request:

python
if effective_token:
    headers["X-Import-Token"] = effective_token

Technical Analysis

The code does not parse or validate API_BASE before using it. It does not enforce HTTPS, restrict the hostname to the documented service, prohibit embedded credentials, reject unexpected ports, or maintain a list of trusted API destinations.

As a result, a modified API_BASE environment variable or api_base configuration value can redirect all authenticated operations to an arbitrary server. A plain HTTP endpoint is also accepted, which would transmit the token without transport encryption.

This issue requires influence over the process environment or local configuration. It is therefore less directly exploitable than the unconditional TLS-validation flaw, but it can become significant in managed agent environments where environment variables or packaged configuration are supplied by automation, wrappers, or another user.

Attack Path

  1. The victim has a valid Import Token in config.json, IMPORT_TOKEN, or a command-line argument.
  2. An attacker or compromised launcher changes API_BASE, or changes api_base in config.json, to an attacker-controlled URL such as http://attacker.example/api.
  3. The victim or agent invokes any authenticated CLI operation.
  4. The script constructs request URLs from the unvalidated destinatio ...[truncated 679 chars]
Remediation
View remediation

Remediation Suggestions

Parse and validate the configured endpoint before issuing any request:

python
from urllib.parse import urlparse

parsed = urlparse(API_BASE)
if parsed.scheme != "https":
    raise RuntimeError("API_BASE must use HTTPS")
if parsed.hostname not in {"pick-read.vip"}:
    raise RuntimeError("Untrusted API host")
if parsed.username or parsed.password:
    raise RuntimeError("Credentials are not allowed in API_BASE")
if parsed.fragment:
    raise RuntimeError("Fragments are not allowed in API_BASE")
if parsed.port not in (None, 443):
    raise RuntimeError("Unexpected API port")

Further hardening should include:

  1. Remove the configurable endpoint entirely if alternate deployments are not required.
  2. If alternate deployments are required, maintain an explicit allowlist of trusted HTTPS hosts.
  3. Do not send the Import Token after a cross-origin redirect.
  4. Disable automatic redirects or validate every redirect destination before retaining sensitive headers.
  5. Combine endpoint validation with fully enabled TLS certificate and hostname verification.
  6. Document the trust implications of changing API_BASE.
  7. Add tests ensuring HTTP, unknown hosts, embedded credentials, and unexpected ports are rejected.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/article_link.py:279
Finding

Import Token Can Be Exposed Through Command-Line Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/article_link.py:279-302
Vulnerability Type: Sensitive information exposure through process arguments
Risk Level: Low

Vulnerable Code

The CLI accepts the Import Token through --token for multiple commands:

python
q.add_argument("--token", help="Import Token")
python
sub.add_argument("--token", help="Import Token")
python
st.add_argument("--token", help="Import Token")
python
jo.add_argument("--token", help="Import Token")
python
ar.add_argument("--token", help="Import Token")

The parsed value is passed to authenticated operations and eventually placed in the request header.

Technical Analysis

Command-line arguments are not an appropriate channel for long-lived secrets. Depending on the operating system and execution environment, arguments can be exposed through process inspection utilities, process metadata, shell history, audit records, debugging output, terminal logging, CI/CD logs, agent tool-call records, or monitoring systems.

The project already supports loading the token from config.json and IMPORT_TOKEN, so exposing an additional command-line secret channel is unnecessary. The environment-variable option may also be visible under some local privilege models, but it is generally less likely to be retained in shell history than an explicit command-line value.

Exploitation requires local process visibility, access to logs, access to shell history, or access to agent execution telemetry.

Attack Path

  1. A user invokes the CLI with a command such as quota --token imp-secret.
  2. The complete command is retained in shell history, agent tool-call logs, automation logs, or operating-system audit records, or is visible while the process is running.
  3. Another local user, administrator, monitoring service, or party with log access reads the command-line arguments.
  4. The party extracts the Import Token.
  5. The stolen token is reused to a ...[truncated 489 chars]
Remediation
View remediation

Remediation Suggestions

Remove all --token command-line options and obtain the token from a safer source.

Recommended options, in order of preference:

  1. Use an operating-system credential store or secret-management service.
  2. Use a permission-restricted configuration file readable only by the owning user.
  3. Allow an interactive hidden prompt through getpass.getpass() when no stored token is available.
  4. Use an environment variable only where the process environment is appropriately protected.
  5. Ensure config.json has restrictive permissions, such as mode 0600 on Unix-like systems.
  6. Avoid printing, logging, or including the token in exception messages.
  7. Add secret-redaction controls to agent and automation logs.
  8. Rotate any token that has previously been supplied through a command line or retained in execution logs.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (14)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · .gitignore (reported line 4)May include surrounding context.

text
__pycache__/
*.pyc
*.pyo
.env
config.json

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The README explicitly states that users submit paywalled article URLs to an external service (pick-read.vip) which may return full article text, but it does not clearly warn users that submitted links and associated metadata are shared with a third-party service. In an AI skill context, users may assume processing is local or platform-native, so the missing disclosure creates a real privacy and data-handling transparency risk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The README repeatedly states that the tool returns or fetches the article's full text in English, but it does not indicate that users can opt for another language or preserve the original language. This is a natural-language policy concern because it imposes a language choice without user opt-in.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill invokes a Python script that reads local configuration and calls a remote API, but the manifest does not declare any explicit tool scope or permissions boundaries. This creates an authorization and review gap: an agent may use filesystem, environment, or network capabilities more broadly than users expect, increasing the chance of secret exposure or unintended external requests.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest description states the tool returns the full article in English, which imposes a language choice by default. This is a natural-language locale policy issue because the user is not offered a choice of output language or asked to opt in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The examples and directive explicitly say to display the title and full article text in English to the user. Because the file does not provide any user language selection or documented justification for enforcing English, this violates the language/locale policy.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The status and article retrieval workflows again direct the agent to present the full text in English. Repeating the English-only requirement across operational steps reinforces a fixed language policy without user opt-in.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

The code reads IMPORT_TOKEN from the environment or config file and sends it as an HTTP header for authenticated requests. While this is functionally necessary, there is no user-facing warning in the CLI output or help text indicating that credentials from local configuration will be used and transmitted to the remote service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code submits a user-provided article URL to an external service and later fetches article content from that service. Although network use is evident in the implementation, there is no explicit user-facing disclosure in the CLI flow warning that input data will be transmitted to a remote endpoint.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest description on L003 claims support for 15 specific outlets and does not include Newsweek. However, the supported-media table later documents Newsweek and still presents the list as the skill's supported sources, indicating the skill claims a broader scope than described in the manifest.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The top-level docstring explicitly enumerates '五个核心能力' and does not include quota retrieval, yet later code defines both get_quota() and a quota subcommand. This is an active documentation-to-code inconsistency rather than a mere omission because the docstring specifically claims a complete numbered set.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The manifest describes five capabilities: listing media, submitting links, checking status, listing jobs, and retrieving article text. The code also implements get_quota() and a quota CLI command, which reveals usage-limit/account state beyond the manifest's declared operations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The natural-language string explicitly says to show the user the English full text, and a similar instruction appears again later. This hard-codes a language choice in user-facing behavior without offering the user a language preference or explaining a justified locale constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

This natural-language instruction tells the agent to display the English full text to the user. It enforces a language choice without presenting an opt-in or alternative, which conflicts with the policy against forcing a specific language or locale.

Content

No source excerpt is available for this finding.