Back to skill

Security audit

ArmorClaw

Security checks for vulnerabilities and agentic risk

Overview

ArmorClaw is a real local secrets manager, but its security claims are stronger than the implementation and it can expose stored credentials in ways users may not expect.

Review this carefully before installing. Do not treat it as a high-assurance vault unless the crypto fallback, password verifier, config-file permissions, and per-skill access model are fixed. Avoid bot auto-unlock, plaintext export, and storing ARMORCLAW_PASSWORD in config; if used anyway, install with a real cryptography dependency and keep the vault/config files tightly permissioned.

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

T09 · Insecure Skill Coding Practices

Error
Location
armorclaw/crypto.py:83
Finding

Non-invertible cryptographic fallback can irreversibly corrupt vault secrets

Content
View full analysis
bytes: pad_len = 16 - (len(data) % 16) return data + bytes([pad_len] * pad_len) def _unpad(data: bytes) -> bytes: pad_len = data[-1] return data[:-pad_len] def _xor(a: bytes, b: bytes) -> bytes: return bytes(x ^ y for x, y in zip(a, b)) def _aes_block(key: bytes, block: bytes) -> bytes: """Single AES block using hashlib as a PRF (simplified, for fallback only).""" return hashlib.sha256(key + block).digest()[:16] def _aes_encrypt_stdlib(data: bytes, key: bytes, iv: bytes) -> bytes: padded = _pad(data) blocks = [padded[i:i+16] for i in range(0, len(padded), 16)] ct, prev = [], iv for block in blocks: xored = _xor(block, prev) encrypted = _aes_block(key, xored) ct.append(encrypted) prev = encrypted return b"".join(ct) def _aes_decrypt_stdlib(ct: bytes, key: bytes, iv: bytes) -> bytes: blocks = [ct[i:i+16] for i in range(0, len(ct), 16)] pt, prev = [], iv for block in blocks: decrypted = _aes_block(key, block) pt.append(_xor(decrypted, prev)) prev = block return _unpad(b"".join(pt)) ``` ### Technical Analysis The fallback is described as AES-CBC, but `_aes_block()` is a truncated SHA-256 operation. A cryptographic hash is a one-way function and is not an invertible block cipher. The purported decryption operation hashes each ciphertext block again instead of applying the inverse of the encryption transformation. The `cryptography` package is only an optional dependency. Therefore, a normal i ...[truncated 1296 chars]
Remediation
View remediation
=42.0` a mandatory runtime dependency rather than an optional extra. - Remove `_aes_encrypt_stdlib()`, `_aes_decrypt_stdlib()`, and the SHA-256 block construction. - Fail closed with a clear installation error if an approved cryptographic implementation is unavailable. - Prefer an authenticated-encryption construction such as AES-256-GCM or ChaCha20-Poly1305. - Version the ciphertext format so implementations can distinguish secure formats from any data generated by the defective fallback. - Add tests that force the missing-dependency branch and verify that initialization or writes fail safely. - Provide a detection or migration utility for vault entries that may have been generated by the fallback. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
armorclaw/machine_crypto.py:119
Finding

Machine-bound bot password fallback is non-invertible and can break auto-unlock

Content
View full analysis
bytes: padded = _pad(data) blocks = [padded[i:i+16] for i in range(0, len(padded), 16)] ct, prev = [], iv for block in blocks: xored = bytes(a ^ b for a, b in zip(block, prev)) enc = _aes_block(key, xored) ct.append(enc); prev = enc return b"".join(ct) def _simple_xor_decrypt(ct: bytes, key: bytes, iv: bytes) -> bytes: blocks = [ct[i:i+16] for i in range(0, len(ct), 16)] pt, prev = [], iv for block in blocks: dec = _aes_block(key, block) pt.append(bytes(a ^ b for a, b in zip(dec, prev))) prev = block return _unpad(b"".join(pt)) ``` The block operation used by both functions is: ```python def _aes_block(key: bytes, block: bytes) -> bytes: return hashlib.sha256(key + block).digest()[:16] ``` ### Technical Analysis When `cryptography` is unavailable, machine-bound storage of the master password falls back to a construction that applies a truncated SHA-256 hash as if it were an AES block cipher. SHA-256 has no corresponding inverse operation, so applying the same hash to ciphertext cannot recover the encrypted block. The resulting token is still HMAC-protected and Base64-encoded, which can make it appear structurally valid even though the encrypted password cannot reliably be recovered. This branch directly affects the master password used for unattended bot access. ### Attack Path 1. Install ArmorClaw without `cryptography`. 2. Select or configure bot auto-unlock. 3. The CLI calls `encrypt_for_machine()` for the master password. 4. The ImportError branch invokes `_simple_xor_encrypt()` and stores the resulting token in OpenClaw configuration. 5. Restart the bot or agent. 6. `resolve_pas ...[truncated 511 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
armorclaw/core.py:88
Finding

Initialized empty vault accepts any master password

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
armorclaw/cli.py:162
Finding

Bot master password is written without enforcing secure file permissions

Content
View full analysis
bytes: mac = uuid.getnode() mac_str = ":".join(f"{(mac >> (i*8)) & 0xff:02x}" for i in reversed(range(6))) host = socket.gethostname() raw = f"armorclaw:machine-bind:v1:{mac_str}:{host}" return hashlib.sha256(raw.encode()).digest() ``` ### Technical Analysis The CLI stores the vault master password in `~/.openclaw/openclaw.json` but does not verify the file’s owner or permission mode before reading and rewriting it. It also does not apply mode `0600` after writing. Although the value is encrypted, its key is derived solely from the MAC address, hostname, and a fixed application string. These are machine identifiers rather than user-held secrets. Another local user who can read the configuration and determine the machine identifiers can reproduce the key derivation and decrypt the password. Rewriting the file directly also lacks an atomic replacement strategy. A failure during serialization could damage a configuration file containing other sensitive values. ### Attack Path 1. `~/.openclaw/openclaw.json` has group-readable or world-readable permissions. 2. The user enables bot auto-unlock. 3. ArmorClaw writes the machine-encrypted master password without correcting those permissions. 4. Another loca ...[truncated 728 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
armorclaw/config_mgr.py:117
Finding

Plaintext secret export is created before restrictive permissions are applied

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
armorclaw/openclaw.py:94
Finding

Caller-controlled skill identity permits audit-log spoofing

Content
View full analysis
str | None: """ Get a single key from the vault without injecting into environment. Useful for one-off lookups. """ ck = ArmorClaw() if not ck.is_setup: return None raw_pwd = password or os.getenv("ARMORCLAW_PASSWORD") if not raw_pwd: return None try: from .machine_crypto import resolve_password pwd = resolve_password(raw_pwd) except Exception: pwd = raw_pwd result = ck.unlock(pwd) if not result["ok"]: return None val = ck.get(name, skill=skill) ck.lock() return val ``` The supplied identity is persisted without verification: ```python def _log_access(conn, name: str, skill: str, action: str): conn.execute( "INSERT INTO access_log(secret_name, skill, accessed_at, ip, action) VALUES(?,?,?,?,?)", (name, skill or "", datetime.utcnow().isoformat(), get_current_ip(), action), ) ``` ### Technical Analysis The `skill` argument is caller-controlled metadata. It is neither authenticated nor derived from a trusted execution context. Any code that can access the bot auto-unlock password can request any vault key and provide the name of another skill. The implementation also has no per-skill access-control policy. The skill name affects only the audit record and does not restrict which credentials the caller can retrieve. Consequently, the advertised per-skill access log cannot reliably establish which skill performed an operation. ### Attack Path 1. A malicious or compromised skill runs in a process that has access to `ARMORCLAW_PASSWORD`. 2. It calls `get_vault_key("TARGET_SECRET", skill="trusted-skill")`. 3. ...[truncated 613 chars]
Remediation
View remediation
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (76)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

A secrets manager that claims vault storage, API key management, .env migration, cross-skill sharing, and access logging but lacks those features is dangerously misleading, especially when combined with non-equivalent machine binding and weak fallback crypto. Users may centralize sensitive credentials under the assumption of strong controls that do not actually exist, amplifying compromise impact across multiple skills or services.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

A secrets manager that claims vault storage, API key management, .env migration, cross-skill sharing, and access logging but lacks those features is dangerously misleading, especially when combined with non-equivalent machine binding and weak fallback crypto. Users may centralize sensitive credentials under the assumption of strong controls that do not actually exist, amplifying compromise impact across multiple skills or services.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

A secrets manager that claims vault storage, API key management, .env migration, cross-skill sharing, and access logging but lacks those features is dangerously misleading, especially when combined with non-equivalent machine binding and weak fallback crypto. Users may centralize sensitive credentials under the assumption of strong controls that do not actually exist, amplifying compromise impact across multiple skills or services.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

A secrets manager that claims vault storage, API key management, .env migration, cross-skill sharing, and access logging but lacks those features is dangerously misleading, especially when combined with non-equivalent machine binding and weak fallback crypto. Users may centralize sensitive credentials under the assumption of strong controls that do not actually exist, amplifying compromise impact across multiple skills or services.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

A secrets manager that claims vault storage, API key management, .env migration, cross-skill sharing, and access logging but lacks those features is dangerously misleading, especially when combined with non-equivalent machine binding and weak fallback crypto. Users may centralize sensitive credentials under the assumption of strong controls that do not actually exist, amplifying compromise impact across multiple skills or services.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

If machine-encrypted storage fails, the fallback guidance tells the user to manually set ARMORCLAW_PASSWORD to the raw master password in OpenClaw config. That places the vault's master secret in effectively plaintext configuration storage, so compromise of the config file or any component that can read it yields full vault access.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
92% confidence
Finding

This export command is an intentional credential-access feature, but in security terms it is dangerous because it converts protected vault contents into plaintext .env material on disk. In the context of a secure secrets manager, that capability materially increases exposure to local compromise, accidental source control inclusion, and leakage via backups or tooling.

Content

Scanner excerpt · armorclaw/cli.py (reported line 457)May include surrounding context.

python
def cmd_export(output_path: str | None = None):
    """Export vault secrets back to a .env file. Requires master password."""
    ck = _get_ck()
    if not ck.is_setup:
        print("❌ Run: armorclaw init"); return

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The warning text confirms the command writes secrets into a plaintext .env file, which creates a known and significant exposure channel. Even though the user is warned, the feature still undermines secure-secret handling by persisting sensitive data in a broadly readable and easily mishandled format.

Content

Scanner excerpt · armorclaw/cli.py (reported line 463)May include surrounding context.

python
print("❌ Run: armorclaw init"); return

    print("\n⚠️  EXPORT WARNING")
    print("   This writes your secrets to a plain text .env file.")
    print("   Make sure you store it securely and delete when done.")
    print()
    confirm = input("Continue? [yes/N]: ").strip().lower()

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

Advertising a direct 'export secrets to .env' command normalizes an insecure workflow that moves credentials out of the vault and back into plaintext storage. Within a secrets manager's threat model, that is a real security weakness because it encourages risky handling of sensitive data.

Content

Scanner excerpt · armorclaw/cli.py (reported line 498)May include surrounding context.

python
"get":     (cmd_get,    "Get a secret:   armorclaw get KEY_NAME"),
    "list":    (cmd_list,   "List all secrets"),
    "delete":  (cmd_delete, "Delete a secret: armorclaw delete KEY_NAME"),
    "import":  (cmd_import, "Import .env file: armorclaw import [path]"),
    "export":  (cmd_export, "Export secrets to .env: armorclaw export [path]"),
    "config":  (cmd_config, "Change lock mode, IP, or machine binding"),
    "log":     (cmd_log,    "View access log: armorclaw log [KEY_NAME]"),

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The file advertises AES-256/AES-CBC encryption, but the stdlib fallback is a custom SHA-256/XOR construction and not AES at all. This creates a dangerous mismatch between security claims and actual behavior, especially in a secrets manager where users may rely on strong, standard cryptography while receiving an unreviewed homegrown cipher instead.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill metadata and module comments promise an AES-256 encrypted secrets manager, but when cryptography is unavailable the implementation does not perform AES encryption. In a credential vault context, this is particularly dangerous because operators may store API keys and tokens under false assumptions about confidentiality and compliance strength.

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 · SKILL.md (reported line 5)May include surrounding context.

md
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · SKILL.md (reported line 6)May include surrounding context.

md
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · SKILL.md (reported line 8)May include surrounding context.

md
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · SKILL.md (reported line 41)May include surrounding context.

md
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · SKILL.md (reported line 80)May include surrounding context.

md
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 39)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 187)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 189)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 194)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 220)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 239)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 309)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 312)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime

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 · armorclaw/cli.py (reported line 350)May include surrounding context.

python
"""ArmorClaw — .env file importer."""
import os, shutil
from pathlib import Path
from datetime import datetime