Back to skill

Security audit

EPAI

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a real EPAI management CLI, but it has high-impact delete/upload/admin powers with weak permission disclosure and limited safeguards.

Install only if you trust the publisher and can tightly control EPAI_API_BASE, EPAI_API_KEY, EPAI_ACCOUNT, and EPAI_VERIFY_TLS. Use HTTPS with TLS verification enabled, avoid broad API keys, verify IDs before running delete commands, and treat uploads as sending the full selected files to the configured EPAI service.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/epaiclt.py:9
Finding
Configurable Insecure Transport Can Expose API Credentials and Uploaded Documents## Vulnerability Details **File Location**: `scripts/epaiclt.py`, lines 9-12, 18-21, and 34-89 **Vulnerability Type**: Unrestricted API endpoint and optional TLS certificate verification **Risk Level**: Medium ### Vulnerable Code ```python API_BASE = os.getenv("EPAI_API_BASE") API_KEY = os.getenv("EPAI_API_KEY") ACCOUNT = os.getenv("EPAI_ACCOUNT") VERIFY_TLS = os.getenv("EPAI_VERIFY_TLS", "true").lower() == "true" ``` ```python HEADERS = { "Authorization": API_KEY, "Account": ACCOUNT } ``` All HTTP operations construct their destination from the unrestricted `API_BASE` value and use the configurable TLS verification setting. Representative credentialed requests include: ```python def kb_list(): url = f"{API_BASE}/knowledge/list" r = requests.get(url, headers=HEADERS, verify=VERIFY_TLS, timeout=TIMEOUT) print(json.dumps(r.json(), ensure_ascii=False, indent=2)) ``` File uploads transmit both credentials and local file contents using the same settings: ```python def document_upload(kb_id, files): files = check_file_exists(files) if not files: print("❌ 没有有效文件可上传") return url = f"{API_BASE}/document/upload" parser_config = json.dumps({"lang_detect_enable": False,"backend": "pipeline-high-acc","chunk_type": "general","chunk_num": 256,"parent_chunk_num": 1024,"embed_model": "bge-m3","use_vision": True,"layout": True}) data = {"parser_config": parser_config,"parse": "true","kb_id": kb_id} upload_files = [("files", (os.path.basename(f), open(f, "rb"))) for f in files] r = requests.post(url, headers=HEADERS, data=data, files=upload_files, verify=VERIFY_TLS, timeout=TIMEOUT) print(json.dumps(r.json(), ensure_ascii=False, indent=2)) ``` ### Technical Analysis `EPAI_API_BASE` is accepted without validating its scheme or hostname. The program therefore permits credentialed requests to plain HTTP endpoints or arbitrary hosts ...[truncated 2074 chars]
Remediation
## Remediation Suggestions 1. Parse `EPAI_API_BASE` with a standard URL parser and reject every scheme except `https`. 2. Validate the destination hostname against an explicit allowlist of approved EPAI service domains. 3. Keep certificate verification mandatory in production and do not expose a general-purpose environment variable that silently disables it. 4. If insecure TLS is required for local development, require an explicit development mode, emit a prominent warning, and reject non-loopback destinations. 5. Reject URLs containing embedded credentials, fragments, unexpected ports, or malformed hostnames. 6. Store the API key in a managed secret store and use a narrowly scoped, short-lived credential where the EPAI platform supports it. 7. Avoid placing account or authentication-related data in query strings. In particular, remove the duplicate `Account` query parameter used by `catalog_delete` when the authenticated header is sufficient. 8. Add automated tests confirming that HTTP URLs, unapproved hosts, and disabled TLS verification are rejected before any credentialed request or file upload occurs.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (21)

