Back to skill

Security audit

Forever Moments

Security checks for vulnerabilities and agentic risk

Overview

This skill is not plainly malicious, but it can use a private wallet key to publish content and submit blockchain transactions automatically without strong local checks or confirmations.

Install only if you are comfortable giving this skill a dedicated, low-privilege LUKSO controller key and reviewing every live action before use. Do not use a high-value wallet key, avoid unattended cron until spend/posting limits and payload validation are added, and treat IPFS uploads and social posts as public and hard to undo.

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

Error
Location
scripts/post-moment-ai.js:159
Finding
Opaque Server-Generated Blockchain Transactions Are Signed Without Local Validation<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/post-moment-ai.js:159-230` and `scripts/post-moment-ai.js:337-339` - `scripts/post-moment.js:39-57` and `scripts/post-moment.js:84-86` - `scripts/post-moment-with-image.js:75-95` and `scripts/post-moment-with-image.js:150-152` - `scripts/mint-likes.js:34-54` and `scripts/mint-likes.js:66-68` **Vulnerability Type**: Signing and execution of unvalidated remote transaction payloads **Risk Level**: High ### Vulnerable Code The AI posting workflow signs a digest and uses transaction parameters supplied by the remote Forever Moments API: ```javascript async function relayExecute(payload, description) { console.log(`\n📡 ${description}`); const relayPrepare = await apiCall('/relay/prepare', 'POST', { upAddress: MY_UP, controllerAddress: CONTROLLER, payload: payload }); if (!relayPrepare.success) { console.error('❌ Relay prepare failed:', relayPrepare.error); return null; } const wallet = new ethers.Wallet(PRIVATE_KEY); const signature = wallet.signingKey.sign(ethers.getBytes(relayPrepare.data.hashToSign)); const relaySubmit = await apiCall('/relay/submit', 'POST', { upAddress: MY_UP, payload: payload, signature: signature.serialized, nonce: relayPrepare.data.lsp15Request.transaction.nonce, validityTimestamps: relayPrepare.data.lsp15Request.transaction.validityTimestamps, relayerUrl: relayPrepare.data.relayerUrl }); if (relaySubmit?.success && !relaySubmit.data?.ok) { let responseText = relaySubmit.data?.responseText || ''; if (typeof responseText === 'string' && responseText.includes('Insufficient balance')) { console.log('⚠️ Relayer quota exhausted. Falling back to direct execution (paying gas from controller)...'); return await directExecute(relayPrepare.data.keyManagerAddress, payload); } } return relaySubmit; } ``` Its direct-execution fallback also trusts the KeyManager destination ret ...[truncated 4689 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Decode and validate every payload locally** - Decode the outer Universal Profile and KeyManager calls. - Decode nested calls and verify contract destinations, function selectors, recipients, asset amounts, and native values. - Maintain a strict allowlist for supported Forever Moments contracts and operations. - Reject unknown selectors, multicalls, delegate calls, and unexpected nested payloads. 2. **Reconstruct the relay digest locally** - Derive the LSP25 digest from the verified payload, Universal Profile address, nonce, validity timestamps, chain ID, and applicable domain fields. - Compare the locally derived digest against `hashToSign`. - Refuse to sign if any field differs. 3. **Verify infrastructure destinations** - Resolve the expected KeyManager address from trusted on-chain Universal Profile state instead of accepting it from the API. - Restrict `relayerUrl` to an explicit allowlist such as the documented HTTPS LUKSO mainnet relayer. - Reject redirects to unapproved hosts. 4. **Protect value-bearing operations** - Parse and display the exact LYX/token amount, recipient, contract, and estimated gas before signing. - Require explicit user confirmation for LIKES minting and direct gas fallback. - Do not trigger direct execution merely from a server-controlled error string. 5. **Apply least privilege** - Use a dedicated controller for this Skill. - Grant only the exact CALL and EXECUTE_RELAY_CALL permissions needed for known Forever Moments contracts. - Avoid broad `SUPER_CALL`, unrestricted value transfer, ownership, and permission-management capabilities. 6. **Fail closed** - Treat malformed API responses, unknown contracts, digest mismatches, and validation failures as fatal. - Do not silently continue or downgrade to direct execution. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/post-moment-with-image.js:36
Finding
Caller-Controlled File Path Can Be Uploaded and Pinned to IPFS Without Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/post-moment-with-image.js:36-55`, `scripts/post-moment-with-image.js:104-110`, and `scripts/post-moment-with-image.js:169-178` **Vulnerability Type**: Unrestricted local file read and external upload **Risk Level**: Medium ### Vulnerable Code The upload function reads any supplied path and transmits its contents to the Forever Moments pinning endpoint: ```javascript async function pinImageToIPFS(imagePath) { console.log(`📤 Pinning image to IPFS: ${imagePath}`); return new Promise((resolve, reject) => { const form = new FormData(); form.append('file', fs.createReadStream(imagePath)); const options = { hostname: API_BASE, path: '/api/pinata', method: 'POST', headers: form.getHeaders() }; const req = https.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const json = JSON.parse(data); if (json.IpfsHash) { console.log(`✅ Image pinned: ipfs://${json.IpfsHash}`); resolve(json.IpfsHash); } else { reject(new Error('No IpfsHash in response')); } } catch (e) { reject(e); } }); }); req.on('error', reject); form.pipe(req); }); } ``` The caller-controlled CLI argument is passed directly to that function: ```javascript const [name, description, tagsStr, imagePath] = args; const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()) : []; postMoment(name, description, tags, imagePath).catch(console.error); ``` No validation occurs before upload: ```javascript if (imagePath) { try { imageCid = await pinImageToIPFS(imagePath); } catch (e) { console.error('Failed to pin image:', e.message); console.log('Continuing without image...'); } } ``` ### Technical Analysis `imagePath` is treated as an image but is not constrai ...[truncated 1831 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Restrict the upload directory** - Require files to reside under an explicitly configured media directory. - Resolve both the allowed directory and requested file with `fs.realpath`. - Reject paths whose canonical form falls outside the allowed directory. 2. **Reject unsafe filesystem objects** - Use `lstat` and reject symbolic links, devices, sockets, directories, and other non-regular files. - Open files with protections against symlink traversal where supported. - Revalidate the file after opening to reduce time-of-check/time-of-use risks. 3. **Validate actual image content** - Inspect file magic bytes with a trusted image-type parser. - Allow only documented image formats such as PNG, JPEG, GIF, and WebP. - Do not rely only on filename extensions or caller-provided MIME types. 4. **Enforce resource limits** - Apply a strict maximum file size before opening the upload stream. - Add request timeouts and response-size limits. - Abort uploads that exceed configured limits. 5. **Require informed confirmation** - Display the canonical path, detected type, and size before transmission. - Require explicit confirmation for interactive uploads. - For automation, require preapproved file paths rather than arbitrary command arguments. 6. **Reduce publication risk** - Warn users that IPFS uploads can be public and persist after deletion attempts. - Avoid automatically publishing the returned CID until the upload and intended content are confirmed. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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
Findings (27)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description claims a multi-function Forever Moments skill covering several decentralized social and token operations. The supplied code chunk is much narrower: it exclusively automates creation of a moment with optional AI-generated imagery, IPFS pinning, and mint execution on LUKSO. That does align with the subset of the description about posting moments and automated AI-generated image posting, but it does not support the other prominently declared use cases such as LIKES token mint/buy, collection membership management, sale listing, or liking moments. This is a material description-to-behavior mismatch because the declared scope is substantially broader than the implemented behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
This is a material description-behavior mismatch. The description presents a broad Forever Moments/LUKSO integration skill with onchain and social-platform actions. The actual code only supports one small preparatory workflow: generate an AI image from predefined prompts and save accompanying metadata locally for later use. The script even states that minting is skipped and the API service is unavailable. While automated AI-generated posting is mentioned in the declared use cases, the actual implementation does not complete posting at all, so it falls well short of the declared primary purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The supplied code chunk has a much narrower scope than the declared description. It implements a single posting flow: build mint payload for a moment, sign relay request with a controller private key, and submit it. That aligns with 'post a moment' on Forever Moments, but none of the other advertised capabilities are present. The success criteria also mention returning an IPFS CID and image pinning, which this code does not do; it only returns a transaction hash. There is no DALLE usage, no LIKES token logic, no collection membership logic, no marketplace/sale logic, and no code for sending likes. This is therefore a description-behavior mismatch due to substantial overstatement of capabilities.

