Back to skill

Security audit

Nadname Agent

Security checks for vulnerabilities and agentic risk

Overview

This skill is not clearly malicious, but it handles wallet keys and irreversible blockchain transactions with several under-disclosed or unsafe behaviors users should review first.

Review this skill before installing. Use only a low-value wallet, prefer dry-run first, do not rely on its availability or ownership lookup as authoritative, and avoid managed keystore mode until the plaintext fallback, custom encryption, transaction validation, and explicit confirmation issues are fixed.

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

T09 Β· Insecure Skill Coding Practices

Error
Location
scripts/register-name.js:341
Finding
Unvalidated API Response Controls a Paid Blockchain Transaction<![CDATA[ ## Vulnerability Details **File Location**: `scripts/register-name.js:341-348`, `scripts/register-name.js:413-427`, and `scripts/register-name.js:476-489` **Vulnerability Type**: Insufficient validation of externally supplied transaction parameters **Risk Level**: High ### Vulnerable Code ```javascript const result = await makeApiRequest('/api/register-request', 'POST', requestBody); if (!result.registerData || !result.signature || !result.price) { throw new Error('Invalid API response: missing required fields'); } console.log('βœ… Got registration data from API'); console.log(`πŸ’° Price: ${result.price} ${paymentToken}`); return result; ``` ```javascript const apiResponse = await getRegistrationData( name, wallet.address, setPrimary, referrer ); const { registerData, signature, price } = apiResponse; let priceInWei; if (typeof price === 'string') { priceInWei = ethers.parseEther(price); } else if (typeof price === 'number') { priceInWei = ethers.parseEther(price.toString()); } else { throw new Error(`Invalid price format: ${price}`); } ``` ```javascript const contractInterface = new ethers.Interface([ 'function registerWithSignature(tuple(string name, address nameOwner, bool setAsPrimaryName, address referrer, bytes32 discountKey, bytes[] discountClaimProof, uint256 nonce, uint256 deadline, bytes attributes, address paymentToken) registerData, bytes signature) payable' ]); const data = contractInterface.encodeFunctionData( 'registerWithSignature', [registerData, signature] ); const tx = { to: NNS_CONTRACT, value: priceInWei, data: data, gasLimit: gasLimit, gasPrice: gasPrice }; const result = await signer.sendTransaction(tx); ``` ### Technical Analysis The remote NAD API supplies both the structured registration parameters and the price used in the transaction. The script only checks that `registerData`, `signature`, and `price` are present. It does not verify that the returned data matches the user's request. ...[truncated 2122 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define and enforce a strict schema for the API response. 2. Validate that `registerData.name` exactly matches the normalized requested name. 3. Require `registerData.nameOwner` to equal the local wallet address. 4. Compare `setAsPrimaryName`, `referrer`, and `paymentToken` with locally constructed expected values. 5. Reject expired or excessively long deadlines and validate nonce semantics. 6. Enforce a user-configurable maximum price and reject negative, noncanonical, non-finite, or excessively precise values. 7. Decode the final calldata locally and display all effective transaction fields. 8. Require explicit interactive confirmation, such as typing the normalized name and final price, unless a separately documented noninteractive flag is supplied. 9. Prefer locally constructing all transaction fields that do not require server authorization. 10. Obtain the expected contract address and signing authority from authenticated, versioned configuration and verify the server signature locally where feasible. ]]>

T09 Β· Insecure Skill Coding Practices

Warning
Location
scripts/check-name.js:252
Finding
Availability Checker Reports Simulated Guesses as On-Chain Results<![CDATA[ ## Vulnerability Details **File Location**: `scripts/check-name.js:160-174` and `scripts/check-name.js:252-287` **Vulnerability Type**: Fail-open response parsing and fabricated blockchain results **Risk Level**: Medium ### Vulnerable Code ```javascript if (apiResult) { // Parse API response (structure may vary) availability = { available: apiResult.available !== false, owner: apiResult.owner || null, source: 'api' }; pricing = { base: apiResult.price || apiResult.basePrice || null, final: apiResult.finalPrice || apiResult.price || null, discount: apiResult.discount || 0, currency: apiResult.currency || 'MON', source: 'api' }; } ``` ```javascript async function checkAvailabilityOnChain(provider, name) { // Fallback on-chain check when API is unavailable try { // In a full implementation, you'd call the NNS contract here // This requires the contract ABI and the correct function name // For now, we'll do a basic simulation with common patterns console.log('πŸ” Checking on-chain availability...'); // Simulate some names as likely taken const commonTaken = [ 'test', 'admin', 'owner', 'nad', 'monad', 'ethereum', 'bitcoin', 'app', 'www' ]; const isTaken = commonTaken.includes(name.toLowerCase()); if (isTaken) { return { available: false, owner: '0x742d35Cc6cC02dC9cC1ee19b2efC0ba87d0527b1', source: 'on-chain' }; } // Most names should be available since NNS is relatively new return { available: true, owner: null, source: 'on-chain' }; } catch (error) { console.warn('⚠️ On-chain check failed, assuming available'); console.warn(` Error: ${error.message}`); return { available: true, owner: null, source: 'assumed' }; } } ``` ### Technical Analysis The API parser treats every response as indicating availability u ...[truncated 1461 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use one documented, versioned API endpoint instead of probing guessed endpoint paths. 2. Validate responses against a strict schema requiring `available` to be a Boolean. 3. Reject unknown fields or incompatible response versions where appropriate. 4. Implement the actual NNS contract view call using a verified ABI and contract address. 5. Verify the connected network's chain ID before trusting blockchain results. 6. If neither the API nor contract can provide a definitive answer, return an β€œunknown” state and a nonzero exit status. 7. Never describe simulated, heuristic, or cached data as β€œon-chain.” 8. Add tests covering missing properties, `"false"` strings, null responses, error objects, unavailable endpoints, and RPC failures. ]]>

T09 Β· Insecure Skill Coding Practices

Warning
Location
scripts/my-names.js:91
Finding
Owned-Name Lookup Uses Address-Based Mock Data Instead of the NNS Contract<![CDATA[ ## Vulnerability Details **File Location**: `scripts/my-names.js:91-126` **Vulnerability Type**: Fabricated ownership results **Risk Level**: Medium ### Vulnerable Code ```javascript async function lookupNames(provider, address) { console.log(`πŸ” Looking up names for: ${address}`); console.log(''); try { // In a real implementation, you'd query the NNS contract for names owned by this address // This would typically involve: // 1. Getting the contract instance with ABI // 2. Calling a view function like getNamesOwnedBy(address) // 3. Parsing the results to get name details // For now, we'll simulate some results console.log('πŸ”„ Querying NNS contract...'); console.log('⚠️ SIMULATION MODE - Using mock data'); console.log(''); // Mock data - replace with real contract calls const mockNames = []; // Simulate some owned names based on address const addressLower = address.toLowerCase(); if (addressLower.includes('beef') || addressLower.includes('dead')) { mockNames.push({ name: 'agent', isPrimary: true, registeredDate: '2026-02-08T10:30:00Z', expiryDate: null }); mockNames.push({ name: '🦞', isPrimary: false, registeredDate: '2026-02-08T11:15:00Z', expiryDate: null }); } if (mockNames.length === 0) { console.log('πŸ“­ No .nad names found for this address'); console.log(''); console.log('πŸ’‘ To register a name:'); console.log(' node scripts/check-name.js <name>'); console.log(' node scripts/register-name.js --name <name>'); return; } ``` ### Technical Analysis The script's documented purpose is to list names owned by an address, but `lookupNames()` does not query the NNS contract or an indexer. It constructs mock ownership records when the textual address contains `beef` or `dead`, and otherwise reports that no names were found. The output notes simula ...[truncated 1064 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove mock behavior from the production command. 2. Query a verified NNS contract view method or an authenticated, documented indexer. 3. If enumeration is not supported by the contract, query indexed transfer events or clearly report that the feature is unsupported. 4. Validate the network chain ID before presenting results. 5. Make failures explicit and return a nonzero exit status rather than reporting an empty ownership set. 6. Keep test fixtures in dedicated test files and activate them only through an explicit test-mode option. 7. Update documentation so it accurately states whether results come from a contract, an indexer, or another source. ]]>

T09 Β· Insecure Skill Coding Practices

Warning
Location
scripts/register-name.js:289
Finding
Managed Keystore Uses Deprecated Cipher APIs and Ignores the Generated IV<![CDATA[ ## Vulnerability Details **File Location**: `scripts/register-name.js:289-325` **Vulnerability Type**: Incorrect custom cryptographic implementation **Risk Level**: Medium ### Vulnerable Code ```javascript function encrypt(text, password) { const algorithm = 'aes-256-gcm'; const salt = crypto.randomBytes(16); const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha512'); const iv = crypto.randomBytes(12); const cipher = crypto.createCipher(algorithm, key); cipher.setAAD(salt); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); const tag = cipher.getAuthTag(); const result = { salt: salt.toString('hex'), iv: iv.toString('hex'), tag: tag.toString('hex'), encrypted: encrypted }; return JSON.stringify(result); } function decrypt(encryptedData, password) { const data = JSON.parse(encryptedData); const algorithm = 'aes-256-gcm'; const salt = Buffer.from(data.salt, 'hex'); const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha512'); const decipher = crypto.createDecipher(algorithm, key); decipher.setAAD(salt); decipher.setAuthTag(Buffer.from(data.tag, 'hex')); let decrypted = decipher.update(data.encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } ``` ### Technical Analysis The implementation generates a 12-byte IV and stores it in the encrypted file, but never passes that IV to the cipher or decipher. Instead, it uses the deprecated `crypto.createCipher()` and `crypto.createDecipher()` APIs. Those deprecated APIs perform implicit password-style derivation internally. Passing an already derived key to them does not provide the explicit key-and-IV control expected for AES-GCM. Consequently, the stored `iv` field is misleading and has no effect on encryption or decryption. The custom format also uses PBKDF2 with 100,000 iterations rather than a memory-hard password KDF. A stolen keystore therefore remains susc ...[truncated 1152 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the custom format with the standard ethers encrypted JSON keystore implementation. 2. If custom encryption is unavoidable, use `crypto.createCipheriv()` and `crypto.createDecipheriv()` with the derived 32-byte key and stored random IV. 3. Authenticate version, algorithm, KDF parameters, salt, and other metadata as additional authenticated data. 4. Use a memory-hard KDF such as scrypt or Argon2id with parameters calibrated for the deployment environment. 5. Add a version field so older keystores can be migrated safely. 6. Enforce stronger password requirements and advise users to use unique, high-entropy passphrases. 7. Zero sensitive buffers where practical and avoid retaining decrypted key material longer than necessary. 8. Add known-answer, tamper-detection, wrong-password, and migration tests. 9. Provide a migration utility for existing keystores and require backup verification before replacing them. ]]>

T09 Β· Insecure Skill Coding Practices

Warning
Location
scripts/register-name.js:199
Finding
Managed Mode Silently Accepts an Unencrypted Private-Key File<![CDATA[ ## Vulnerability Details **File Location**: `scripts/register-name.js:199-212` **Vulnerability Type**: Plaintext sensitive credential storage fallback **Risk Level**: Medium ### Vulnerable Code ```javascript async function getManagedKey() { // Check for encrypted key first if (fs.existsSync(ENCRYPTED_KEY_FILE)) { try { const password = await promptPassword('πŸ” Enter keystore password: '); return decryptPrivateKey(password); } catch (error) { console.error('❌ Failed to decrypt private key:', error.message); process.exit(1); } } // Check for plain key (fallback) if (fs.existsSync(PLAIN_KEY_FILE)) { console.warn('⚠️ Using unencrypted private key (consider re-running setup with encryption)'); return fs.readFileSync(PLAIN_KEY_FILE, 'utf8').trim(); } // No keystore found - create new one console.log('πŸ“¦ No keystore found. Creating new encrypted wallet...'); return await createManagedWallet(); } ``` ### Technical Analysis Managed mode is documented as an encrypted keystore mode, but it silently reads `~/.nadname/private-key` when an encrypted file is absent. The warning does not prevent use, require explicit consent, verify restrictive permissions, or migrate the credential to encrypted storage. This contradicts the Skill's stated policy that only the `PRIVATE_KEY` environment variable or an encrypted managed keystore is used. It creates a path in which long-lived wallet credentials remain on disk in plaintext. ### Attack Path 1. A plaintext private-key file is created manually, left by an older release, restored from backup, or introduced by another local process. 2. The user runs the registration script with `--managed`. 3. The script automatically reads and uses the plaintext key. 4. Local users, malware, backup software, synchronization clients, or overly permissive file permissions expose the file contents. 5. An attacker imports the recovered private key into another wallet. 6. The ...[truncated 339 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the plaintext-key fallback from normal managed-mode execution. 2. If backward compatibility is required, provide a separate explicit migration command. 3. Before migration, verify that the file is a regular file, is owned by the current user, is not a symbolic link, and has restrictive permissions. 4. Encrypt the key using a standard keystore format and verify that decryption succeeds before deleting the original. 5. Warn that secure deletion cannot be guaranteed on journaled, copy-on-write, or synchronized filesystems. 6. Refuse to proceed when a plaintext key is detected unless the user explicitly selects migration. 7. Update documentation to reflect any supported migration behavior accurately. ]]>

T08 Β· Insecure Dependencies

Note
Location
package.json:10
Finding
Unpinned Dependency Resolution Prevents Reproducible Installation<![CDATA[ ## Vulnerability Details **File Location**: `package.json:10-12` **Vulnerability Type**: Unpinned third-party dependency without a committed lockfile **Risk Level**: Low ### Vulnerable Code ```json "dependencies": { "ethers": "^6.0.0" } ``` ### Technical Analysis The project declares `ethers` with a broad caret range and does not include a dependency lockfile in the audited directory structure. As a result, `npm install` can resolve different package versions over time. No malicious dependency was identified during the audit. The issue is that installations are not reproducible and automatically trust future versions allowed by the range. Any future compromised, vulnerable, or incompatible release selected by dependency resolution would execute in the same Node.js process that handles private keys and signs transactions. ### Attack Path 1. A user follows the documented `npm install` instruction. 2. npm resolves the newest package version permitted by `^6.0.0`. 3. The resolved dependency differs from the version originally reviewed. 4. If the resolved package or its transitive dependency has been compromised or contains a vulnerability, its installation or runtime code executes locally. 5. Runtime dependency code receives wallet secrets through constructor calls and participates directly in transaction creation and signing. ### Impact Assessment The potential scope is significant because the dependency operates in a process that handles private keys and blockchain transactions. However, exploitation depends on a future compromised or vulnerable dependency release; the audited manifest itself does not establish that the current `ethers` package is malicious. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `ethers` to a specifically reviewed version. 2. Generate and commit `package-lock.json`. 3. Use `npm ci` in automated and production installation workflows. 4. Review transitive dependencies and package lifecycle scripts before release. 5. Enable dependency vulnerability and integrity monitoring. 6. Update dependencies through reviewed pull requests rather than unconstrained installation-time resolution. 7. Consider disabling lifecycle scripts during installation where compatible with the dependency set. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (28)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill is presented as a naming-service registration tool, but the analysis indicates it also involves local wallet creation, keystore handling, and potential storage of private keys or mnemonic backups on disk. That is materially more sensitive than the declared purpose, and hidden credential-management behavior in an agent skill can lead to secret theft, insecure storage, or irreversible blockchain account compromise.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill is presented as a naming-service registration tool, but the analysis indicates it also involves local wallet creation, keystore handling, and potential storage of private keys or mnemonic backups on disk. That is materially more sensitive than the declared purpose, and hidden credential-management behavior in an agent skill can lead to secret theft, insecure storage, or irreversible blockchain account compromise.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill is presented as a naming-service registration tool, but the analysis indicates it also involves local wallet creation, keystore handling, and potential storage of private keys or mnemonic backups on disk. That is materially more sensitive than the declared purpose, and hidden credential-management behavior in an agent skill can lead to secret theft, insecure storage, or irreversible blockchain account compromise.

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Content
---
name: NadName Agent
description: "🌐 Register .nad names on Monad blockchain via Nad Name Service (NNS). Real API integration with registerWithSignature, dynamic gas estimation, permanent ownership."
---

# 🌐 NadName Agent v2.0 - .nad Names on Monad

> Register permanent .nad names on Monad blockchain via Nad Name Service with real API integration

**TL;DR:** Get `yourname.nad` on Monad. One-time fee, lifetime ownership. Now with real NAD API integration and accurate pricing!

## What is NNS?

**Nad Name Service (NNS)** is a web3 name service built on Monad blockchain that maps human-readable names like `age
Confidence
80% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Ae1

High
Category
analysis-evasion
Content
node scripts/check-name.js myname
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/check-name.js myname
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/check-name.js myname
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/check-name.js myname
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/check-name.js myname
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/check-name.js myname
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/check-name.js myname
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/check-name.js myname
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/my-names.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/my-names.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/my-names.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/my-names.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/my-names.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/my-names.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The script advertises accurate blockchain-backed availability checks, but its fallback logic does not query the NNS contract and instead fabricates availability based on a hardcoded list plus optimistic defaults. Users may rely on false 'available' results and proceed toward registration attempts, causing misinformed decisions, failed transactions, or trust abuse in a blockchain registration workflow where correctness is security-relevant.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The inline comments and function naming state that an on-chain availability check is performed, but the implementation only simulates results and even returns 'available' on errors. In the context of a blockchain naming/registration tool, this deceptive behavior can mislead operators and downstream automation into trusting unverified state, increasing the chance of failed or wasteful transactions.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The file and surrounding skill metadata imply real NNS functionality, but this script only performs read-only lookup behavior and even relies on simulated results. This is dangerous because users may make operational decisions based on false blockchain state, and the mismatch undermines trust in a wallet- or blockchain-facing tool.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README instructs users to export a private key and run a registration command, but it does not clearly warn that this will submit a real blockchain transaction that spends funds and is generally irreversible once confirmed. In a blockchain-registration skill with real API integration, this omission can cause users to trigger unintended on-chain actions or fees under the assumption they are performing a harmless local test.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documents use of the PRIVATE_KEY environment variable and managed keystore flows, but the manifest declares no explicit tool scope or permission boundary for environment access. In an agent setting, undeclared access to secrets increases the risk of accidental credential exposure or misuse because operators cannot clearly constrain what the skill is allowed to read.

External Transmission

Medium
Category
Data Exfiltration
Content
**Step 1: Get Registration Data**
```bash
POST https://api.nad.domains/api/register-request
Body: {
  "name": "myname",
  "owner": "0x...",
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The implementation explicitly enters simulation mode and fabricates name ownership results based on address substrings rather than querying the contract. In a blockchain naming context, presenting mock data as if it were authoritative can mislead users about ownership, primary names, or account state.

Static analysis

No suspicious patterns detected.