Back to skill

Security audit

Lsp28 Grid

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but it asks users to run live blockchain update code with a controller private key and limited safety checks.

Review this skill before installing or using it with any real account. Use only a least-privileged controller key, avoid shared shells, verify the RPC, chain ID, Key Manager, and target Universal Profile yourself, and inspect every external URL or iframe source before broadcasting a transaction.

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
scripts/update-grid.js:68
Finding

Unvalidated Active-Content URLs Persisted On-Chain

Content
View full analysis

Vulnerability Details

File Location: scripts/update-grid.js:27-31, scripts/update-grid.js:68-74
Vulnerability Type: Missing schema and URL validation
Risk Level: Medium

Vulnerable Code

javascript
// Encode grid data as VerifiableURI
const jsonString = JSON.stringify(gridData);
const base64Data = Buffer.from(jsonString).toString('base64');
const verifiableUri = `data:application/json;base64,${base64Data}`;
javascript
if (args[0] === '--file') {
  // Load grid from JSON file
  const gridFile = args[1];
  const gridData = JSON.parse(fs.readFileSync(gridFile, 'utf8'));
  updateGrid(gridData).catch(console.error);
} else if (args[0] === '--example') {

Technical Analysis

The script accepts arbitrary JSON from a user-selected file and immediately serializes it into an on-chain grid value. It does not validate the grid schema, required fields, item types, URL protocols, URL credentials, or origins.

The supported iframe and external item types contain active-content destinations in their src and url properties. Consequently, a crafted grid file can contain phishing destinations, untrusted iframe origins, or non-HTTPS schemes. Although references/lsp28-spec.md recommends HTTPS and trusted sources, the executable script does not enforce those requirements.

Whether a malicious scheme results in script execution depends on the security controls implemented by the downstream grid renderer. The confirmed issue in this project is that unsafe values can be signed and persisted without validation.

Attack Path

  1. An attacker creates or modifies a grid JSON file containing a malicious external.url or iframe.src.
  2. The attacker persuades an authorized operator to execute:
    bash
    node scripts/update-grid.js --file malicious-grid.json
    
  3. The script parses the file without schema or URL validation.
  4. The complete attacker-controlled object is b ...[truncated 831 chars]
Remediation
View remediation

Remediation Suggestions

  1. Validate the entire input against a strict JSON schema before serialization:

    • Require isEditable to be a Boolean.
    • Require items to be an array with a reasonable maximum length.
    • Permit only the documented miniapp, iframe, and external item types.
    • Enforce all required fields and reject unknown properties.
    • Require unique, length-limited item IDs.
    • Validate colors and optional size values.
  2. Parse every url and src using the standard URL class and permit only HTTPS:

    javascript
    function validateHttpsUrl(value) {
      const parsed = new URL(value);
      if (parsed.protocol !== 'https:') {
        throw new Error('Only HTTPS URLs are permitted');
      }
      if (parsed.username || parsed.password) {
        throw new Error('URLs containing credentials are not permitted');
      }
      return parsed.toString();
    }
    
  3. Consider enforcing an explicit origin allowlist for iframe sources. At minimum, reject loopback, link-local, and private-network destinations where relevant to consuming clients.

  4. Display a normalized preview of all external and iframe destinations and require explicit operator confirmation before signing.

  5. Downstream renderers must independently sanitize links, apply restrictive iframe sandbox attributes, and avoid relying exclusively on this producer-side validation.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/update-grid.js:23
Finding

Transaction Target and Blockchain Network Are Not Verified Before Signing

Content
View full analysis

Vulnerability Details

File Location: scripts/update-grid.js:4-7, scripts/update-grid.js:23-25, scripts/update-grid.js:41-44
Vulnerability Type: Missing transaction destination and network verification
Risk Level: Low

Vulnerable Code

javascript
// Configuration - set via environment variables or edit
const PRIVATE_KEY = process.env.UP_PRIVATE_KEY || 'YOUR_CONTROLLER_PRIVATE_KEY';
const UP_ADDRESS = process.env.UP_ADDRESS || 'YOUR_UP_ADDRESS';
const KEY_MANAGER = process.env.KEY_MANAGER || 'YOUR_KEY_MANAGER_ADDRESS';
const RPC_URL = process.env.RPC_URL || 'https://rpc.mainnet.lukso.network';
javascript
const provider = new ethers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
javascript
// Execute via KeyManager
const keyManager = new ethers.Contract(KEY_MANAGER, LSP6_ABI, wallet);

console.log('\n📤 Sending transaction...');
const tx = await keyManager.execute(setDataCalldata);

Technical Analysis

The RPC endpoint and Key Manager address are fully controlled by environment variables. Before signing, the script does not verify:

  • That the connected network has LUKSO Mainnet chain ID 42.
  • That bytecode exists at KEY_MANAGER.
  • That the target implements the expected Key Manager behavior.
  • That the Key Manager controls the Universal Profile specified by UP_ADDRESS.
  • That the configured controller has the expected permissions for the intended profile.

More importantly, UP_ADDRESS is declared but never used. The encoded payload contains only the setData calldata, and the destination profile is implicitly determined by the configured Key Manager. Therefore, setting UP_ADDRESS does not ensure that the intended Universal Profile will be updated.

Attack Path

  1. An attacker or configuration error changes RPC_URL or KEY_MANAGER in the execution environment.
  2. The operator runs the update script ...[truncated 1125 chars]
Remediation
View remediation

Remediation Suggestions

  1. Verify the network before constructing or sending the transaction:

    javascript
    const network = await provider.getNetwork();
    if (network.chainId !== 42n) {
      throw new Error(`Unexpected chain ID: ${network.chainId}`);
    }
    
  2. Validate all configured addresses with ethers.isAddress and reject placeholder values before initializing the wallet or contract.

  3. Confirm that deployed bytecode exists at both the Key Manager and Universal Profile addresses:

    javascript
    const code = await provider.getCode(KEY_MANAGER);
    if (code === '0x') {
      throw new Error('No contract deployed at KEY_MANAGER');
    }
    
  4. Query the Key Manager's controlled target using the appropriate verified LSP6 interface and require it to exactly equal UP_ADDRESS.

  5. Verify that the controller has only the permissions required to update the LSP28 data key.

  6. Print the chain ID, RPC endpoint, signer address, Key Manager address, and resolved Universal Profile address before signing. For interactive usage, require confirmation when any value differs from an expected configuration.

  7. Remove UP_ADDRESS if it is not part of a validated transaction flow; retaining an unused security-relevant setting can give operators a false assurance about the actual destination.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill includes executable code patterns that rely on sensitive environment variables, including a private key, but declares no explicit tool scope or permissions boundary. In an agent setting, this increases the risk that the skill is invoked with broader-than-expected access to secrets or execution capabilities, making unintended credential use or transaction submission more likely.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill instructs users to place a controller private key in an environment variable without clearly warning that this is a highly sensitive credential capable of controlling a Universal Profile. In agentic or shared-shell environments, environment variables may be exposed to logs, subprocesses, debugging output, or other tools, creating a realistic path to credential theft and account compromise.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill provides ready-to-run code that submits a real mainnet transaction to update on-chain profile data, but does not prominently warn that this is a live, state-changing, and potentially irreversible operation with cost and security consequences. Users may execute the example assuming it is a harmless demo, leading to unintended profile modifications or unauthorized use of a controlling key.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The specification explicitly promotes embedding third-party iframes and linking to external sites, but only gives minimal URL guidance and does not warn about privacy, tracking, phishing, or trust implications for end users. In the context of a grid/profile feature that can surface arbitrary external content, this omission can lead implementers to expose users to embedded untrusted content without adequate disclosure or safeguards.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.