Back to skill

Security audit

expense_invoice_ocr

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed invoice OCR uploader, but it can be configured to send sensitive documents and the API bearer key to an arbitrary endpoint instead of only the declared Scnet service.

Review before installing. Use this only for files you are allowed to upload to a third-party OCR service, and keep SCNET_API_BASE at the documented Scnet HTTPS endpoint unless the code is changed to enforce an allowlist. Do not process regulated, confidential, medical, tax, banking, or identity documents without organizational approval, and avoid sharing the API key in chat.

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

Error
Location
scripts/main.py:56
Finding

Unrestricted OCR Endpoint Allows Disclosure of API Credentials and Sensitive Documents

Content
View full analysis

Vulnerability Details

File Location: scripts/main.py:56-60, 78, 91-93, 117-129
Vulnerability Type: Arbitrary outbound destination for sensitive data
Risk Level: High

Vulnerable Code

python
if '=' in line:
    key, value = line.split('=', 1)
    key = key.strip()
    value = value.strip().strip('"').strip("'")
    config[key] = value

config.setdefault('SCNET_API_BASE', 'https://api.scnet.cn/api/llm/v1')
return config
python
api_base = config['SCNET_API_BASE']
api_key = config['SCNET_API_KEY']
url = f"{api_base}/ocr/recognize"
python
headers = {
    'Authorization': f'Bearer {api_key}'
}

try:
    with open(file_path, 'rb') as f:
        files = {
            'file': (os.path.basename(file_path), f, mime_type)
        }
        data = {
            'ocrType': ocr_type,
            'channelTag': "scnetSkills"
        }
        response = requests.post(
            url,
            headers=headers,
            data=data,
            files=files,
            timeout=60
        )

Technical Analysis

The SCNET_API_BASE configuration value is used directly to construct the request URL. The implementation does not validate:

  • That the URL uses HTTPS.
  • That its hostname is exactly api.scnet.cn.
  • That it uses an approved port.
  • That the URL has no embedded credentials or unexpected path components.
  • That redirects remain within the approved Scnet origin.

The resulting request contains both the complete user-selected document and the SCNET_API_KEY bearer credential. Therefore, changing SCNET_API_BASE can redirect both sensitive assets to an arbitrary server, including over plaintext HTTP.

This behavior exceeds the declared outbound permission in skill.yaml:27 and SKILL.md:22, which describes network access specifically to the Scnet OCR API at api.scnet.cn.

Uploading the selected document to the default Scnet endpoint is disclosed and required for the declared remote OCR functionality. The vulnerabili ...[truncated 1704 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove arbitrary endpoint configurability where it is unnecessary. Use a constant approved endpoint:

    python
    OCR_URL = "https://api.scnet.cn/api/llm/v1/ocr/recognize"
    
  2. If endpoint configuration is operationally required, enforce a strict allowlist. Parse the URL with urllib.parse.urlsplit and require:

    • Scheme exactly https.
    • Hostname exactly api.scnet.cn.
    • Port absent or explicitly approved.
    • No username or password component.
    • An approved path prefix.
    • No fragments or other ambiguous components.
  3. Construct the endpoint from validated components rather than concatenating an unrestricted string.

  4. Disable redirects for requests containing credentials and documents, or validate every redirect target before following it:

    python
    response = requests.post(
        url,
        headers=headers,
        data=data,
        files=files,
        timeout=60,
        allow_redirects=False,
    )
    
  5. Fail closed when endpoint validation fails. Do not fall back to an unverified destination.

  6. Keep declared permissions consistent with implementation. If only api.scnet.cn is declared, runtime controls must technically enforce that boundary.

  7. Add automated security tests covering malicious configurations such as:

    • http://api.scnet.cn
    • https://attacker.example
    • https://api.scnet.cn.attacker.example
    • URLs with embedded credentials
    • URLs using unexpected ports
    • Redirect responses to unapproved hosts
  8. Add explicit upload authorization as defense in depth. The documentation requires prior affirmative consent, but the script only prints a warning. Require a dedicated confirmation flag or interactive approval before transmitting the document.

  9. Rotate the API key and investigate document exposure if an untrusted SCNET_API_BASE value has previously been used.

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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (14)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/main.py (reported line 20)May include surrounding context.

python
# 获取技能根目录(脚本所在目录的上一级)
SKILL_ROOT = Path(__file__).parent.parent.absolute()
ENV_FILE = SKILL_ROOT / "config" / ".env"

# --- 新增:重试配置 ---
MAX_RETRIES = 3            # 最大重试次数

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 70)May include surrounding context.

md
# --------------------

