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

Content
View full analysis

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
View 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
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 36)May include surrounding context.

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

def kb_create(name, description="", catalog_id=None):

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 68)May include surrounding context.

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

def kb_create(name, description="", catalog_id=None):

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 45)May include surrounding context.

python
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):

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 51)May include surrounding context.

python
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):

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 77)May include surrounding context.

python
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):

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 95)May include surrounding context.

python
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):

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

Critical
Category
Data Flow
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.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 63)May include surrounding context.

python
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():

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 89)May include surrounding context.

python
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):

Scope Creep

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

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

High
Category
Data Flow
Confidence
80% confidence
Finding

File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 63)May include surrounding context.

python
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():

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 45)May include surrounding context.

python
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):

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 51)May include surrounding context.

python
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):

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 77)May include surrounding context.

python
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):

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/epaiclt.py (reported line 95)May include surrounding context.

python
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):

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.