Back to skill

Security audit

巴娜房产信息

Security checks for vulnerabilities and agentic risk

Overview

This real-estate API skill is mostly coherent, but it stores reusable API credentials by default and can make paid calls without explicit confirmation.

Install only if you are comfortable giving the skill your Bana AppID and SecureKey, having them saved locally by default, and allowing the agent to spend account balance after only notifying you. Prefer one-time credential use, clear saved credentials when no longer needed, and avoid running it in environments where BANA_REALESTATE_BASE_URL or credential file paths could be controlled by someone else.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/realestate_api.py:175
Finding

Environment-Controlled API Endpoint Can Exfiltrate Credentials

Content
View full analysis

Vulnerability Details

File Location: scripts/realestate_api.py:175-182
Vulnerability Type: Unvalidated destination override for sensitive requests
Risk Level: High

Vulnerable Code

python
base_url = os.environ.get("BANA_REALESTATE_BASE_URL", DEFAULT_BASE_URL).rstrip("/")
url = f"{base_url}/realestate/{method}"
request = urllib.request.Request(
    url,
    data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
    headers={"Content-Type": "application/json", "Accept": "application/json"},
    method="POST",
)

The request payload is constructed with the user's credentials:

python
app_id, secure_key = get_credentials(args)

spec = METHODS[args.method]
payload: dict[str, Any] = {"app_id": app_id, "secure_key": secure_key}

Technical Analysis

The destination of every API request can be replaced through the inherited BANA_REALESTATE_BASE_URL environment variable. The implementation does not validate the URL scheme, hostname, port, or origin before transmitting the request.

Because every payload includes both app_id and secure_key, a process launcher, local configuration, compromised automation environment, or other actor capable of controlling the script's environment can redirect credentials and user query data to an arbitrary server. An http:// override is also accepted, allowing credentials to be transmitted without transport encryption.

SKILL.md states that the override should only be used for a user-provided or trusted test environment, but this is an instructional safeguard rather than an enforced security control.

Attack Path

  1. An attacker obtains control over the environment used to launch the Skill, such as a wrapper script, automation configuration, inherited shell environment, or service definition.
  2. The attacker sets BANA_REALESTATE_BASE_URL to an attacker-controlled URL, for example https://attacker.example.
  3. The user invok ...[truncated 886 chars]
Remediation
View remediation

Remediation Suggestions

  • Pin production requests to the expected https://wxpub.aibana.art origin.
  • If endpoint overrides are required for testing, replace the implicit environment override with an explicit command-line option such as --allow-test-endpoint.
  • Require an explicit warning or confirmation before sending credentials to a non-production origin.
  • Parse the destination with urllib.parse.urlsplit and enforce:
    • HTTPS only.
    • An allowlisted hostname and port.
    • No embedded username or password.
    • No unexpected fragments or malformed authority components.
  • Use separate, non-production credentials for test endpoints.
  • Reject production credentials when the destination differs from the production origin.
  • Consider clearing proxy-sensitive environment settings or documenting their trust requirements when the execution environment is not fully controlled.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/realestate_api.py:57
Finding

SecureKey Is Passed Through Process Arguments and Persisted in Plaintext

Content
View full analysis

Vulnerability Details

File Location: scripts/realestate_api.py:57-63, 122-136
Vulnerability Type: Insecure credential handling and plaintext secret storage
Risk Level: Medium

Vulnerable Code

Command-line credential handling:

python
parser.add_argument("--app-id", help="Bana Skill Center AppID.")
parser.add_argument("--secure-key", help="Bana Skill Center SecureKey.")
parser.add_argument(
    "--no-save-credentials",
    action="store_true",
    help="Use supplied credentials once instead of saving them.",
)

Plaintext persistence:

python
def save_credentials(app_id: str, secure_key: str) -> None:
    path = credentials_path()
    path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
    flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
    fd = os.open(path, flags, 0o600)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as handle:
            json.dump(
                {"app_id": app_id, "secure_key": secure_key},
                handle,
                ensure_ascii=False,
            )
            handle.write("\n")
        path.chmod(0o600)

Technical Analysis

The Skill instructs the Agent to supply credentials using --app-id and --secure-key. Command-line arguments can be exposed through process inspection facilities, execution telemetry, debugging output, audit systems, or command-history mechanisms, depending on the host platform and invocation method.

