Back to skill

Security audit

Twhidden Bitwarden

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Bitwarden/Vaultwarden helper, but it handles full vault secrets in ways that create material review risks before installation.

Install only if you are comfortable giving this skill and your OpenClaw agent access equivalent to an unlocked Bitwarden vault. Use a dedicated low-scope vault account if possible, require manual approval for retrieve/create/edit/delete/register commands, avoid plaintext master-password files where you can, ensure BW_SERVER is HTTPS, and lock/logout after use.

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

T09 · Insecure Skill Coding Practices

Error
Location
bw.sh:19
Finding
Predictable Shared Session File Enables Token Exposure, Substitution, and File-Clobbering Risks<![CDATA[ ## Vulnerability Details **File Location**: `bw.sh:19`, `bw.sh:58-64`, `bw.sh:93-101` **Vulnerability Type**: Predictable and non-atomic sensitive temporary file **Risk Level**: High ### Vulnerable Code ```bash SESSION_FILE="/tmp/.bw_session" ``` ```bash get_session() { if [[ -f "$SESSION_FILE" ]]; then cat "$SESSION_FILE" elif [[ -n "${BW_SESSION:-}" ]]; then echo "$BW_SESSION" fi } ``` ```bash if [[ "$status" == "unauthenticated" ]]; then local session session=$(bw login "$BW_EMAIL" "$BW_MASTER_PASSWORD" --raw 2>/dev/null) echo "$session" > "$SESSION_FILE" chmod 600 "$SESSION_FILE" echo "Logged in successfully." elif [[ "$status" == "locked" ]]; then local session session=$(bw unlock "$BW_MASTER_PASSWORD" --raw 2>/dev/null) echo "$session" > "$SESSION_FILE" chmod 600 "$SESSION_FILE" echo "Vault unlocked." ``` ### Technical Analysis The script stores a security-sensitive Bitwarden session token at the fixed, globally predictable path `/tmp/.bw_session`. This path is shared across users, workspaces, Bitwarden accounts, and configured servers. The token is written using shell redirection before `chmod 600` is applied. Its initial permissions therefore depend on the process umask. Under a permissive umask, another local user may have a short opportunity to read the token before the permissions are restricted. The script also does not verify that the existing path is a regular file owned by the current user, does not protect against symbolic links, and does not create the file atomically. On systems without adequate protected-symlink enforcement, an attacker who can create entries in `/tmp` could prepare `/tmp/.bw_session` as a symbolic link. The subsequent redirection may follow that link and overwrite a file writable by the victim. Because the path is not namespaced by account or server, concurrent invocations can overwrite each other's sessions. The script will also blindly import any content already present ...[truncated 1574 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the session under a private, per-user runtime directory such as `$XDG_RUNTIME_DIR`, rather than directly under `/tmp`. 2. If no private runtime directory is available, create one safely with `mktemp -d`, set it to mode `700`, and remove it with a shell `trap`. 3. Set `umask 077` before creating any file containing authentication material. 4. Create the session file atomically and exclusively rather than using ordinary `>` redirection. 5. Refuse to use an existing object unless it is a regular file, is not a symbolic link, and is owned by the current effective user. 6. Namespace the session by a cryptographic hash of the server and account identity to prevent collisions between different configurations. 7. Prefer `BW_SESSION` in process memory where practical and avoid persistent session storage if automatic reuse is not essential. 8. Ensure cleanup occurs on logout, lock, normal exit, and relevant termination signals. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
bw.sh:83
Finding
Bitwarden Master Password Is Exposed Through Process Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `bw.sh:83-101`, `bw.sh:134-145` **Vulnerability Type**: Sensitive information exposure through process arguments **Risk Level**: High ### Vulnerable Code ```bash do_login() { load_creds # Configure server bw config server "$BW_SERVER" >/dev/null 2>&1 || true local status # Parse status JSON without python - extract "status":"value" pattern local raw_status raw_status=$(bw status 2>/dev/null || echo '{}') status=$(echo "$raw_status" | grep -oP '"status"\s*:\s*"\K[^"]+' 2>/dev/null || echo "unauthenticated") if [[ "$status" == "unauthenticated" ]]; then local session session=$(bw login "$BW_EMAIL" "$BW_MASTER_PASSWORD" --raw 2>/dev/null) echo "$session" > "$SESSION_FILE" chmod 600 "$SESSION_FILE" echo "Logged in successfully." elif [[ "$status" == "locked" ]]; then local session session=$(bw unlock "$BW_MASTER_PASSWORD" --raw 2>/dev/null) echo "$session" > "$SESSION_FILE" chmod 600 "$SESSION_FILE" echo "Vault unlocked." ``` ```bash # Step 1: Master key = PBKDF2-SHA256(password, email_lower, 600000, 32 bytes) local master_key_hex master_key_hex=$(openssl kdf -keylen 32 -kdfopt digest:SHA256 \ -kdfopt "pass:$reg_pass" -kdfopt "hexsalt:$(echo -n "$email_lower" | xxd -p | tr -d '\n')" \ -kdfopt "iter:$kdf_iterations" -binary PBKDF2 | xxd -p | tr -d '\n') # Step 2: Master password hash = PBKDF2-SHA256(master_key, password, 1, 32) → base64 local master_password_hash master_password_hash=$(openssl kdf -keylen 32 -kdfopt digest:SHA256 \ -kdfopt "hexpass:$master_key_hex" \ -kdfopt "hexsalt:$(echo -n "$reg_pass" | xxd -p | tr -d '\n')" \ -kdfopt "iter:1" -binary PBKDF2 | base64 -w0) ``` ### Technical Analysis The login and unlock operations pass `BW_MASTER_PASSWORD` directly as an argument to the `bw` executable. Registration similarly places the plaintext password and a hexadecimal representation of it in OpenSSL command arguments through ` ...[truncated 2047 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not pass the master password or derived secret material through command-line arguments. 2. Use a Bitwarden CLI authentication mechanism that accepts the password through standard input or a protected file descriptor. 3. For OpenSSL, use supported input mechanisms that read secret material from a protected file descriptor or temporary file rather than embedding it in `-kdfopt` arguments. 4. If a temporary secret file is unavoidable, create it inside a private directory with `umask 077`, mode `600`, atomic creation, and guaranteed cleanup through `trap`. 5. Disable shell tracing around all credential-handling code and document that users must not invoke the script with `set -x`. 6. Minimize the lifetime of plaintext passwords and derived keys by keeping operations in as few processes as possible. 7. Review platform-specific process visibility and restrict `/proc` access where applicable, but treat this only as defense in depth rather than the primary correction. 8. Encourage MFA and a unique master password to reduce the consequences of credential disclosure. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
bw.sh:83
Finding
Unvalidated Server URL Allows Authentication and Registration Over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `bw.sh:83-86`, `bw.sh:180-195` **Vulnerability Type**: Missing transport security enforcement **Risk Level**: Medium ### Vulnerable Code ```bash do_login() { load_creds # Configure server bw config server "$BW_SERVER" >/dev/null 2>&1 || true ``` ```bash # Submit registration via curl local response http_code body response=$(curl -s -w "\n%{http_code}" -X POST \ "${BW_SERVER}/api/accounts/register" \ -H "Content-Type: application/json" \ -d "$(cat <<JSON { "name": "$reg_name", "email": "$reg_email", "masterPasswordHash": "$master_password_hash", "masterPasswordHint": "", "key": "$encrypted_key", "kdf": 0, "kdfIterations": $kdf_iterations } JSON )" --max-time 30) ``` The security documentation makes an unconditional transport-security claim: ```markdown **What leaves your machine:** - Authentication requests (email, master password) to your configured Bitwarden server - Encrypted vault data (create/read/update/delete operations) - All communication uses HTTPS/TLS ``` ### Technical Analysis `BW_SERVER` is accepted from the environment or credentials file without URL parsing or scheme validation. The value is passed directly to both `bw config server` and `curl`. Consequently, values beginning with `http://` are accepted even though the documentation states that all communication uses HTTPS/TLS. The registration payload contains the account email, display name, authentication hash, encrypted account key, and KDF parameters. Login and subsequent vault operations are delegated to the Bitwarden CLI configured with the same URL. Without TLS, a network attacker can observe traffic and may modify responses or impersonate the configured server. The script does not disable certificate verification for HTTPS, which is positive. The flaw is specifically that HTTPS is not required despite the unconditional security assurance. ### Attack Path 1. A user accidentally configures `BW_SERVE ...[truncated 1446 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse and validate `BW_SERVER` before passing it to either `bw` or `curl`. 2. Require the URL to use the `https://` scheme by default. 3. Reject embedded credentials, unexpected control characters, and malformed URLs. 4. Normalize trailing slashes before appending API paths. 5. If plaintext HTTP is required for a loopback-only development instance, support it only through an explicit option such as `BW_ALLOW_INSECURE_HTTP=1`. 6. Restrict any insecure exception to loopback addresses where possible and print a prominent warning. 7. Preserve TLS certificate verification and do not introduce `curl -k` or equivalent bypasses. 8. Update the documentation so its HTTPS guarantee accurately reflects enforced behavior. 9. Consider allowing certificate pinning or a custom CA bundle for self-hosted Vaultwarden deployments rather than encouraging TLS verification bypasses. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (17)

Credential Access

High
Category
Privilege Escalation
Content
You can also override the credentials file path:

```bash
export CREDS_FILE=/path/to/your/credentials.env
```

### Server Configuration Examples
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Session Persistence

Medium
Category
Rogue Agent
Content
## Configuration

Create a credentials file at `secrets/bitwarden.env` in your OpenClaw workspace:

```bash
BW_SERVER=https://vault.bitwarden.com
Confidence
85% confidence
Finding
The README instructs users to store the Bitwarden master password in a persistent plaintext file under the workspace. Even with `chmod 600`, plaintext at-rest storage of a master password materially increases risk from local compromise, backups, accidental inclusion in workspace syncs, or other tooling that can read the file; in a password-manager integration, this secret has especially high value.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Secure the file:

```bash
chmod 600 ~/.openclaw/workspace/secrets/bitwarden.env
```

Alternatively, set these as environment variables directly.
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The README documents commands such as `get-password`, `get-username`, `get-notes`, and `get` that print vault secrets directly to standard output, but it does not warn users that terminal output may be exposed through shell history capture, terminal scrollback, logging, CI output, or agent/tool transcripts. In the context of a password-manager skill, encouraging direct secret printing increases the chance of accidental credential disclosure even if the underlying behavior is intentional.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill explicitly documents shell-based capabilities and invocation of a local script (`bash skills/bitwarden/bw.sh`), but it does not declare any tool scope such as permissions or allowed-tools. In an autonomous agent setting, missing tool restrictions increases the risk that the skill can be invoked with broader shell access than intended, especially given it handles credentials and secret retrieval.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The manifest and high-level description understate the capability of the skill: it does not only wrap the Bitwarden CLI, but can also register brand-new accounts and transmit registration material to a remote server. In an agent setting, incomplete disclosure is security-relevant because users or orchestrators may invoke the skill under false assumptions about what actions it can perform and what data it sends externally.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
local session
    session=$(bw login "$BW_EMAIL" "$BW_MASTER_PASSWORD" --raw 2>/dev/null)
    echo "$session" > "$SESSION_FILE"
    chmod 600 "$SESSION_FILE"
    echo "Logged in successfully."
  elif [[ "$status" == "locked" ]]; then
    local session
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
local session
    session=$(bw login "$BW_EMAIL" "$BW_MASTER_PASSWORD" --raw 2>/dev/null)
    echo "$session" > "$SESSION_FILE"
    chmod 600 "$SESSION_FILE"
    echo "Logged in successfully."
  elif [[ "$status" == "locked" ]]; then
    local session
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
local session
    session=$(bw login "$BW_EMAIL" "$BW_MASTER_PASSWORD" --raw 2>/dev/null)
    echo "$session" > "$SESSION_FILE"
    chmod 600 "$SESSION_FILE"
    echo "Logged in successfully."
  elif [[ "$status" == "locked" ]]; then
    local session
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
local session
    session=$(bw login "$BW_EMAIL" "$BW_MASTER_PASSWORD" --raw 2>/dev/null)
    echo "$session" > "$SESSION_FILE"
    chmod 600 "$SESSION_FILE"
    echo "Logged in successfully."
  elif [[ "$status" == "locked" ]]; then
    local session
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
local session
    session=$(bw login "$BW_EMAIL" "$BW_MASTER_PASSWORD" --raw 2>/dev/null)
    echo "$session" > "$SESSION_FILE"
    chmod 600 "$SESSION_FILE"
    echo "Logged in successfully."
  elif [[ "$status" == "locked" ]]; then
    local session
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
This code implements custom cryptographic registration logic and uses curl directly against the server API, which is materially different from a simple CLI wrapper. That mismatch increases risk because custom security-sensitive logic is harder to validate, easier to get subtly wrong, and may bypass expectations, controls, or audit assumptions built around the official CLI behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
# Format: type 2 = AES-CBC-256 + HMAC-SHA256
  local encrypted_key="2.${iv_b64}|${ct_b64}|${mac_b64}"

  # Submit registration via curl
  local response http_code body
  response=$(curl -s -w "\n%{http_code}" -X POST \
    "${BW_SERVER}/api/accounts/register" \
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 delete command removes vault items immediately with no confirmation, dry-run, or secondary validation. In a password-manager context this is dangerous because mistaken or agent-triggered deletion can irreversibly destroy credentials, causing account lockout, operational disruption, or loss of recovery data.

Description-Behavior Mismatch

Low
Confidence
85% confidence
Finding
The manifest claims the skill 'wraps the Bitwarden CLI (bw) with automatic session management.' The changelog shows part of the implementation uses openssl, curl, and custom JSON parsing for registration, meaning the skill does more than just wrap the CLI and implements protocol-level operations itself.

Description-Behavior Mismatch

Low
Confidence
88% confidence
Finding
The manifest describes a Bitwarden/Vaultwarden integration for storing, retrieving, generating, or managing passwords and credentials via the Bitwarden CLI. The changelog states the skill performs HTTP-based registration and earlier calls it a CLI wrapper with registration and CRUD operations, which extends beyond vault management into account creation on the service itself.

Missing User Warnings

Low
Confidence
84% confidence
Finding
Registration transmits account data, including derived authentication material, to a user-configured remote server without an explicit warning in the command flow. In this skill’s context, where BW_SERVER is configurable and may point to self-hosted infrastructure, lack of an in-band warning raises the chance that credentials are sent to an unintended or untrusted endpoint.

Static analysis

No suspicious patterns detected.