Back to skill

Security audit

Agent Identity

Security checks for vulnerabilities and agentic risk

Overview

This identity skill does what it says, but it needs review because it can create long-lived private keys insecurely and can write key files outside the intended folder.

Review before installing in any environment where agent keys matter. Use only in an isolated, non-privileged environment, provide a password for generated keys, restrict key-directory permissions yourself, avoid command-line passwords for real secrets, and use simple safe agent names without slashes or parent-directory components.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
identity.py:31
Finding
Private keys may be stored unencrypted with inherited filesystem permissions<![CDATA[ ## Vulnerability Details **File Location**: `identity.py:31-55` and `identity.py:86-110` **Vulnerability Type**: Insecure private-key storage **Risk Level**: High ### Vulnerable Code ```python if password: # Encrypt private key private_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(password.encode()) ) else: # No encryption (for convenience, but less secure) private_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) public_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) private_path = os.path.join(KEY_DIR, f"{name}_private.pem") public_path = os.path.join(KEY_DIR, f"{name}_public.pem") with open(private_path, "wb") as f: f.write(private_pem) with open(public_path, "wb") as f: f.write(public_pem) ``` The same storage pattern is used for RSA keys at lines 86-110. ### Technical Analysis When `--password` is omitted, the private key is serialized using `serialization.NoEncryption()`. The resulting plaintext PEM is then created with Python's normal `open()` function without explicitly enforcing owner-only permissions. The final permissions therefore depend on the process umask and surrounding environment. Under a permissive umask, another local account or process may be able to read the private key. Existing destination files are also opened with truncation rather than exclusive creation. Although unencrypted key generation is documented as less secure, protecting a signing identity requires secure defaults. Plaintext storage and implicit permissions do not provide adequate protection for sensitive private-key material. ### Attack Path 1. A user invo ...[truncated 930 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Encrypt private keys by default and require an explicit unsafe override for plaintext storage. - Prompt for passwords securely with `getpass.getpass()` instead of encouraging command-line password arguments. - Create private-key files atomically with owner-only mode `0600`, such as by using `os.open()` with `O_CREAT | O_EXCL` and an explicit mode. - Set restrictive permissions on the `keys` directory, such as `0700`. - Refuse to overwrite an existing private-key file unless the user provides an explicit, carefully validated overwrite option. - Verify the resulting file permissions after creation and abort if adequate protection cannot be established. - Consider integrating an operating-system credential store, hardware-backed key store, or dedicated secrets manager for production use. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
identity.py:47
Finding
Unsanitized agent names permit writes outside the intended key directory<![CDATA[ ## Vulnerability Details **File Location**: `identity.py:47-55` and `identity.py:102-110` **Vulnerability Type**: Path traversal and arbitrary file creation **Risk Level**: Medium ### Vulnerable Code ```python private_path = os.path.join(KEY_DIR, f"{name}_private.pem") public_path = os.path.join(KEY_DIR, f"{name}_public.pem") with open(private_path, "wb") as f: f.write(private_pem) with open(public_path, "wb") as f: f.write(public_pem) ``` The agent name originates directly from the command line: ```python gen_parser.add_argument("--name", required=True, help="Agent name") ``` The same vulnerable path construction is present in both Ed25519 and RSA key generation. ### Technical Analysis The user-controlled `name` value is incorporated into filesystem paths without validation, canonicalization, or a containment check. Path separators and parent-directory components such as `../` are therefore interpreted by the operating system. For example, a name containing `../../target` causes the generated paths to escape the intended `keys/` directory. The fixed `_private.pem` and `_public.pem` suffixes limit the exact filenames that can be targeted, but an attacker can still create or truncate matching files anywhere writable by the invoking process. The operation uses `"wb"`, so an existing destination is truncated before the newly generated PEM data is written. ### Attack Path 1. An attacker controls or influences the `--name` argument passed to the generate command. 2. The attacker supplies a traversal value, such as `../../some/writable/path/target`. 3. `os.path.join()` constructs `keys/../../some/writable/path/target_private.pem`. 4. The operating system resolves the parent-directory components outside `keys/`. 5. The program creates or overwrites `target_private.pem` and `target_public.pem` with generated PEM content. 6. If a targeted suffixed file is operationally significant, the overwrite may disrupt the affected application or repla ...[truncated 559 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Restrict agent names to a conservative allowlist, for example `^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$`. - Reject names containing path separators, parent-directory components, absolute paths, null bytes, or platform-specific reserved names. - Resolve the key directory and destination using `pathlib.Path.resolve()`, then verify that every destination remains beneath the resolved key directory. - Create files with exclusive semantics to prevent unintended overwrites. - Keep display names separate from filesystem-safe identifiers rather than using an arbitrary display name as a path component. - Apply the same validation consistently to both Ed25519 and RSA generation. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:5
Finding
Cryptography dependency is installed without version or integrity pinning<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:5` and `SKILL.md:27-31` **Vulnerability Type**: Unpinned third-party dependency **Risk Level**: Low ### Vulnerable Configuration ```yaml metadata: {"openclaw": {"emoji": "🆔", "category": "security", "requires": {"bins": ["python"], "pip": ["cryptography"]}, "homepage": "https://github.com"}} ``` ```powershell # Install Python dependency pip install cryptography ``` ### Technical Analysis The Skill requires the legitimate `cryptography` package, and the audited code imports APIs consistent with that package. However, neither the metadata nor the installation instructions pin a reviewed version or package hash. As a result, installation resolves whatever compatible release the configured package index serves at that time. This makes installations non-reproducible and exposes users to future upstream compromise, unsafe releases, index misconfiguration, or unexpected compatibility changes. No evidence of typosquatting, dependency confusion, a malicious package source, or an existing compromised release was found. The risk arises from insufficient supply-chain controls rather than demonstrated malicious behavior. ### Attack Path 1. A user or automated Skill installer processes the unpinned `cryptography` dependency. 2. The package manager queries its configured index and selects the currently available release. 3. If that release or distribution channel has been compromised, malicious installation or runtime content enters the environment. 4. The compromised dependency executes with the permissions of the installation or Skill process. 5. It may access private keys, messages, signatures, or other resources available to that process. ### Impact Assessment The potential impact is bounded by the privileges of the package installation and runtime process. In a compromise scenario, dependency code could execute arbitrary code and access generated private keys. The likelihood is assessed as low be ...[truncated 127 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `cryptography` to a reviewed version or narrowly controlled version range. - Use a lockfile or requirements file with cryptographic hashes, such as `pip --require-hashes`. - Retrieve packages only from a trusted, explicitly configured package index. - Install dependencies in an isolated virtual environment under a non-privileged account. - Use automated dependency monitoring to identify security advisories and update pinned versions after review. - Record supported Python and dependency versions to make installation reproducible. ]]>
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 (3)

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The manifest description limits the skill's purpose to signing and verifying agent messages, but the code also creates new private/public key material on disk and constructs signed agent identity cards with metadata like capabilities and endpoints. Key management and agent-card issuance are broader identity-management functions than the narrower manifest description suggests.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The Ed25519 private key may be serialized with NoEncryption() when no password is supplied, causing sensitive signing material to be stored on disk in plaintext. In an agent environment, compromise of the host, workspace, backups, or logs exposing the file path can lead to key theft and full impersonation of the agent.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The RSA private key may likewise be written using NoEncryption() when no password is provided, leaving long-lived private key material exposed at rest. If an attacker obtains the file, they can forge signatures, impersonate the agent, and undermine any trust model built on this identity mechanism.

Static analysis

No suspicious patterns detected.