Back to skill

Security audit

Password Manager

Security checks for vulnerabilities and agentic risk

Overview

This is a local password manager, but it exposes or mishandles secrets in several user-facing paths and has misleading safety-critical documentation.

Review carefully before installing. The skill is local and does not show exfiltration or remote-code behavior, but it handles highly sensitive credentials and currently has unsafe defaults and examples. Avoid putting master passwords or saved secrets in chat, command-line arguments, or long-lived environment variables; do not rely on the documented backup/restore behavior; and treat the vault/cache files as sensitive assets requiring restrictive filesystem permissions.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/password-manager.mjs:238
Finding
Secrets Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/password-manager.mjs:238-242`, `scripts/password-manager.mjs:623-631`; documented in `SKILL.md:36`, `SKILL.md:130`, and `SKILL.md:151` **Vulnerability Type**: Sensitive information exposure through process arguments **Risk Level**: High ### Vulnerable Code ```javascript const name = args.find((_, i) => args[i - 1] === '--name'); const type = args.find((_, i) => args[i - 1] === '--type') || 'password'; const username = args.find((_, i) => args[i - 1] === '--username'); const password = args.find((_, i) => args[i - 1] === '--password'); const tagsArg = args.find((_, i) => args[i - 1] === '--tags'); ``` The master-password change command also accepts both master passwords through arguments: ```javascript async function cmdChangePassword() { const rl = createReadline(); const oldPassword = args.find((_, i) => args[i - 1] === '--old'); const newPassword = args.find((_, i) => args[i - 1] === '--new'); if (!oldPassword || !newPassword) { console.log('❌ Missing --old or --new parameter'); console.log('Usage: password-manager change-password --old <old-password> --new <new-password>'); rl.close(); return; } ``` ### Technical Analysis The CLI accepts stored passwords, tokens, and old and new master passwords directly through command-line arguments. Process arguments are not a protected secret-input channel. Depending on the operating system and execution environment, they may be exposed through: - Process inspection utilities and process-monitoring APIs - Shell history - CI/CD job definitions and logs - Terminal session recording - Audit and observability systems - Parent-process telemetry - Error reports that capture complete command lines Exposure of the master password is especially severe because it permits decryption of the entire vault. Exposure of an entry password or token directly compromises that individual credential. ### Attack Path 1. A user follows the docume ...[truncated 1167 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove support for `--old` and `--new` master-password arguments. 2. Read both master passwords using hidden interactive input, with confirmation for the new password. 3. Replace entry `--password` input with hidden prompting or a protected file-descriptor mechanism. 4. For automation, support reading secrets from standard input only when explicitly requested, or from a caller-provided file descriptor with restrictive permissions. 5. Avoid environment variables as the preferred secret channel because they may also be exposed through process environments and orchestration metadata. 6. Remove all documentation examples that place secrets directly in command lines. 7. Display a deprecation warning before removing legacy secret-bearing arguments. 8. Ensure errors and debug telemetry never include raw argument arrays. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/storage.js:51
Finding
Sensitive Vault Files Created Without Explicit Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/storage.js:51-56`, `scripts/storage.js:132-148`, `scripts/storage.js:164-179`, `scripts/storage.js:286-321`, `scripts/storage.js:373-393` **Vulnerability Type**: Insecure file and directory permissions **Risk Level**: Medium ### Vulnerable Code ```javascript function ensureDirectories() { [DEFAULT_DATA_DIR, DEFAULT_CACHE_DIR].forEach(dir => { if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } }); } ``` The vault and cache are written without an explicit file mode: ```javascript const packed = crypto.encryptAndPack(key, vaultData, salt); writeFileSync(VAULT_FILE, packed); // Cache key (encrypted version using master password) cacheKey(key, masterPassword); ``` ```javascript const encrypted = crypto.encryptAndPack(cacheEncryptionKey, keyData); writeFileSync(CACHE_FILE, encrypted, 'utf8'); ``` History files and subsequent vault versions are handled in the same way: ```javascript if (existsSync(VAULT_FILE)) { const timestamp = Date.now(); const historyFile = join(DEFAULT_DATA_DIR, `vault.${timestamp}.enc`); // Copy old file to history const oldPacked = readFileSync(VAULT_FILE); writeFileSync(historyFile, oldPacked); vaultData.history = vaultData.history || []; vaultData.history.push({ version: vaultData.history.length + 1, timestamp: new Date().toISOString(), file: historyFile }); } vaultData.updatedAt = new Date().toISOString(); // Convert hex salt back to buffer for consistent encryption const saltBuffer = Buffer.from(vaultData.salt, 'hex'); // Encrypt and save with the original salt const packed = crypto.encryptAndPack(decryptionKey, vaultData, saltBuffer); writeFileSync(VAULT_FILE, packed); ``` ### Technical Analysis Directory and file permissions are inherited from the process umask because the code does not explicitly request restrictive modes. In environments with a permissive umask, another local account or service may be ab ...[truncated 1482 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create `data` and `.cache` directories with mode `0700`. 2. Create the vault, cache, and history files with mode `0600`. 3. Apply `chmod` after opening existing files to correct legacy permissions. 4. Verify that each sensitive path is owned by the effective user and is a regular file. 5. Reject symbolic links and other unexpected filesystem objects. 6. Use atomic writes: - Create a temporary file in the same protected directory. - Open it with exclusive creation and mode `0600`. - Write and synchronize the content. - Atomically rename it over the destination. 7. Keep history files under the same restrictive permission policy. 8. Fail securely when ownership or permission validation fails rather than continuing with a warning. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/crypto.js:13
Finding
Globally Fixed Salt Used for Cache-Key Derivation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/crypto.js:13-14`, `scripts/crypto.js:157-165` **Vulnerability Type**: Weak password-based encryption design **Risk Level**: Medium ### Vulnerable Code ```javascript const PBKDF2_DIGEST = 'sha256'; const CACHE_SALT_SUFFIX = '_cache_key_derivation'; const CACHE_SALT_FIXED = Buffer.from('openclaw_cache_salt_v1_fixed_16', 'utf8').subarray(0, 16); ``` ```javascript export function deriveCacheKey(masterPassword, salt = null) { const saltedPassword = masterPassword + CACHE_SALT_SUFFIX; // Use fixed salt for consistent cache key derivation return deriveKey(saltedPassword, salt || CACHE_SALT_FIXED); } ``` ### Technical Analysis All installations derive the cache-encryption key from the master password using the same fixed salt. A cryptographic salt should be random and unique for each protected artifact. Its purpose is to prevent attackers from reusing password-derived values and precomputation across users and files. Appending a public constant to the password does not compensate for a fixed salt. Because both constants are known, attackers can build a reusable dictionary of PBKDF2 results and apply it to cache files from any installation of this Skill. The cache contains the vault encryption key: ```javascript const keyData = { key: key.toString('hex'), createdAt: new Date().toISOString(), lastUsedAt: new Date().toISOString(), expiresAt: new Date(Date.now() + config.cacheTimeout * 1000).toISOString() }; ``` Consequently, successful cache decryption directly exposes the key required to decrypt the entire vault. ### Attack Path 1. An attacker obtains `.cache/key.enc` through local file access, backup exposure, or an accidental archive. 2. The attacker prepares a dictionary of likely master passwords. 3. Because the cache salt is globally fixed, the attacker derives each candidate cache key once and reuses the results against cache files from multiple installations. 4. AES-GCM authenti ...[truncated 553 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate a cryptographically random salt for every cache file. 2. Store the salt in the existing unencrypted packed-file header. 3. Extract that salt before deriving the cache-decryption key. 4. Replace PBKDF2 with a memory-hard KDF such as Argon2id or scrypt where platform compatibility permits. 5. Select KDF parameters based on a documented target derivation time and memory cost. 6. Version the encrypted cache format so KDF and parameter upgrades can be performed safely. 7. Migrate existing cache files by requiring the master password once, then rewriting the cache with a unique salt and the upgraded KDF. 8. Continue using authenticated encryption and independently random IVs. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
hooks/openclaw/handler.mjs:556
Finding
Sensitive-Information Hook Returns Raw Detected Secrets<![CDATA[ ## Vulnerability Details **File Location**: `scripts/detector.js:128-143`, `hooks/openclaw/handler.mjs:556-577` **Vulnerability Type**: Plaintext secret propagation through hook output **Risk Level**: High ### Vulnerable Code The detector stores the raw value and complete matching text: ```javascript let match; while ((match = rule.pattern.exec(text)) !== null) { // Check if threshold is met const meetsThreshold = sensitivityLevels[rule.sensitivity] >= threshold; // Extract value (use capture group if available, otherwise use full match) const value = match[1] || match[0]; results.push({ type, name: rule.name, sensitivity: rule.sensitivity, value, fullMatch: match[0], position: match.index, length: match[0].length, shouldAsk: rule.autoAsk && meetsThreshold, suggestedEntryName: generateEntryName(rule.suggestedEntryName, results.length) }); } ``` The complete detection objects are returned by the user-message hook: ```javascript export async function onUserMessage(message) { const config = storage.loadConfig(); if (!config.autoDetect.enabled) { return {}; } const detections = detector.detect(message.content, config.autoDetect); if (detections.length > 0) { // Log detection events detections.forEach(d => { storage.logDetection(d.type, 'asked', 'pending'); }); return { hasSensitiveInfo: true, detections, prompt: detector.generatePrompt(detections) }; } return {}; } ``` ### Technical Analysis The hook returns `detections` without redaction. Each detection includes both `value` and `fullMatch`, which can contain complete API keys, tokens, passwords, and database connection strings. This conflicts with the documented hook shape in `hooks/openclaw/HOOK.md`, which shows only metadata such as type, name, sensitivity, and suggested entry name. Returning raw secrets expands the number of components that process plaintext credentials. Hook re ...[truncated 1336 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never return `value` or `fullMatch` from `onUserMessage()`. 2. Map detections to a minimal metadata-only structure before returning them: ```javascript const safeDetections = detections.map(d => ({ type: d.type, name: d.name, sensitivity: d.sensitivity, position: d.position, length: d.length, suggestedEntryName: d.suggestedEntryName })); ``` 3. Keep raw matched values only in the narrowest possible in-memory scope. 4. Redact secrets from hook exceptions, debugging output, traces, and telemetry. 5. Add automated tests asserting that hook responses never contain known test credentials. 6. Document the exact hook response schema and enforce it at runtime. 7. If saving a detected value is required, use an explicit protected handoff rather than returning the plaintext through general hook output. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/storage.js:132
Finding
Vault Initialization Overwrites an Existing Vault Without Destructive Confirmation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/password-manager.mjs:182-221`, `scripts/storage.js:132-154` **Vulnerability Type**: Unsafe destructive operation **Risk Level**: High ### Vulnerable Code The initialization command invokes storage initialization without checking whether a vault already exists: ```javascript async function cmdInit() { const rl = createReadline(); console.log('🔐 Initialize Password Manager'); console.log(''); console.log('Master password is used to encrypt all sensitive information, please store it securely.'); console.log('Once lost, it cannot be recovered!'); console.log(''); const password = await promptPassword(rl, 'Set Master Password: '); console.log(); const confirm = await promptPassword(rl, 'Confirm Master Password: '); console.log(); if (password !== confirm) { console.log('❌ Passwords do not match'); rl.close(); process.exit(1); } // Validate master password strength const validation = validator.validateMasterPassword(password); if (!validation.valid) { console.log('⚠️ Master password strength is insufficient:'); validation.errors.forEach(e => console.log(` - ${e}`)); console.log(''); const force = args.includes('--force'); if (!force) { const continueAnyway = await prompt(rl, 'Continue anyway? (y/N): '); if (continueAnyway.toLowerCase() !== 'y') { console.log('Cancelled'); rl.close(); process.exit(0); } } } const result = storage.initializeVault(password); console.log('✅', result.message); rl.close(); } ``` The storage function writes directly over the current vault: ```javascript export function initializeVault(masterPassword) { ensureDirectories(); const { key, salt } = crypto.deriveKey(masterPassword); const vaultData = { version: 1, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), salt: salt.toString('hex'), entries: [], ...[truncated 2138 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make `initializeVault()` fail if `VAULT_FILE` already exists. 2. Introduce a separate, explicitly named reset command for destructive reinitialization. 3. Require a clear warning and independent confirmation phrase for reset operations. 4. Require successful master-password verification before replacing an existing vault where feasible. 5. Create and cryptographically verify a protected backup before any reset. 6. Write the new vault to a temporary file and atomically rename it only after successful encryption and verification. 7. Do not allow a generic `--force` option to bypass destructive-operation confirmation. 8. Add tests proving that repeated `init` calls cannot alter an existing vault. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (29)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The code aligns with part of the description: it does provide local cryptographic primitives using AES-256-GCM and PBKDF2, plus secure random utilities. However, the declared purpose describes a full local password management skill with password generation and sensitive info detection. This code chunk is only a cryptography helper module and does not show vault/storage management, credential CRUD, password generation policy/composition features, or any detection/scanning of sensitive information. Therefore the description overstates the implemented behavior in this supplied chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description presents a broader password management skill featuring AES-256-GCM encryption, password generation, and sensitive info detection. However, this code chunk only covers password-related generation and evaluation utilities. It does not implement encryption, decryption, vault/storage behavior, or any scanning/detection of sensitive information. While password generation aligns with part of the description, the actual code materially underdelivers on the stated primary capabilities and adds some unmentioned utility behavior like strength checking and API-key-format generation. Therefore, the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The supplied code is a validator module, not an encryption, password generation, or sensitive-information detection module. It performs schema-like checks on entries, password strength validation, search query validation, and backup path validation. While this behavior is broadly related to a password management skill, the declared description specifically highlights AES-256-GCM encryption, password generation, and sensitive info detection, none of which are implemented in this chunk. Because the actual code’s primary purpose is narrower and materially different from the declared feature set, this is a description-behavior mismatch for this code chunk.

Ae1

High
Category
analysis-evasion
Content
- **Documentation**: `SKILL.md`, `tests/SECURITY-FIXES.md`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

High
Confidence
97% confidence
Finding
The non-TTY fallback accepts the master password using visible input without any warning, which can expose the secret directly on-screen, in terminal recordings, logs, or upstream pipeline captures. Because this is the master password for a local password vault, disclosure can lead to full compromise of all stored credentials.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
For markdown files, safety-affecting behaviors should include warnings when they may impact user data or system integrity. This entry notes that the vault is automatically re-encrypted and cache updated during `change-password`, but does not warn users to back up data, verify the old password carefully, or understand the consequences if the operation fails or is misused.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This markdown file announces `PASSWORD_MANAGER_MASTER_PASSWORD` support, which affects handling of sensitive credentials. The entry provides no warning that environment variables may be exposed through shell history, process listings, CI logs, or shared automation environments, so users are not alerted to the privacy and security implications.

Lp3

Medium
Category
MCP Least Privilege
Confidence
74% confidence
Finding
The skill advertises handling highly sensitive secrets and explicitly documents use of the PASSWORD_MANAGER_MASTER_PASSWORD environment variable, yet the manifest declares no tool scope or permissions boundaries. In a password-manager context, undeclared environment access weakens reviewability and can enable unintended secret exposure or overbroad runtime privileges.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The examples instruct users to place secrets directly on the command line, where they may be exposed through shell history, process listings, terminal logging, and CI job records. In a password-manager skill, normalizing this practice directly undermines the core goal of protecting credentials.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
Documenting --show-password without any caution encourages plaintext disclosure of secrets on-screen and into terminal scrollback, screen recordings, logs, and remote session transcripts. Because this skill handles credentials, secret display features require strong friction and explicit warnings.

Ssd 3

Medium
Confidence
97% confidence
Finding
The examples explicitly teach the agent to ask for and receive the user's master password in chat, which is a poor pattern because chat content may be retained in transcripts, telemetry, memory, or logs outside the vault's encryption boundary. In an agent setting, collecting master secrets through conversational channels significantly increases exposure surface.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
Backup and restore are sensitive operations, yet the documentation omits warnings that backups contain highly sensitive encrypted vault material and that restore may overwrite current data. This increases the chance of accidental data loss and insecure backup handling.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
| `--tags` | Tags (comma-separated, optional) |
| `--length` | Password length (default: 32) |
| `--show-password` | Show password in plaintext |
| `--confirm` | Skip confirmation (for sensitive operations) |
| `--old` | Old master password (for change-password) |
| `--new` | New master password (for change-password) |
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
| `--tags` | Tags (comma-separated, optional) |
| `--length` | Password length (default: 32) |
| `--show-password` | Show password in plaintext |
| `--confirm` | Skip confirmation (for sensitive operations) |
| `--old` | Old master password (for change-password) |
| `--new` | New master password (for change-password) |
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The document recommends storing the master password in an environment variable for automation, but the warning given is too narrow and understates broader leakage channels such as inherited environments, crash dumps, debug logs, CI metadata, container introspection, and multi-user host exposure. For a password manager, weakening master secret handling materially increases compromise risk.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The FAQ explicitly states that the current version does not support changing the master password and requires reinitialization, which directly contradicts earlier documented commands, tools, and usage examples for `change-password`. This is an intent-level contradiction in the skill's own documentation that could mislead users about a sensitive security operation.

Session Persistence

Medium
Category
Rogue Agent
Content
| Tool Name | Description |
|-----------|-------------|
| `password_manager_add` | Add entry to password manager |
| `password_manager_get` | Get entry content |
| `password_manager_update` | Update entry |
| `password_manager_delete` | Delete entry (sensitive operation) |
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
| Tool Name | Description |
|-----------|-------------|
| `password_manager_add` | Add entry to password manager |
| `password_manager_get` | Get entry content |
| `password_manager_update` | Update entry |
| `password_manager_delete` | Delete entry (sensitive operation) |
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation explicitly shows retrieving an entry with `showPassword: true` but does not warn that this exposes plaintext secrets to the chat/UI, logs, screenshots, or other downstream consumers. In a password-manager skill, normalizing secret display without a clear warning or safer default increases the risk of accidental credential disclosure even if the underlying feature is intended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
`password_manager_get` defaults `showPassword` to `true`, so plaintext secrets are returned unless the caller explicitly opts out. In an LLM agent context, this increases the chance of accidental credential exposure in model outputs, logs, chat history, or downstream tool chains.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The delete tool schema/documentation says deletion requires the user to re-enter the master password, but the implementation only trusts a caller-supplied boolean `confirmed`. In an agent/tooling environment, another component or prompt-influenced agent can set `confirmed: true` and trigger irreversible deletion without the stronger step-up authentication the interface promises.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The user message hook automatically scans all message content for sensitive information whenever auto-detect is enabled, but this file shows no explicit disclosure or consent gate. Even if intended for protection, automatic inspection of arbitrary user content can create privacy, trust, and data-handling risks, especially if detections are logged via `storage.logDetection`.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
Reading the master password from an environment variable creates a real secret-exposure risk because environment variables are often inherited by child processes, captured by CI/CD systems, dumped in crash reports, or viewable through process inspection in some environments. In a password manager context, exposing the master password compromises the entire vault, making this more sensitive than ordinary application credentials.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The help output claims the skill supports `backup`, `restore`, and `config` commands, but the `main()` command dispatcher only implements init/add/get/update/change-password/search/list/generate/check-strength/status/lock/unlock/delete/help. This is an active contradiction between the user-facing documentation and the actual behavior of the program.

Intent-Code Divergence

Medium
Confidence
99% confidence
Finding
The help text instructs users to supply a master password on the command line even though the code comments indicate this is no longer supported and such usage is insecure. Command-line arguments are commonly exposed via shell history, process listings, logging, and audit tooling, so documenting this pattern materially increases the chance that users disclose their master password.

Static analysis

No suspicious patterns detected.