Back to skill

Security audit

secrets-inject

Security checks for vulnerabilities and agentic risk

Overview

The skill is upfront about handling secrets, but it writes decrypted credentials into lingering local temp scripts and under-declares that write behavior, so it should be reviewed carefully before use.

Install only if you deliberately need to turn stored secrets into shell commands. Use it only on a trusted single-user machine, avoid shared containers or CI, prefer safer secret-passing methods when available, use narrowly scoped credentials, review generated commands before running them, and immediately run cleanup after use.

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
secrets-inject.js:126
Finding

Plaintext secrets persist in generated temporary shell scripts

Content
View full analysis

Vulnerability Details

File Location: secrets-inject.js:126-133
Vulnerability Type: Plaintext secret exposure through persistent temporary files
Risk Level: High

Vulnerable Code

javascript
const tmpFile = path.join(os.tmpdir(), `secrets-inject-${process.pid}-${Date.now()}.sh`);
writeSecure(tmpFile, '#!/bin/sh\n' + result + '\n');
trackTmpInjection(tmpFile, Object.keys(secrets));
console.log(`[secrets-inject] ✅ Injected ${injected} secret(s) into: ${tmpFile}`);
console.log(`[secrets-inject]    Run with:  sh ${tmpFile}`);
console.log(`[secrets-inject]    File mode 0600. Clear with --cleanup-tmp.`);
console.log(`[secrets-inject]    ⚠️ This temp file contains PLAINTEXT secrets. Delete it after use.`);
return tmpFile;

Technical Analysis

The implementation embeds decrypted secret values directly into a shell command and writes the resulting plaintext command to a file under the operating system's shared temporary directory. Although writeSecure applies mode 0600, file permissions do not protect secrets from other processes running under the same account, backups, snapshots, crash collection, forensic recovery, or subsequent permission and ownership mistakes.

The files are not automatically deleted after command execution. Cleanup depends on the user explicitly invoking --cleanup-tmp, creating an indefinite secret-retention window if cleanup is forgotten or fails.

The behavior is prominently disclosed in the documentation and is intentional, but it remains a high-risk secret-handling design because the confidentiality of every substituted credential becomes dependent on temporary-file lifecycle management.

Attack Path

  1. A user invokes --inject with a command containing one or more secret placeholders.
  2. The skill reads the master key and decrypts matching records from the secrets store.
  3. The decrypted values are inserted directly into the shell command.
  4. The complete ...[truncated 944 chars]
Remediation
View remediation

Remediation Suggestions

  • Avoid embedding secrets in command-line text or persistent scripts.
  • Prefer passing secrets through protected standard input, inherited file descriptors, or narrowly scoped environment variables when the target program supports them.
  • If a file is unavoidable, create a private directory with fs.mkdtempSync, apply mode 0700, and place the secret file inside it.
  • Execute the generated command through a controlled wrapper and remove the file in a finally block immediately after execution.
  • Add signal and process-exit handlers as defense-in-depth cleanup measures, while recognizing that they cannot guarantee cleanup after abrupt termination.
  • Minimize retention by writing only the secret value required by the target program rather than a complete executable command.
  • Document and enforce a maximum lifetime for generated material, and purge stale files automatically during startup.

T09 · Insecure Skill Coding Practices

Warning
Location
secrets-inject.js:72
Finding

Predictable and non-exclusive temporary-file creation permits symlink race attacks

Content
View full analysis

Vulnerability Details

File Location: secrets-inject.js:72-78 and secrets-inject.js:126
Vulnerability Type: Unsafe temporary-file creation and symbolic-link following
Risk Level: Medium

Vulnerable Code

javascript
function writeSecure(file, data) {
  fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
  const tmp = file + '.tmp.' + process.pid;
  fs.writeFileSync(tmp, data, { mode: 0o600 });
  fs.renameSync(tmp, file);
  try { fs.chmodSync(file, 0o600); } catch {}
}
javascript
const tmpFile = path.join(os.tmpdir(), `secrets-inject-${process.pid}-${Date.now()}.sh`);

Technical Analysis

Temporary path names are derived from the process identifier and current time. The intermediate path is also formed deterministically by appending the process identifier. These values are observable or potentially predictable by another local process.

fs.writeFileSync uses ordinary write semantics and does not request exclusive creation. It can therefore open an existing path and follow a symbolic link. The implementation also does not use lstat, reject links, verify ownership, or use an operating-system no-follow option before writing plaintext secret material.

If an attacker can pre-create or race the expected intermediate path as a symbolic link, the write may follow the link and place the plaintext command into an attacker-selected file writable by the victim. The subsequent rename does not undo the data already written through that symbolic link.

Attack Path

  1. An attacker with access to the shared temporary directory observes or predicts the victim process identifier.
  2. The attacker estimates or monitors the timestamp used in the final filename.
  3. The attacker creates the corresponding intermediate path, ending in .tmp.<pid>, as a symbolic link to a file that the victim can write.
  4. The victim invokes --inject.
  5. fs.writeFileSync opens the ...[truncated 879 chars]
