{"id":"97c977e0f8d6af180d86d6bdf9d7086bd898060ff4a3f1fc6c64b46635a8f788","pubkey":"e771af0b05c8e95fcdf6feb3500544d2fb1ccd384788e9f490bb3ee28e8ed66f","created_at":1782505584,"kind":30817,"tags":[["d","nip-5d-nostr-web-applets"],["title","[NIP-5D] Nostr Web Applets (napplets)"],["alt","Nostr Implementation Possibility: [NIP-5D] Nostr Web Applets (napplets)"],["k","5129","napplet snapshot"],["k","15129","root napplet"],["k","35129","named napplet"],["client","NostrHub"]],"content":"NIP-5D\n======\n\nNostr Web Applets\n-----------------\n\n`draft` `optional`\n\nThis NIP defines a web projection for sandboxed web applications (\"napplets\") running in iframes to communicate with a hosting application (\"shell\"). Shells expose granted NAP (Nostr Applet Protocol) domains through an injected `window.napplet` namespace. Under that namespace, domain calls are carried over `postMessage` using a generic JSON envelope. Protocol messages are defined by NAP extension specs.\n\n## Philosophy\n\nA napplet is a Nostr applet - a small, focused application that does one thing well. Napplets SHOULD be single-purpose rather than monolithic. A chat widget, a feed viewer, a profile editor, and a relay manager are four napplets, not one application with four tabs. The shell composes napplets; napplets do not compose themselves.\n \n## Terminology\n\n| Term | Definition |\n|------|------------|\n| Shell | Web application hosting napplet iframes |\n| Napplet | Sandboxed iframe application communicating with the shell via postMessage |\n| dTag | Napplet identifier from the napplet manifest `d` tag |\n| Aggregate hash | A napplet's content address — the [NIP-5A](5A.md) aggregate hash of its `path` tags, carried in the manifest `x` tag |\n| Napplet manifest | Nostr event (kind `5129` / `15129` / `35129`) describing a napplet; tag schema adopted from [NIP-5A](5A.md) |\n| NAP | Nostr Applet Protocol -- extension spec defining protocol messages for a capability domain |\n\n## Transport\n\nCommunication uses an injected `window.napplet` namespace backed by `postMessage`. Napplet to shell: `window.parent.postMessage(msg, '*')`. Shell to napplet: `iframeWindow.postMessage(msg, '*')`. The `'*'` target origin is required because napplets have opaque origins (no `allow-same-origin`).\n\nNapplet iframes are loaded via `srcdoc` (see [Identity](#identity)) and MUST use this sandbox attribute:\n\n    sandbox=\"allow-scripts\"\n\nThe `allow-same-origin` token MUST NOT be present. Shells MAY add additional sandbox tokens (`allow-forms`, `allow-modals`, `allow-downloads`, `allow-popups`) based on shell policy. Napplets have no access to `localStorage`, `sessionStorage`, `IndexedDB`, direct WebSocket connections, or signing keys. All storage, signing, encryption, and relay access is proxied through the shell.\n\nThe shell identifies senders via `MessageEvent.source` (unforgeable Window reference). Messages from unknown sources (iframes not created by the shell) MUST be silently dropped.\n\nShells MUST inject `window.napplet` before any napplet script runs, including classic scripts, module scripts, reloads, and development wrappers. The namespace MUST contain only the NAP domain objects the shell exposes to that napplet. Presence of a domain object means that domain is available to the napplet. Absence means unavailable.\n\nShells MUST NOT provide `window.nostr` (NIP-07) to napplet iframes. Signing and encryption are security-critical operations that MUST be mediated by the shell. See the Security Rationale section below.\n\n## Wire Format\n\nAll messages between napplet and shell are JSON objects with a `type` field:\n\n    { \"type\": \"<domain>.<action>\", ...payload }\n\nThe `type` field is a string discriminant in `domain.action` format. Domains correspond to NAP capability names (e.g., a NAP named `foo` owns all `foo.*` types). NAP specs define the valid type strings and payload shapes for their domain. This NIP does not enumerate message types.\n\nExample — a hypothetical `foo` NAP with a request/response pattern:\n\n    { \"type\": \"foo.bar\", \"id\": \"abc\", \"data\": {...} }\n    { \"type\": \"foo.bar.result\", \"id\": \"abc\", \"result\": {...} }\n\nMessages with an unrecognized `type` MUST be silently ignored. This allows forward compatibility as new NAPs are defined.\n\n## Identity\n\nA napplet's identity is the `(dTag, aggregateHash)` tuple. The runtime **computes** it from the napplet's own bytes; it MUST NOT accept it from a host.\n\nBefore creating the iframe, the runtime MUST:\n\n1. Resolve the napplet manifest event (kind `5129`, `15129`, or `35129`, see [Manifest](#manifest)) from relays and verify its signature.\n2. Fetch each `path` blob from Blossom by its sha256 and verify `sha256(blob)` matches the `path` hash.\n3. Recompute the aggregate hash from the `path` tags per [NIP-5A](5A.md); if the manifest carries an `x` tag it MUST match. This is `aggregateHash`.\n4. Inject the verified bytes via `srcdoc` (never a navigated `src`) and map the iframe's `Window` reference to `(dTag, aggregateHash)`.\n\nA gateway MAY serve the bytes as an accelerator or fallback, but the runtime MUST verify its output against the signed manifest — the gateway is never trusted.\n\nIdentity is thus fixed before any code runs and bound to the exact bytes that run. The shell MUST verify `MessageEvent.source` on every inbound message and silently drop messages from Window references not mapped to a napplet.\n\n## Manifest\n\nA napplet is published as a **napplet manifest** event. Its tag schema is adopted from [NIP-5A](5A.md) — `path`, the `x` aggregate tag, `server`, and optional `title` / `description` / `source` — under NIP-5D's own kinds:\n\n| Kind | Type | `d` tag |\n|------|------|---------|\n| `5129` | snapshot (regular) | none |\n| `15129` | root napplet (replaceable) | none |\n| `35129` | named napplet (addressable) | identifier |\n\nDistinct kinds keep napplets out of nsite gateway resolution: a napplet is resolved and verified by the runtime ([Identity](#identity)), never served as an nsite.\n\nA napplet is a single self-contained `/index.html`. The manifest MUST include a `path` tag per file — `[\"path\", \"/index.html\", \"<sha256>\"]` — and `server` tags SHOULD hint the Blossom servers holding those blobs. `aggregateHash` is the [NIP-5A](5A.md) aggregate hash of the `path` tags, carried in the `x` tag (`[\"x\", \"<sha256-hex>\", \"aggregate\"]`). A `5129` snapshot is a regular event pinning a specific napplet version, per [NIP-5A](5A.md).\n\nThe manifest declares required capabilities with `requires` tags:\n\n    [\"requires\", \"<nap-name>\"]\n\nEach value is a bare NAP domain (e.g. `relay`, never `NAP-RELAY`). At load the shell checks `requires` against its capabilities; if one is absent it SHOULD reject the napplet or warn. With no `requires` tags it loads with whatever the shell provides.\n\n### Runtime Domain Availability\n\nNapplets detect runtime domain availability from the injected namespace. If `window.napplet.<domain>` is present, the shell exposes that NAP domain to the napplet. If it is absent, the napplet MUST gracefully degrade or stop using that domain.\n\nDomain object presence is only an availability signal. It does not define the domain's operations, payloads, error model, version support, or semantics. Those contracts remain in the matching NAP specs. If a domain needs semantic compatibility checks, version negotiation, or diagnostics, that domain MUST define them in its own NAP.\n\n## NAP Extension Framework\n\nProtocol messages are defined by [NAP (Nostr Applet Protocol)](https://github.com/napplet/naps) specs. Each NAP owns a message domain and defines the `type` strings, payload shapes, injected domain object behavior, and semantics for that domain. A NAP spec is self-contained — it references this NIP only for web namespace injection, envelope format, and transport.\n\nFor example, a NAP named `foo` would own all `foo.*` message types (e.g., `foo.bar`, `foo.bar.result`) and define their payloads and shell behavior.\n\nNAP specs MUST:\n- Define all valid `type` strings for their domain\n- Specify the payload shape for each message type\n- Document expected shell behavior for each message\n- Be independently implementable — a shell MAY support any subset of NAPs\n\n## Security Considerations\n\nNapplets are untrusted code. The shell is trusted. The browser enforces iframe sandbox boundaries. `MessageEvent.source` provides unforgeable sender identity.\n\n**Mitigations:**\n1. Iframe sandbox: `allow-scripts` is the only required token -- shells MUST NOT add `allow-same-origin`. Adding `allow-same-origin` would grant the napplet a real origin, allowing it to register a service worker, read shell `localStorage`, and bypass shell mediation entirely -- this prohibition is the load-bearing precondition for browser-enforced isolation of any kind.\n2. postMessage `'*'` origin is required for opaque-origin iframes; sender identification uses `MessageEvent.source`, NOT `event.origin`.\n3. Identity binding: the runtime computes `(dTag, aggregateHash)` from the napplet's verified bytes before execution and maps it to the iframe `Window`. `MessageEvent.source` is unforgeable within the same browsing context.\n4. Content-addressed loading: the runtime verifies the manifest signature and each blob's sha256, then recomputes the aggregate from the `path` tags and asserts it equals any `x` tag (see [Identity](#identity)); a napplet failing any check MUST be rejected. Gateways are untrusted — their output is verified against the signed manifest.\n5. Runtime injection is outside the signed napplet artifact. It MUST be limited to the `window.napplet` namespace and MUST NOT change the napplet bytes used to compute `aggregateHash`.\n6. Unrecognized message types are silently ignored, preventing capability probing.\n7. Napplets produce cleartext only. Shells MUST NOT sign or broadcast events containing ciphertext received from a napplet. Shells MUST NOT provide `window.nostr` (NIP-07) or any signing/encryption primitives.\n\nStorage isolation, relay access control, and ACL enforcement are defined by their respective NAP specs.\n\n**Non-Guarantees:** The protocol does NOT protect against a compromised browser, a malicious shell, side-channel attacks, or social engineering.\n\n## References\n\n- [NIP-5A](5A.md) -- manifest tag schema adopted by the napplet manifest\n- [NAPs](https://github.com/napplet/naps) -- NAP domain and message-protocol registry","sig":"fdcc81985df5954cfc9225a7301fe53640eeea8ac3e73e3137850894d3e21d1cec79045fe6c463527703573a25714a807e1d2761c12d88d0cf6bd04274a8aa49"}