Back to skill

Security audit

Gangtise 数据库

Security checks for vulnerabilities and agentic risk

Overview

This financial data skill mostly matches its stated purpose, but it needs Review because one script can redirect an authenticated Gangtise request to an environment-controlled server.

Install only if you trust the Gangtise integration and can protect the credentials. Before use, remove or tightly control any GANGTISE_DOMAIN environment variable, and prefer a version that validates the shareholder API host before sending the Authorization header. Expect the skill to contact Gangtise APIs and write query results plus usage records under workspace/gangtise.

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/shareholder.py:17
Finding

User-Controlled API Origin Can Receive the Gangtise Authorization Token

Content
View full analysis

Vulnerability Details

File Location: scripts/shareholder.py:17-20, 133-146, 184
Vulnerability Type: Authorization token disclosure through an unvalidated, environment-controlled API origin
Risk Level: Medium

Vulnerable Code

python
GANGTISE_FUNDAMENTAL_DOMAIN = os.getenv(
    "GANGTISE_DOMAIN", "https://open.gangtise.com/application/open-fundamental"
)
TOP_HOLDERS_URL = f"{GANGTISE_FUNDAMENTAL_DOMAIN}/capital-structure/top-holders"
python
payload = {
    "securityCode": security_code,
    "holderType": holder_type,
    "period": period or ["latest"],
}
if start_date:
    payload["startDate"] = start_date
if end_date:
    payload["endDate"] = end_date
if fiscal_year:
    payload["fiscalYear"] = fiscal_year

try:
    r = requests.post(TOP_HOLDERS_URL, headers=headers, json=payload, timeout=120)
python
headers = {"Authorization": GTS_AUTHORIZATION}

Technical Analysis

The shareholder API origin is read from the GANGTISE_DOMAIN environment variable without validating its scheme or hostname. The resulting URL is subsequently used for an HTTP request carrying GTS_AUTHORIZATION in the Authorization header.

This differs from the other audited API destinations, which are fixed to the documented https://open.gangtise.com service. The Skill documentation does not declare a requirement to contact arbitrary API origins, so this override exceeds the minimum network privileges needed for the stated functionality.

An attacker who can influence the process environment does not need to modify the Skill files. By setting GANGTISE_DOMAIN to an attacker-controlled server, the attacker can cause the legitimate shareholder command to transmit the bearer token and query parameters to that server. HTTPS does not mitigate this issue when the attacker controls a valid HTTPS endpoint.

Attack Path

  1. The attacker influences the environment used to launch the Skill, such as a shell profile, deployment manifest, wrapper, CI ...[truncated 1321 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the undocumented environment override and define the endpoint as a fixed official URL:
python
GANGTISE_FUNDAMENTAL_DOMAIN = (
    "https://open.gangtise.com/application/open-fundamental"
)
  1. If alternate endpoints are genuinely required, parse and validate the URL before use:

    • Require the https scheme.
    • Require an exact hostname from a small explicit allowlist.
    • Reject embedded credentials, unexpected ports, IP literals, redirects to other hosts, and hostname suffix tricks.
    • Maintain separate credentials for production, staging, and development.
  2. Do not attach a production authorization header unless the final destination has passed validation.

  3. Disable automatic redirects for authenticated requests or revalidate every redirect destination before forwarding credentials:

python
r = requests.post(
    TOP_HOLDERS_URL,
    headers=headers,
    json=payload,
    timeout=120,
    allow_redirects=False,
)
  1. Apply the same centralized endpoint validation policy to every authenticated network request in the Skill.

  2. Rotate or revoke affected tokens if the Skill has run in an environment where GANGTISE_DOMAIN may have been modified, and review API access logs for requests from unexpected clients or locations.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (37)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The request target is derived from the GANGTISE_DOMAIN environment variable and used directly in requests.post while sending the Authorization header. If an attacker can influence the runtime environment, they can redirect this request to an attacker-controlled host and exfiltrate the API credential and requested financial data. In an agent/skill context, environment-driven endpoint overrides are more dangerous because skills often run in shared or loosely controlled deployment environments.

Content

Scanner excerpt · scripts/shareholder.py (reported line 146)May include surrounding context.

python
payload["fiscalYear"] = fiscal_year

    try:
        r = requests.post(TOP_HOLDERS_URL, headers=headers, json=payload, timeout=120)
        if r.status_code != 200:
            return pd.DataFrame()
        body = r.json()

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **A 股日 K**(可选全市场、`--adjust` 不复权/前复权/后复权;前复权默认,复权因子来自独立接口):`scripts/quote.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **A 股日 K**(可选全市场、`--adjust` 不复权/前复权/后复权;前复权默认,复权因子来自独立接口):`scripts/quote.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **A 股日 K**(可选全市场、`--adjust` 不复权/前复权/后复权;前复权默认,复权因子来自独立接口):`scripts/quote.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **财务报表**(利润表/资产负债表/现金流量表):`scripts/financial.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **财务报表**(利润表/资产负债表/现金流量表):`scripts/financial.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **财务报表**(利润表/资产负债表/现金流量表):`scripts/financial.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **财务报表**(利润表/资产负债表/现金流量表):`scripts/financial.py`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- **财务报表**(利润表/资产负债表/现金流量表):`scripts/financial.py`

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
82% confidence
Finding

The primary skill description is written entirely in Chinese and presents the skill's intended use in that language, without indicating that other languages are supported or that the user may choose a locale. Under the stated policy, language constraints should be opt-in or clearly justified; no such opt-in or justification appears here.

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/earning_forecast.py (reported line 156)May include surrounding context.

python
"consensusList": consensus_fields,
        }
        try:
            r = requests.post(EARNING_FORECAST_URL, headers=headers, json=payload, timeout=120)
            if r.status_code != 200:
                return pd.DataFrame(), f"接口请求失败: HTTP {r.status_code}"
            body = r.json()

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/financial.py (reported line 272)May include surrounding context.

