Back to skill

Security audit

Bohrium Dev Node Management

Security checks for vulnerabilities and agentic risk

Overview

This Bohrium node skill matches its stated purpose, but it needs review because it executes an unverified remote installer and exposes sensitive access details too casually.

Review carefully before installing. Use a verified installation path for the bohr CLI instead of running the curl-to-bash commands as written, use a narrowly scoped and revocable ACCESS_KEY, confirm whether openapi.dp.tech is an authorized Bohrium endpoint, avoid running the SSH credential command where output is logged, and require explicit confirmation before delete, create, restart, or dataset-bind actions.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T03 · Remote Payload Retrieval and Execution

Error
Location
SKILL.md:26
Finding
Unverified Remote Installer Downloaded and Executed by Bash## Vulnerability Details **File Location**: `SKILL.md`, lines 26–28 **Vulnerability Type**: Remote payload retrieval and execution **Risk Level**: Critical **Vulnerable Code**: ```bash # macOS /bin/bash -c "$(curl -fsSL https://dp-public.oss-cn-beijing.aliyuncs.com/bohrctl/1.0.0/install_bohr_mac_curl.sh)" # Linux /bin/bash -c "$(curl -fsSL https://dp-public.oss-cn-beijing.aliyuncs.com/bohrctl/1.0.0/install_bohr_linux_curl.sh)" source ~/.bashrc && export PATH="$HOME/.bohrium:$PATH" ``` ### Technical Analysis The documented installation procedure downloads shell code from an external server and immediately passes it to Bash. No cryptographic digest, digital signature, immutable artifact identifier, or manual review step is used. Consequently, the code that ultimately executes is not fixed by the reviewed Skill package. Compromise of the storage object, publisher account, DNS resolution, delivery infrastructure, or TLS trust chain could cause arbitrary attacker-controlled shell commands to run. Even though installing the Bohrium CLI supports the declared functionality, granting a mutable network response immediate code-execution privileges exceeds the minimum safe privilege required for installation. ### Attack Path 1. An attacker compromises or replaces the remotely hosted installation script, its hosting account, or another component of the delivery path. 2. A user or agent follows the prerequisite instructions in `SKILL.md`. 3. `curl` downloads the modified shell program. 4. Command substitution places the downloaded content directly into the Bash invocation. 5. Bash executes the attacker-controlled commands with the privileges of the invoking account. 6. The payload can access files, environment variables, credentials, and network resources available to that account, and may attempt persistence or further privilege escalation. ### Impact Assessment Successful exploitation provides arbitrary command ...[truncated 391 chars]
Remediation
## Remediation Suggestions - Do not execute network responses directly through Bash or command substitution. - Distribute the audited installer within the Skill package where practical. - Otherwise, download a versioned artifact to a dedicated file, verify a publisher signature or a SHA-256 digest pinned in the reviewed package, and execute it only after verification succeeds. - Ensure that installation fails closed if signature or digest validation cannot be completed. - Use a dedicated, non-privileged installation account and avoid requesting administrator privileges unless strictly necessary. - Document the exact files and configuration changes performed by the installer. - Prefer a trusted package manager with signed repository metadata where an official package is available.

T09 · Insecure Skill Coding Practices

Error
Location
node_manager.py:75
Finding
SSH Password Exposed in Plaintext Process Output## Vulnerability Details **File Location**: `node_manager.py`, lines 75–86 **Vulnerability Type**: Plaintext sensitive-data disclosure **Risk Level**: High **Vulnerable Code**: ```python def get_ssh_info(machine_id: int): """Get SSH credentials for a node.""" r = requests.get(f"{BASE}/{machine_id}", headers=HEADERS) data = r.json().get("data", {}) print(f"Node: {data.get('nodeName')}") print(f"Status: {data.get('status')}") print(f"IP: {data.get('ip')}") print(f"Domain: {data.get('domainName')}") print(f"User: {data.get('nodeUser')}") print(f"Password: {data.get('nodePwd')}") print(f"\nSSH command:") domain = data.get("domainName") or data.get("ip") print(f" ssh {data.get('nodeUser', 'root')}@{domain}") ``` ### Technical Analysis The node-details API response contains an SSH password in `nodePwd`. The function unconditionally prints that secret to standard output alongside non-sensitive connection information. Standard output is not an appropriate secret-handling channel because it may be retained by terminal scrollback, agent conversation transcripts, CI/CD logs, process wrappers, shell redirection, session recording software, or monitoring systems. Retrieving node connection information is consistent with the Skill's purpose, but disclosing the password by default is not necessary to generate the SSH command. ### Attack Path 1. An authorized user invokes `python node_manager.py ssh --machine_id ...`. 2. The application authenticates to the Bohrium API and retrieves node details containing `nodePwd`. 3. The application prints the complete password to standard output. 4. A terminal recorder, automation log, redirected output file, agent transcript, or another party with access to the session captures the password. 5. An unauthorized party obtains the node address, username, and password from the captured output. 6. The party attempt ...[truncated 518 chars]
Remediation
## Remediation Suggestions - Do not print `nodePwd` by default. - Return only the node name, status, hostname or IP address, username, and SSH command during normal operation. - Prefer SSH public-key authentication, short-lived certificates, or scoped temporary access tokens over reusable passwords. - If password disclosure is operationally unavoidable, require an explicit option such as `--reveal-password`, display a prominent warning, and write directly to an interactive TTY rather than ordinary stdout. - Refuse secret disclosure when output is redirected or when no interactive terminal is present. - Ensure API responses and exceptions containing credentials are never included in logs. - Rotate any password that may already have been captured in logs or transcripts.

