Back to skill

Security audit

Private Secrets

Security checks for vulnerabilities and agentic risk

Overview

This is a simple local secret manager, but it stores credentials in plaintext and its helper script can let crafted secret names or values execute code.

Review this before installing. Do not store real API keys, passwords, tokens, or production credentials with this version unless the script is fixed to treat inputs as data, use a secure secret store or encrypted file with strict permissions, and avoid printing secrets by default.

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

T09 · Insecure Skill Coding Practices

Error
Location
private-secrets.sh:24
Finding
Arbitrary JavaScript and OS Command Execution Through Unescaped Secret Inputs## Vulnerability Details **File Location**: `private-secrets.sh`, lines 24-30 and 50-61 **Vulnerability Type**: JavaScript injection leading to arbitrary command execution **Risk Level**: High **Vulnerable code:** ```bash # Use node to update JSON node -e " const fs = require('fs'); const data = JSON.parse(fs.readFileSync('$SECRETS_FILE', 'utf8')); data['$NAME'] = '$VALUE'; fs.writeFileSync('$SECRETS_FILE', JSON.stringify(data, null, 2)); console.log('已添加: $NAME'); " ``` The `get` operation uses the same unsafe source-code construction: ```bash node -e " const fs = require('fs'); const data = JSON.parse(fs.readFileSync('$SECRETS_FILE', 'utf8')); if (data['$NAME']) { console.log(data['$NAME']); } else { console.log('未找到: $NAME'); process.exit(1); } " ``` ### Technical Analysis The script inserts the user-controlled `NAME` and `VALUE` arguments directly into JavaScript source passed to `node -e`. These values are not encoded, escaped, validated, or passed as data parameters. An attacker can include JavaScript quote delimiters and statements in a value, terminate the intended string literal, and inject additional JavaScript. Because Node.js exposes APIs such as `require('child_process')`, successful JavaScript injection can be escalated directly to operating-system command execution. Shell quoting around the `node -e` argument does not prevent this vulnerability. The shell first substitutes the variables, and Node.js subsequently parses the resulting text as executable JavaScript source. ### Attack Path 1. The attacker gains the ability to invoke the skill or influence a secret name or value supplied to its `add` command. 2. The attacker supplies a value containing a closing quote, an injected JavaScript statement, and a JavaScript comment marker. A conceptual payload can invoke `require('child_process').execSync(...)`. 3. The shell expands `$VALUE` inside ...[truncated 1369 chars]
Remediation
## Remediation Suggestions - Never concatenate user input into source passed to `node -e`. - Move the JavaScript implementation into a standalone script and obtain the command, name, value, and file path through `process.argv`. - If an inline program must be retained, pass values after the program and read them from `process.argv`, for example: ```bash node - "$SECRETS_FILE" "$NAME" "$VALUE" <<'NODE' const fs = require('fs'); const [file, name, value] = process.argv.slice(2); const data = JSON.parse(fs.readFileSync(file, 'utf8')); data[name] = value; fs.writeFileSync(file, JSON.stringify(data, null, 2)); NODE ``` - Validate secret names against an explicit policy, such as a conservative length limit and an allowlist of letters, digits, underscores, periods, and hyphens. - Treat secret values strictly as opaque data; do not attempt to sanitize them for insertion into executable source. - Add regression tests using quotes, backslashes, newlines, comment markers, and JavaScript syntax in both names and values. - Return generic errors without including untrusted data in an executable or otherwise interpreted context.

T09 · Insecure Skill Coding Practices