python
else:
        payload["fiscalYear"] = None
    try:
        r = requests.post(report_url, headers=headers, json=payload, timeout=120)
        if r.status_code != 200:
            return pd.DataFrame()
        body = r.json()

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/main_business.py (reported line 147)May include surrounding context.

python
if period:
        payload["period"] = period
    try:
        r = requests.post(MAIN_BUSINESS_URL, headers=headers, json=payload, timeout=120)
        if r.status_code != 200:
            return pd.DataFrame()
        body = r.json()

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/quote.py (reported line 135)May include surrounding context.

python
"limit": min(int(limit), 10000),
    }
    try:
        r = requests.post(QUOTE_ADJUST_FACTOR_URL, headers=headers, json=payload, timeout=300)
        if r.status_code != 200:
            return pd.DataFrame(), r.text
        body = r.json()

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/quote.py (reported line 285)May include surrounding context.

python
def _fetch_kline_data(url: str, headers: dict, payload: dict) -> Tuple[pd.DataFrame, Optional[str]]:
    try:
        r = requests.post(url, headers=headers, json=payload, timeout=300)
        if r.status_code != 200:
            return pd.DataFrame(), r.text
        body = r.json()

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/shareholder.py (reported line 146)May include surrounding context.

python
payload["fiscalYear"] = fiscal_year

    try:
        r = requests.post(TOP_HOLDERS_URL, headers=headers, json=payload, timeout=120)
        if r.status_code != 200:
            return pd.DataFrame()
        body = r.json()

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The module automatically reads API credentials from environment variables and a local .authorization file at import time, even though this utility file does not present a clear trust boundary or user-consent flow. In an agent skill context, silent credential discovery expands access to sensitive external services and increases the risk of unintended secret use or exfiltration if the skill is invoked in an untrusted workspace.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The code sends accessKey and secretAccessKey to an external Gangtise endpoint to obtain a token, creating a credential-bearing outbound transmission. In a skill with no provided metadata or declared network purpose, this is dangerous because users and reviewers cannot verify necessity, destination trust, or whether the skill is over-collecting secrets.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
95% confidence
Finding

This line transmits sensitive credentials off-host to an external service. Even if the destination is legitimate, outbound transfer of primary secrets is a security-sensitive operation that can enable account compromise or unauthorized API use if mishandled, intercepted through environment compromise, or performed without proper authorization.

Content

Scanner excerpt · scripts/utils.py (reported line 24)May include surrounding context.

python
"accessKey": ak,
        "secretAccessKey": sk
    }
    response = requests.post(AUTHORIZATION_URL, json=payload)
    if response.status_code != 200:
        return None
    try:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The credential-bearing POST occurs silently, with no user-facing disclosure that raw access credentials are being sent to a third-party service. In an agent skill, lack of transparency around secret transmission undermines informed consent and makes accidental credential misuse harder to detect.

Content

No source excerpt is available for this finding.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · scripts/utils.py (reported line 32)May include surrounding context.

python
except Exception as e:
        return None

GTS_AUTHORIZATION = None
if GTS_ACCESS_KEY and GTS_SECRET_KEY:
    GTS_AUTHORIZATION = get_authorization(GTS_ACCESS_KEY, GTS_SECRET_KEY)
elif os.path.exists(GTS_AUTHORIZATION_PATH):

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · scripts/utils.py (reported line 41)May include surrounding context.

python
except Exception as e:
        return None

GTS_AUTHORIZATION = None
if GTS_ACCESS_KEY and GTS_SECRET_KEY:
    GTS_AUTHORIZATION = get_authorization(GTS_ACCESS_KEY, GTS_SECRET_KEY)
elif os.path.exists(GTS_AUTHORIZATION_PATH):

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The utility creates persistent workspace directories automatically, modifying the filesystem as a side effect of import/runtime initialization. In an agent environment, undocumented persistence can leak data across sessions, surprise users, and create a storage location later used for sensitive output without explicit approval.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

format_response does more than formatting: it persists usage telemetry and API-returned content to disk, including potentially sensitive query results and absolute file paths. In a skill context with unknown purpose, this creates a real confidentiality risk because external data and activity records are stored locally without clear user consent, retention controls, or sanitization.

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/valuation.py (reported line 126)May include surrounding context.

python
"fieldList": list(_FIELD_LIST),
    }
    try:
        r = requests.post(
            VALUATION_URL,
            headers=headers,
            json=payload,

Static analysis

No suspicious patterns detected.