T09 · Insecure Skill Coding Practices

Warning
Location
node_manager.py:14
Finding
Access Key Sent to an API Host Different from the Documented Destination## Vulnerability Details **File Location**: `node_manager.py`, lines 14–16; related documentation at `SKILL.md`, line 126 **Vulnerability Type**: Inconsistent credential destination and trust boundary **Risk Level**: Medium **Implementation Code**: ```python AK = os.environ.get("ACCESS_KEY", "") BASE = "https://openapi.dp.tech/openapi/v1/node" HEADERS = {"accessKey": AK} HEADERS_JSON = {**HEADERS, "Content-Type": "application/json"} ``` **Documented API Destination**: ```python AK = os.environ.get("ACCESS_KEY", "") BASE = "https://open.bohrium.com/openapi/v1/node" HEADERS = {"accessKey": AK} HEADERS_JSON = {**HEADERS, "Content-Type": "application/json"} ``` ### Technical Analysis The documentation identifies `open.bohrium.com` as the API destination, while the executable implementation sends the `ACCESS_KEY` header to `openapi.dp.tech`. Both URLs use HTTPS, and transmitting an authentication credential to the legitimate service is necessary for authenticated node management. However, the repository does not establish that the two hostnames have equivalent ownership, authorization, security controls, and credential scope. This inconsistency prevents users from reliably determining the network principal receiving their secret. It also broadens the effective credential trust boundary beyond the destination presented in the usage documentation. ### Attack Path 1. A user reviews `SKILL.md` and approves sending the access key to `open.bohrium.com`. 2. The user exports the Bohrium access key through the `ACCESS_KEY` environment variable. 3. The user runs `node_manager.py`. 4. The implementation places the key in the `accessKey` request header. 5. The request is sent to `openapi.dp.tech`, rather than the host documented in `SKILL.md`. 6. If that host is not an authorized equivalent endpoint, or if it has a different compromise boundary, the credential may be exposed to an unintended recipient and subsequentl ...[truncated 509 chars]
Remediation
## Remediation Suggestions - Select one canonical, provider-verified API hostname and use it consistently in both documentation and executable code. - Verify and document the ownership and authorization relationship between `open.bohrium.com` and `openapi.dp.tech`. - Maintain an explicit allowlist of approved HTTPS API hosts before attaching the `accessKey` header. - If endpoint configuration is supported, parse the URL and reject non-HTTPS schemes, embedded credentials, redirects to different hosts, and destinations outside the allowlist. - Configure requests not to forward authentication headers across cross-origin redirects. - Use narrowly scoped, revocable access keys with only the node-management permissions needed by this Skill. - Rotate the access key if it was transmitted to a destination that cannot be confirmed as authorized.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (21)