Warning
Location
private-secrets.sh:5
Finding
Plaintext Secret Storage Without Enforced File Permissions or Symlink Protection## Vulnerability Details **File Location**: `private-secrets.sh`, lines 5-9 and 27-28; `SKILL.md`, lines 11 and 31-35 **Vulnerability Type**: Insecure storage of sensitive information **Risk Level**: Medium **Vulnerable initialization code:** ```bash SECRETS_FILE="/workspace/skills/private-secrets-1.0.0/secrets.json" # Initialize file if not exists if [ ! -f "$SECRETS_FILE" ]; then echo '{}' > "$SECRETS_FILE" fi ``` **Vulnerable write operation:** ```javascript const data = JSON.parse(fs.readFileSync('$SECRETS_FILE', 'utf8')); data['$NAME'] = '$VALUE'; fs.writeFileSync('$SECRETS_FILE', JSON.stringify(data, null, 2)); ``` The documentation explicitly confirms that the file is not encrypted: ```markdown - 此文件存储在本地,未加密 - 建议定期备份 - 如需更高安全性,可使用加密工具手动加密文件 ``` ### Technical Analysis API keys, passwords, tokens, and other secrets are serialized directly into a plaintext JSON file. Anyone able to read that file can recover every stored value without requiring the skill or additional authentication. File initialization uses ordinary shell redirection and does not set a restrictive `umask` or explicitly apply mode `0600`. Consequently, the resulting permissions depend on the caller's environment and may permit access by other local users or processes. The script also does not verify that the path is a regular file owned by the expected user. It does not reject symbolic links, and its existence check is separate from subsequent reads and writes. If another principal can manipulate the containing directory or path, this creates symlink and time-of-check/time-of-use risks. Node.js then follows the path during `readFileSync` and `writeFileSync`. The use of a predictable, fixed path increases the consequences of weak directory permissions. No atomic replacement strategy or integrity protection is used, so interruptions or concurrent writes can also corrupt the secret database. ### Attack Path **Plainte ...[truncated 1785 chars]
Remediation
## Remediation Suggestions - Prefer an operating-system credential store, managed secrets service, or keychain instead of a plaintext JSON file. - If file-based storage is unavoidable, encrypt values using authenticated encryption and keep the encryption key outside the same file and directory. - Create the parent directory with mode `0700` and ensure it is owned by the expected account. - Set a restrictive `umask`, such as `077`, before creating any secret material. - Atomically create the file with exclusive-create semantics and mode `0600`; do not rely on default shell redirection permissions. - Before every operation, use `lstat` to reject symbolic links and verify that the target is a regular file owned by the expected user. - Open files with protections equivalent to `O_NOFOLLOW` where the platform supports them. - Write updates to a securely created temporary file in the same protected directory, set mode `0600`, synchronize it, and atomically rename it over the destination. - Add locking or an equivalent concurrency mechanism to prevent lost updates and JSON corruption. - Do not recommend ordinary unencrypted backups of the file. Backups must receive encryption and access controls at least as strong as the primary storage. - Update the documentation so that it does not characterize plaintext storage as secure and clearly states the local disclosure threat model.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (7)

Credential Access

High
Category
Privilege Escalation
Content
## 存储位置

`/workspace/skills/private-secrets-1.0.0/secrets.json`

## 使用方式
Confidence
97% confidence
Finding
The skill explicitly stores secrets in a local plaintext file (`secrets.json`) and also provides a command to read back secret contents. This creates a clear credential exposure risk: any local process, user, backup, log, or misconfigured permission affecting that path could disclose API keys, passwords, or tokens.

Credential Access

High
Category
Privilege Escalation
Content
# Private Secrets Skill - Manage private information

SECRETS_FILE="/workspace/skills/private-secrets-1.0.0/secrets.json"

# Initialize file if not exists
if [ ! -f "$SECRETS_FILE" ]; then
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The skill description and invocation examples are written entirely in Chinese, including the command phrases users are expected to type. This creates a language policy concern because it implicitly requires Chinese for use and does not indicate that other languages are supported or that the language choice is optional.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script stores secret material in a predictable JSON file on disk and never warns the user that the values are persisted in plaintext. In a shared workspace or environment with backups, logs, snapshots, or lax file permissions, this can expose credentials or other sensitive data to unintended parties.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The get command prints secret contents directly to stdout, which can leak sensitive values into terminal scrollback, logs, transcripts, agent output capture, or other monitoring systems. In an agent skill context, stdout is especially risky because responses may be recorded or relayed automatically.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
User-visible strings in the script are presented in Chinese, and there is no indication that the skill is region-specific or that users can select another language. This creates a natural-language policy concern because the skill imposes a locale without opt-in or justification.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
Multiple help and status messages are emitted only in Chinese across listing, retrieval, and usage output. Without documented regional scope or a configurable language option, this constitutes a locale-policy issue in the skill's natural-language interface.

Static analysis

No suspicious patterns detected.