Back to skill

Security audit

Artifact Signing

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says at a high level, but it handles signing keys in ways users should review carefully before trusting it for real artifact signing.

Install only if you understand that this is raw private-key signing, not certificate-chain signing. Do not use the provided plaintext key-generation example for production keys; use encrypted keys or a secure key store, restrict file permissions, and verify exactly which artifact and output path the agent will 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
verify_skill.py:11
Finding
Test workflow writes an unencrypted private key and may leave it on disk after failure<![CDATA[ ## Vulnerability Details **File Location**: `verify_skill.py`, lines 11-24, 38-41, and 54-64 **Vulnerability Type**: Plaintext sensitive data and incomplete cleanup **Risk Level**: Medium ### Vulnerable Code ```python private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) private_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() ) public_pem = private_key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) with open("test_private.pem", "wb") as f: f.write(private_pem) with open("test_public.pem", "wb") as f: f.write(public_pem) ``` The signing subprocess failure path returns before reaching the later cleanup block: ```python if result.returncode != 0: print(f"Error: {result.stderr}") return False ``` Cleanup is scoped only to the signature-verification block: ```python try: public_key = serialization.load_pem_public_key(public_pem) public_key.verify( signature, artifact_content, padding.PKCS1v15(), hashes.SHA256() ) print("VERIFICATION SUCCESSFUL: Signature is valid!") return True except Exception as e: print(f"VERIFICATION FAILED: {e}") return False finally: # Cleanup for f in ["test_private.pem", "test_public.pem", "test_artifact.txt", "test_artifact.txt.sig"]: if os.path.exists(f): os.remove(f) ``` ### Technical Analysis The test serializes private-key material using `NoEncryption()` and writes it into the current working directory through a normal `open()` operation. The resulting permissions depend on the process umask and surrounding filesystem configuration; the code does not explicitly ensure owner-only access. The cleanup `finally` block begins only after the signing subprocess succeeds and the generated signature has b ...[truncated 1640 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Perform the entire test lifecycle inside an outer `try/finally` block so cleanup runs for subprocess failures, missing signature files, keyboard interrupts, and verification errors. - Use `tempfile.TemporaryDirectory()` to isolate all generated files in a uniquely named private temporary directory. - Create private-key files with explicit owner-only permissions such as `0o600`; on POSIX systems, use `os.open()` with `O_CREAT | O_EXCL` and the desired mode to avoid a permission window. - Avoid writing the private key when possible. Refactor the signing implementation to accept an in-memory key for tests. - If a key must persist beyond a tightly controlled test, serialize it with `BestAvailableEncryption()` and obtain the passphrase from an appropriate secret source. - Resolve `sign_artifact.py` relative to `__file__` rather than the caller's working directory, reducing avoidable test failures. - Ensure test failures produce a nonzero process exit status after cleanup. A safer structure is: ```python import os import tempfile def test_signing_flow(): with tempfile.TemporaryDirectory() as temp_dir: key_path = os.path.join(temp_dir, "test_private.pem") artifact_path = os.path.join(temp_dir, "test_artifact.txt") fd = os.open(key_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) with os.fdopen(fd, "wb") as key_file: key_file.write(private_pem) # Run signing and verification while all test files remain isolated. # TemporaryDirectory removes the files when the block exits. ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
examples/usage.md:19
Finding
Usage documentation encourages creation of an unencrypted private-key file without explicit access controls<![CDATA[ ## Vulnerability Details **File Location**: `examples/usage.md`, lines 19-24 **Vulnerability Type**: Insecure private-key storage guidance **Risk Level**: Medium ### Vulnerable Code ```python # Save private key to PEM with open("private_key.pem", "wb") as f: f.write(private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() )) ``` ### Technical Analysis The documented example serializes the private key without encryption and creates `private_key.pem` using permissions inherited from the operating system and current umask. The example does not establish owner-only access, warn that the code is limited to disposable test keys, or provide an encrypted production alternative. Users commonly copy usage examples directly. Consequently, this guidance can result in reusable signing keys being stored in plaintext in shared working directories, source trees, backups, synchronization services, or other locations readable by unintended users or processes. ### Attack Path 1. A user follows the documented key-generation example in a shared or insufficiently protected directory. 2. The example writes the complete private key to `private_key.pem` without encryption. 3. Filesystem permissions, backups, synchronization, accidental source-control inclusion, or another local process expose the PEM file. 4. An attacker copies the private key. 5. The attacker uses the key to create signatures that verify under the corresponding public key. 6. Systems that trust that public key may accept attacker-controlled artifacts as originating from the legitimate signer. ### Impact Assessment Successful disclosure grants possession of the signing private key itself. An attacker can generate valid signatures for arbitrary artifacts under the associated identity until the key is revoked or removed from trust stores. The precise scope depen ...[truncated 226 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace `NoEncryption()` with `serialization.BestAvailableEncryption(passphrase)` for reusable keys. - Obtain the passphrase from an interactive prompt or an approved secret-management facility rather than hardcoding it. - Create private-key files with explicit owner-only permissions and avoid placing them in project or shared directories. - Clearly label any unencrypted variant as suitable only for disposable test keys in an isolated environment. - Add guidance covering secret scanning, source-control exclusion, secure backups, key rotation, and revocation following suspected disclosure. - For hardware-backed or production signing, recommend keeping the private key in an HSM, TPM, operating-system keystore, or dedicated signing service. For example: ```python import os from cryptography.hazmat.primitives import serialization pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(passphrase), ) fd = os.open( "private_key.pem", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600, ) with os.fdopen(fd, "wb") as key_file: key_file.write(pem) ``` ]]>
Vulnerability Patterns
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • 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
Findings (4)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill claims signing with a digital certificate and private key, but the documented behavior only references a PEM private key and detached signature generation. This mismatch can mislead users or downstream agents into assuming certificate-backed trust properties, causing incorrect security decisions about artifact provenance and verification.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill exposes shell execution behavior via an example command and implied script invocation, but it does not declare any tool scope restrictions such as permissions or allowed-tools. In an agent environment, missing scope declarations can allow broader-than-expected command execution or make review and policy enforcement harder, increasing the chance of misuse of file paths and key material.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The example generates a private key and writes it to disk using NoEncryption(), which stores the key unprotected at rest. Even though this is presented as a testing example, readers may reuse it in real workflows, and compromise of the PEM file would allow unauthorized signing of artifacts and undermine trust in distributed binaries.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 3. Use the sign_artifact.py script
    print("Running sign_artifact.py...")
    script_path = os.path.join("scripts", "sign_artifact.py")
    result = subprocess.run(
        ["python", script_path, "test_artifact.txt", "test_private.pem"],
        capture_output=True, text=True
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.