Back to skill

Security audit

Hiq Cortex En

Security checks for vulnerabilities and agentic risk

Overview

This LCA lookup skill is mostly coherent, but it needs review because its code can send credentials to configurable endpoints and its privacy text understates query and metadata uploads.

Review before installing. Use this only for LCA lookups you are willing to send to HiQ, avoid submitting confidential BOM or supplier text without approval, and do not run it with HIQ_API_BASE, HIQ_AUTH_BASE, or HIQ_CRED_PATH set unless you control those values. Prefer short-lived credentials or a managed secret store where available.

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/cortex.py:33
Finding
Authentication credentials can be transmitted to an arbitrary configurable endpoint<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cortex.py:33-39, 68-84` **Vulnerability Type**: Credential exfiltration through an unrestricted endpoint override **Risk Level**: High ### Vulnerable Code ```python BASE = os.environ.get("HIQ_API_BASE", "https://x.hiqlcd.com") # 授权与查数同域,都在 BASE 上。 AUTH_BASE = os.environ.get("HIQ_AUTH_BASE", f"{BASE}/api/cortex") CRED_PATH = pathlib.Path(os.environ.get("HIQ_CRED_PATH", "")) if os.environ.get("HIQ_CRED_PATH") \ else pathlib.Path.home() / ".hiq" / "credentials.json" MCP_URL = f"{BASE}/api/cortex/mcp" SEARCH_URL = f"{BASE}/api/cortex/search" ``` ```python def _auth_header() -> dict: cred, kind = _credential() # 网关按凭据类型自动选校验方式,客户端只需二选一给对头。 return {"X-API-Key": cred} if kind == "api_key" else {"Authorization": f"Bearer {cred}"} # Cloudflare fronts the API and blocks the default `Python-urllib/3.x` agent with # error 1010 ("blocked based on your browser's signature"). Any conventional agent # string passes — this is not an auth issue and retrying without it will keep failing. _UA = "hiq-cortex-skill/1.0 (+https://www.hiqlcd.com)" def _post(url: str, data: bytes, headers: dict, timeout: int) -> str: # The gateway authenticates on X-API-Key only; Authorization: Bearer is rejected. req = urllib.request.Request( url, data=data, headers={**_auth_header(), "User-Agent": _UA, **headers} ) ``` ### Technical Analysis The `HIQ_API_BASE` environment variable controls both authenticated service destinations. `_post()` unconditionally adds either the `HIQ_API_KEY` value or the locally stored bearer token to requests sent to those destinations. The code does not verify that the resulting URL: - Uses HTTPS. - Has the exact expected hostname `x.hiqlcd.com`. - Has an approved port and path. - Has not been redirected to an untrusted origin. Consequently, a process environment modified by another launcher, CI configuration, shell profile, wrapper script, or compromised parent pr ...[truncated 1706 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove endpoint overrides from production builds unless they are strictly required for development. 2. Before attaching credentials, parse the destination with `urllib.parse.urlsplit()` and enforce: - Scheme exactly equal to `https`. - Hostname exactly equal to `x.hiqlcd.com`. - No embedded username or password. - No unexpected port. - An approved path prefix such as `/api/cortex/`. 3. Use separate allowlists for API and authentication endpoints. 4. Disable redirects for authenticated requests or validate every redirect target before forwarding credentials. 5. Never forward authentication headers across an origin change. 6. If development endpoints must remain configurable, require an explicit development mode and prevent production credentials from being used in that mode. 7. Update `SKILL.md` so its destination guarantees accurately match enforced behavior. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cortex.py:347
Finding
Credential file is written before restrictive permissions are applied<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cortex.py:347-358` **Vulnerability Type**: Non-atomic sensitive-file creation and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python CRED_PATH.parent.mkdir(parents=True, exist_ok=True) CRED_PATH.write_text(json.dumps({ "access_token": token, "kind": "sso_token", "owner": body.get("owner", ""), "scope": body.get("scope", "lca_data"), }, ensure_ascii=False)) try: CRED_PATH.chmod(0o600) # 凭据即登录态,不留给同机其他用户读 except Exception: pass ``` ### Technical Analysis The access token is first written using `Path.write_text()`. Its initial permissions therefore depend on the process umask. Only after the write completes does the code attempt to change the mode to `0600`. This creates a time-of-check/time-of-use exposure window in which the file may be more broadly readable than intended. The code also silently ignores a failed `chmod()`, potentially leaving the credential with insecure permissions indefinitely. `Path.write_text()` follows symbolic links. If an attacker can prepare the credential path, particularly when `HIQ_CRED_PATH` points to a shared or attacker-influenced location, the token can be redirected into another file. The default `~/.hiq` location reduces exploitability when the home directory and parent directory are correctly protected, but the implementation does not enforce those assumptions. ### Attack Path 1. The Skill runs on a multi-user machine or with `HIQ_CRED_PATH` configured to a shared or insufficiently protected directory. 2. A local attacker monitors the target path or creates a symbolic link at that path. 3. The user completes browser authorization. 4. `CRED_PATH.write_text()` creates or follows the target using permissions derived from the process umask and writes the bearer token. 5. Before the subsequent `chmod(0o600)`, the attacker ...[truncated 653 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the credential directory with mode `0700` and verify that it is owned by the current user. 2. Create a temporary credential file atomically with mode `0600`, using `os.open()` with appropriate flags such as: - `O_CREAT` - `O_EXCL` - `O_WRONLY` - `O_NOFOLLOW` where supported 3. Write and flush the credential through the returned file descriptor. 4. Use `fsync()` where durability is required, then atomically replace the final path with `os.replace()`. 5. Reject symbolic links and verify with `lstat()` that the destination is a regular file owned by the current user. 6. Treat permission-setting failures as fatal rather than silently ignoring them. 7. Validate custom `HIQ_CRED_PATH` values and reject shared, world-writable, or non-user-owned parent directories. 8. Consider using an operating-system credential store instead of a plaintext JSON token file. ]]>

