Sandboxed NFT Gallery Architecture in Nautilus Wallet: A Technical Deep Dive

The Nautilus Wallet implements a sandboxed NFT gallery using Vue 3 components that render untrusted NFT media inside restrictive iframes pointing to an isolated Azure-hosted sandbox service, preventing malicious code execution while supporting IPFS and HTTP content.

The Nautilus Wallet employs a sophisticated sandboxed NFT gallery architecture to safely display blockchain-based artwork without exposing users to potentially malicious embedded code. This Vue 3-based implementation isolates untrusted media through restrictive iframe sandboxes and remote rendering services. Understanding this architecture reveals how modern cryptocurrency wallets balance rich media experiences with stringent security requirements.

Component Hierarchy and Responsibilities

The gallery follows a layered component architecture where each layer handles specific concerns:

Type System and Asset Classification

The wallet uses strict typing to identify NFT assets. In src/types/internal.ts, the system defines:

enum AssetType {
  NFT = "NFT",
  // ... other types
}

enum AssetSubtype {
  PictureArtwork = "PictureArtwork",
  AudioArtwork = "AudioArtwork", 
  VideoArtwork = "VideoArtwork"
}

The GraphQL service in src/chains/ergo/services/graphQlService.ts detects NFTs by checking if assetInfo.type === AssetType.NFT or if the encoded string starts with the NFT prefix.

Data Flow and Rendering Pipeline

IPFS Resolution and Gateway Configuration

Before sandboxing, the system resolves IPFS URIs to user-configured gateways. The AssetImageSandbox component computes:

const gateway = computed(() =>
  app.settings.ipfsGateway.endsWith("/") 
    ? app.settings.ipfsGateway 
    : `${app.settings.ipfsGateway}/`
);

function resolveIpfs(url?: string): string {
  if (!url) return "";
  if (!url.startsWith(IPFS_PROTOCOL_PREFIX)) return url;
  return url.replace(IPFS_PROTOCOL_PREFIX, gateway.value);
}

This ensures users control their IPFS infrastructure while preventing direct IPFS fetching by the wallet.

Sandbox URL Construction and Iframe Isolation

The component constructs a query string for the remote sandbox service at https://nautilus-nft-sandbox.azurewebsites.net:

const sandboxUrl = computed(() => {
  if (!props.src) return;
  let query = "";
  if (props.height) query += `&height=${props.height}`;
  if (props.objectFit) query += `&fit=${props.objectFit}`;
  if (props.overflow) query += `&overflow=${props.overflow}`;
  return `${CONTENT_SANDBOX_URL}/?url=${encodeURIComponent(content.value)}${query}`;
});

The iframe uses the most restrictive sandbox settings:

<iframe
  v-show="!loading && sandboxUrl"
  :key="sandboxUrl"
  :src="sandboxUrl"
  sandbox=""
  class="m-0 p-0"
  frameborder="0"
  @load="loading = false"
/>

The empty sandbox attribute disables scripts, same-origin access, forms, and other privileged operations.

Security Guarantees of the Sandboxed Architecture

The Nautilus Wallet implements defense-in-depth for NFT rendering:

  • Script execution prevention – The empty sandbox attribute on iframes prevents JavaScript execution, mitigating XSS attacks from malicious NFT metadata.
  • Origin isolation – Content renders in a separate Azure-hosted sandbox service (nautilus-nft-sandbox.azurewebsites.net), isolating the wallet's extension context from the media domain.
  • User-controlled IPFS gateways – The wallet never fetches IPFS content directly; users specify their gateway in app.settings.ipfsGateway, limiting exposure to trusted infrastructure.
  • MIME type validation – The sandbox service validates content types before rendering, preventing polyglot attacks where non-media files masquerade as images.

Implementation Examples

To display NFT artwork in a responsive grid, use the AssetImageSandbox component with sizing constraints:

<template>
  <div class="grid grid-cols-3 gap-4">
    <AssetImageSandbox
      v-for="nft in collectibles"
      :key="nft.tokenId"
      :src="nft.metadata?.artworkUrl"
      class="h-40 w-full rounded-md"
      height="10rem"
      object-fit="cover"
      overflow="hidden"
      display-external-link
    />
  </div>
</template>

<script setup lang="ts">
import { AssetImageSandbox } from "@/components/asset";
import { computed } from "vue";
import { AssetType } from "@/types/internal";

const props = defineProps<{ assets: Asset[] }>();

const collectibles = computed(() => 
  props.assets.filter(a => a.type === AssetType.NFT)
);
</script>

Opening Detailed NFT Previews

For full-size previews in a modal dialog, the AssetInfoDialog component reuses the same sandbox infrastructure:

<template>
  <AssetInfoDialog ref="dialogRef">
    <AssetImageSandbox
      display-external-link
      :src="contentUrl"
      object-fit="cover"
      class="border-input min-h-[240px] w-full rounded-md border"
    />
  </AssetInfoDialog>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { AssetInfoDialog, AssetImageSandbox } from "@/components/asset";

const dialogRef = ref<InstanceType<typeof AssetInfoDialog>>();
const contentUrl = ref("ipfs://Qm.../artwork.png");

function openAssetInfoDialog(tokenId: string) {
  dialogRef.value?.open();
}
</script>

Summary

  • The sandboxed NFT gallery in Nautilus Wallet isolates untrusted media using restrictive iframes pointing to a remote Azure sandbox service.
  • AssetImageSandbox.vue handles IPFS resolution, URL construction, and iframe rendering with empty sandbox attributes that disable scripts and same-origin access.
  • The architecture supports extensible media types (Picture, Audio, Video) through the AssetSubtype enum and user-configurable IPFS gateways.
  • Security relies on defense-in-depth: origin isolation, script sandboxing, user-controlled gateways, and MIME validation at the sandbox service level.

Frequently Asked Questions

How does the Nautilus Wallet prevent malicious code in NFTs from affecting the wallet?

The wallet renders all NFT media inside an iframe with an empty sandbox attribute in AssetImageSandbox.vue. This disables JavaScript execution, same-origin access, form submission, and other privileged operations. Additionally, the content loads from a separate Azure-hosted sandbox service (nautilus-nft-sandbox.azurewebsites.net), providing origin isolation from the wallet's extension context.

Can users choose their own IPFS gateway for loading NFT artwork?

Yes. The AssetImageSandbox component reads the ipfsGateway setting from app.settings and resolves ipfs:// URIs to the user-specified gateway before constructing the sandbox URL. This allows users to trust their own infrastructure or preferred public gateways rather than relying on hardcoded endpoints, limiting exposure to potentially malicious IPFS nodes.

The type system in src/types/internal.ts defines AssetSubtype.PictureArtwork, AssetSubtype.AudioArtwork, and AssetSubtype.VideoArtwork. While the current implementation primarily renders visual artwork through the sandboxed iframe, the architecture supports extending the sandbox service to handle audio and video MIME types without requiring changes to the Vue component layer, as the same URL construction and isolation mechanisms apply.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →