Back to skill

Security audit

MFA WORD

Security checks for vulnerabilities and agentic risk

Overview

This security-gate skill is not malicious, but its advertised protection is weaker than users would reasonably expect and it stores authentication state locally with weak safeguards.

Review before installing. This skill may be useful only as an advisory prompt workflow, not as a real MFA or access-control boundary. Do not rely on it to protect secrets, shell commands, .env files, or deletions unless the surrounding runtime separately enforces those checks. Use strong, unique secret words if testing it, and be aware it writes a local vault and audit log under ~/.openclaw.

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

T09 · Insecure Skill Coding Practices

Error
Location
index.js:23
Finding

Sensitive-resource policy is not enforced by executable code

Content
View full analysis
{ const data = { secret_hash: hash(secret), super_hash: hash(super_secret), sensitive_list: sensitive_list || [".env", "password", "config", "sudo"], dead_mans_switch: use_dead_mans_switch }; if (!fs.existsSync(path.dirname(STORE_PATH))) fs.mkdirSync(path.dirname(STORE_PATH), { recursive: true }); fs.writeFileSync(STORE_PATH, JSON.stringify(data)); logEvent("INITIALIZATION", "SUCCESS"); return `✅ MFA Word active. Mode: ${use_dead_mans_switch ? "Dead Man's Switch" : "15-min Window"}`; }; ``` ```javascript export const check_gate_status = () => { if (!fs.existsSync(STORE_PATH)) return { status: "LOCKED" }; const vault = JSON.parse(fs.readFileSync(STORE_PATH)); if (sessionState.isUnlocked && Date.now() < sessionState.expiry) { if (vault.dead_mans_switch) { sessionState.isUnlocked = false; logEvent("AUTO_LOCK", "DEAD_MAN_SWITCH_TRIGGERED"); return { status: "OPEN_ONCE" }; } return { status: "OPEN" }; } sessionState.isUnlocked = false; return { status: "LOCKED" }; }; ``` ### Technical Analysis The configured `sensitive_list` is persisted but never evaluated by any executable authorization mechanism. `check_gate_status` merely reports process-local state; it does not receive an operation, normalize a target path, evaluate whether a resource is sensitive, or intercept filesystem and command-execution tools. The protection therefore depends exclusively on an agent following the prose in `SKILL.md`. It is not an enforceable security boundary. Any code path, tool, ...[truncated 1083 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
index.js:8
Finding

Primary and recovery secrets are stored using unsalted fast SHA-256 hashes

Content
View full analysis
crypto.createHash('sha256').update(word).digest('hex'); ``` ```javascript export const initialize_mfa = ({ secret, super_secret, sensitive_list, use_dead_mans_switch = false }) => { const data = { secret_hash: hash(secret), super_hash: hash(super_secret), sensitive_list: sensitive_list || [".env", "password", "config", "sudo"], dead_mans_switch: use_dead_mans_switch }; ``` ```javascript export const verify_access = ({ word }) => { if (!fs.existsSync(STORE_PATH)) return "❌ MFA not configured."; const vault = JSON.parse(fs.readFileSync(STORE_PATH)); if (hash(word) === vault.secret_hash) { ``` ```javascript export const reset_mfa = ({ super_word, new_secret }) => { const vault = JSON.parse(fs.readFileSync(STORE_PATH)); if (hash(super_word) === vault.super_hash) { ``` ### Technical Analysis The primary secret and emergency reset secret are processed with raw SHA-256 and stored without unique salts. SHA-256 is designed to be computationally fast and is unsuitable for storing user-selected authentication secrets. A “secret word” is likely to have substantially less entropy than a cryptographically random token. An attacker who obtains `~/.openclaw/mfa_vault.json` can test password candidates offline at high speed. The absence of salts also permits precomputed dictionaries and reveals when two stored values correspond to the same secret. Both the normal authentication secret and the more powerful reset secret are exposed to this weakness. ### Attack Path 1. Obtain read access to `~/.openclaw/mfa_vault.json`, such as through another local process, a backup, or permissive file permissions. 2. Extract `secret_hash` and `super_hash`. 3. Generate candidate ...[truncated 864 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:23
Finding

MFA vault is written without explicit restrictive permissions