Ae1

High
Category
analysis-evasion
Content
node scripts/post-moment-ai.js "Title" "Desc" "tags" "image prompt"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/post-moment-ai.js "Title" "Desc" "tags" "image prompt"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README explicitly documents automated posting via cron together with on-chain minting and relay submission, but it does not clearly warn that these actions create external side effects such as publishing content to a public social platform, pinning data to IPFS, and potentially spending assets or consuming relay quota. In an agent setting, this increases the risk that an operator enables unattended execution without understanding that the skill can trigger irreversible public and blockchain-visible actions.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill documents access to environment variables and multiple network endpoints, but it does not declare any tool scope or permission boundaries. In an agent setting, missing explicit scope increases the chance the skill can access secrets or make outbound requests beyond what reviewers and users expect.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The file says all operations are gasless while elsewhere stating LIKES minting costs LYX and may require spending approval. That contradiction can mislead users into authorizing value-bearing operations without understanding the financial impact, which is especially risky in a blockchain skill handling signing and token spending.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill instructs users to provide a controller private key and demonstrates raw-digest signing, but it does not prominently warn about the sensitivity of that key, the scope of permissions it may grant, or the irreversible consequences of signing arbitrary payloads. In this context, the combination of secret handling, transaction building, relay submission, and spending-related actions materially raises the risk of wallet compromise or unauthorized asset movement if the skill is misused or the API is malicious.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The document opens by saying the repo exposes an agent-friendly API for building transaction plans, which suggests a planner/builder scope. Later sections document `POST /api/agent/v1/relay/submit` as a proxy for sending signed requests to the relayer, which goes beyond merely building plans and into transaction submission/execution support.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The documentation explicitly tells agents/operators they need a controller EOA private key for signing relay digests, but it does not include strong handling guidance, storage restrictions, or warnings against exposing that key to untrusted agent flows. In this skill context, that is more dangerous because the skill is intended for automated posting and blockchain actions, increasing the chance that an agent runtime or integration will ingest, persist, or misuse highly sensitive signing credentials.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This script directly signs and submits a relay transaction that spends value on-chain to mint LIKES, using a configured private key and command-line amount, without any interactive confirmation, explicit irreversible-action warning, or spending guardrails. In the context of an agent skill for automated social actions, this is more dangerous because accidental invocation, parameter tampering, or downstream misuse can trigger real LYX expenditure from the configured Universal Profile with no last-mile user approval.

