Back to skill

Security audit

LinkMind Interaction

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its LinkMind channel-management purpose, but it can automatically read generic local credential files and send a discovered value to a configurable remote server.

Install only if you are comfortable with a skill that sends a LinkMind API key to a remote service and can change channel state. Use an explicit private --config file containing only the LinkMind key, avoid running it from directories with .env or generic config files, and do not set --base-url or LINKMIND_BASE_URL unless you trust that server to receive the key.

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/linkmind_interaction.py:122
Finding

Overbroad credential discovery can transmit unrelated secrets to LinkMind

Content
View full analysis

Vulnerability Details

File Location: scripts/linkmind_interaction.py:28-40, 122-168, 171-212, 271-275
Vulnerability Type: Overbroad secret discovery and unintended credential disclosure
Risk Level: High

Vulnerable Code

python
CONFIG_FILENAMES = (
    "linkmind.key",
    "linkmind_api_key.txt",
    "config.json",
    "config.txt",
    "config.md",
    ".env",
)
API_KEY_ENV_NAMES = (
    "LINKMIND_API_KEY",
    "LAGI_LINKMIND_API_KEY",
    "LAGI_API_KEY",
)
python
def extract_api_key_from_json(value: object) -> str | None:
    if isinstance(value, str):
        trimmed = value.strip()
        return trimmed or None
    if isinstance(value, dict):
        for key in ("apiKey", "api_key", "apikey", "key", "LINKMIND_API_KEY"):
            candidate = value.get(key)
            if isinstance(candidate, str) and candidate.strip():
                return candidate.strip()
        for candidate in value.values():
            found = extract_api_key_from_json(candidate)
            if found:
                return found
    if isinstance(value, list):
        for item in value:
            found = extract_api_key_from_json(item)
            if found:
                return found
    return None


def extract_api_key_from_text(text: str) -> str | None:
    stripped = text.strip()
    if not stripped:
        return None

    try:
        parsed = json.loads(stripped)
        found = extract_api_key_from_json(parsed)
        if found:
            return found
    except json.JSONDecodeError:
        pass

    assignment = re.search(
        r"(?im)^\s*(?:LINKMIND_API_KEY|LAGI_LINKMIND_API_KEY|LAGI_API_KEY|api[_-]?key|apikey|key)\s*[:=]\s*[\"']?([^\"'\s`]+)",
        text,
    )
    if assignment:
        return assignment.group(1).strip()

    raw_key = re.search(r"\bsk-[A-Za-z0-9._-]+\b", text)
    if raw_key:
        retur
...[truncated 4500 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require an explicit --config path by default, particularly for operations that transmit credentials.
  2. If automatic discovery is retained, restrict it to LinkMind-specific filenames such as linkmind.key and linkmind_api_key.txt; do not inspect .env, config.json, config.txt, or config.md automatically.
  3. Accept only exact LinkMind-specific fields such as LINKMIND_API_KEY or apiKey.
  4. Remove support for generic names such as key and broad environment variables such as LAGI_API_KEY.
  5. Remove recursive traversal that accepts arbitrary JSON string values.
  6. Remove the fallback that treats any single-token text file as a valid credential.
  7. Validate the expected LinkMind key format before network transmission and reject ambiguous files containing multiple candidate values.
  8. Warn the user and require confirmation if a credential was discovered implicitly rather than supplied through an explicit LinkMind configuration source.
  9. Keep configuration-file permissions restricted to the agent user and avoid storing credential files in project repositories.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/linkmind_interaction.py:595
Finding

Externally controllable base URL can redirect raw API keys to an attacker-controlled endpoint

Content
View full analysis

Vulnerability Details

File Location: scripts/linkmind_interaction.py:224-250, 271-275, 595
Vulnerability Type: Unrestricted sensitive-data destination and insecure transport configuration
Risk Level: Medium

Vulnerable Code

python
def http(self, method: str, path: str, params: dict[str, object] | None = None,
         payload: dict[str, object] | None = None) -> tuple[int, object]:
    query = ""
    if params:
        clean_params = {
            key: value for key, value in params.items()
            if value is not None and str(value) != ""
        }
        if clean_params:
            query = "?" + urllib.parse.urlencode(clean_params)
    url = self.base_url + path + query
    data = None
    headers = {"Accept": "application/json"}
    if payload is not None:
        data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        headers["Content-Type"] = "application/json;charset=utf-8"
    req = urllib.request.Request(url, data=data, method=method.upper(), headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=self.timeout) as resp:
            body = resp.read().decode("utf-8", errors="replace")
            return resp.getcode(), parse_json_or_text(body)
python
for path in USER_ID_PATHS:
    try:
        body = self.api(
            "POST",
            path,
            payload={"apiKey": self.api_key},
            require_success=False,
        )
python
parser.add_argument(
    "--base-url",
    default=os.environ.get("LINKMIND_BASE_URL", DEFAULT_BASE_URL),
)

Technical Analysis

The destination used by the HTTP client can be changed through either the --base-url command-line option or the LINKMIND_BASE_URL environment variable. The script does not enforce HTTPS, validate the destination hostname, use an allowlist, or require explicit confirmation before sending the API key to a nondef ...[truncated 1871 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pin normal operation to https://ai.linkmind.top.
  2. Enforce the https scheme for every endpoint that receives credentials.
  3. Validate the hostname against an allowlist for hosted operation.
  4. Do not allow an environment variable alone to redirect credentials loaded from disk.
  5. Move private-deployment support behind an explicit option such as --allow-private-server, accompanied by a clear warning that the selected server will receive the API key.
  6. Require interactive confirmation or a separately protected trust configuration before first use of a nondefault host.
  7. Reject URLs containing user information, unexpected ports, fragments, or non-HTTP schemes.
  8. Consider binding each credential configuration to an expected server origin and refuse to send it elsewhere.
  9. Avoid following cross-origin redirects for requests containing the API key, or validate every redirect destination before continuing.
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Credential Access

High
Category
Privilege Escalation
Confidence
79% confidence
Finding

The skill searches broadly for API keys in common files including config.json, config.md, config.txt, and .env in both the current working directory and the skill directory, then extracts any matching secret-like value. In an agent environment this can unintentionally harvest unrelated credentials from nearby project files and use them for outbound requests, which is a credential-access and secret-scoping issue.

Content

Scanner excerpt · scripts/linkmind_interaction.py (reported line 34)May include surrounding context.

python
"config.json",
    "config.txt",
    "config.md",
    ".env",
)
API_KEY_ENV_NAMES = (
    "LINKMIND_API_KEY",

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill invokes a Python script that reads local config files, consumes an API key, and makes network requests to a remote server, but the manifest does not declare any tool scope or allowed-tools restrictions. This creates an authority mismatch: an agent runtime may grant broader file, environment, or network access than users expect, increasing the risk of secret exposure or unintended outbound requests if the skill is misused or modified.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill transmits the user's API key-derived identity and user identifiers to remote LinkMind endpoints as part of normal operation, but the CLI surface does not clearly disclose that it will resolve the API key to a server-side user ID and register/save login state. In an agent-skill context, users may invoke a seemingly local management command without understanding that sensitive identifiers are being sent to a third-party service, which creates a privacy and consent problem even though TLS is used.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The translate subcommand restricts language selection to exactly zh-CN and en-US. This is a natural-language locale constraint, and the file does not document why only those locales are permitted or present it as a justified region-specific limitation.

Content

No source excerpt is available for this finding.