other

Warning
Location
SKILL.md:27
Finding
Privacy claims omit query uploads and client attribution metadata<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:27-33`; `scripts/cortex.py:280-320` **Vulnerability Type**: Privacy disclosure mismatch and unnecessary client attribution **Risk Level**: Medium ### Vulnerable Documentation and Code ```markdown **Search runs server-side.** Pass the user's own wording or raw BOM lines straight to the search endpoint — translating material names into LCA terminology, identifying production routes, and mapping categories to process direction all happen on the server. Returned candidates are already ranked and carry a match-quality marker. Do not re-derive search terms locally. ## Privacy and security - The API key is **read only from the environment or the host's MCP config**. The skill never writes it to a file and never echoes it in output. - Queries go **only to `x.hiqlcd.com`** (HiQ's API), never to any third party. - **Nothing is collected or uploaded** — no local files, directory structure, or conversation content. ``` ```python def _origin() -> dict: here = pathlib.Path(__file__).resolve() p = str(here) # 宿主:各家把技能装在自己的目录下 host = ("workbuddy" if "/.workbuddy/" in p else "claude-code" if "/.claude/" in p else "cursor" if "/.cursor/" in p else "cline" if "/.cline/" in p else "other") # 技能目录 = scripts/ 的上一层 skill_dir = here.parent.parent skill = skill_dir.name # _meta.json 是 SkillHub 打包时注入的;GitHub / 手工安装没有这个文件 channel, version = "github", "" meta = skill_dir / "_meta.json" if meta.is_file(): channel = "skillhub" try: m = json.loads(meta.read_text(encoding="utf-8")) skill = m.get("slug") or skill version = str(m.get("version") or "") except Exception: pass return {"client_host": host, "client_channel": channel, "client_skill": skill, "client_version": version} ``` ```python status, rec = _auth_post("/oauth/device_authorization", { ...[truncated 2432 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the categorical privacy statement with an accurate field-by-field disclosure. 2. Explicitly state that searches transmit user wording, BOM lines, source filters, dataset keys, and related request parameters to HiQ. 3. Explain what data is retained, for how long, for what purpose, and under which privacy policy. 4. Warn users before transmitting BOM or product information that may be proprietary. 5. Obtain explicit confirmation before sending raw BOM content when sensitivity is plausible. 6. Minimize query content and allow users to review or redact it before transmission. 7. Remove conversion-attribution metadata from the authentication request because it is not required for the declared lookup function, or make it clearly disclosed and opt-in. 8. If attribution remains enabled, avoid deriving information from installation paths and transmit only the minimum non-identifying fields. 9. Reconcile the separate claim that the Skill never writes credentials to a file with the documented and implemented `~/.hiq/credentials.json` login flow. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (13)

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

Critical
Category
Data Flow
Content
url, data=data, headers={**_auth_header(), "User-Agent": _UA, **headers}
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            return resp.read().decode("utf-8", "replace")
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", "replace")[:300]
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/json", "User-Agent": _UA},
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return r.status, json.loads(r.read().decode() or "{}")
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", "replace")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
python3 scripts/cortex.py login    # ← the default when no credential is present
```

The command prints an authorization link. **Hand the link to the user verbatim, have them click "authorize"**, then carry on with the original task. Credentials land in `~/.hiq/credentials.json` (mode 600) and every command works from then on; the visible data scope matches that account, **including any commercial databases it has access to**. `logout` removes them.

Only reach for an API key in three cases: the user asks for one, the environment is CI / server-side with no browser, or sign-in failed.
Confidence
82% confidence
Finding
The skill directs storage and reuse of bearer credentials in ~/.hiq/credentials.json and encourages ongoing use of those credentials for access to commercial databases. Even though it says mode 600 and not to echo secrets, persistent local credential material increases the risk of credential theft by other local processes, misconfigured environments, or future agent actions with file access.

Credential Access

High
Category
Privilege Escalation
Content
# 授权与查数同域,都在 BASE 上。
AUTH_BASE = os.environ.get("HIQ_AUTH_BASE", f"{BASE}/api/cortex")
CRED_PATH = pathlib.Path(os.environ.get("HIQ_CRED_PATH", "")) if os.environ.get("HIQ_CRED_PATH") \
    else pathlib.Path.home() / ".hiq" / "credentials.json"
MCP_URL = f"{BASE}/api/cortex/mcp"
SEARCH_URL = f"{BASE}/api/cortex/search"
# Search runs a validating workflow upstream; 20-40s is normal, not a hang.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill explicitly instructs use of shell, network access, environment variables, and credential storage, but declares no restrictive tool scope or allowed-tools boundary. In a host that auto-grants broad capabilities to skills, this increases the attack surface and can enable unintended file, network, or secret access beyond the minimum needed for LCA lookup.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger text is very broad, covering generic terms like carbon footprint, benchmark, inventory data, and BOM, which can cause the skill to activate in conversations where external lookup is unnecessary or inappropriate. Over-triggering matters here because activation leads users or agents toward network calls and credentialed access to an external service.

Session Persistence

Medium
Category
Rogue Agent
Content
## Access

**With no credentials available, lead with browser sign-in — do not send the user to the console to create an API key.**

Sign-in is one command plus one click, with no registration. Creating an API key means logging into a console, finding the right page, copying a secret, and setting an environment variable — an order of magnitude more friction. Putting that first is how you lose the user.
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.

External Transmission

Medium
Category
Data Exfiltration
Content
The flow is three plain HTTP requests (standard device flow, RFC 8628) — any agent that can run a shell can do it without the script:

```bash
curl -sX POST https://x.hiqlcd.com/api/cortex/oauth/device_authorization \
  -H 'Content-Type: application/json' \
  -d '{"agent_id":"my-agent","agent_name":"My Assistant","scope":"lca_data"}'
# hand the returned verification_uri_complete to the user → they approve
Confidence
87% confidence
Finding
The device authorization flow sends data to an external service and instructs the agent to hand an authorization URL to the user, establishing a trust path to a third-party system. While this is expected functionality, it is still an external transmission and credential acquisition path that can expose user context, agent identity, and account linkage if invoked without clear consent.

External Transmission

Medium
Category
Data Exfiltration
Content
Search has no MCP tool — it is a REST endpoint, wrapped by the script. Direct call:

```bash
curl -sN -X POST https://x.hiqlcd.com/api/cortex/search \
  -H "X-API-Key: sk_xxx" -H "Content-Type: application/x-www-form-urlencoded" \
  -d "query=304 stainless steel&sources=BAFU,Ecoinvent"
```
Confidence
91% confidence
Finding
The search endpoint transmits raw user wording or BOM lines to x.hiqlcd.com, and the skill explicitly instructs passing them straight through server-side. In practice, BOM lines and material descriptions may contain proprietary product, supplier, geography, or process information, so this creates a real data-exfiltration channel to a third party.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The module docstring and subsequent user-facing messages are written exclusively in Chinese, and the argument parser description/help text continues this pattern throughout the script. The policy only permits locale constraints when the skill offers user opt-in or clearly documents and justifies the constraint, which this file does not do.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The inline comment on `_post` states that the gateway authenticates only on `X-API-Key` and rejects `Authorization: Bearer`. However, `_auth_header()` can return a Bearer token header from stored SSO credentials, and `_post` always uses `_auth_header()`, so the code clearly intends to support Bearer-based calls.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The skill goes beyond read-only LCA lookup and implements device-flow login plus local credential storage, increasing the security sensitivity of the tool. While not malicious on its face, handling access tokens in a general-purpose skill expands the attack surface and can expose account tokens if the host environment or credential path is compromised.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The _origin function infers host application, installation channel, skill name, version, and filesystem-derived context, then sends it during login without an explicit opt-in. This is a telemetry/privacy issue because local environment details are collected from path structure and transmitted to the service despite not being strictly necessary for core LCA lookup functionality.

Static analysis

No suspicious patterns detected.