Remediation
View remediation

Remediation Suggestions

  • Create a private temporary directory with fs.mkdtempSync(path.join(os.tmpdir(), 'secrets-inject-')) and enforce mode 0700.
  • Open each generated file with exclusive creation, such as the wx flag or O_CREAT | O_EXCL.
  • Use O_NOFOLLOW where supported to prevent symbolic-link traversal.
  • Validate the path with lstat and reject symbolic links or unexpected file types.
  • Verify the created file's owner, mode, and inode through the open file descriptor before writing sensitive data.
  • Generate names using cryptographically secure randomness rather than process identifiers and timestamps.
  • Keep the file descriptor open through the complete write operation and avoid reopening the path by name.
  • Remove the private directory and its contents automatically after use.

T09 · Insecure Skill Coding Practices

Warning
Location
secrets-inject.js:96
Finding

Temporary-file cleanup trusts registry paths and can delete unintended files

Content
View full analysis

Vulnerability Details

File Location: secrets-inject.js:96-107
Vulnerability Type: Unvalidated path deletion
Risk Level: Medium

Vulnerable Code

javascript
function cleanupTmpInjections() {
  const reg = loadJSON(TMP_INJECTIONS_FILE, { files: [] });
  let removed = 0;
  const remaining = [];
  for (const e of reg.files) {
    try {
      if (fs.existsSync(e.path)) { fs.unlinkSync(e.path); removed++; }
      else remaining.push(e);
    } catch { remaining.push(e); }
  }
  writeSecure(TMP_INJECTIONS_FILE, JSON.stringify({ files: remaining }, null, 2));
  console.log(`[secrets-inject] Removed ${removed} temp injection file(s).`);
}

Technical Analysis

The cleanup routine treats every path value in .tmp-injections.json as trusted and passes it directly to fs.unlinkSync. It does not canonicalize the path, require it to reside in the intended temporary directory, enforce the expected filename pattern, verify ownership, or confirm that the entry was generated by the current skill installation.

Consequently, corruption or attacker-controlled modification of the registry can transform the cleanup function into a file-deletion primitive operating with the invoking user's filesystem privileges.

The registry is normally written with mode 0600, which reduces exposure. Exploitation therefore requires an attacker or another component capable of modifying the registry, a permission or ownership mistake, or prior corruption of the file.

Attack Path

  1. An attacker or compromised local component obtains the ability to modify memory/secrets/.tmp-injections.json.
  2. The attacker inserts a registry entry whose path references a victim-owned target rather than a generated injection file.
  3. The user invokes --cleanup-tmp.
  4. The cleanup routine checks only whether the target exists.
  5. fs.unlinkSync deletes the attacker-selected target using the invoking user's permission ...[truncated 740 chars]
Remediation
View remediation

Remediation Suggestions

  • Store generated identifiers rather than arbitrary absolute paths in the registry.
  • Resolve the intended temporary-directory root and require every cleanup target to remain strictly beneath that root.
  • Enforce the exact generated filename pattern before deletion.
  • Use lstat to inspect targets and reject symbolic links and unexpected file types.
  • Verify ownership and restrictive permissions before unlinking a file.
  • Create all generated files under a dedicated private directory and remove only entries discovered within that directory.
  • Protect registry updates with exclusive file creation, ownership checks, and appropriate locking.
  • Treat malformed registry entries as security errors and report them without attempting deletion.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (14)

Scope Creep

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The manifest understates the skill's effective capabilities: the documentation says it writes plaintext temp shell scripts and a tracking file, yet only filesystem.read is declared. This creates a dangerous transparency gap where reviewers or policy engines may approve the skill without realizing it performs secret materialization to disk, increasing the risk of plaintext secret exposure and misuse.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The skill is explicitly designed to read encrypted secret storage and materialize plaintext secrets into shell commands, scripts, or stdout. Even though this is declared functionality, it creates a high-risk secret exposure pathway: any misuse, downstream command injection, compromised host, or lingering temp file can lead to credential disclosure and broad compromise.

Content

Scanner excerpt · clawhub.yaml (reported line 24)May include surrounding context.

yaml
- secrets-manager
permissions:
  - name: filesystem.read
    description: Read the secrets-manager store (memory/secrets/secrets.json, .master-key)
  - name: environment.read
    description: Read SECRETS_DIR and SECRETS_MASTER_KEY environment variables
  - name: crypto.aes-256-gcm

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding

The skill uses 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

The skill is explicitly designed to access an encrypted secret store and master key, decrypt credentials, and inject them into command material. In this context the behavior is intentional, but it still meaningfully increases credential exposure because it transforms protected-at-rest secrets into plaintext that can be printed or written for later execution, making exfiltration much easier if the skill is misused or invoked on untrusted commands.

