Back to skill

Security audit

Bitwarden Secrets

Security checks for vulnerabilities and agentic risk

Overview

This Bitwarden skill is not clearly malicious, but it handles vault credentials in unsafe and under-disclosed ways that need review before installation.

Install only after reviewing and hardening the bootstrap/export flow. Avoid writing or sourcing plaintext credential export files, avoid running the setup from root unless strictly necessary, and prefer direct bw CLI use or the redacted vw_cli.py commands until the bulk export and shell-sourcing behavior is removed or tightly gated.

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
scripts/vw_env_export.sh:44
Finding
Command Injection Through Unescaped Vault Values Sourced as Shell Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/vw_env_export.sh:44-48` and `scripts/vw_bootstrap.sh:39-41` **Vulnerability Type**: Shell command injection through unsafe code generation and `source` **Risk Level**: High ### Vulnerable Code ```bash cat <<EOF export BW_CLIENTID='$CID' export BW_CLIENTSECRET='$CSEC' export BW_PASSWORD='$CPW' EOF ``` The generated output is executed by the bootstrap script: ```bash EXPORTED="$("$SCRIPT_DIR"/vw_env_export.sh)" || _vw_fail "vw_env_export.sh failed" # shellcheck disable=SC1090 source /dev/stdin <<< "$EXPORTED" ``` ### Technical Analysis The export helper inserts Vaultwarden password values directly into single-quoted shell assignments. It does not escape single quotes, line breaks, command substitutions, or other shell syntax. Single quotes only protect the value while the parser remains inside the quoted string. A vault value containing a single quote can terminate that string and append arbitrary shell commands. Although command substitution embedded entirely inside a valid single-quoted value would not execute, an attacker can escape the generated quoting first. The bootstrap script captures the generated text and passes it to Bash's `source` built-in. Consequently, the generated text is treated as executable shell code rather than inert data. This converts control over any selected vault value into command execution. For example, a selected password could contain a payload structurally equivalent to: ```text '; id > /tmp/vw-injected; # ``` This would produce shell source resembling: ```bash export BW_PASSWORD=''; id > /tmp/vw-injected; #' ``` ### Attack Path 1. An attacker gains permission to create or modify one of the selected Vaultwarden items, or causes an attacker-controlled item to be selected through the helper's ambiguous search behavior. 2. The attacker sets the item's password to a value that closes the generated single-quoted assignment and appends a shell command. 3. A vict ...[truncated 901 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not serialize secrets as shell source code and do not execute generated content with `source`. Refactor the bootstrap so values are assigned directly in the current process through a data-only interface. Suitable approaches include: 1. Move the retrieval logic into the sourced bootstrap script and assign command output directly to fixed variables. 2. Return a structured data format such as JSON and parse it without evaluating the values as shell syntax. 3. Use a dedicated process that consumes the secrets without exporting them into a broad shell environment. If shell assignment serialization is unavoidable, use Bash's `%q` escaping for every value: ```bash printf 'export BW_CLIENTID=%q\n' "$CID" printf 'export BW_CLIENTSECRET=%q\n' "$CSEC" printf 'export BW_PASSWORD=%q\n' "$CPW" ``` Even with escaping, avoid `source` where possible. Validate the generated record count, use fixed variable names, reject unexpected output, minimize the lifetime of sensitive environment variables, and clear them after use. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/vw_env_export.sh:19
Finding
Fuzzy First-Result Vault Lookup Permits Selection of an Attacker-Controlled Item<![CDATA[ ## Vulnerability Details **File Location**: `scripts/vw_env_export.sh:7-9` and `scripts/vw_env_export.sh:19-27` **Vulnerability Type**: Ambiguous security-sensitive object selection **Risk Level**: Medium ### Vulnerable Code ```bash ITEM_CLIENT_ID="${VW_ITEM_CLIENT_ID:-oc-bw-clientid}" ITEM_CLIENT_SECRET="${VW_ITEM_CLIENT_SECRET:-oc-bw-clientsecret}" ITEM_PASSWORD="${VW_ITEM_PASSWORD:-oc-bw-password}" ``` ```bash extract_password_by_name() { local name="$1" local json id value json="$(bw list items --search "$name")" id="$(printf '%s' "$json" | python3 -c 'import sys,json; a=json.load(sys.stdin); print(a[0]["id"] if a else "")')" if [ -z "$id" ]; then echo "" return fi value="$(bw get item "$id" | python3 -c 'import sys,json; o=json.load(sys.stdin); print((o.get("login") or {}).get("password") or "")')" printf '%s' "$value" } ``` ### Technical Analysis The helper uses `bw list items --search "$name"` and unconditionally selects `a[0]`. Search results may contain partial or otherwise non-exact matches, and the script neither verifies that the returned item's name exactly equals the requested name nor requires the match to be unique. Security-sensitive credentials are therefore selected according to search-result ordering rather than a stable identity. The `VW_ITEM_CLIENT_ID`, `VW_ITEM_CLIENT_SECRET`, and `VW_ITEM_PASSWORD` environment variables can also redirect the searches, which increases the risk when the caller inherits an untrusted environment. This flaw compounds the command-injection issue because the password of the incorrectly selected item is eventually emitted as shell code and sourced by `vw_bootstrap.sh`. ### Attack Path 1. An attacker with limited vault item creation or modification rights creates an item whose name matches the search for one of the expected credential items. 2. The attacker arranges for the malicious or unintended item to appear first in the Bitwarden CLI search results. 3. Alternatively, ...[truncated 1037 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use immutable Bitwarden item IDs rather than name searches for security-sensitive records. Configure and validate one exact ID for each required credential. If name-based lookup must remain: 1. Parse all returned objects. 2. Retain only records whose `name` exactly equals the requested name. 3. Require exactly one exact match. 4. Abort on zero matches or multiple matches. 5. Verify the expected item type and required field before use. 6. Do not rely on search-result ordering. 7. Treat `VW_ITEM_*` overrides as privileged configuration: validate them, document the trust boundary, or remove them when runtime overrides are unnecessary. The lookup hardening must be combined with removal of the `source`-based export mechanism so that a malicious vault value cannot become executable code. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
setup-checklist.md:15
Finding
Predictable Temporary File Stores Plaintext Credentials and Permits Symlink-Based File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `setup-checklist.md:15-18` **Vulnerability Type**: Unsafe temporary file containing plaintext secrets **Risk Level**: High ### Vulnerable Code ```bash 3. Test helper: - `cd skills/bitwarden-secrets` - `./scripts/vw_env_export.sh > /tmp/vw_exports.sh` - `source /tmp/vw_exports.sh` ``` ### Technical Analysis The documented workflow redirects the export helper's output into a fixed, predictable path under the shared `/tmp` directory. That output contains the Bitwarden client ID, client secret, and account master password in plaintext shell assignments. The created file's permissions depend on the caller's `umask`; the instructions do not establish a restrictive mode. The file also remains on disk because the checklist does not remove it. Shell redirection opens the destination before running the helper and normally follows symbolic links. A local attacker can pre-create `/tmp/vw_exports.sh` as a symbolic link to a file writable by the victim. If the checklist is followed from the documented root shell, the redirection may truncate and replace a root-writable target with exported credential text. The subsequent `source /tmp/vw_exports.sh` also creates a race between writing and sourcing. Another local process able to replace or modify the path could attempt to make the victim execute attacker-controlled shell commands. ### Attack Path **Plaintext credential disclosure:** 1. A user follows the checklist and redirects the helper output to `/tmp/vw_exports.sh`. 2. The resulting file contains the client ID, client secret, and master password. 3. If the user's `umask` permits broader read access, another local user reads the file. 4. Because no cleanup is specified, the secrets may remain recoverable after the setup operation. **Symlink overwrite:** 1. A local attacker anticipates use of the documented fixed path. 2. The attacker creates `/tmp/vw_exports.sh` as a symbolic link to a target file tha ...[truncated 1330 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Remove the temporary-file workflow from the checklist. Prefer a design that keeps secrets in memory and never writes executable plaintext containing credentials to disk. If a temporary file is strictly necessary: 1. Set a restrictive umask before creation: ```bash umask 077 ``` 2. Create the file atomically with `mktemp` rather than using a predictable name: ```bash tmp_file="$(mktemp "${TMPDIR:-/tmp}/vw_exports.XXXXXX")" || exit 1 ``` 3. Verify that the resulting object is a regular file owned by the current user. 4. Register immediate cleanup with a trap: ```bash trap 'rm -f -- "$tmp_file"' EXIT HUP INT TERM ``` 5. Avoid sourcing the file. Parse a non-executable structured format instead. 6. If disk-backed storage cannot be avoided, use a private runtime directory with restrictive permissions and ensure secrets are securely removed as soon as they are no longer required. 7. Update the documentation to warn against placing the master password, client secret, or `BW_SESSION` in shared temporary locations. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
If the skill reveals multiple secret values in plaintext, exports them as environment variables, or performs bulk extraction without the advertised confirmation gate, then the 'safe-by-default' claim is materially false. In the context of a Bitwarden skill, this increases the likelihood of accidental exfiltration through shell history, logs, child processes, or downstream tools that inherit the environment.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
If the skill reveals multiple secret values in plaintext, exports them as environment variables, or performs bulk extraction without the advertised confirmation gate, then the 'safe-by-default' claim is materially false. In the context of a Bitwarden skill, this increases the likelihood of accidental exfiltration through shell history, logs, child processes, or downstream tools that inherit the environment.

Ae1

High
Category
analysis-evasion
Content
source scripts/vw_bootstrap.sh
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The script retrieves three separate secret values from Vaultwarden and emits shell export statements containing the plaintext secrets. This directly conflicts with the stated skill behavior of redacted access by default and revealing only a single secret field with explicit confirmation, increasing the chance of unintended disclosure through terminal output, shell history, logs, or downstream command evaluation.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares shell and environment-based operational behavior but does not define an explicit tool scope or permissions boundary. For a secret-handling skill, this omission is risky because consumers cannot easily tell what commands or environment access are intended, making misuse or privilege expansion easier.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script sources itself into the caller's shell and exports BW_SESSION, BW_CLIENTID, BW_CLIENTSECRET, and BW_PASSWORD into that live environment without an explicit interactive warning or confirmation at the point of loading. This increases the chance of inadvertent secret exposure to later commands, subprocesses, shell history or debugging output, especially because the skill's purpose is secret handling and the bootstrap flow automatically unlocks and refreshes credentials.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_bw(args):
    p = subprocess.run(["bw", *args], capture_output=True, text=True)
    if p.returncode != 0:
        raise RuntimeError((p.stderr or p.stdout).strip())
    return p.stdout.strip()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The heredoc prints multiple vault secrets as environment variable assignments, creating a bulk exfiltration mechanism that is broader than the skill's documented safe/redacted purpose. Even if intended for local use, exporting several high-value credentials at once expands exposure to shell capture, process inheritance, accidental pasting, and reuse in unrelated contexts.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The checklist instructs writing environment exports derived from Bitwarden/Vaultwarden session state to `/tmp/vw_exports.sh` and then sourcing that file. Temporary locations like `/tmp` are broadly accessible and can increase the chance of local disclosure, accidental reuse, or recovery of sensitive session material, especially if the script contains `BW_SESSION` or other secret-bearing variables. In the context of a secrets-access skill, this is more dangerous because the exported data directly enables vault access rather than being an incidental credential.

Static analysis

No suspicious patterns detected.