NAP-LINK / PR #53
Shell-Mediated Link Opening
Full specification from the fetched upstream revision ↗. The document below is rendered from its original Markdown. Follow the latest source on GitHub ↗.
NAP-LINK
Shell-Mediated Link Opening
draft
NAP ID: NAP-LINK
Domain: link
Depends: none.
Web binding (NIP-5D): window.napplet.link; domain presence signals availability.
Description
NAP-LINK lets a sandboxed napplet ask the shell to open an external URL for the user. The shell owns the navigation, policy, prompting, and browser context. The napplet never gets direct navigation authority, network access, opener access, or fetched bytes.
This is for user navigation, including links to pages that cannot be opened correctly inside the napplet sandbox because of Cross-Origin-Opener-Policy (COOP), frame, popup, or cross-origin restrictions. It is not a fetch API. Use NAP-RESOURCE when the napplet needs bytes returned. Parsers that turn external URLs into clickable UI SHOULD route those clicks to link.open.
API Surface
| Operation | Parameters | Result | Wire |
|---|---|---|---|
open |
url (text), optional options (LinkOpenOptions) |
LinkOpenResult |
link.open / link.open.result |
Schemas
LinkOpenOptions:
| Field | Type | Required | Description |
|---|---|---|---|
label |
text |
no | Human-readable prompt text supplied by the napplet. |
LinkOpenResult:
| Field | Type | Required | Description |
|---|---|---|---|
status |
"opened" or "denied" |
yes | Whether the shell accepted or refused the navigation request. |
error |
text |
no | Reason when status is "denied"; same value as the wire field; absent when status is "opened". |
open(url, options?) -- Requests that the shell open url for the user. url MUST be an absolute URL. label is optional human-readable text for the shell prompt; it is not trusted policy input. Resolves with a LinkOpenResult: status is "opened" when the shell accepted the request and handed it to the user agent, or "denied" when user or shell policy refused it; error, when present on the wire, is passed through unchanged.
Wire Protocol
link.* messages use the NIP-5D wire format ({ "type": "domain.action", ...payload }).
| Type | Direction | Payload fields |
|---|---|---|
link.open |
napplet -> shell | id, url, options? |
link.open.result |
shell -> napplet | id, status, error? |
Key design notes:
idcorrelates the request and result.link.openis user navigation, not resource retrieval.- The shell chooses the final target: new tab, external browser, in-shell browser, native handler, or denial.
- The shell MUST NOT expose
window.openeror equivalent authority back to the napplet.
Examples
Open an external page:
-> { "type": "link.open", "id": "l1", "url": "https://example.com/post/123", "options": { "label": "Read post" } }
<- { "type": "link.open.result", "id": "l1", "status": "opened" }
Denied by policy:
-> { "type": "link.open", "id": "l2", "url": "file:///etc/passwd" }
<- { "type": "link.open.result", "id": "l2", "status": "denied", "error": "unsupported-scheme" }
Error Handling
Result messages MAY include error when status is "denied". Common errors are "invalid-url", "unsupported-scheme", "blocked-by-policy", and "user-denied".
Shell Behavior
- The shell MUST validate
urlbefore opening it. - The shell MUST reject relative URLs.
- The shell MUST reject
javascript:,data:,blob:,file:, and other local or code-bearing schemes. - The shell MUST respond to every request with a result message carrying the same
id. - The shell SHOULD open web links in a context outside the napplet sandbox.
- The shell SHOULD use
noopeneror an equivalent opener-isolation mechanism. - The shell MAY prompt the user before opening a link.
- The shell MAY persist user choices per napplet, per hostname, or per URL.
- The shell MAY support non-web schemes such as
nostr:ormailto:by policy.
Prompt choices MAY include:
- allow once
- allow all links from this napplet
- allow links to this hostname from this napplet
- deny once
- deny all links from this napplet
Security Considerations
- Opening a link is a user-visible action. Shells SHOULD require a user gesture or prompt unless policy already allows the request.
- Prompts SHOULD show the napplet name, stable napplet identifiers, author when known, and destination URL or hostname.
- Shells SHOULD rate-limit denied or repeated link requests to prevent prompt spam.
- Shells SHOULD treat IDN, punycode, redirects, and lookalike hostnames as phishing risks in prompt UI.
labelis napplet-supplied text. Shells MUST NOT use it as proof of destination or intent.- NAP-LINK does not grant direct network access. A napplet that needs direct fetch, WebSocket, SSE, or custom headers needs a different capability.
Implementations
- (none yet)
Changelog
88a3431- Introduced NAP-LINK as a shell-owned external link opener for sandboxed napplets.b40b51e- Adopted injected-domain availability and linked the current upstream web binding.59f3090- Exposed optional denial reasons in theopenresult.