Back to skill

Security audit

env-secure-manager

Security checks for vulnerabilities and agentic risk

Overview

This skill is a secret manager, but its implementation can expose the secrets and encryption key it claims to protect.

Review before installing. This skill handles highly sensitive values, but it can print its encryption key, import the whole process environment if invoked with a broad prefix, and return stored secrets to any caller that sets the documented flags. Use only in a tightly controlled environment after fixing key handling, environment import scoping, and real authorization for secret retrieval.

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

T09 · Insecure Skill Coding Practices

Error
Location
index.ts:67
Finding
AES Master Key Disclosed Through Application Logs<![CDATA[ ## Vulnerability Details **File Location**: `index.ts:67-72` **Vulnerability Type**: Plaintext disclosure of cryptographic key material **Risk Level**: High ### Vulnerable Code ```typescript const randomKey = crypto.getRandomValues(new Uint8Array(32)); keyStr = encodeHex(randomKey); Deno.env.set("OPENCLAW_ENV_ENCRYPTION_KEY", keyStr); console.warn("⚠️ 自动生成环境变量加密密钥,请保存到安全位置:", keyStr); console.warn("⚠️ 重启后如果没有设置此密钥,加密的环境变量将无法解密!"); ``` ### Technical Analysis When no custom key or `OPENCLAW_ENV_ENCRYPTION_KEY` environment variable is present, the implementation generates an AES-256 key and writes the complete key to standard error through `console.warn`. Cryptographic keys must not be included in application logs. Standard error is frequently captured by container runtimes, CI systems, centralized logging services, process supervisors, or agent conversation infrastructure. Access controls and retention rules for these systems are often weaker than those applied to secret-management systems. Possession of this key destroys the confidentiality boundary provided by AES-GCM for every value encrypted under the same key. Storing the key in the process environment also increases its exposure to other code running with access to that environment. ### Attack Path 1. An attacker or ordinary caller invokes any action before encryption has been initialized, or explicitly invokes the `init` action without supplying a key. 2. The implementation generates a new AES key. 3. The complete key is emitted to standard error. 4. The execution environment captures standard error in local, CI, container, or centralized logs. 5. An attacker with access to those logs obtains the AES key. 6. If the attacker can also obtain encrypted records and their IVs through memory disclosure, debugging output, a later persistence layer, or another application flaw, the attacker can decrypt every record protected by that key. ### Impact Assessment The disclosed value is the master ...[truncated 375 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Never print encryption keys, passwords, tokens, plaintext secrets, or equivalent key material to any output stream. - Remove the key argument from the warning and log only a non-sensitive initialization status. - Obtain the key from a dedicated secret manager, protected runtime secret, or externally injected key file with restrictive permissions. - Avoid placing newly generated keys into the general process environment when a narrower storage mechanism is available. - If automatic generation is required, return the key only through an explicitly protected provisioning channel and never through normal application responses or logs. - Rotate any key that may already have appeared in logs. - Remove historical key-bearing log records where possible and review access logs for unauthorized retrieval. - Add automated tests or secret-scanning rules that fail builds when cryptographic keys or secret values are passed to logging functions. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
index.ts:162
Finding
Caller-Controlled Flags Permit Unauthenticated Plaintext Secret Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `index.ts:162-190` **Vulnerability Type**: Missing authorization for secret retrieval **Risk Level**: High ### Vulnerable Code The input schema permits callers to select the disclosure flags directly: ```typescript z.object({ action: z.literal("get"), key: z.string(), allowSecret: z.boolean().optional().default(false), }), z.object({ action: z.literal("list"), showSecrets: z.boolean().optional().default(false), }), ``` The corresponding handlers treat those caller-provided flags as authorization: ```typescript case "get": { const { key, allowSecret } = validatedParams; const item = envStore[key]; if (!item) { return { success: false, error: "Key not found" }; } if (item.isSecret && !allowSecret) { return { success: false, error: "Access denied: secret value requires allowSecret=true" }; } let value = item.value; if (item.encrypted && item.iv) { value = await decrypt(value, item.iv); } return { success: true, key, value }; } case "list": { const { showSecrets } = validatedParams; const result: Record<string, any> = {}; for (const [key, item] of Object.entries(envStore)) { if (item.isSecret && !showSecrets) { result[key] = "***REDACTED***"; } else if (item.encrypted && item.iv && showSecrets) { result[key] = await decrypt(item.value, item.iv); } else { result[key] = item.value; } } return { success: true, env: result, count: Object.keys(envStore).length }; } ``` ### Technical Analysis The implementation does not authenticate callers or evaluate a trusted authorization policy. Instead, it accepts `allowSecret` and `showSecrets` from the same untrusted request that asks to retrieve the data. A Boolean value supplied by a caller cannot establish authorization. Any caller capable of invoking the skill can set either flag to `true`. The `list` operation is especially dangerous because it supports bulk decrypti ...[truncated 1501 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `allowSecret` and `showSecrets` as authorization mechanisms. - Authenticate every caller through trusted execution context that callers cannot populate or modify themselves. - Enforce server-side authorization based on caller identity, role, secret ownership, purpose, and requested operation. - Associate each stored record with an owner or tenant and prevent cross-principal access. - Remove bulk plaintext secret listing. If enumeration is required, return only key names and metadata. - Use narrowly scoped, non-exportable secret handles where possible instead of returning plaintext values. - Require explicit policy grants for secret retrieval and maintain tamper-resistant access audit records. - Separate administrative operations from ordinary skill invocation. - Apply rate limiting and monitoring to failed and successful secret-access attempts. - Ensure the surrounding runtime cannot invoke privileged operations merely by supplying additional request properties. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
index.ts:224
Finding
Arbitrary Prefix Allows Harvesting of the Entire Process Environment<![CDATA[ ## Vulnerability Details **File Location**: `index.ts:224-235` **Vulnerability Type**: Excessive environment access and unsafe secret classification **Risk Level**: High ### Vulnerable Code ```typescript case "loadFromEnv": { const { prefix } = validatedParams; let loaded = 0; for (const [key, value] of Object.entries(Deno.env.toObject())) { if (key.startsWith(prefix)) { const isSecret = key.includes("KEY") || key.includes("SECRET") || key.includes("PASSWORD"); envStore[key] = isSecret ? { ...await encrypt(value), isSecret: true, encrypted: true } : { value, isSecret: false, encrypted: false }; loaded++; } } return { success: true, loaded, prefix }; } ``` The schema accepts any string as the prefix: ```typescript z.object({ action: z.literal("loadFromEnv"), prefix: z.string().optional().default("OPENCLAW_"), }), ``` ### Technical Analysis The function first reads the complete process environment through `Deno.env.toObject()` and then applies a caller-controlled prefix filter. The schema imposes no minimum length and no allowlist, so an empty string is valid. Every string starts with an empty string, meaning `prefix: ""` imports the entire process environment. The secret-detection mechanism is also incomplete. It marks a value as sensitive only when the variable name contains `KEY`, `SECRET`, or `PASSWORD`. Common credential names containing `TOKEN`, `AUTH`, `CREDENTIAL`, `COOKIE`, `SESSION`, `PRIVATE`, or connection-string terminology may therefore be stored as non-secret values and returned by the default `list` operation without requiring `showSecrets`. Because the environment belongs to the hosting process rather than necessarily to this skill, this behavior crosses least-privilege and isolation boundaries. ### Attack Path #### Complete environment harvesting 1. An attacker invokes: ```typescript { action: "loadFromEnv", prefix: "" } ``` 2. `Deno.env.toObject()` reads e ...[truncated 1435 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not call `Deno.env.toObject()` for a secret-management import operation. - Replace arbitrary prefix scanning with an explicit allowlist of exact environment-variable names. - Reject empty prefixes and enforce a sufficiently restrictive, server-configured prefix if prefix loading must remain available. - Do not permit untrusted callers to choose which portion of the process environment can be read. - Treat every imported environment value as sensitive by default; allow downgrading only through trusted configuration. - Avoid name-based secret detection as the primary protection mechanism. - Add explicit handling for common credential terms only as defense in depth, not as an authorization boundary. - Combine environment import with authenticated, server-side authorization and tenant isolation. - Run the skill with the minimum required environment permissions and avoid injecting unrelated credentials into its process. - Return only import counts or approved metadata, never imported values, unless a separately authorized retrieval operation permits access. - Add tests confirming that empty, wildcard-like, short, and unapproved prefixes are rejected. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (8)

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
When no key is provided, the skill generates an encryption key, writes it into the process environment, and logs the key value to the console. This exposes the master key to logs, terminals, subprocesses, and other components that can read environment variables, undermining all encryption performed by the skill.

Missing User Warnings

High
Confidence
99% confidence
Finding
The code emits the generated encryption key directly in console warnings without any safe handling or interface disclosure. Console output is frequently captured in logs or agent transcripts, so this can permanently leak the key and allow retrospective decryption of all stored secrets.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The entire skill description, parameters, and examples are presented only in Chinese, with no indication that users may choose another language or that the locale restriction is intentional. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The visible instructional content of the skill is written entirely in Chinese, including headings, parameter explanations, and examples. For a general-purpose skill, this imposes a specific language/locale on users without any opt-in or documented region-specific justification, which matches the language-policy violation criteria.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The skill claims permission control and leak prevention, but there is no real access-control boundary: any caller can request secret exposure by passing `allowSecret=true` or `showSecrets=true`. In a multi-tool or agent setting, such misleading claims are dangerous because operators may trust the skill to protect secrets when it does not.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill markets itself as secure secret management, but both `get` with `allowSecret=true` and `list` with `showSecrets=true` return decrypted plaintext secrets to any caller who sets those flags. There is no actual authorization or policy enforcement, so secrecy depends entirely on caller honesty, which defeats the claimed protection model and can directly leak credentials.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
`loadFromEnv` bulk-reads all environment variables matching a prefix and imports them into the skill's store, potentially sweeping in sensitive system secrets without strong user awareness or consent boundaries. In agent environments, bulk environment access increases blast radius because a single call can ingest many secrets for later exposure through other weak interfaces.

Description-Behavior Mismatch

Low
Confidence
83% confidence
Finding
The manifest describes environment variable and secret management, but `set`, `get`, `list`, and `delete` operate on `envStore`, an internal object, not the process environment. Aside from reading and writing the encryption key env var and importing existing env vars, the code does not actually manage environment variables in the runtime environment as the description implies.

Static analysis

No suspicious patterns detected.