Back to skill

Security audit

star-search

Security checks for vulnerabilities and agentic risk

Overview

This is a real search tool, but it bundles unsafe account, payment, credential, upload, and broad web-fetching behavior that needs careful review before installation.

Treat this as a Review install. Do not run it as a public service or on a machine/account with valuable local credentials until payment endpoints are removed or secured, auth secrets and password storage are replaced, arbitrary URL fetching is sandboxed or allowlisted, LLM endpoints are restricted to trusted HTTPS hosts, upload retention is defined, and dependencies are pinned. Avoid applying the Cloudflare bot/WAF instructions unless an administrator has explicitly approved that infrastructure change.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (8)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/api_server.py:823
Finding
Unauthenticated Payment Completion and Account-Tier Upgrade<![CDATA[ ## Vulnerability Details **File Location**: `scripts/api_server.py:823-847`; `scripts/payment.py:139-198` **Vulnerability Type**: Missing authentication and payment-callback verification **Risk Level**: Critical ### Vulnerable Code ```python @app.post("/v1/pay/mock_pay") async def pay_mock_pay(req: dict): """v20.59: Sandbox mode - simulate successful payment.""" order_id = req.get('order_id', '') if not order_id: return {'error': 'order_id is required'} r = _pay.mark_paid(order_id) return r @app.post("/v1/pay/callback") async def pay_callback(request: Request): try: form = await request.form() params = dict(form) except Exception: body = await request.body() params = json.loads(body.decode()) if body else {} r = _pay.alipay_callback(params) return r ``` ```python def mark_paid(order_id: str, trade_no: str = '') -> Dict: data = _load_orders() order = data['orders'].get(order_id) if not order: return {'error': 'order not found'} if order['status'] != 'pending': return {'error': f'order status: {order["status"]}'} now = int(time.time()) order['status'] = 'active' order['paid_at'] = now order['activated_at'] = now order['expire_at'] = now + order['tier_info']['period_days'] * 86400 order['trade_no'] = trade_no or f'MOCK_TRADE_{order_id}' try: import user_auth as _ua users_file = Path('/home/ubuntu/star-search/users.json') if users_file.exists(): with open(users_file) as f: ud = json.load(f) for u in ud.get('users', {}).values(): if u['user_id'] == order['user_id']: u['tier'] = order['tier'] u['tier_expire_at'] = order['expire_at'] u['tier_started_at'] = now with open(users_file, 'w') as f: json.dump(ud, f, indent=2, ensure_ascii=False) ...[truncated 2100 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `/v1/pay/mock_pay` from all production builds. - If mock payment is needed for tests, expose it only in an isolated test environment and require administrative authentication. - Verify that the authenticated caller owns the order before allowing any order-state transition. - Validate Alipay RSA2 signatures using the configured public key. - Verify the application ID, seller identity, order number, exact amount, currency, and expected pending state. - Add callback replay protection and idempotent transaction processing. - Store provider transaction IDs under a uniqueness constraint. - Fail closed if production payment credentials are absent instead of silently enabling mock mode. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/user_auth.py:20
Finding
Source-Visible HMAC Secret Permits Authentication-Token Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/user_auth.py:20-57` **Vulnerability Type**: Hardcoded cryptographic secret **Risk Level**: High ### Vulnerable Code ```python USERS_FILE = Path('/home/ubuntu/star-search/users.json') SECRET = b'star-search-<system-name>-2026' TOKEN_TTL = 12 * 3600 def _make_token(user_id: str) -> str: payload = { 'uid': user_id, 'exp': int(time.time()) + TOKEN_TTL, 'jti': secrets.token_hex(8), } payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode() sig = hmac.new(SECRET, payload_b64.encode(), hashlib.sha256).hexdigest()[:32] return f"{payload_b64}.{sig}" def _verify_token(token: str) -> Optional[Dict]: try: payload_b64, sig = token.split('.', 1) expected_sig = hmac.new( SECRET, payload_b64.encode(), hashlib.sha256 ).hexdigest()[:32] if not hmac.compare_digest(sig, expected_sig): return None payload = json.loads(base64.urlsafe_b64decode(payload_b64).decode()) if payload.get('exp', 0) < time.time(): return None return payload except Exception: return None ``` ### Technical Analysis Every deployment uses the same secret embedded in publicly reviewable source code. Anyone with access to the package can generate signatures accepted by `_verify_token()`. The MAC is also truncated to 128 bits without a demonstrated need. The token payload is merely Base64-encoded and contains the user ID. Consequently, disclosure of a valid token, API response, log entry, or user record may provide the identifier needed to forge another token with an attacker-selected expiration. ### Attack Path 1. The attacker obtains a target user ID from an exposed token, response, user data file, or another information leak. 2. The attacker constructs a payload containing the target `uid` and a future `exp`. 3. The attacker Base64-encodes the payload. 4. The attacker comp ...[truncated 494 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate a cryptographically random, deployment-specific signing key. - Load the key from a protected secret manager or environment variable. - Refuse to start when the key is missing or equals a documented default. - Use the complete HMAC-SHA-256 output. - Add key identifiers and a controlled rotation mechanism. - Invalidate existing tokens after replacing the compromised shared secret. - Consider a well-reviewed token library rather than a custom token format. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
index.html:1367
Finding
Bearer Tokens Are Stored in localStorage and Transmitted in URL Query Strings<![CDATA[ ## Vulnerability Details **File Location**: `index.html:1311, 1367-1375`; `scripts/api_server.py:675-682` **Vulnerability Type**: Sensitive authentication data exposed through unsafe storage and transport **Risk Level**: High ### Vulnerable Code ```javascript let __authToken = localStorage.getItem('star_token') || ''; function updateAuthChip() { if (__authToken) { fetch('/v1/auth/me?token=' + encodeURIComponent(__authToken)) .then(r => r.json()) .then(u => { // Update account UI. }); } } function clearAuth() { __authToken = ''; localStorage.removeItem('star_token'); } function getAuthHeader() { return __authToken ? { 'Authorization': 'Bearer ' + __authToken } : {}; } ``` ```python @app.get("/v1/auth/me") async def auth_me(token: str = ''): if not token: return {'error': 'token is required'} u = _ua.get_user(token) if not u: return {'error': 'invalid or expired token'} u.pop('password', None) return u ``` Other endpoints use the same query-token pattern, including `/v1/auth/quota`, `/v1/pay/order`, and `/v1/pay/orders`. ### Technical Analysis Credentials in URL query strings may be recorded in browser history, reverse-proxy logs, web-server logs, monitoring platforms, analytics systems, and debugging traces. They may also be disclosed through URL sharing or referrer behavior. Keeping the bearer token in `localStorage` makes it accessible to every script executing under the same origin. Any same-origin cross-site scripting issue or compromised third-party script can read and exfiltrate it. ### Attack Path 1. A user signs in and the token is stored in `localStorage`. 2. The frontend calls `/v1/auth/me?token=<credential>`. 3. The request URL is recorded by a proxy, server, browser, or monitoring system. 4. An attacker with access to that record extracts the token. 5. Alternatively, a same-origin injected script reads `localStorage.star_token`. 6. The attacker re ...[truncated 353 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Never transmit authentication credentials in URL query parameters. - Accept bearer tokens only through the `Authorization` header, or use Secure, HttpOnly, SameSite session cookies. - Prefer HttpOnly cookies when the browser does not need direct access to the token. - Remove tokens from existing logs and monitoring data where feasible. - Rotate potentially exposed tokens. - Add a restrictive Content Security Policy and minimize third-party scripts. - Use short-lived access tokens with refresh-token rotation and server-side revocation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/api_server.py:652
Finding
Registration Endpoint Ignores the Submitted CAPTCHA Token<![CDATA[ ## Vulnerability Details **File Location**: `scripts/api_server.py:652-662`; `index.html:1333-1342` **Vulnerability Type**: Missing server-side anti-automation validation **Risk Level**: Medium ### Vulnerable Code The frontend submits a CAPTCHA token: ```javascript let cfToken = ''; if (__authMode === 'register' && window.turnstile && window.turnstile.getResponse) { cfToken = window.turnstile.getResponse(); if (!cfToken) { errEl.textContent = 'Please complete the verification challenge'; errEl.style.display = 'block'; return; } } body: JSON.stringify({ phone, password, cf_token: cfToken }) ``` The server never reads or validates `cf_token`: ```python @app.post("/v1/auth/register") async def auth_register(req: dict): phone = req.get('phone', '').strip() password = req.get('password', '') email = req.get('email', '').strip() r = _ua.register(phone, password, email) return r ``` The verification module also defaults to Cloudflare test keys: ```python TURNSTILE_SITE_KEY = os.environ.get( 'TURNSTILE_SITE_KEY', '1x00000000000000000000AA' ) TURNSTILE_SECRET_KEY = os.environ.get( 'TURNSTILE_SECRET_KEY', '1x0000000000000000000000000000000AA' ) ``` ### Technical Analysis Client-side CAPTCHA enforcement is not a security boundary because an attacker can call the HTTP endpoint directly. The registration route accepts phone and password fields without validating the submitted challenge. The bundled default test credentials further make accidental insecure deployment likely. ### Attack Path 1. The attacker bypasses the web interface and sends direct requests to `/v1/auth/register`. 2. Requests omit `cf_token` entirely. 3. The server creates accounts because it never calls `verify_captcha()`. 4. The attacker repeats the process with generated phone identifiers. 5. The resulting accounts are used to evade per-account quotas or consume storage and service resources. ### Impact Assessment ...[truncated 230 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require `cf_token` in the registration request. - Validate it server-side through `verify_captcha()` before creating an account. - Fail closed when CAPTCHA verification times out or returns an error. - Reject Cloudflare test keys when running in production mode. - Add IP-, device-, and account-based registration rate limits. - Add global request throttling and abuse monitoring. - Bind the challenge to the expected hostname and registration action where supported. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/user_auth.py:24
Finding
Weak Custom Password Hashing and Inadequately Protected Credential Storage<![CDATA[ ## Vulnerability Details **File Location**: `scripts/user_auth.py:24-34, 60-78` **Vulnerability Type**: Insecure password hashing and file-based credential storage **Risk Level**: High ### Vulnerable Code ```python def _hash_password(password: str, salt: str = None) -> str: if salt is None: salt = secrets.token_hex(16) h = hashlib.sha256((salt + password).encode()).hexdigest() for _ in range(1000): h = hashlib.sha256((h + salt).encode()).hexdigest() return f"{salt}${h}" def _verify_password(password: str, stored: str) -> bool: try: salt, _ = stored.split('$', 1) return _hash_password(password, salt) == stored except Exception: return False ``` ```python def _load_users() -> Dict: if not USERS_FILE.exists(): return {'users': {}, 'quota_usage': {}} try: with open(USERS_FILE) as f: return json.load(f) except Exception: return {'users': {}, 'quota_usage': {}} def _save_users(data: Dict): USERS_FILE.parent.mkdir(parents=True, exist_ok=True) with open(USERS_FILE, 'w') as f: json.dump(data, f, indent=2, ensure_ascii=False) ``` ### Technical Analysis The password function is a custom construction based on only approximately 1,000 SHA-256 iterations. SHA-256 is designed to be fast, making offline guessing substantially cheaper than with modern memory-hard password hashing functions. The user database is written as a regular JSON file without explicitly setting restrictive permissions, atomic replacement, or concurrent-access locking. File permissions therefore depend on the process umask, and simultaneous requests can cause lost updates or corruption. ### Attack Path 1. An attacker gains read access to `users.json` through a local permission error, backup exposure, server compromise, or unrelated file-disclosure issue. 2. The attacker extracts salts and password hashes. 3. The attacker performs high-speed dictionary or brute- ...[truncated 508 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace the custom hash with Argon2id, scrypt, or bcrypt using current recommended parameters. - Rehash existing passwords after successful login. - Store user and quota state in a transactional database. - If file storage remains necessary, create files with mode `0600`. - Use atomic temporary-file replacement, file locking, and restrictive directory permissions. - Compare password hashes through the selected library's constant-time verification API. - Add password-compromise screening and stronger minimum-password requirements. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch_content.py:70
Finding
Unrestricted Content Fetching Enables SSRF and Uses a Shared Predictable Temporary File<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_content.py:70-126, 186-219`; invoked by `scripts/api_server.py:376-387` **Vulnerability Type**: Server-side request forgery and unsafe temporary-file handling **Risk Level**: High ### Vulnerable Code ```python def fetch_url_curl(url: str, timeout: int = 5, ua: str = MOBILE_UA) -> Dict: result = { 'url': url, 'success': False, 'title': '', 'content': '', 'source': 'curl', 'status': 0, 'error': '' } try: r = subprocess.run( [ 'curl', '-s', '-L', '-A', ua, '--max-time', str(timeout), '-H', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', '-H', 'Accept: text/html,application/xhtml+xml,' 'application/xml;q=0.9,*/*;q=0.8', '-o', '/tmp/_fetch_tmp.html', '-w', '%{http_code}|%{size_download}|%{url_effective}', url ], capture_output=True, text=True, timeout=timeout + 3 ) if r.returncode != 0: result['error'] = f'curl failed: {r.returncode}' return result parts = r.stdout.split('|') status = int(parts[0]) if parts[0].isdigit() else 0 size = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 0 result['status'] = status with open('/tmp/_fetch_tmp.html', 'rb') as f: raw = f.read() ``` ```python def fetch_url(url: str, use_playwright: bool = True) -> Dict: if not url or not url.startswith('http'): return { 'url': url, 'success': False, 'error': 'invalid_url', 'title': '', 'content': '', 'source': '', 'status': 0 } is_weixin = 'mp.weixin.qq.com' in url is_sogou_link = 'sogou.com/link' in url or 'sogoucdn.com' in url if i ...[truncated 2499 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Parse URLs with a standards-compliant URL parser and permit only `http` and `https`. - Resolve the hostname and reject loopback, private, link-local, reserved, multicast, and unspecified addresses for both IPv4 and IPv6. - Revalidate the destination after every redirect. - Prevent DNS rebinding by connecting to the previously validated address while preserving the expected hostname for TLS. - Apply an allowlist when the expected source domains are known. - Disable automatic content fetching for unauthenticated requests or require explicit user opt-in. - Run the fetcher in a sandbox with restricted network egress. - Replace the fixed path with `tempfile.NamedTemporaryFile` or process content in memory. - Ensure temporary files are private, unique, and deleted in a `finally` block. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/answer.py:21
Finding
Service Reads and Reuses an Unrelated Hermes Credential<![CDATA[ ## Vulnerability Details **File Location**: `scripts/answer.py:21-61, 871-885` **Vulnerability Type**: Cross-application credential discovery and reuse **Risk Level**: High ### Vulnerable Code ```python # Load multiple possible key sources. # 1) env var LLM_API_KEY # 2) /home/ubuntu/star-search/.env # 3) ~/.hermes/auth.json _NEWAPI_KEY = None _env_path = os.path.expanduser("~/star-search/.env") _env_overrides = {} if os.path.exists(_env_path): with open(_env_path) as f: for line in f: line = line.strip() if not line or line.startswith("#") or "=" not in line: continue k, v = line.split("=", 1) k, v = k.strip(), v.strip().strip('"').strip("'") if k == "LLM_API_KEY": _NEWAPI_KEY = v _env_overrides[k] = v elif k in ( "LLM_BASE_URL", "LLM_MODEL", "LLM_TIMEOUT", "ANSWER_CACHE_TTL" ): _env_overrides[k] = v for _k, _v in _env_overrides.items(): os.environ.setdefault(_k, _v) if not _NEWAPI_KEY: try: auth_path = os.path.expanduser("~/.hermes/auth.json") if os.path.exists(auth_path): with open(auth_path) as f: raw = f.read() m = re.search(r'sk-[A-Z0-9]{4,}[a-zA-Z0-9]+', raw) if m: _NEWAPI_KEY = m.group() except Exception: pass LLM_BASE_URL = os.environ.get( "LLM_BASE_URL", "http://<server-ip>:8080/v1" ) LLM_API_KEY = os.environ.get("LLM_API_KEY", _NEWAPI_KEY or "") ``` ```python req = urllib.request.Request( f"{LLM_BASE_URL}/chat/completions", data=json.dumps(req_data).encode(), headers={ "Authorization": f"Bearer {LLM_API_KEY}", "Content-Type": "application/json" } ) with urllib.request.urlopen(req, timeout=LLM_TIMEOUT) as resp: return json.loads(resp.read()) ``` ### Technical Analysis T ...[truncated 1259 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove all automatic reading of `~/.hermes/auth.json`. - Accept only a credential explicitly configured for this application. - Document the data sent to the LLM provider, including queries and fetched search content. - Require HTTPS for non-loopback LLM endpoints. - Optionally allowlist approved LLM hostnames. - Validate endpoint configuration before transmitting credentials. - Give the service a dedicated operating-system account with no access to unrelated user configuration files. - Rotate the Hermes credential if the affected code has already run against an untrusted endpoint. ]]>

T08 · Insecure Dependencies

Warning
Location
install.sh:20
Finding
Installer Executes Unpinned Third-Party Packages Without Integrity Verification<![CDATA[ ## Vulnerability Details **File Location**: `install.sh:20-27, 49-51` **Vulnerability Type**: Unpinned and unverifiable dependency installation **Risk Level**: Medium ### Vulnerable Code ```bash echo "[1/4] Installing Python dependencies..." pip3 install --user --quiet \ fastapi uvicorn pydantic httpx aiohttp lxml \ python-multipart aiosqlite 2>&1 | tail -3 || { echo "Dependency installation failed" exit 1 } ``` ```bash if [[ $REPLY =~ ^[Yy]$ ]]; then pip3 install --user --quiet playwright 2>&1 | tail -3 python3 -m playwright install chromium 2>&1 | tail -5 echo "Playwright installation completed" fi ``` ### Technical Analysis The installer requests package names without exact versions, hashes, a lock file, or an isolated virtual environment. Installation behavior can change over time, and a newly compromised or incompatible package release would be installed automatically. The packages are installed into the invoking user's package directory, potentially affecting other Python applications under the same account. Playwright also downloads a browser binary without a project-level integrity policy. No evidence of deliberate dependency confusion or typosquatting was found; the risk arises from unsafe supply-chain installation practices. ### Attack Path 1. A user runs `install.sh`. 2. Pip resolves the latest versions from the configured package index. 3. A compromised upstream release, package-index account, mirror, or network configuration supplies malicious package content. 4. Package installation hooks execute under the user's account. 5. The malicious dependency gains access to data and privileges available to that account. ### Impact Assessment A compromised dependency can execute code with the installing user's permissions, read application secrets, modify user files, or persist through installed Python packages. Unpinned versions also create reproducibility and availability risks. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Use a dedicated virtual environment. - Pin all direct and transitive dependencies to reviewed versions. - Maintain a lock file generated through a reproducible dependency-management process. - Require package hashes, for example through `pip install --require-hashes`. - Use an explicitly configured trusted package index. - Scan dependencies and browser artifacts for known vulnerabilities. - Review and test upgrades before changing the lock file. - Document and verify the source and version of the Playwright browser download. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (85)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
result = {'url': url, 'success': False, 'title': '', 'content': '', 'source': 'curl', 'status': 0, 'error': ''}
    try:
        # 用 curl 因为它自动处理 gzip/charset/redirects
        r = subprocess.run(
            ['curl', '-s', '-L', '-A', ua, '--max-time', str(timeout),
             '-H', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
             '-H', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
Confidence
92% confidence
Finding
The subprocess call itself is not shell-injection vulnerable because it uses an argument list rather than a shell string, but it still creates a real SSRF-style risk by allowing attacker-controlled URLs to be fetched by curl from the host environment. In this skill’s context, a web-search/fetch tool is expected to access arbitrary URLs, which makes access to internal services, cloud metadata endpoints, or other sensitive network locations more dangerous unless strict URL and network egress controls are enforced.

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

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/json"}
    )
    try:
        resp = urllib.request.urlopen(req, timeout=30)
        return json.loads(resp.read())
    except Exception as e:
        return {"error": str(e)}
Confidence
92% confidence
Finding
The request destination is derived from an environment variable without validation, then used for an outbound network call. If an attacker or untrusted deployment context can set STAR_SEARCH_BASE, searches may be redirected to an attacker-controlled host, enabling SSRF-style behavior, query exfiltration, and unexpected access to internal services.

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

Critical
Category
Data Flow
Content
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        })
        with _ur.urlopen(req, timeout=timeout) as resp:
            ct = resp.headers.get('Content-Type', '')
            # 非 HTML 直接返回 (PDF/图片等不抓)
            if 'text/html' not in ct and 'application/xhtml' not in ct:
Confidence
94% confidence
Finding
The module fetches arbitrary user-influenced URLs with urllib without robust allowlisting, internal-network blocking, or scheme validation. That creates SSRF risk: an attacker can supply URLs in a query and cause the server to request attacker-chosen endpoints, including internal services or metadata endpoints, and then include fetched content in later processing.

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

Critical
Category
Data Flow
Content
import zlib as _zlib
    try:
        req = _ur.Request(url, headers={'User-Agent': 'star-search/18.0'})
        with _ur.urlopen(req, timeout=timeout) as resp:
            data = resp.read()
        if not data.startswith(b'%PDF'):
            return {"ok": False, "error": "not a PDF file", "url": url}
Confidence
95% confidence
Finding
The PDF fetcher retrieves arbitrary user-supplied URLs and downloads entire remote files before light validation. This enables SSRF and resource-exhaustion attacks, especially if the target points to internal services or very large files, and the fetched content is then processed on the server.

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

Critical
Category
Data Flow
Content
"Content-Type": "application/json"
            }
        )
        with urllib.request.urlopen(req, timeout=LLM_TIMEOUT) as resp:
            return json.loads(resp.read())

    try:
Confidence
90% confidence
Finding
User queries, search snippets, fetched page text, history, and special-intent content are transmitted to an LLM endpoint whose base URL is environment-configurable and may be plain HTTP by default. If misconfigured or pointed at an untrusted service, this can leak sensitive user data and fetched content off-box without adequate controls.

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

Critical
Category
Data Flow
Content
data=json.dumps(req_data).encode(),
            headers={"Authorization": f"Bearer {LLM_API_KEY}", "Content-Type": "application/json"}
        )
        with urllib.request.urlopen(req, timeout=10) as resp:
            return json.loads(resp.read())

    try:
Confidence
90% confidence
Finding
The follow-up generation call also sends user query and answer content to the external LLM service, expanding the same data-exfiltration surface. Because this is a second call, it can leak already-summarized or sensitive content again even when users may not expect extra downstream processing.

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

Critical
Category
Data Flow
Content
},
    )
    try:
        with urllib.request.urlopen(req, timeout=LLM_TIMEOUT) as resp:
            data = json.loads(resp.read())
            return data['choices'][0]['message']['content']
    except Exception as e:
Confidence
94% confidence
Finding
The request destination and bearer token are sourced from environment or a local .env file, then user queries and search-derived content are sent to that external endpoint. If an attacker can influence LLM_BASE_URL or the local configuration, sensitive prompts, search context, and credentials may be exfiltrated to an attacker-controlled service.

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

Critical
Category
Data Flow
Content
},
    )
    try:
        with urllib.request.urlopen(req, timeout=LLM_TIMEOUT) as resp:
            data = json.loads(resp.read())
            return data['choices'][0]['message']['content']
    except Exception:
Confidence
93% confidence
Finding
The code sends user-derived entity content to an externally configured LLM endpoint using credentials from environment or disk. Because the destination is controlled by LLM_BASE_URL and the payload includes user queries, this creates a real exfiltration/privacy risk and potential SSRF-like misuse if configuration is tampered with, even though the immediate sink is an intended API call.

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

Critical
Category
Data Flow
Content
},
    )
    try:
        with urllib.request.urlopen(req, timeout=LLM_TIMEOUT) as resp:
            data = json.loads(resp.read())
            return data['choices'][0]['message']['content']
    except Exception:
Confidence
92% confidence
Finding
The request URL is built from LLM_BASE_URL, which is sourced from environment variables and a local .env file without validation. If an attacker can influence that configuration, this code will send prompts and the bearer API key to an arbitrary endpoint, creating an SSRF-like outbound connection and secret exfiltration path.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill advertises substantial capabilities including environment-variable use, shell commands, file writes, and outbound network access, but does not declare permissions or clearly bound those operations. In an agent setting, this reduces transparency and can lead to over-privileged execution, making it harder for users or platforms to assess what data may be accessed or modified.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented behavior goes well beyond a search skill into authentication, payments, quota enforcement, captcha handling, OCR uploads, finance endpoints, and operational monitoring. This scope creep is security-relevant because users and orchestrators may invoke or trust the skill as a simple search tool while it actually exposes broader attack surface and handles more sensitive workflows.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The documentation instructs operators to obtain privileged Cloudflare tokens and disable or bypass bot protections on a zone. Even if framed as troubleshooting, this normalizes weakening perimeter defenses and handling high-privilege credentials in a search-skill context, which materially increases the chance of misuse or credential exposure.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
This section gives direct API guidance for PATCH/PUT changes to Cloudflare security settings unrelated to ordinary end-user search behavior. Embedding remote security-administration steps in a broadly invocable skill increases the risk that agents or operators will treat those actions as acceptable automation targets, causing accidental weakening of defenses.

Intent-Code Divergence

Medium
Confidence
82% confidence
Finding
The page tells users that payment is not yet publicly available, but the embedded JavaScript still performs live payment creation and redirects to a returned payment URL. In a payment context, this mismatch is risky because users may trigger unfinished or test payment flows, and hidden active billing logic increases the chance of accidental charges, exposure to misconfigured payment environments, or abuse of a not-yet-hardened endpoint.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The document explicitly recommends 'UA 模拟', '跨域爬', and mobile/private-style API use to access content from platforms with anti-bot controls. In a web-search skill, these are bypass-oriented acquisition methods that can encourage scraping beyond intended access boundaries and create legal, compliance, and account/network blocking risk.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The answer-generation component reads credentials from ~/.star-search/.env and ~/.hermes/auth.json even though its main role is summarization. Expanding credential search into local auth stores increases secret exposure risk and creates surprising privilege boundaries if this code runs in multi-user or shared environments.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
A module described as answer summarization also performs arbitrary URL, PDF, GitHub, and arXiv fetching based on query content. This significantly broadens the attack surface, enabling SSRF, unbounded outbound access, and ingestion of attacker-supplied remote content beyond the original search results.

Intent-Code Divergence

Medium
Confidence
82% confidence
Finding
The prompts say answers should rely only on provided results, but later instructions explicitly allow the model to supplement with its own knowledge and injected entity context. This mismatch can cause fabricated or unverifiable facts to be presented as sourced conclusions, undermining trust and potentially enabling prompt-driven misinformation.

Description-Behavior Mismatch

High
Confidence
90% confidence
Finding
The file imports and exposes user auth, payment, captcha, multimodal, and deep-research capabilities that are materially broader than a stated web-search skill. In a skill ecosystem, this scope expansion increases attack surface, enables unexpected data collection and external interactions, and can bypass operator expectations about what the skill is allowed to do.

Context-Inappropriate Capability

High
Confidence
95% confidence
Finding
Payment and order-management endpoints are unrelated to a web-search skill and materially expand the server into financial workflow handling. This creates unnecessary exposure to fraud, abuse, and sensitive business logic risks, especially because the same API surface also handles public search traffic.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
This web-search skill reads LLM configuration and credentials from a fixed filesystem path outside the immediate request flow. That expands its access to local secrets beyond what users would reasonably expect from a search helper and creates a hidden dependency on host-resident credentials.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
This helper reads secrets from a fixed .env path on disk unrelated to the immediate function of returning entity cards from search results. In a hosted agent environment, hardcoded secret-file access broadens the component's privileges and can expose credentials if the skill is reused in a different trust boundary or if local files are mounted unexpectedly.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The code creates a persistent upload directory and stores user-supplied images on disk, which exceeds the minimally necessary behavior for a search skill and introduces retention/privacy risk. Because there is no visible cleanup, deletion policy, or access control in this file, sensitive uploaded content may remain on the server longer than intended.

Description-Behavior Mismatch

Medium
Confidence
86% confidence
Finding
This file implements image upload and OCR-driven multimodal search behavior that is materially broader than a plain web-search skill description, creating a mismatch between declared and actual capabilities. That mismatch matters because users and operators may not anticipate image ingestion, OCR extraction, and downstream processing of image contents.

Context-Inappropriate Capability

Low
Confidence
95% confidence
Finding
Returning absolute server-side file paths leaks internal filesystem layout and confirms where user uploads are stored. This can aid attackers in reconnaissance, troubleshooting later path-targeting attacks, and exposing implementation details unrelated to the search function.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
index.html:1311

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/verify.py:6