Tainted flow: 'url' from os.getenv (line 93, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
def kb_list():
    url = f"{API_BASE}/knowledge/list"
    r = requests.get(url, headers=HEADERS, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_create(name, description="", catalog_id=None):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 93, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
def kb_list():
    url = f"{API_BASE}/knowledge/list"
    r = requests.get(url, headers=HEADERS, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_create(name, description="", catalog_id=None):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 93, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
sys.exit(1)
    url = f"{API_BASE}/knowledge/create"
    payload = {"name": name, "language": "zh-en", "description": description, "kb_type": "document", "catalog_id": catalog_id}
    r = requests.post(url, headers=HEADERS, json=payload, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_delete(kb_ids):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 93, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
sys.exit(1)
    url = f"{API_BASE}/knowledge/create"
    payload = {"name": name, "language": "zh-en", "description": description, "kb_type": "document", "catalog_id": catalog_id}
    r = requests.post(url, headers=HEADERS, json=payload, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_delete(kb_ids):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 93, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
sys.exit(1)
    url = f"{API_BASE}/knowledge/create"
    payload = {"name": name, "language": "zh-en", "description": description, "kb_type": "document", "catalog_id": catalog_id}
    r = requests.post(url, headers=HEADERS, json=payload, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_delete(kb_ids):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 93, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
sys.exit(1)
    url = f"{API_BASE}/knowledge/create"
    payload = {"name": name, "language": "zh-en", "description": description, "kb_type": "document", "catalog_id": catalog_id}
    r = requests.post(url, headers=HEADERS, json=payload, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_delete(kb_ids):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 93, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
parser_config = json.dumps({"lang_detect_enable": False,"backend": "pipeline-high-acc","chunk_type": "general","chunk_num": 256,"parent_chunk_num": 1024,"embed_model": "bge-m3","use_vision": True,"layout": True})
    data = {"parser_config": parser_config,"parse": "true","kb_id": kb_id}
    upload_files = [("files", (os.path.basename(f), open(f, "rb"))) for f in files]
    r = requests.post(url, headers=HEADERS, data=data, files=upload_files, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def catalog_list():
Confidence
98% confidence
Finding
The upload request sends local file contents and authentication headers to a URL derived entirely from EPAI_API_BASE, while TLS verification can also be disabled via environment variable. In any environment where those variables can be influenced, this enables exfiltration of arbitrary local files and API credentials to an attacker-controlled endpoint.

Tainted flow: 'url' from os.getenv (line 93, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
def document_list(kb_id):
    url = f"{API_BASE}/document/list"
    params = {"kb_id": kb_id}
    r = requests.get(url, headers=HEADERS, params=params, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def document_delete(doc_ids):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Scope Creep

High
Confidence
98% confidence
Finding
The manifest advertises only local file-read permission, but the skill documentation clearly enables remote administrative actions such as creating and deleting knowledge bases, catalogs, and documents via an API client. This creates a misleading trust boundary: a user or platform may underestimate the skill’s ability to modify remote state, increasing the risk of unauthorized or accidental destructive actions.

Tainted flow: 'upload_files' from open (line 62, file read) → requests.post (network output)

High
Category
Data Flow
Content
parser_config = json.dumps({"lang_detect_enable": False,"backend": "pipeline-high-acc","chunk_type": "general","chunk_num": 256,"parent_chunk_num": 1024,"embed_model": "bge-m3","use_vision": True,"layout": True})
    data = {"parser_config": parser_config,"parse": "true","kb_id": kb_id}
    upload_files = [("files", (os.path.basename(f), open(f, "rb"))) for f in files]
    r = requests.post(url, headers=HEADERS, data=data, files=upload_files, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def catalog_list():
Confidence
80% confidence
Finding
File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill documents direct knowledge base deletion commands without any warning, confirmation step, or indication that the action may be irreversible. In an agent-assisted context, this omission makes accidental or overbroad destructive execution more likely, especially when IDs are passed in bulk.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
Directory deletion is presented as a simple command with no caution about potential downstream effects on nested content, organization structure, or dependent resources. Without user-facing safeguards, operators may delete important hierarchy elements unintentionally, causing service disruption or data management issues.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Bulk document deletion is exposed without any warning about irreversible data loss or validation of the selected document IDs. In a skill context, this increases the chance that an agent or user deletes large numbers of documents through mistaken targeting or ambiguous instructions.

External Transmission

Medium
Category
Data Exfiltration
Content
sys.exit(1)
    url = f"{API_BASE}/knowledge/create"
    payload = {"name": name, "language": "zh-en", "description": description, "kb_type": "document", "catalog_id": catalog_id}
    r = requests.post(url, headers=HEADERS, json=payload, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_delete(kb_ids):
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
sys.exit(1)
    url = f"{API_BASE}/knowledge/create"
    payload = {"name": name, "language": "zh-en", "description": description, "kb_type": "document", "catalog_id": catalog_id}
    r = requests.post(url, headers=HEADERS, json=payload, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_delete(kb_ids):
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
sys.exit(1)
    url = f"{API_BASE}/knowledge/create"
    payload = {"name": name, "language": "zh-en", "description": description, "kb_type": "document", "catalog_id": catalog_id}
    r = requests.post(url, headers=HEADERS, json=payload, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_delete(kb_ids):
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
sys.exit(1)
    url = f"{API_BASE}/knowledge/create"
    payload = {"name": name, "language": "zh-en", "description": description, "kb_type": "document", "catalog_id": catalog_id}
    r = requests.post(url, headers=HEADERS, json=payload, verify=VERIFY_TLS, timeout=TIMEOUT)
    print(json.dumps(r.json(), ensure_ascii=False, indent=2))

def kb_delete(kb_ids):
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script exposes knowledge-base deletion via `kb_delete` and immediately sends the delete request, only printing the API response afterward. There is no confirmation prompt, pre-action warning, or explanatory comment/docstring disclosing that this method performs a destructive operation.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
`catalog_delete` issues an HTTP DELETE request that can remove catalog data, but the code provides no prior disclosure beyond the method name and prints output only after the action completes. This lacks the user warning expected for destructive operations in code files.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
`document_delete` submits document IDs to a delete endpoint without any pre-action disclosure to the user. Because this is a destructive operation, the absence of a visible warning, confirmation, or documented notice makes accidental deletion more likely.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
User-visible CLI output is presented in Chinese, and the script does not provide any language or locale selection mechanism. This can violate language/locale policy when users are not given an opt-in or alternative locale.

Static analysis

No suspicious patterns detected.