Back to skill

Security audit

Local-Passwords-Manager

Security checks for vulnerabilities and agentic risk

Overview

This is a real local password manager, but it handles credentials with weak safeguards that users should review before installing.

Install only if you accept local credential storage with these limitations. Avoid entering real passwords as command-line arguments, do not use export unless you can protect and delete the CSV securely, verify cryptography is installed before adding secrets, and consider tightening file permissions on the vault, key, and any exports.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
password_manager.py:12
Finding
Credentials Are Silently Stored in Plaintext When Encryption Is Unavailable<![CDATA[ ## Vulnerability Details **File Location**: `password_manager.py:12-18` and `password_manager.py:55-64` **Vulnerability Type**: Fail-open encryption handling resulting in plaintext credential storage **Risk Level**: High ### Vulnerable Code ```python try: from cryptography.fernet import Fernet ENCRYPTION_AVAILABLE = True except ImportError: ENCRYPTION_AVAILABLE = False print("警告: cryptography 未安装,密码将以明文存储") ``` ```python def encrypt_password(password: str) -> str: """加密密码""" if not ENCRYPTION_AVAILABLE: return password cipher = get_cipher() if cipher: return cipher.encrypt(password.encode()).decode() return password ``` ### Technical Analysis The application fails open when the `cryptography` dependency is unavailable. Instead of refusing to accept or persist credentials, `encrypt_password()` returns the original password unchanged. The caller then stores that value in `passwords.json` as though it were encrypted. A console warning is insufficient protection because users or automated agents may overlook it. This also contradicts the documented claim that passwords are encrypted at rest. The final `return password` creates an additional fail-open path if a cipher is unexpectedly unavailable. ### Attack Path 1. The application runs in an environment where `cryptography` is absent or cannot be imported. 2. `ENCRYPTION_AVAILABLE` is set to `False`. 3. A user or agent invokes the `add` or `import` command. 4. `encrypt_password()` returns the original credential without encryption. 5. `save_passwords()` writes the plaintext credential to `~/.openclaw/workspace/passwords.json`. 6. Any local user or process with read access to that file can recover the credential directly. ### Impact Assessment An attacker who can read the vault file can obtain every credential added while encryption was unavailable. The exposure includes passwords for all represented services and accounts and may consequently enab ...[truncated 146 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Fail closed if `cryptography` cannot be imported or cipher initialization fails. - Refuse all credential-writing operations until encryption is available and validated. - Remove every path that returns the original password from `encrypt_password()`. - Return a clear nonzero exit status with installation or recovery instructions. - Mark records with an explicit encryption format/version so plaintext cannot be mistaken for ciphertext. - Detect existing plaintext records and provide a controlled migration process that encrypts them before normal operation resumes. - Add tests verifying that no vault file is written when encryption initialization fails. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
password_manager.py:473
Finding
Passwords Are Accepted Through Process Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `password_manager.py:473-481` and `password_manager.py:519-524`; documented examples in `SKILL.md:59-71`, `SKILL.md:130-133`, and `SKILL.md:155-158` **Vulnerability Type**: Sensitive information exposure through command-line arguments **Risk Level**: High ### Vulnerable Code ```python if command == "add": if len(sys.argv) < 5: print("用法: add <服务名> <账号> <密码> [备注] [姓名] [标签]") sys.exit(1) service = sys.argv[2] username = sys.argv[3] password = sys.argv[4] note = sys.argv[5] if len(sys.argv) > 5 else "" name = sys.argv[6] if len(sys.argv) > 6 else "" tags = sys.argv[7] if len(sys.argv) > 7 else "" add_password(service, username, password, note, name, tags) ``` ```python elif command == "update": if len(sys.argv) < 5: print("用法: update <服务名> <账号> <新密码>") sys.exit(1) update_password(sys.argv[2], sys.argv[3], sys.argv[4]) ``` The documented interface reinforces this unsafe pattern: ```bash python3 scripts/password_manager.py add GitHub user1@email.com 123456 python3 scripts/password_manager.py update GitHub user1@email.com newpass123 ``` ### Technical Analysis Passwords and replacement passwords are obtained directly from `sys.argv`. Command-line arguments can be exposed through shell history, process inspection facilities, terminal recording, orchestration logs, debugging output, audit systems, and agent or tool invocation transcripts. This disclosure occurs before encryption and is therefore independent of the cryptographic protection applied to the vault. Quoting or escaping a password does not prevent it from being present in the process argument vector. ### Attack Path 1. A user follows the documented `add` or `update` command format and supplies a password as a command-line argument. 2. The shell may record the complete command in history. 3. While the command is running, another permitted local process may inspect its argument v ...[truncated 587 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove passwords from the positional command-line interface. - Prompt interactively with `getpass.getpass()` for `add` and `update`. - For noninteractive use, support a protected standard-input or file-descriptor mechanism that does not expose the secret in argv. - Do not use ordinary environment variables as the primary replacement because they may also be exposed through process inspection or diagnostic collection. - Update every example in `SKILL.md` so no literal password appears on a command line. - Redact secrets from application, agent, automation, and tool-call logs. - Where feasible, clear temporary in-memory references after use, while recognizing Python does not guarantee complete memory erasure. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
password_manager.py:27
Finding
Credential Vault and Temporary File Are Created Without Explicit Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `password_manager.py:27-34` **Vulnerability Type**: Insecure permissions for sensitive and temporary files **Risk Level**: Medium ### Vulnerable Code ```python def save_passwords(data): """保存密码文件(原子写入,防止竞态)""" PASSWORD_FILE.parent.mkdir(parents=True, exist_ok=True) temp_file = PASSWORD_FILE.with_suffix('.tmp') with open(temp_file, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) temp_file.replace(PASSWORD_FILE) ``` ### Technical Analysis The complete vault is written to a predictable temporary path and then renamed into place. Neither the temporary file nor the resulting vault receives an explicit restrictive mode such as `0600`. Their effective permissions therefore depend on the process umask and any pre-existing file or directory state. The key file is protected with `chmod(0600)` only when newly created, but equivalent protection is not applied to the vault. Because the key and vault are stored in the same workspace, filesystem access to both can defeat encryption. The issue becomes more severe when the plaintext fallback is active. Although `replace()` helps make the final write atomic, it does not itself ensure confidentiality or prevent separate processes from racing over the predictable `.tmp` path. ### Attack Path 1. The password manager runs under a permissive umask or inside a workspace with overly broad access. 2. `save_passwords()` creates `passwords.tmp` using default mode calculation. 3. The temporary file receives the complete vault contents. 4. The file is renamed to `passwords.json` without permission hardening. 5. Another local user or process reads the temporary or final file. 6. If that actor can also read `.password_key`, the encrypted credentials can be decrypted; records stored during encryption fallback are directly readable. ### Impact Assessment The issue can disclose usernames, service names, notes, tags, timestamps ...[truncated 257 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create the vault and temporary files using low-level exclusive creation with mode `0600`, such as `os.open()` with `O_WRONLY | O_CREAT | O_EXCL`. - Use a uniquely named temporary file in the same trusted directory rather than a predictable shared `.tmp` name. - Explicitly enforce mode `0600` on the final vault after every replacement. - Validate that the workspace and vault are owned by the current user and are not symbolic links. - Restrict the workspace directory to mode `0700`. - Revalidate and repair the key file permissions on every startup, rather than only when the key is first created. - Flush and `fsync()` the temporary file before replacement where durability is required. - Add tests under permissive umasks to confirm that the vault, key, and temporary files remain inaccessible to other users. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
password_manager.py:345
Finding
Plaintext Credential Exports Are Written Without Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `password_manager.py:345-370` **Vulnerability Type**: Plaintext sensitive-data export with insecure default file permissions **Risk Level**: High ### Vulnerable Code ```python def export_passwords(format: str = "csv"): """导出密码""" data = load_passwords() if not data: print("暂无保存的密码") return if format == "csv": import csv output = [] for service, accounts in data.items(): for acc in accounts: output.append({ '服务': service, '账号': acc['username'], '密码': decrypt_password(acc['password']), '姓名': acc.get('name', ''), '备注': acc.get('note', ''), '标签': ','.join(acc.get('tags', [])), '创建时间': acc['created_at'], '更新时间': acc['updated_at'] }) if not output: print("没有数据") return filename = f"passwords_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" with open(filename, 'w', newline='', encoding='utf-8-sig') as f: writer = csv.DictWriter(f, fieldnames=['服务', '账号', '密码', '姓名', '备注', '标签', '创建时间', '更新时间']) writer.writeheader() writer.writerows(output) print(f"✓ 已导出到: {filename}") print(f" 共 {len(output)} 条记录") ``` ### Technical Analysis The export operation decrypts every password and writes the complete credential set to a CSV file in the current working directory. The file is created with ordinary `open()` behavior, so its permissions depend on the process umask. There is no explicit `0600` mode, encrypted export option, confirmation prompt, secure destination requirement, or cleanup policy. The export bypasses all at-rest protection applied to `passwords.json`. The resulting CSV may also be collected by backup tools, cloud ...[truncated 1021 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require explicit confirmation before producing a plaintext export. - Clearly warn that the export contains unencrypted credentials. - Require an explicit destination rather than defaulting to the current directory. - Create the export atomically with exclusive mode `0600`. - Refuse to export into shared, world-accessible, synchronized, or repository directories unless the user explicitly overrides the warning. - Prefer an authenticated encrypted export format protected by a separately supplied passphrase or recipient key. - Avoid retaining a second in-memory list containing all decrypted credentials where streaming output is practical. - Document secure transfer, storage, deletion, and backup considerations for any plaintext export. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:238
Finding
Security-Critical Dependency Is Installed Without Version or Hash Pinning<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:238-243` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Low ### Vulnerable Code ```bash pip install cryptography ``` ```text - `cryptography`:用于密码加密(必需) ``` ### Technical Analysis The installation instruction resolves the latest available `cryptography` release and transitive dependency set at installation time. No reviewed version, lock file, package hash, index restriction, or isolated environment is specified. This makes installations non-reproducible. A future incompatible or compromised dependency release could alter the password manager’s security behavior or execute code during installation or import. The dependency is particularly security-sensitive because it directly handles encryption and decryption of all stored credentials. The reviewed files do not demonstrate an existing malicious dependency. The finding concerns the unsafe and mutable dependency acquisition process. ### Attack Path 1. A user follows the documented installation command. 2. `pip` resolves the dependency and its transitive packages at that point in time. 3. The selected artifacts have not been constrained to reviewed versions or verified against expected hashes. 4. If an upstream artifact, configured package index, or resolved dependency is compromised, its code may execute during installation or later import. 5. Malicious dependency code running in the user’s context could read the password vault, encryption key, plaintext exports, or credentials processed by the application. ### Impact Assessment Exploitation would execute with the privileges of the user running `pip` or the password manager. Within that scope, a compromised dependency could access the entire local credential vault and key, modify application behavior, or expose credentials. System-wide impact would be greater if installation were performed with elevated privileges, although elevated installation is not ...[truncated 42 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin a reviewed `cryptography` version or tightly controlled compatible range. - Maintain a lock file containing exact transitive dependency versions. - Record and verify package hashes, for example through a requirements file used with `pip --require-hashes`. - Install dependencies inside a dedicated virtual environment. - Specify a trusted package index and prevent unintended fallback to untrusted indexes. - Use automated dependency vulnerability monitoring and controlled update reviews. - Test dependency updates before publishing revised installation instructions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The documented purpose does not fully disclose sensitive behaviors including access to a local encryption key file, CSV import/export, tag management, and bulk deletion operations. In a password-management context, undisclosed export and deletion capabilities are especially dangerous because they enable silent exfiltration or destructive modification of credential stores beyond what a user may reasonably expect.

Vague Triggers

High
Confidence
95% confidence
Finding
The trigger phrases are broad enough to activate on ordinary requests like '查一下xxx' or '搜索xxx', which may be unrelated to credential handling. Because this skill can retrieve, modify, delete, export, or copy passwords, accidental invocation could disclose secrets or alter stored credentials in response to ambiguous user input.

Missing User Warnings

High
Confidence
95% confidence
Finding
The skill documents password export and clipboard-copy behavior without prominent warnings about the security consequences. Exporting plaintext credentials to CSV or placing them on the clipboard creates high-risk disclosure channels, since other local processes, users, backups, or sync tools may access that data.

Missing User Warnings

High
Confidence
98% confidence
Finding
The export function decrypts every stored password and writes them to a plaintext CSV file without a prominent warning or safeguard. In a password-manager context, this creates a single bulk-compromise artifact that can be copied, synced, indexed, or recovered from disk far more easily than the encrypted store.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares shell, file-read, and file-write behavior but does not define any explicit tool scope or permissions boundaries. For a password manager handling highly sensitive secrets, missing scope declarations increases the chance the agent can invoke powerful actions without clear review or least-privilege constraints.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The module claims encrypted local storage, but when the cryptography dependency is missing it silently degrades to plaintext storage while continuing to operate. For a password manager, this is especially dangerous because users are likely to trust the security claim and store highly sensitive credentials that may then be saved unencrypted on disk.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
User-facing strings throughout the file, including the module description, warnings, usage text, and command output, are written only in Chinese. This creates a language policy concern because the skill forces one locale without opt-in, fallback, or documentation that it is intended solely for a Chinese-language environment.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill adds CSV import/export functionality that is outside the narrowly described password CRUD scope, and the export path writes decrypted passwords to disk in bulk. In a password-manager context, scope expansion matters because it materially increases the attack surface and creates a mass-exfiltration path for all stored credentials.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
Importing plaintext passwords from CSV is inherently sensitive, and the code performs this operation without warning the user that the source file contains raw credentials. While import itself can be legitimate, the lack of safety messaging and handling guidance increases the chance that users leave sensitive CSV files exposed or mishandle them.

Description-Behavior Mismatch

Low
Confidence
87% confidence
Finding
The manifest advertises support for multi-account storage, encrypted storage, timestamps, notes/URL/name fields, and full-field search, but does not mention tags, bulk tagging, tag listing, tag-based filtering, or mass deletion by tag/search/empty-tags. These are additional record-management operations beyond the stated user intents of remembering, querying, modifying, deleting, and searching passwords.

Static analysis

No suspicious patterns detected.