Content

Scanner excerpt · secrets-inject.js (reported line 42)May include surrounding context.

js
})();

const DATA_DIR = path.join(WORKSPACE, 'memory', 'secrets');
const SECRETS_FILE = path.join(DATA_DIR, 'secrets.json');
const MASTER_KEY_FILE = path.join(DATA_DIR, '.master-key');
const TMP_INJECTIONS_FILE = path.join(DATA_DIR, '.tmp-injections.json');

Scope Creep

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

This skill decrypts secrets and writes them in plaintext to temporary shell scripts and to a registry file, creating durable secret exposure on disk. Even with 0600 permissions, plaintext secrets in temp files can be recovered by the same user, copied by other tooling, backed up, or left behind if cleanup is missed; the manifest also omits filesystem.write despite performing writes.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 50)May include surrounding context.

md
node skills/secrets-manager/secrets-manager.js --store api_key sk-abc123

# 2) Inject into a command → writes a temp script, prints its path
node skills/secrets-inject/secrets-inject.js --inject "curl -H 'Authorization: Bearer {{api_key}}' https://api.example.com/v1"
# Output: [secrets-inject] ✅ Injected 1 secret(s) into: /tmp/secrets-inject-12345-1.sh
#         [secrets-inject]    Run with:  sh /tmp/secrets-inject-12345-1.sh

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 78)May include surrounding context.

md
- Temp scripts are chmod 0600 but are still **plaintext on disk** — the only
  protection is file permissions and your diligence in deleting them.
- Prefer `secrets-manager --get --raw > /tmp/k && chmod 600 /tmp/k` and pass
  the file path to a command instead of inline injection when possible.
- Never run this on a shared host, container, or CI runner you don't fully
  trust.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 6)May include surrounding context.

md
slug: secrets-inject
name: secrets-inject
owner: jlacroix82
description: "HIGH-PRIVILEGE companion to secrets-manager. Substitutes encrypted secrets into command strings and materializes them as executable shell scripts (chmod 0600 temp files) or prints them to stdout. A secret-exfiltration-capable capability by design — use only to hand secrets to a shell command. Reads the secrets-manager store (memory/secrets). Requires secrets-manager to be used first."
version: 1.0.0
type: skill
author: OpenClaw

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 76)May include surrounding context.

md
slug: secrets-inject
name: secrets-inject
owner: jlacroix82
description: "HIGH-PRIVILEGE companion to secrets-manager. Substitutes encrypted secrets into command strings and materializes them as executable shell scripts (chmod 0600 temp files) or prints them to stdout. A secret-exfiltration-capable capability by design — use only to hand secrets to a shell command. Reads the secrets-manager store (memory/secrets). Requires secrets-manager to be used first."
version: 1.0.0
type: skill
author: OpenClaw

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · clawhub.yaml (reported line 4)May include surrounding context.

yaml
slug: secrets-inject
name: secrets-inject
owner: jlacroix82
description: "HIGH-PRIVILEGE companion to secrets-manager. Substitutes encrypted secrets into command strings and materializes them as executable shell scripts (chmod 0600 temp files) or prints them to stdout. A secret-exfiltration-capable capability by design — use only to hand secrets to a shell command. Reads the secrets-manager store (memory/secrets). Requires secrets-manager to be used first."
version: 1.0.0
type: skill
author: OpenClaw

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · clawhub.yaml (reported line 32)May include surrounding context.

yaml
slug: secrets-inject
name: secrets-inject
owner: jlacroix82
description: "HIGH-PRIVILEGE companion to secrets-manager. Substitutes encrypted secrets into command strings and materializes them as executable shell scripts (chmod 0600 temp files) or prints them to stdout. A secret-exfiltration-capable capability by design — use only to hand secrets to a shell command. Reads the secrets-manager store (memory/secrets). Requires secrets-manager to be used first."
version: 1.0.0
type: skill
author: OpenClaw

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · secrets-inject.js (reported line 17)May include surrounding context.

js
slug: secrets-inject
name: secrets-inject
owner: jlacroix82
description: "HIGH-PRIVILEGE companion to secrets-manager. Substitutes encrypted secrets into command strings and materializes them as executable shell scripts (chmod 0600 temp files) or prints them to stdout. A secret-exfiltration-capable capability by design — use only to hand secrets to a shell command. Reads the secrets-manager store (memory/secrets). Requires secrets-manager to be used first."
version: 1.0.0
type: skill
author: OpenClaw

Lp4

Low
Category
MCP Least Privilege
Confidence
65% confidence
Finding

Declared permissions with no matching code capability may indicate removed functionality or pre-staging for future abuse.

Content

No source excerpt is available for this finding.

Lp4

Low
Category
MCP Least Privilege
Confidence
65% confidence
Finding

Declared permissions with no matching code capability may indicate removed functionality or pre-staging for future abuse.

Content

No source excerpt is available for this finding.