Back to skill

Security audit

Mema Vault

Security checks for vulnerabilities and agentic risk

Overview

This is a local credential vault with no evidence of exfiltration, but its handling of secrets is under-scoped and weaker than its security claims.

Install only if you are comfortable with a prototype-style local vault. Avoid putting real passwords on the command line, keep the workspace private, use a strong master key, and treat listed service names/usernames/metadata as not encrypted. The package should be fixed before storing high-value credentials.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/vault.py:102
Finding
Plaintext secrets are accepted through command-line arguments<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:15-17`; `scripts/vault.py:102-108` **Vulnerability Type**: Plaintext secret exposure through process arguments **Risk Level**: High ### Vulnerable Code ```markdown ### 1. Store a Secret Encrypt and save a new credential. - **Usage**: `python3 $WORKSPACE/skills/mema-vault/scripts/vault.py set <service> <user> <password> [--meta "info"]` ``` ```python add_p = subparsers.add_parser("set") add_p.add_argument("service") add_p.add_argument("username") add_p.add_argument("password") add_p.add_argument("--meta", default="") ``` ### Technical Analysis The vault accepts the plaintext password as a positional command-line argument. Command-line arguments can be exposed through shell history, terminal logging, process-monitoring utilities, operating-system process metadata, and automation logs. This handling contradicts the security objective of a credential vault because the secret may be disclosed before encryption occurs. Encryption of the database does not protect copies of the password retained by the shell or exposed through process inspection. ### Attack Path 1. A user invokes the documented `set` command and includes a plaintext credential in the command line. 2. The shell may retain the complete command in its history. 3. While the process is running, a local process with sufficient visibility may inspect its arguments. 4. Terminal capture, process monitoring, CI output, or shell-history access reveals the plaintext password. 5. The attacker can reuse the credential against the associated service. ### Impact Assessment A local user or monitoring process may obtain the complete plaintext credential supplied to the vault. The resulting privileges are those granted by the exposed credential and may extend beyond the local machine to databases, APIs, or other services represented by the secret. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the positional `password` argument. - Prompt interactively using `getpass.getpass()` so the password is not echoed or placed in process arguments. - For automation, accept the secret through a protected file descriptor or standard input only when explicitly requested. - Warn users against placing secrets directly in shell commands. - Update `SKILL.md` so its examples use the secure input mechanism. - Review deployment and CI logs for previously exposed credentials and rotate any affected secrets. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/vault.py:68
Finding
Wildcard credential lookup can disclose unintended secrets<![CDATA[ ## Vulnerability Details **File Location**: `scripts/vault.py:68-86` **Vulnerability Type**: Overbroad credential selection and disclosure **Risk Level**: Medium ### Vulnerable Code ```python def get(service, show=False): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT username, encrypted_password, meta FROM credentials WHERE service LIKE ?", (f"%{service}%",)) rows = c.fetchall() conn.close() if not rows: print("Not found") return for r in rows: try: dec_pass = decrypt(r[1]) masked = dec_pass[:2] + "*" * (len(dec_pass)-4) + dec_pass[-2:] if len(dec_pass) > 4 else "****" print(f"Service: {service}") print(f"User: {r[0]}") print(f"Pass: {dec_pass if show else masked}") print(f"Meta: {r[2]}") except Exception as e: print(f"Decryption failed for {service}. Check Master Key.") ``` ### Technical Analysis The lookup surrounds user-controlled input with SQL wildcard syntax and executes a `LIKE` query. SQL parameterization prevents conventional SQL injection, but it does not prevent wildcard expansion. An input such as `%` can match every credential, while a common substring can match multiple unrelated records. When combined with `--show`, every matching record is decrypted and printed. This exceeds the minimum data access needed for a command documented as retrieving a credential for one service. The output also prints the supplied search term as the service name rather than the actual stored service name, making it harder to identify which records were disclosed. ### Attack Path 1. A caller with access to the vault and master key invokes `get` with `%` or a broadly shared service-name substring. 2. The `LIKE` expression matches multiple or all credential records. 3. The caller supplies `--show`. 4. The loop decrypts every matching password. 5. All matching usernames, plaintext passwords, ...[truncated 426 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Use exact service matching for secret retrieval: ```python c.execute( "SELECT service, username, encrypted_password, meta " "FROM credentials WHERE service = ?", (service,), ) ``` - If search functionality is required, implement it as a separate command that returns identifiers only and never decrypts passwords. - Reject SQL wildcard characters in exact-lookup input. - Require explicit selection when more than one record could match. - Print the actual stored service name instead of the user-supplied search term. - Avoid printing raw passwords where possible; provide a controlled secret-injection mechanism instead. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/vault.py:21
Finding
Vault storage files are created without enforced restrictive permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/vault.py:21-30`; `scripts/vault.py:42-48` **Vulnerability Type**: Insecure local storage permissions **Risk Level**: Medium ### Vulnerable Code ```python def get_fernet(): password = get_master_key() if not os.path.exists(SALT_PATH): os.makedirs(os.path.dirname(SALT_PATH), exist_ok=True) salt = os.urandom(16) with open(SALT_PATH, 'wb') as f: f.write(salt) else: with open(SALT_PATH, 'rb') as f: salt = f.read() ``` ```python def init_db(): os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("CREATE TABLE IF NOT EXISTS credentials (id INTEGER PRIMARY KEY, service TEXT UNIQUE, username TEXT, encrypted_password TEXT, meta TEXT)") conn.commit() conn.close() ``` ### Technical Analysis The code creates the `data` directory, salt file, and SQLite database without explicitly enforcing owner-only permissions. Their effective permissions therefore depend on the process umask and the surrounding workspace configuration. Although passwords are encrypted, the database stores service names, usernames, and metadata in plaintext. A user who can read both the database and salt can immediately inspect this credential inventory and obtain the material needed to perform offline guesses against a weak master password. SQLite may also create journal or WAL sidecar files whose permissions need equivalent protection. ### Attack Path 1. The vault runs in an environment with a permissive umask or a shared workspace. 2. The `data` directory, `vault.db`, or `salt.bin` becomes readable by another local account or process. 3. The attacker copies the database and salt. 4. Service names, usernames, and metadata are read directly from SQLite. 5. The attacker conducts offline master-password guesses against the copied salt and encrypted credential records. 6. If the master pass ...[truncated 509 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create the data directory with mode `0700`. - Create and enforce mode `0600` for `salt.bin`, `vault.db`, and any SQLite journal, shared-memory, or WAL files. - Validate existing file ownership and permissions before reading or writing the vault. - Refuse to operate when the vault files are owned by an unexpected account or are accessible to group/other users. - Consider encrypting usernames and metadata when credential-inventory confidentiality is required. - Require a strong, high-entropy master key because possession of the database and salt permits offline guessing. - Document secure backup and workspace-permission requirements. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/vault.py:89
Finding
Credential inventory can be listed without validating the master key<![CDATA[ ## Vulnerability Details **File Location**: `scripts/vault.py:89-97`; `scripts/vault.py:124-125`; `references/security-policy.md:13-16` **Vulnerability Type**: Missing authorization check for credential enumeration **Risk Level**: Medium ### Vulnerable Code ```python def list_creds(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT service, username FROM credentials") rows = c.fetchall() conn.close() print("Vault Contents:") for r in rows: print(f"- {r[0]} (User: {r[1]})") ``` ```python elif args.command == "list": list_creds() ``` The documented policy states: ```markdown ## Access Control - **Master Key**: Required for all read/write operations. - **Process Isolation**: Secrets are only decrypted in memory during the execution of the `vault` script. - **Output Masking**: Passwords are masked unless the `--show` flag is explicitly provided. ``` ### Technical Analysis The `list` command does not invoke `get_master_key()`, derive a key, or otherwise validate possession of the master key. It opens the database directly and prints every service and username. This behavior conflicts with the stated policy that the master key is required for all read operations. While filesystem access is still needed, the command removes the application-level access-control boundary for credential inventory. ### Attack Path 1. An attacker or unauthorized local user obtains access to execute the script against the workspace or read the vault database. 2. The attacker leaves `MEMA_VAULT_MASTER_KEY` unset. 3. The attacker runs `vault.py list`. 4. The command queries the plaintext service and username columns without validating a key. 5. The complete credential inventory is printed. ### Impact Assessment An unauthorized local user can enumerate stored service names and usernames without knowing the master key. This information can support account targeting, phishing, credential-stuffing preparation, and id ...[truncated 96 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require master-key validation before listing any vault content. - Add an encrypted verifier or authenticated vault header so the supplied key can be checked without exposing a credential. - Encrypt service names, usernames, and metadata if inventory confidentiality is part of the security model. - Apply the same authorization routine consistently to every read and write command. - Add tests confirming that `list`, `get`, and `set` fail securely when the master key is missing or invalid. - Reconcile the implementation with the access-control guarantees in `references/security-policy.md`. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:1
Finding
Cryptography dependency is installed without version or integrity pinning<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:1-5` **Vulnerability Type**: Unpinned third-party dependency **Risk Level**: Low ### Vulnerable Code ```markdown --- name: mema-vault description: Secure credential manager using AES-256 (Fernet) encryption. Stores, retrieves, and rotates secrets using a mandatory Master Key. Use for managing API keys, database credentials, and other sensitive tokens. metadata: {"openclaw":{"requires":{"env":["MEMA_VAULT_MASTER_KEY"]},"install":[{"id":"pip","kind":"exec","command":"pip install cryptography"}]}} --- ``` ### Technical Analysis The installation command retrieves whichever `cryptography` release satisfies pip at installation time. It does not pin a reviewed version or verify package hashes. This makes installations non-reproducible and permits future upstream changes to enter the execution environment without a corresponding Skill review. The audited files contain no evidence of dependency confusion, typosquatting, or an intentionally malicious package. The risk is the unsafe, unconstrained supply-chain configuration rather than confirmed dependency compromise. ### Attack Path 1. The Skill is installed at a later date or in a different environment. 2. Pip resolves the current `cryptography` release rather than a version reviewed with the Skill. 3. A compromised, incompatible, or unexpectedly changed upstream release is downloaded. 4. Package installation or later import executes code from that release in the Skill environment. 5. Because the Skill handles master keys and decrypted credentials, compromised dependency code could access those values within the process. ### Impact Assessment A compromised dependency would execute with the privileges of the Skill installation or runtime process. At runtime, it could potentially access the master key, plaintext credentials during encryption or decryption, and local vault files. No such compromise was identified in the audited project; this finding co ...[truncated 45 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `cryptography` to a reviewed, supported version. - Use a lock file or hashed requirements file with `--require-hashes`. - Retrieve packages only from an approved package index over authenticated TLS. - Incorporate dependency vulnerability scanning and scheduled update review. - Rebuild and test the Skill before changing the pinned dependency. - Avoid installation commands whose resolved contents can change without modification to the Skill package. ]]>
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The documented purpose claims secure storage, retrieval, and rotation of secrets, but the observed behavior includes listing stored credential identifiers and lacks the promised rotation capability. This mismatch can cause users to rely on nonexistent security properties, leading to poor secret hygiene, failed key-rotation expectations, and unintended metadata disclosure such as service names and usernames.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares access to an environment variable containing a master key but does not define any explicit tool scope or permissions boundary. In a credential-management skill, undeclared access to sensitive environment data weakens reviewability and increases the chance the skill can read or expose secrets beyond what operators expect.

Intent-Code Divergence

Medium
Confidence
85% confidence
Finding
The documentation claims AES-256-CBC with PBKDF2HMAC while the skill description elsewhere references Fernet semantics, which use a specific authenticated encryption construction and token format. Conflicting cryptographic claims can mislead users and auditors about integrity guarantees, interoperability, and threat assumptions, causing insecure deployment or incorrect trust decisions.

Static analysis

No suspicious patterns detected.