Skip to content
nappletPublic

About

a home for evil napplets

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

evil-napplets

A NIP-5D / napplet red team. A suite of deliberately hostile napplets whose job is to break out of — or prove the integrity of — Nostr applet runtimes. Every napplet here is a legitimate, spec-shaped NIP-5D artifact (sandbox="allow-scripts", opaque origin, srcdoc loading) that attacks its host the way a real adversary would, so runtimes can find and fix their gaps before an adversary does.

Each napplet targets one attack class, ordered from the lowest-hanging fruit (misconfigured or lazy runtimes) up to attacks that exploit loopholes in the NIP-5D specification itself (hardened runtimes). See ROADMAP.md.

Rules of engagement

Demo by default. A loaded evil napplet only probes and reports. It proves each attack primitive is available — with masked, non-actionable evidence — and then stops:

  • no writes outside its own frame (demo mode may write one immediately-cleaned marker key to prove storage access, nothing more)
  • no network egress (no fetch, no WebSocket, no beacons, no popups)
  • no signing, no navigation, no persistence, no destruction (one exception: detonation rows — see below)

Detonation rows. Attacks that prove their primitive by replacing the napplet's own document (autoRun: false, evil-09) never fire in the auto-run sweep — the operator clicks DETONATE. The replacement is in-frame only: unverified bytes loaded into the same Window (a data: URL, or a same-host network target — harness/replace-target.html — standing in for the attacker's server), no egress, no writes. The probe UI dies with the verified document by design; reload the napplet from the harness to restore it.

"VERY EVIL" mode. Each napplet can be armed from inside the napplet. Arming requires a stern confirmation dialog that lists every armed attack and exactly what executing it will do — payload, destination, and destructive consequences — and a typed confirmation (EXECUTE EVIL, case-sensitive). Only after that does it actually execute the evil payloads, in a safe order (destructive payloads last), logging what happened as evidence.

Default exfiltration destination is https://evil.example.invalid/collect — a non-resolving RFC-2606 host. Even in VERY EVIL mode nothing leaves the machine; operators point it at their own collector to test real egress.

Not a weapon. These are QA tools for runtime authors. The evil payloads are deliberately loud (marker keys, marker notes, visible defacement banner) rather than quiet. They demonstrate harm; they don't optimize for it.

The ladder

Tier Target Napplets
0 Baseline misconfig — poorly implemented runtimes evil-01 sandbox escape, evil-02 CSP / network egress
1 Transport confusion — lazy runtimes evil-03 envelope forging & reply injection
2 Manifest & identity confusion evil-04 manifest confusion, evil-05 Nostr-level substitution
3 Side channels & spec loopholes — hardened runtimes evil-06…evil-09

All nine rungs are built and validated — 104 automated checks against real Chromium (tests/). One napplet per rung, lowest fruit first. Per-rung status and validated findings live in ROADMAP.md.

Each rung is a self-contained single-file NIP-5D artifact (napplets/<id>/index.html) you can srcdoc into any runtime, or into the included victim harness. Artifacts are built from src/ (shared chrome + per-napplet attack definitions) via node build.mjs.

Running locally

python3 -m http.server 8787          # from the repo root
# open http://localhost:8787/harness/

The harness is a deliberately misconfigurable victim shell — every knob re-arms a different subset of the attacks:

  • runtime preset — spec (sandbox="allow-scripts" + CSP), same-origin (+allow-same-origin), no-sandbox (no sandbox attr) — arms the evil-01 escape tree; spec is the green gate your runtime's CI should hold.
  • identity binding — window (pre-#14 NIP-5D: the shell maps the iframe Window to (dTag, artifactHash) — a Window survives same-frame navigation, which arms the evil-09 self-replacement inheritance) vs port (post-#14: the injected bootstrap hands a MessageChannel port over once via napplet.hello; the shell revokes identity, grants, and the iframe on a second hello, a second load, post-handoff window-level NAP, or the port closing — the green gate evil-09 must not survive). Port mode is strict: legacy napplets that speak window-level NAP get dropped and the frame torn down, exactly as the fixed spec demands.
  • CSP meta — on/off. Off arms every evil-02 egress channel; on holds them (except WebRTC — and the grandchild-frame fresh realm — the spec gaps, see ROADMAP).
  • nested-context lock — on/off. The kehto/web#393-style prevention gate for the evil-02 grandchild-frame WebRTC vector (kehto/web#381): a Trusted Types default policy (canonical HTML passes through, frame-tag strings are rejected — closes innerHTML/outerHTML/DOMParser/createContextualFragment/ setHTMLUnsafe/srcdoc/document.write), pinned element factories (createElement/createElementNS/DOMImplementation.createDocument non-configurable, frame tag names denied), and a fail-closed byte scan of the verified artifact before load. Off — the battery arms (fresh grandchild realms report a live RTCPeerConnection); on — zero nested contexts minted, window.length stays 0. tests/evil-02.spec.mjs gates both paths.
  • shell window.nostr — a fake NIP-07 extension, arms ambient-signing probes.
  • sibling iframe — arms parent.length enumeration.
  • fake NAP runtime — injects a window.napplet.storage domain (pre-script, outside the hashed artifact bytes, like a real runtime) with a selectable namespace policy: by-(dTag+hash) (spec-safe), by-dTag (version substitution leaks), or global (everything shared) — arms the evil-05 inheritance attacks, evil-06 oracles, and evil-03 router attacks.
  • trust DECLARED identity — a sloppy message router that trusts envelope windowId/dTag fields over MessageEvent.source — arms the evil-03 spoof.
  • launcher — safe (textContent) vs unsafe (innerHTML) manifest-metadata render — arms the evil-04 shell-origin XSS end to end.

It also seeds victim canaries (nsec marker in localStorage, inherited v1 secrets per namespace policy, and a per-load __evil09_victim__ grant seed), echoes evil.ping with a shell-side wall clock (so frames can measure shell starvation), logs every ACCEPTED/DROPPED/REVOKED message, and dumps the runtime store.

Repo layout

src/
  chrome.template.html                # shared napplet chrome: probes UI, arm gate, report
  napplets/<id>/{attacks.js,meta.json} # the ONLY per-napplet code
build.mjs                             # assembles src → napplets/<id>/index.html
napplets/
  <id>/index.html                     # built single-file NIP-5D artifacts (committed)
harness/
  index.html                          # configurable victim shell
  replace-target.html                 # evil-09 network-replacement fixture (attacker-page stand-in)
tests/
  _helper.mjs + evil-0[1-9].spec.mjs   # 95 Playwright checks vs real Chromium
ROADMAP.md                            # attack ladder + validated NIP-5D spec findings

Evil napplets use their own dark palette (a red-team tool must stay legible even in a hostile runtime) and the napplet UI contract otherwise: no title header, tool density, usable at any frame size.

About

a home for evil napplets

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages