Back to skill

Security audit

Secure Storage

Security checks for vulnerabilities and agentic risk

Overview

This skill is a local secret-storage tool, but its implementation materially undercuts its security claims and could expose stored API keys if users rely on it.

Install only for low-risk local convenience, not for real credential protection. Do not store production API keys, cloud credentials, repository tokens, or account secrets in this version unless you accept that the storage file can be decrypted offline with the public code. Prefer an OS keychain, password manager, KMS-backed secret store, or a revised version using user-controlled key material and authenticated encryption.

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/secure-storage.js:14
Finding
Universal Hardcoded Encryption Key Allows Offline Secret Recovery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/secure-storage.js:14-22` **Vulnerability Type**: Hardcoded cryptographic key material **Risk Level**: High ### Vulnerable Code ```javascript const SIMPLE_KEY = 'openclaw-secure-storage-v1'; function encrypt(text) { const iv = crypto.randomBytes(16); const key = crypto.scryptSync(SIMPLE_KEY, 'salt', 32); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return iv.toString('hex') + ':' + encrypted; } ``` ### Technical Analysis The encryption key is deterministically derived from two constants distributed with the skill: - Password: `openclaw-secure-storage-v1` - Salt: `salt` Consequently, every installation derives the same AES-256 key. The random initialization vector prevents identical plaintext values from producing identical ciphertext, but it does not compensate for a publicly known encryption key. This also contradicts `SKILL.md:63`, which states that the encryption key is obtained from an environment variable. The implementation does not read any environment variable for key material. Anyone who obtains `secure-storage.json` can reproduce the `scryptSync` derivation and decrypt every stored value without interacting with the original system. ### Attack Path 1. The user stores API keys or other credentials with the skill. 2. The skill writes encrypted records to `$HOME/.openclaw/workspace/memory/secure-storage.json`. 3. An attacker obtains this file through a backup leak, accidental publication, compromised process, or access to the user's files. 4. The attacker downloads or reconstructs the publicly available skill code. 5. The attacker derives the AES key using the hardcoded password and salt. 6. For each record, the attacker parses the stored IV and ciphertext and decrypts the secret offline. 7. The recovered API keys or credentials are used against their corresponding exte ...[truncated 595 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all hardcoded passwords, keys, and salts from the repository. 2. Obtain a high-entropy master key from an operating-system keychain, hardware-backed secret store, or managed KMS. 3. If environment-based configuration is required, reject execution when the key is absent rather than using a fallback. 4. If deriving a key from a user passphrase, generate a unique random salt per storage database and persist only that salt. 5. Use a memory-hard password derivation configuration with parameters selected for the deployment environment. 6. Provide a migration procedure that decrypts existing records and re-encrypts them under a unique protected key. 7. Rotate credentials previously stored with this version if the storage file may have been exposed. 8. Update `SKILL.md` so that its key-management and storage-path documentation matches the implementation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/secure-storage.js:89
Finding
Secret Values Are Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/secure-storage.js:89-95` **Vulnerability Type**: Plaintext sensitive data in process arguments **Risk Level**: Medium ### Vulnerable Code ```javascript const [command, ...args] = process.argv.slice(2); ``` ```javascript setKey(args[0], args.slice(1).join(' ')); ``` The documented invocation in `SKILL.md:39` likewise instructs users to place the secret directly in the command: ```bash node skills/secure-storage/scripts/secure-storage.js set <key> <value> ``` ### Technical Analysis The `set` operation receives the plaintext secret through `process.argv`. Command-line arguments are not an appropriate secret-input channel because they may be recorded or exposed by: - Shell history files - Process inspection utilities - Operating-system process accounting or audit systems - Terminal session recording - Automation wrappers and command telemetry - CI/CD job logs Encryption occurs only after Node.js receives the argument. It cannot remove plaintext copies already retained by the shell, operating system, or monitoring infrastructure. ### Attack Path 1. A user invokes the documented `set` command with an API key as the value. 2. The shell records the complete command in its history or an operational tool captures the invocation. 3. During execution, the plaintext value is also present in the process argument vector. 4. A local observer, monitoring service, administrator, or attacker with access to retained logs reads the secret. 5. The attacker uses the recovered credential against the service for which it was issued. ### Impact Assessment The exposed scope includes every secret entered through the `set` command. Exploitation does not itself grant operating-system privileges, but a captured credential grants whatever permissions that credential has in its target service. Exposure may persist after the command terminates if the value was retained in shell history, process-accounting records, CI l ...[truncated 32 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not accept secret values as command-line arguments. 2. Prompt for the value through a hidden interactive input mechanism that disables terminal echo. 3. Support reading the value from standard input for automation, while clearly documenting safe piping practices. 4. Keep only the non-sensitive record name in the argument vector. 5. Avoid printing the plaintext secret during storage. 6. Document that users should remove any historical commands that contained secrets. 7. Recommend rotation of secrets previously supplied through command-line arguments where logs or shell history may have captured them. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/secure-storage.js:18
Finding
AES-CBC Encryption Does Not Authenticate Stored Records<![CDATA[ ## Vulnerability Details **File Location**: `scripts/secure-storage.js:18-37` **Vulnerability Type**: Unauthenticated encryption and ciphertext malleability **Risk Level**: Medium ### Vulnerable Code ```javascript function encrypt(text) { const iv = crypto.randomBytes(16); const key = crypto.scryptSync(SIMPLE_KEY, 'salt', 32); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return iv.toString('hex') + ':' + encrypted; } function decrypt(text) { const parts = text.split(':'); const iv = Buffer.from(parts[0], 'hex'); const key = crypto.scryptSync(SIMPLE_KEY, 'salt', 32); const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); let decrypted = decipher.update(parts[1], 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } ``` ### Technical Analysis AES-256-CBC provides confidentiality but does not provide integrity or authenticity. The implementation stores only the IV and ciphertext and does not calculate or verify a message authentication code. CBC ciphertext is malleable. In particular, changes to the IV cause predictable changes to the first plaintext block, while changes to preceding ciphertext blocks alter subsequent plaintext blocks. Because no authentication tag is checked before plaintext is released, unauthorized changes are not reliably detected. Some modifications may produce padding errors, but padding validation is not an integrity mechanism. Carefully selected modifications can retain valid padding or manipulate blocks that do not affect the final padding. The hardcoded key finding further increases practical exploitability: an attacker with the storage file can derive the key and replace records with correctly encrypted attacker-selected values. ### Attack Path 1. An attacker gains write access to `secure-storage.json`, a synchronized copy, or a backup that will later replace t ...[truncated 1057 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace AES-256-CBC with an authenticated-encryption construction such as AES-256-GCM or ChaCha20-Poly1305. 2. Generate a fresh cryptographically random nonce for every encrypted value and never reuse a nonce with the same key. 3. Store the algorithm version, nonce, ciphertext, and authentication tag in a structured record. 4. Verify the authentication tag before returning or printing any plaintext. 5. Bind relevant metadata, such as the record key and format version, as additional authenticated data to prevent record swapping. 6. Treat authentication failures as security errors and do not reveal partially decrypted content. 7. Migrate existing CBC records to the authenticated format after successful legacy decryption. 8. Combine this change with proper master-key management; authenticated encryption does not correct the hardcoded-key vulnerability by itself. ]]>
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
This is a substantive security issue, not just a documentation mismatch: the stated purpose is secure storage of secrets, but the described behavior includes decrypting secrets to console output, listing metadata, using reversible encryption with a static key, and storing data in a local JSON file. In the context of a secret-storage skill, these behaviors materially weaken confidentiality and can expose API keys through logs, local file compromise, or predictable key recovery.

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill documentation describes a program that reads encryption material from environment variables, but it does not declare any explicit tool scope or permissions boundary for that capability. In agent environments, undeclared access to env-sourced secrets reduces auditability and can enable broader-than-expected secret handling, especially for a skill explicitly designed to process sensitive data.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The skill description and operational guidance are written in Chinese, including the title, feature descriptions, trigger notes, and warnings. This creates a language-policy concern because the file presents a fixed locale without offering an alternative language or stating that the skill is intended only for Chinese-speaking users.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file's natural-language strings and locale behavior are explicitly fixed to Chinese, including the bilingual header and zh-CN date formatting. This can violate language/locale policy because the skill does not offer opt-in, fallback, or configuration for other languages/locales.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The `get` command decrypts stored secrets and prints them directly to stdout, which can expose sensitive values through terminal history, shell logging, CI logs, process capture, or shoulder-surfing. In a skill explicitly marketed as secure storage for API keys and other secrets, returning plaintext by default materially weakens the protection boundary.

Intent-Code Divergence

Low
Confidence
97% confidence
Finding
The inline documentation says the implementation is simple encryption based on Base64 and key obfuscation, which materially differs from the actual code path using scrypt-derived keys and AES-256-CBC. This is an active documentation/code contradiction, even though the broader warning that it is not production-grade is still present.

Static analysis

No suspicious patterns detected.