Tainted flow: 'HEADERS' from os.environ.get (line 21, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
def list_resources():
    """Show available machine configs and disk sizes."""
    r = requests.get(f"{BASE}/resources", headers=HEADERS)
    data = r.json().get("data", {})

    print("Available disk sizes:", data.get("disks", []))
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'HEADERS' from os.environ.get (line 21, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
def query_price(sku_id: int, project_id: int):
    """Query hourly price for a machine SKU."""
    r = requests.get(
        f"{BASE}/resources/price",
        headers=HEADERS,
        params={"skuId": sku_id, "projectId": project_id},
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'HEADERS_JSON' from os.environ.get (line 22, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
def create_node(project_id: int, name: str, image_id: int, sku_id: int, disk_size: int = 20):
    """Create a node via API (non-interactive)."""
    r = requests.post(
        f"{BASE}/add",
        headers=HEADERS_JSON,
        json={
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'HEADERS' from os.environ.get (line 21, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
def get_ssh_info(machine_id: int):
    """Get SSH credentials for a node."""
    r = requests.get(f"{BASE}/{machine_id}", headers=HEADERS)
    data = r.json().get("data", {})
    print(f"Node:     {data.get('nodeName')}")
    print(f"Status:   {data.get('status')}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The documented behavior exceeds the declared scope by including retrieval of SSH credentials and other actions not clearly reflected in the manifest. Scope mismatches are dangerous because operators may grant or invoke the skill under the assumption that it only manages lifecycle tasks, while it can also expose sensitive access material.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
sys.exit(1)

    if args.cmd == "list":
        os.system("bohr node list")
    elif args.cmd == "resources":
        list_resources()
    elif args.cmd == "price":
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill documents shell execution, environment-variable handling, and network/API access but does not declare any explicit tool scope or permission boundaries. In an agent setting, this increases the chance of overbroad execution authority and makes it harder to enforce least privilege for destructive node-management operations.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# macOS
/bin/bash -c "$(curl -fsSL https://dp-public.oss-cn-beijing.aliyuncs.com/bohrctl/1.0.0/install_bohr_mac_curl.sh)"
# Linux
/bin/bash -c "$(curl -fsSL https://dp-public.oss-cn-beijing.aliyuncs.com/bohrctl/1.0.0/install_bohr_linux_curl.sh)"
source ~/.bashrc && export PATH="$HOME/.bohrium:$PATH"
Confidence
93% confidence
Finding
Fetching installer code from an external URL and executing it via shell combines external transmission with immediate code execution. This is especially dangerous because compromise of the download source, network path, or hosting account would directly compromise the machine using the skill.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Piping a remote script from `curl` directly into `bash` executes unaudited code from the network with the user's privileges. In this skill context, that can compromise the host running the agent or operator terminal before any Bohrium-specific safeguards apply.

Missing User Warnings

Medium
Confidence
82% confidence
Finding
The markdown includes `bohr node delete 1431145` and notes only in an inline comment that deletion is irreversible. For a destructive operation, the skill description should provide a more explicit warning so users understand the data and lifecycle impact before invoking it.

External Transmission

Medium
Category
Data Exfiltration
Content
HEADERS_JSON = {**HEADERS, "Content-Type": "application/json"}

# Programmatic node creation (non-interactive)
r = requests.post(f"{BASE}/add", headers=HEADERS_JSON, json={
    "projectId": 154, "name": "my-node", "imageId": 2168,
    "machineConfig": {"type": 0, "value": 388, "label": "c2_m4_cpu"},
    "diskSize": 20,
Confidence
70% 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 API example explicitly retrieves node details that include `nodeUser` and `nodePwd` without any credential-handling safeguards. In an agent or logging environment, this can lead to accidental disclosure of SSH credentials in outputs, logs, traces, or downstream tools.

External Transmission

Medium
Category
Data Exfiltration
Content
requests.post(f"{BASE}/restart/{machine_id}", headers=HEADERS)

# Rename
requests.post(f"{BASE}/modify/{machine_id}", headers=HEADERS_JSON,
    json={"name": "new-name"})

# View/bind datasets
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
86% confidence
Finding
The skill includes dataset view/bind operations even though its stated purpose excludes broader resource management beyond node lifecycle/resources/pricing. This widens the operational scope into data-access changes, which can affect confidentiality or integrity of mounted datasets if an agent uses it unexpectedly.

External Transmission

Medium
Category
Data Exfiltration
Content
# View/bind datasets
r = requests.get(f"{BASE}/ds", headers=HEADERS, params={"nodeId": node_id})
requests.post(f"{BASE}/ds/bind", headers=HEADERS_JSON,
    json={"nodeId": node_id, "datasetId": dataset_id})
```
Confidence
70% 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
def create_node(project_id: int, name: str, image_id: int, sku_id: int, disk_size: int = 20):
    """Create a node via API (non-interactive)."""
    r = requests.post(
        f"{BASE}/add",
        headers=HEADERS_JSON,
        json={
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
99% confidence
Finding
The ssh subcommand prints the node password directly to stdout, which can leak credentials into terminal scrollback, shell history capture, CI logs, remote session recordings, or agent transcripts. In an agent skill context this is more dangerous because tool output is often persisted or relayed to other systems, expanding exposure beyond the local operator.

Tainted flow: 'machine_id' from requests.post (line 67, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
def get_ssh_info(machine_id: int):
    """Get SSH credentials for a node."""
    r = requests.get(f"{BASE}/{machine_id}", headers=HEADERS)
    data = r.json().get("data", {})
    print(f"Node:     {data.get('nodeName')}")
    print(f"Status:   {data.get('status')}")
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Description-Behavior Mismatch

Low
Confidence
76% confidence
Finding
The manifest frames the skill around node lifecycle actions such as create/start/stop/delete and resource/pricing checks. Renaming a node is a metadata-management operation outside that explicitly described lifecycle scope, so the documentation advertises behavior broader than claimed.

Context-Inappropriate Capability

Low
Confidence
78% confidence
Finding
The manifest frames this skill as a node lifecycle manager, but the implementation also depends on reading credentials directly from the environment. Credential access is not explicitly part of the stated purpose, and environment-secret handling is a broader capability than simple node management.

Intent-Code Divergence

Low
Confidence
83% confidence
Finding
The top-level documentation describes programmatic node management via API-oriented actions, but the 'list' implementation actually invokes an external shell command with bohr CLI. This is not just incomplete documentation: the inline help explicitly signals a different execution path than the rest of the documented API-based behavior.

Static analysis

No suspicious patterns detected.