Unless --no-save-credentials is specified, supplied credentials are also written to a JSON file in plaintext. The implementation appropriately attempts to restrict the directory to mode 0700 and the file to mode 0600, which protects against access by different unprivileged users under ordinary Unix permission rules. However, the secret remains available to processes running under the same user account, backup systems, malware with same-user access, and tools that collect config ...[truncated 1438 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not accept SecureKey values directly through command-line arguments.
  • Read secrets from protected standard input, a dedicated inherited file descriptor, or an operating-system credential manager.
  • Prefer system keychains such as Secret Service, Keychain, or Credential Manager instead of plaintext JSON.
  • Make credential persistence an explicit opt-in and clearly inform users before storing the secret.
  • Preserve the existing restrictive 0700 directory and 0600 file permissions as defense in depth if file-based storage must remain available.
  • Write credential files atomically using a securely created temporary file in the same directory, followed by an atomic replacement.
  • Avoid printing, logging, or returning SecureKey values in errors, telemetry, or subsequent Agent responses.
  • Provide a supported command to delete saved credentials and document their exact storage location and residual same-user access risk.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (12)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The request destination is derived from BANA_REALESTATE_BASE_URL, an environment variable, and the payload includes app_id and secure_key. If an attacker can influence the process environment, they can redirect requests to an attacker-controlled server and capture credentials or sensitive query data; the skill context makes this more dangerous because the code automatically reuses saved credentials for authenticated outbound requests.

Content

Scanner excerpt · scripts/realestate_api.py (reported line 185)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(request, timeout=timeout) as response:
            body = response.read().decode("utf-8")
    except urllib.error.HTTPError as exc:
        body = exc.read().decode("utf-8", errors="replace")

Credential Access

High
Category
Privilege Escalation
Confidence
92% confidence
Finding

The skill instructs that sensitive credentials are saved by default to a local credentials.json file under the user configuration directory. Even with restricted file permissions, storing reusable API secrets in a plain file materially increases exposure risk from local malware, backup/sync leakage, accidental disclosure, or other tools reading the config directory; the real-estate skill context does not require long-term plaintext persistence, so this is more dangerous than necessary.

Content

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

md
- 收费接口返回 `402` 余额不足:前往巴娜 Skill 技能中心 <https://wxpub.aibana.art> 充值。
- 普通用户无需配置环境变量或操作终端;没有已保存凭证时,智能体直接在对话中请求 AppID 和 SecureKey。
- 用户提供的凭证默认保存,供后续查询自动使用;用户明确要求不保存时,仅使用一次。
- 保存位置为当前用户配置目录下的 `bana-real-estate/credentials.json`,客户端会限制文件访问权限。
- 不要在后续答复中重复展示用户的 SecureKey。

## 方法与参数

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/realestate_api.py (reported line 105)May include surrounding context.

python
return Path(configured).expanduser()
    config_home = os.environ.get("XDG_CONFIG_HOME")
    root = Path(config_home).expanduser() if config_home else Path.home() / ".config"
    return root / "bana-real-estate" / "credentials.json"


def load_saved_credentials() -> tuple[str, str]:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill invokes shell, network, file read/write, and environment-backed behavior but does not declare any explicit tool scope or allowed-tools boundary. That increases the blast radius of the skill because an agent runtime may permit broader capabilities than are actually needed, making misuse or prompt-induced abuse harder to constrain and audit.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill authorizes paid API calls immediately after merely notifying the user of cost and explicitly says not to wait for confirmation. In an agent setting, that weak consent model can lead to unintended real-world charges, especially when requests are ambiguous, repeated, or triggered through conversational misunderstanding.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs the agent to ask users for AppID and SecureKey directly in chat and to save them by default for future use, but it does not provide a clear consent flow, retention warning, or secure secret-handling boundary. This creates a real risk of credential leakage through chat history, logs, model context, or unintended reuse across sessions.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The visible interface strings and default prompt are entirely in Chinese, which signals a fixed language/locale experience. The file does not indicate that users may choose another language or that the Chinese-only behavior is a documented regional constraint, so this may violate language/locale policy requirements.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The documentation says user-provided AppID and SecureKey are persisted by default, but it does not present this as an explicit consent/security warning before collection and storage. Persisting credentials without clear opt-in increases the chance users unknowingly leave long-lived secrets on disk, which can later be exposed through local compromise, backup leakage, or multi-user system access.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · references/api.md (reported line 55)May include surrounding context.

原始请求示例

bash
curl -X POST https://wxpub.aibana.art/realestate/getCommunityList \
  -H 'Content-Type: application/json' \
  -d '{
    "app_id": "你的AppID",

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code includes natural-language user-facing output in Chinese, which effectively forces a specific language for warnings and guidance. The file does not offer an opt-in, fallback language, or documentation that this skill is intentionally limited to Chinese-speaking users.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/realestate_api.py (reported line 82)May include surrounding context.

python
spec = METHODS[args.method]
    payload: dict[str, Any] = {"app_id": app_id, "secure_key": secure_key}
    for name in spec.required:
        value = getattr(args, name)
        if value is None or (isinstance(value, str) and not value.strip()):
            raise ValueError(f"--{name} is required for {args.method}.")
        payload[name] = value.strip() if isinstance(value, str) else value

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/realestate_api.py (reported line 88)May include surrounding context.

python
spec = METHODS[args.method]
    payload: dict[str, Any] = {"app_id": app_id, "secure_key": secure_key}
    for name in spec.required:
        value = getattr(args, name)
        if value is None or (isinstance(value, str) and not value.strip()):
            raise ValueError(f"--{name} is required for {args.method}.")
        payload[name] = value.strip() if isinstance(value, str) else value