def load_config():
    """从 .env 文件加载配置,若文件不存在则抛出友好错误"""
    if not ENV_FILE.exists():
        error_msg = (
            "\n===============================================\n"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 139)May include surrounding context.

md
# --------------------

def load_config():
    """从 .env 文件加载配置,若文件不存在则抛出友好错误"""
    if not ENV_FILE.exists():
        error_msg = (
            "\n===============================================\n"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/main.py (reported line 29)May include surrounding context.

python
# --------------------

def load_config():
    """从 .env 文件加载配置,若文件不存在则抛出友好错误"""
    if not ENV_FILE.exists():
        error_msg = (
            "\n===============================================\n"

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown file uses only Chinese for user-facing documentation, and there is no indication that the skill is intentionally limited to a Chinese-speaking audience or region-specific deployment. Under the stated policy, forcing a specific language without user opt-in or justification is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This markdown file appears to be the primary README for a skill named "expense_invoice_ocr", but it is still an unedited template and does not describe the skill's behavior or warn users about handling potentially sensitive financial documents. For a skill likely to process invoices and expense records, omission of any warning about personal, financial, or document data handling is a missing user disclosure under the markdown-file criteria.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · README.md (reported line 41)May include surrounding context.

md
* [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
* [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
* [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
* [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
* [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · SKILL.md (reported line 62)May include surrounding context.

SCNET_API_KEY=your_scnet_api_key_here

API 基础地址(一般无需修改)

SCNET_API_BASE=https://api.scnet.cn/api/llm/v1

text
2. 添加:`SCNET_API_KEY=你的密钥`
3. 设置文件权限为 600(仅所有者可读写)

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · SKILL.md (reported line 121)May include surrounding context.

SCNET_API_KEY=your_scnet_api_key_here

API 基础地址(一般无需修改)

SCNET_API_BASE=https://api.scnet.cn/api/llm/v1

text
2. 添加:`SCNET_API_KEY=你的密钥`
3. 设置文件权限为 600(仅所有者可读写)

External Transmission

Medium
Category
Data Exfiltration
Confidence
95% confidence
Finding

The document explicitly states that local user files are uploaded to a third-party OCR service and that those files may contain highly sensitive information such as invoices, medical records, bank account numbers, and personal identifiers. Even though this is disclosed, the behavior still creates a real data exfiltration/privacy risk because sensitive content leaves the local environment and is processed by an external party.

Content

Scanner excerpt · references/api-docs.md (reported line 6)May include surrounding context.

md
> **⚠️ 数据安全警告**:调用本接口会将用户本地文件上传到第三方远程 OCR 服务 `https://api.scnet.cn`。发票、行程单、医疗单据等文件通常包含个人身份信息、纳税人识别号、银行账号、金额、病史等敏感数据。请确保用户已明确同意上传,且文件不包含其组织禁止外发的机密或受监管信息。

## 接口地址
`POST https://api.scnet.cn/api/llm/v1/ocr/recognize`

## 请求头
- `Content-Type: multipart/form-data`

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

python
"   b) 配置文件:\n"
            f"      mkdir -p {SKILL_ROOT}/config\n"
            f"      echo 'SCNET_API_KEY=你的密钥' > {ENV_FILE}\n"
            f"      chmod 600 {ENV_FILE}\n"
            "\n配置完成后重新运行。"
        )
        sys.exit(error_msg)

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/main.py (reported line 169)May include surrounding context.

python
"   b) 配置文件:\n"
            f"      mkdir -p {SKILL_ROOT}/config\n"
            f"      echo 'SCNET_API_KEY=你的密钥' > {ENV_FILE}\n"
            f"      chmod 600 {ENV_FILE}\n"
            "\n配置完成后重新运行。"
        )
        sys.exit(error_msg)

External Transmission

Medium
Category
Data Exfiltration
Confidence
95% confidence
Finding

The script uploads the user-supplied file to an external OCR service at api.scnet.cn, which creates a real data exfiltration/privacy risk because the targeted documents are explicitly financial and may contain sensitive personal or regulated information. Although the code prints a warning, it does not obtain explicit affirmative consent, enforce data classification checks, or restrict what files can be sent.

Content

Scanner excerpt · scripts/main.py (reported line 78)May include surrounding context.

python
)
        sys.exit(error_msg)

    config.setdefault('SCNET_API_BASE', 'https://api.scnet.cn/api/llm/v1')
    return config

def recognize_with_retry(ocr_type, file_path, config, retry_count=0):

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

This markdown file is entirely written in Chinese and does not indicate that language selection is optional or limited to a China-specific deployment. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.