External Transmission

Medium
Category
Data Exfiltration
Content
throw new Error('DALLE_API_KEY not configured. Set it in environment variables.');
  }
  
  const response = await fetch('https://api.openai.com/v1/images/generations', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
throw new Error('DALLE_API_KEY not configured. Set it in environment variables.');
  }
  
  const response = await fetch('https://api.openai.com/v1/images/generations', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
User-supplied prompts are transmitted to OpenAI, and generated-image requests may expose sensitive or private content to third-party services without an explicit warning at the point of use. In a social-posting skill, users may assume local processing, so silent external transmission creates privacy and data-handling risk.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script uploads generated images to IPFS via an external pinning endpoint without warning that the content may become publicly accessible and effectively permanent. Users may unintentionally publish sensitive, copyrighted, or private material, and the skill context makes this more dangerous because the upload is coupled directly to automated posting.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script can sign and submit blockchain transactions using FM_PRIVATE_KEY with no interactive confirmation, policy check, spending cap, or dry-run gate. In the context of an automation/cron-capable social-posting skill, this increases the chance of unintended minting, gas spend, or abusive repeated posting if the script is misused or triggered unexpectedly.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The inline comment at L379 says random cron mode 'uses DALL-E 3 (premium)', while the earlier function documentation states Pollinations.ai is for cron/scheduled posts and DALL-E 3 is for manual posts (L36, L77). The code for `--random` also passes `true` at L382, which actually selects DALL-E, so the file contains actively conflicting intent documentation about which provider cron automation should use.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The header comment asserts that this version attempts direct posting without the API service. In practice, the code only downloads an image, writes metadata locally, and explicitly skips minting because the service is unavailable, so the documentation overstates the implemented behavior.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The code reads FM_PRIVATE_KEY, which is a credential-like secret, but only logs an error when variables are absent. There is no user disclosure or warning that the script handles sensitive key material, which is relevant for a cron-invoked automation script.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script sends prompts to an external image-generation service without surfacing that data will leave the local environment. In an agent skill context, prompts can contain sensitive or user-derived content, so silent third-party transmission creates privacy and data-governance risk.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The manifest describes a skill for posting moments to Forever Moments and returning on-chain/IPFS results, but this script stops after generating an image and writing metadata to /tmp. Its own return path marks success even though no Forever Moments API call, IPFS pin, blockchain transaction, or minting step occurs.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script automatically signs and submits a blockchain mint transaction using FM_PRIVATE_KEY with no interactive confirmation, spending review, or policy gate at the point of execution. In the context of a social-posting automation skill, this is risky because any caller or automation path that reaches postMoment can trigger on-chain actions under the configured Universal Profile controller, potentially causing unauthorized posting, relay abuse, or unintended asset/account activity if inputs or invocation are compromised.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script signs and submits a transaction with a raw private key immediately after receiving relay data, with no interactive confirmation, policy check, or verification that the payload matches the user's intent. In this skill's context, that is especially risky because posting triggers an on-chain action from a Universal Profile controller, so misuse, compromised inputs, or unexpected API responses could cause unauthorized transactions to be signed and relayed automatically.

Intent-Code Divergence

Low
Confidence
81% confidence
Finding
The 'What this API does' section frames the API as returning transaction plans for onchain actions. However, the endpoint map and later sections also document `POST /api/pinata`, which uploads binaries to IPFS and is not a transaction-plan endpoint, creating an intent/documentation inconsistency.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/post-moment-ai.js:7