Content
View full analysis
{ const data = { secret_hash: hash(secret), super_hash: hash(super_secret), sensitive_list: sensitive_list || [".env", "password", "config", "sudo"], dead_mans_switch: use_dead_mans_switch }; if (!fs.existsSync(path.dirname(STORE_PATH))) fs.mkdirSync(path.dirname(STORE_PATH), { recursive: true }); fs.writeFileSync(STORE_PATH, JSON.stringify(data)); ``` ```javascript export const reset_mfa = ({ super_word, new_secret }) => { const vault = JSON.parse(fs.readFileSync(STORE_PATH)); if (hash(super_word) === vault.super_hash) { vault.secret_hash = hash(new_secret); fs.writeFileSync(STORE_PATH, JSON.stringify(vault)); logEvent("RESET", "SUCCESS"); return "🔄 Secret word successfully reset."; } ``` ### Technical Analysis The vault contains hashes of both authentication secrets, but `fs.writeFileSync` is called without an explicit restrictive file mode. New-file permissions therefore depend on the process umask. Rewriting an existing file also preserves its existing permissions rather than correcting an insecure mode. The implementation additionally does not verify file ownership or reject symbolic links before reading or writing the vault. The confirmed permission weakness can expose the hashes to other local principals when the environment uses permissive defaults or the vault already has broad permissions. ### Attack Path 1. Run initialization under a permissive umask, or arrange for the vault path to refer to an existing file with permissive permissions. 2. Allow the Skill to write the primary and recovery hashes to that file. 3. Read the vault from another lo ...[truncated 603 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:32
Finding

Authentication and recovery endpoints permit unlimited guessing attempts

Content
View full analysis
{ if (!fs.existsSync(STORE_PATH)) return "❌ MFA not configured."; const vault = JSON.parse(fs.readFileSync(STORE_PATH)); if (hash(word) === vault.secret_hash) { sessionState.isUnlocked = true; sessionState.expiry = Date.now() + (15 * 60 * 1000); logEvent("CHALLENGE", "SUCCESS"); return "🔓 Access Granted."; } logEvent("CHALLENGE", "FAILED_ATTEMPT"); return "🚫 Incorrect Secret Word."; }; ``` ```javascript export const reset_mfa = ({ super_word, new_secret }) => { const vault = JSON.parse(fs.readFileSync(STORE_PATH)); if (hash(super_word) === vault.super_hash) { vault.secret_hash = hash(new_secret); fs.writeFileSync(STORE_PATH, JSON.stringify(vault)); logEvent("RESET", "SUCCESS"); return "🔄 Secret word successfully reset."; } logEvent("RESET", "FAILED_CRITICAL"); return "💀 CRITICAL: Super Secret Word incorrect."; }; ``` ### Technical Analysis Neither `verify_access` nor `reset_mfa` limits the number or frequency of failed attempts. Failed attempts are logged, but logging alone does not delay or block further guesses. Because each guess uses a single fast SHA-256 operation, an attacker able to invoke these exported functions can test candidate words rapidly. The recovery endpoint is particularly sensitive because a correct `super_word` immediately replaces the primary secret. ### Attack Path 1. Obtain the ability to invoke the exported `verify_access` or `reset_mfa` function. 2. Build a candidate list of common words, leaked passwords, or likely organization-specific terms. 3. Repeatedly call `verify_access({ word: candidate })` and inspect the distinct success response. 4. Alternatively, r ...[truncated 735 chars]
Remediation
View remediation
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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (9)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

text
# Local security vault - DO NOT SHARE
mfa_vault.json
node_modules/
.env

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared behavior says the skill simply challenges for a secret word before sensitive actions, but the implementation reportedly also stores credential material and configuration on disk, maintains unlock state, supports a reset secret, logs activity, and ignores the provided sensitive_list for actual enforcement. This mismatch is dangerous because users and downstream agents may trust the skill as a narrow MFA gate while it actually introduces secret persistence, broadened attack surface, and ineffective protection logic that can be bypassed or misunderstood.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · index.js (reported line 26)May include surrounding context.

js
const data = {
        secret_hash: hash(secret),
        super_hash: hash(super_secret),
        sensitive_list: sensitive_list || [".env", "password", "config", "sudo"],
        dead_mans_switch: use_dead_mans_switch
    };
    if (!fs.existsSync(path.dirname(STORE_PATH))) fs.mkdirSync(path.dirname(STORE_PATH), { recursive: true });

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill exposes code capabilities involving environment access without declaring a restrictive tool scope such as permissions or allowed-tools. In a security-themed skill that mediates access to sensitive resources, undocumented capability access weakens least-privilege guarantees and can let the implementation read or influence sensitive data outside the stated gatekeeping behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

Security-relevant events are written persistently to an audit log without user disclosure, creating an unexpected local record of authentication attempts and resets. Even if the log does not include secrets, it can reveal operational behavior and may aid an attacker with local access.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill advertises that it protects access to sensitive files or system commands, but the implementation only tracks an in-memory unlocked flag and never actually intercepts or enforce-gates any file reads or command execution. This creates a misleading security boundary: users or downstream agents may believe sensitive operations are protected when in reality nothing prevents direct access.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The additional 'super secret' creates a second long-lived credential and recovery path that is broader than the stated purpose of a simple secret-word challenge. Any extra reset credential increases attack surface and, if disclosed or weakly managed, allows an attacker to rotate the primary secret and take over the gating mechanism.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill persists hashes of the secret and super-secret to disk without any user-facing disclosure, which is risky because users may not expect authentication material to be retained locally. Although the values are hashed, offline guessing remains possible if the file is exposed, especially for weak secrets.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The manifest frames the skill as a challenge gate for access decisions, but the code creates and maintains a local vault file and audit log in ~/.openclaw. Persistent storage may be a possible implementation choice, but the logging and vault management capability is not disclosed by the stated purpose and is not necessary to understand from the manifest alone.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.