Back to skill

Security audit

Aip Identity

Security checks for vulnerabilities and agentic risk

Overview

This identity skill mostly matches its stated purpose, but some security-sensitive features can expose content or credentials in ways users would not reasonably expect.

Review this skill carefully before installing. Use it only if you are comfortable with the AIP service receiving identity metadata and signed content, avoid signing sensitive files, avoid sending sensitive replies until the reply path is encrypted, protect aip_credentials.json with strict local permissions, and pin/audit dependencies before using it in a security-sensitive workspace.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/aip.py:237
Finding
Signing Command Uploads Complete User Content to a Remote Service<![CDATA[ ## Vulnerability Details **File Location**: `scripts/aip.py:237-251` **Vulnerability Type**: Unnecessary remote disclosure of user-selected content **Risk Level**: High ### Vulnerable Code ```python def cmd_sign(args): creds = load_creds(args.credentials) content = args.content if args.file: with open(args.file, "rb") as f: content = f.read().decode(errors="replace") if not content: print("--content or --file required", file=sys.stderr) sys.exit(1) content_hash = hashlib.sha256(content.encode()).hexdigest() ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") msg = f"{creds['did']}|sha256:{content_hash}|{ts}" sig = sign_message(msg.encode(), creds["private_key"]) result = api("POST", "/skill/sign", { "author_did": creds["did"], "skill_content": content, "signature": sig, }) ``` ### Technical Analysis The command reads the complete contents of a user-selected file and submits those contents as `skill_content` to the externally hosted AIP service. Cryptographic authorship can ordinarily be established by signing and registering a content hash; transmitting the original content is not inherently required for signing. The local signature is calculated over a SHA-256 hash, but the remote request nevertheless contains the complete plaintext. This exceeds the minimum data access and disclosure needed for the declared signing operation. The skill documentation does not prominently warn that signing a file uploads its entire contents. The `AIP_SERVICE_URL` environment variable can also redirect this disclosure to another server selected by the execution environment. ### Attack Path 1. A user invokes `python3 scripts/aip.py sign --file <path>`. 2. The script reads the entire selected file into memory. 3. The script calculates a hash and signature locally. 4. It serializes the original content into the `skill_content` JSON field. 5. The complete ...[truncated 631 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Perform content hashing and signing entirely locally. - Submit only the author DID, hash algorithm, content hash, timestamp, and signature. - If server-side access to content is an unavoidable product requirement, require explicit confirmation before uploading and display the destination host. - Add a non-uploading mode and make it the default. - Reject files likely to contain credentials unless the user explicitly overrides the warning. - Document the precise remote data flow and retention policy. - Add tests asserting that file contents do not appear in outbound signing requests. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/aip.py:443
Finding
Reply Command Sends Message Content Without End-to-End Encryption<![CDATA[ ## Vulnerability Details **File Location**: `scripts/aip.py:443-449` **Vulnerability Type**: Plaintext transmission of message content **Risk Level**: High ### Vulnerable Code ```python recipient_did = original.get("sender_did") content = f"[Re: {args.message_id[:8]}] {args.content}" send_sig = sign_message(f"{creds['did']}|{recipient_did}|{content}".encode(), creds["private_key"]) result = api("POST", "/messages/send", { "sender_did": creds["did"], "recipient_did": recipient_did, "content": content, "signature": send_sig, }) if result: print(f"✅ Reply sent to {recipient_did}") ``` ### Technical Analysis The normal `message` command encrypts message text before transmission. In contrast, `reply` places the reply directly into a plaintext `content` field and sends it to `/messages/send`. A digital signature provides integrity and sender authentication, but it does not provide confidentiality. Consequently, the AIP service can read reply content. This behavior contradicts the documented claim that agent-to-agent messages are end-to-end encrypted and that the server sees only ciphertext. The reply endpoint and plaintext payload are also inconsistent with the encrypted `/message` endpoint documented in `references/api.md`. ### Attack Path 1. A user receives an encrypted message. 2. The user invokes `reply <message_id> "<sensitive response>"`. 3. The command retrieves the original message to determine its sender. 4. It constructs a plaintext reply in `content`. 5. It signs the plaintext but does not encrypt it. 6. The plaintext is transmitted to the remote service, where it can be read or logged. ### Impact Assessment The AIP service, service operators, server-side logs, and any party obtaining access to those systems may read reply contents. Sensitive agent communications may therefore be exposed despite the user's reasonable expectation of end-to-end encryption. The issue compromises message confidentiality. It does not itself grant ...[truncated 135 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove or disable the `reply` command until it uses authenticated end-to-end encryption. - Resolve the recipient's public key and reuse the same encrypted message implementation as `cmd_message`. - Send only `encrypted_content`, authenticated metadata, a timestamp, and a signature. - Use a single documented endpoint and payload format for both new messages and replies. - Bind the reply reference into authenticated metadata rather than adding it only to plaintext. - Add integration tests proving that reply text never appears in outbound HTTP payloads. - Update the documentation if any messaging mode intentionally lacks encryption. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/aip.py:177
Finding
Private Keys Are Written to Plaintext Files Without Enforced Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/aip.py:177-178` and `scripts/aip.py:369-370` **Vulnerability Type**: Insecure private-key storage **Risk Level**: Medium ### Vulnerable Code Registration writes the credentials as follows: ```python with open(out, "w") as f: json.dump(creds, f, indent=2) ``` The stored object contains the private key: ```python creds = { "did": did, "public_key": pub_b64, "private_key": priv_b64, "platform": args.platform, "username": args.username, "registered_at": datetime.now(timezone.utc).isoformat(), } ``` Key rotation repeats the unsafe write: ```python out = args.credentials or DEFAULT_CREDS with open(out, "w") as f: json.dump(creds, f, indent=2) ``` ### Technical Analysis The credentials file contains an unencrypted Ed25519 private key. It is created through ordinary `open(..., "w")`, so its final permissions depend on the process umask and any permissions on an existing file. The code neither creates the file with mode `0600` nor verifies that existing credentials are accessible only to the owner. The code also does not use an atomic, no-follow write strategy. A pre-existing symbolic link or unsafe target location can therefore redirect credential output to another file accessible to an attacker. Base64 encoding is only serialization and provides no confidentiality. The flagged expression that Base64-encodes `sk.verify_key` handles a public key and is not itself secret exfiltration. The material security concern is the plaintext storage of `private_key`. ### Attack Path 1. A user registers an identity or rotates its key. 2. The generated private key is inserted into the credentials JSON object. 3. The script writes the object using process-default file permissions. 4. Under a permissive umask, unsafe shared directory, or pre-existing permissive file mode, another local principal reads the credentials file. 5. The attacker extracts the Base64-encoded private key. 6 ...[truncated 552 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create new credential files atomically with owner-only mode `0600`, for example by using `os.open` with `O_CREAT | O_EXCL` and mode `0o600`. - Apply and verify owner-only permissions after updating an existing file. - Reject symbolic links and validate that the destination is a regular file owned by the current user. - Write to a securely created temporary file in the same directory, flush and synchronize it, then atomically replace the destination. - Prefer an operating-system keyring, hardware-backed keystore, or encrypted credential store for private keys. - Warn and refuse to continue when an existing credentials file has unsafe ownership or permissions. - Avoid storing credentials in shared working directories by default. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:31
Finding
Installation Guidance Uses Unpinned Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:31` and `scripts/aip.py:301` **Vulnerability Type**: Unpinned dependency installation **Risk Level**: Medium ### Vulnerable Code and Instructions The skill documentation recommends an unpinned package: ```text pip install aip-identity ``` The runtime error guidance also recommends an unpinned dependency: ```python except ImportError: print("❌ nacl library required for encryption. Install: pip install pynacl", file=sys.stderr) sys.exit(1) ``` ### Technical Analysis Both installation commands resolve the latest package available from the user's configured Python package index. No exact version, lock file, package hash, or verified artifact is supplied. The separately installed `aip-identity` CLI may differ from the bundled and reviewed `scripts/aip.py`. Future package changes, package-index compromise, dependency confusion through a malicious index, or compromise of a transitive dependency could introduce unreviewed code. Python packages can execute code during installation or when imported, so dependency compromise can lead directly to arbitrary code execution under the installing user's privileges. ### Attack Path 1. A user follows the documented `pip install` instruction. 2. `pip` queries the configured package index and selects a mutable latest release. 3. A compromised, replaced, or unexpectedly changed package or dependency is downloaded. 4. Package installation hooks or imported runtime code execute. 5. Malicious code gains the same filesystem, network, and process privileges as the user running `pip` or the CLI. ### Impact Assessment A compromised dependency could read credentials, alter signed content, intercept private keys, transmit local data, or execute arbitrary commands. The available privilege level is that of the user or environment performing installation and running the package. The project does not contain evidence that the currently named packages are malicio ...[truncated 121 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every dependency to a reviewed exact version. - Publish a lock file or requirements file containing cryptographic hashes. - Use `pip install --require-hashes -r requirements.txt` for reproducible installation. - Document the expected package index and warn against untrusted extra indexes. - Audit and pin transitive dependencies. - Clearly distinguish the bundled script from the separately distributed PyPI CLI. - Provide signed releases and verify package provenance in the release process. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/aip.py:44
Finding
Messaging Uses Incompatible and Incorrect Public-Key Encryption Constructions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/aip.py:44-49` and `scripts/aip.py:340-345` **Vulnerability Type**: Cryptographic key-type and protocol mismatch **Risk Level**: Medium ### Vulnerable Code The sender treats raw Ed25519 key material as Curve25519 key material and uses `Box`: ```python def _encrypt_nacl(plaintext: bytes, recipient_pub_b64: str, sender_priv_b64: str) -> str: import nacl.public sender_sk = nacl.public.PrivateKey(base64.b64decode(sender_priv_b64)[:32]) recipient_pk = nacl.public.PublicKey(base64.b64decode(recipient_pub_b64)) box = nacl.public.Box(sender_sk, recipient_pk) encrypted = box.encrypt(plaintext) return base64.b64encode(encrypted).decode() ``` The receiver converts the Ed25519 signing key and attempts to decrypt using `SealedBox`: ```python priv_bytes = base64.b64decode(creds["private_key"]) signing_key = nacl.signing.SigningKey(priv_bytes) curve_priv = signing_key.to_curve25519_private_key() sealed_box = nacl.public.SealedBox(curve_priv) plaintext = sealed_box.decrypt(base64.b64decode(content)) print(f" Content: {plaintext.decode()}") ``` ### Technical Analysis Ed25519 signing keys and Curve25519 encryption keys are not interchangeable by simply reinterpreting their raw bytes. PyNaCl provides explicit conversion methods because the correct Curve25519 key is derived through a defined conversion process. The sender constructs `nacl.public.PrivateKey` and `PublicKey` directly from raw Ed25519 bytes, while the receiver correctly converts its signing key with `to_curve25519_private_key()`. The resulting keypairs will not generally correspond. There is also a construction mismatch: encryption uses authenticated `Box`, while decryption uses `SealedBox`. Ciphertexts produced by `Box.encrypt()` are not `SealedBox` ciphertexts and cannot be decrypted through `SealedBox.decrypt()`. This can cause systematic decryption failure and undermines the skill's stated secure-messaging functionalit ...[truncated 1063 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Select one supported encryption construction and use it consistently on both sides. - For authenticated sender-recipient encryption, convert both Ed25519 keys with PyNaCl's explicit Ed25519-to-Curve25519 conversion APIs and use `Box` for encryption and decryption. - Alternatively, use `SealedBox` for both encryption and decryption if anonymous-sender encryption is intended. - Do not construct Curve25519 key objects directly from raw Ed25519 bytes. - Define and version the ciphertext envelope, including algorithm, nonce, sender key identifier, and encoding. - Add sender-to-recipient round-trip tests, rotated-key tests, malformed-ciphertext tests, and tamper-detection tests. - Fail closed rather than suggesting or automatically using a plaintext messaging path when encryption fails. ]]>
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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (20)

Tainted flow: 'req' from os.environ.get (line 106, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = urllib.request.Request(url, data=body, method=method,
                                headers={"Content-Type": "application/json"} if body else {})
    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err = e.read().decode()
Confidence
92% confidence
Finding
The base service URL is taken directly from the AIP_SERVICE_URL environment variable and then used for all network requests, including registration, challenge-response authentication, key rotation, and message operations. In an agent environment, a hostile parent process or workspace configuration could redirect the client to an attacker-controlled service, causing exfiltration of DIDs, usernames, signed challenges, encrypted messages, and even private keys in deprecated registration mode.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The documented purpose emphasizes secure identity, signing, and encrypted messaging, but the referenced behavior includes undeclared administrative-style registry access and a reply path that may send plaintext message content. That mismatch is dangerous because users and orchestrators may trust the skill for privacy-sensitive workflows while hidden or under-disclosed behaviors expose metadata, registration data, or message content beyond expected boundaries.

Credential Access

High
Category
Privilege Escalation
Content
## Credentials

Stored as JSON in `aip_credentials.json`: `{ "did", "public_key", "private_key", "platform", "username" }`.
**Never share `private_key`.** DID and public_key are safe to share.

Set `AIP_CREDENTIALS_PATH` env var to use a custom credential file location instead of the default search path.
Confidence
89% confidence
Finding
The skill explicitly stores a private key in a local JSON credential file and supports path selection via an environment variable, which creates a high-value target for theft or misuse if filesystem permissions, logging, backups, or path handling are weak. In the context of an identity skill, compromise of this file enables impersonation, fraudulent signatures, message decryption, and unauthorized trust actions.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### PATCH /message/\<id\>/read
Mark message as read (requires signature of message_id).

### DELETE /message/\<id\>
Signs: `{message_id}`

---
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Credential Access

High
Category
Privilege Escalation
Content
from datetime import datetime, timezone

AIP_BASE = os.environ.get("AIP_SERVICE_URL", "https://aip-service.fly.dev")
DEFAULT_CREDS = "aip_credentials.json"

def _find_creds_file(filename=DEFAULT_CREDS):
    """Search for credentials in multiple standard locations."""
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Intent-Code Divergence

High
Confidence
97% confidence
Finding
The reply command presents itself as replying to a received message, but instead of reusing the encrypted messaging flow it sends plaintext content to a different endpoint. Users may reasonably assume replies are protected like messages, so this mismatch can leak sensitive content to the service or intermediaries and undermines the security guarantees implied by the skill.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises commands that require shell execution, network access, environment-variable use, and local file writes, but it does not declare any tool scope or permission boundaries. This creates a transparency and governance gap: an agent may invoke a capability-rich skill without explicit authorization controls, increasing the chance of unintended network calls, credential handling, or filesystem modification.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The description says to use the skill whenever an agent needs identity verification, authentication, trust scoring, secure messaging, or reputation management, followed by a very broad coverage list. This is invocation guidance in a manifest context, and it lacks explicit boundaries or negative examples to distinguish when this skill should not be selected, increasing the chance of unintended activation.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The reference documents destructive and sensitive operations such as message deletion, key rotation, revocation, and admin registration access without warning users that these actions are security-sensitive and potentially irreversible. In a skill meant for autonomous agents handling identity and trust, omission of guardrails increases the chance that an agent or operator invokes high-impact endpoints unsafely or with incomplete authorization assumptions.

Context-Inappropriate Capability

Medium
Confidence
83% confidence
Finding
The API reference exposes admin-only registration listing and detail endpoints in general documentation without any stated authorization requirements or justification tied to the skill’s end-user identity/trust workflow. In an agent ecosystem, documenting enumeration-capable admin surfaces can encourage misuse, probing, and bulk collection of identity metadata even if the backend is intended to enforce auth.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
with tempfile.NamedTemporaryFile(suffix=".pem", delete=False) as f:
        kf = f.name
    try:
        subprocess.run(["openssl", "genpkey", "-algorithm", "Ed25519", "-out", kf],
                       check=True, capture_output=True)
        raw = subprocess.run(["openssl", "pkey", "-in", kf, "-outform", "DER"],
                             check=True, capture_output=True).stdout
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
        subprocess.run(["openssl", "genpkey", "-algorithm", "Ed25519", "-out", kf],
                       check=True, capture_output=True)
        raw = subprocess.run(["openssl", "pkey", "-in", kf, "-outform", "DER"],
                             check=True, capture_output=True).stdout
        seed = raw[-32:]
        pub_raw = subprocess.run(
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
raw = subprocess.run(["openssl", "pkey", "-in", kf, "-outform", "DER"],
                             check=True, capture_output=True).stdout
        seed = raw[-32:]
        pub_raw = subprocess.run(
            ["openssl", "pkey", "-in", kf, "-pubout", "-outform", "DER"],
            check=True, capture_output=True).stdout
        pub = pub_raw[-32:]
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
with tempfile.NamedTemporaryFile(suffix=".dat", delete=False) as f:
            f.write(message)
            df = f.name
        result = subprocess.run(
            ["openssl", "pkeyutl", "-sign", "-inkey", kf, "-rawin", "-in", df],
            check=True, capture_output=True)
        return base64.b64encode(result.stdout).decode()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The registration flow writes a credentials file containing the private key to disk in plaintext. Although the script later mentions backup, it does not provide a meaningful pre-write consent prompt, secure-storage choice, or permission hardening, which is risky in shared agent workspaces where other tools or users may access the file.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
Key rotation silently overwrites the credentials file with new private key material, again storing secrets on disk without an explicit warning or secure-storage handling. This can surprise users, disrupt backup expectations, and expose fresh credentials if the file location is accessible to other processes.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The list command exposes bulk registry enumeration through an admin-style endpoint, which exceeds the stated purpose of point identity verification and secure messaging. This enables large-scale discovery of registered identities and associated platform usernames, increasing privacy and reconnaissance risk.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
Calling /admin/registrations directly from a general-purpose client enables bulk discovery of all registered agents, which is not required for ordinary identity operations. In context, this increases privacy exposure and makes targeted abuse or scraping easier.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The trust-graph command performs registry-wide enumeration and then queries vouches for each discovered agent to build a global social graph. That creates unnecessary reconnaissance capability and can expose relationship metadata well beyond normal identity verification needs.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The code chains administrative registration listing with per-agent vouch retrieval to perform registry-wide trust reconnaissance. For an identity tool, this broad collection of network relationship data is excessive and raises privacy and targeting concerns.

Static analysis

No suspicious patterns detected.