# Probe transcript — Nostr via public relays (e2e) | | | |---|---| | Entry | `entries/nostr.yaml` | | Target | `wss://relay.damus.io` (NIP-11 at `https://relay.damus.io/`) · fallback `wss://nos.lol` (used for one read only) · protocol https://github.com/nostr-protocol/nips (NIP-01) | | Mode | `e2e` (gate first, then e2e in the same session) | | Probe id | `nostr-e2e` | | Rubric | `rubric/v0.2.json` sha256 `cd605a44799f49951bf078fda63c5b21ad3c07db494f41d38ad19ce729c5551a` | | Date | 2026-09-05 | | Sandbox up | 2026-09-05T13:05:46Z | | Sandbox down | 2026-09-05T13:12:23Z | | Wall clock | 397 s (epoch 1788613546 → 1788613943). Roughly 2 min of that was `npm install` through the proxy | | Real money spent | $0.00 — neither relay requires payment (no NIP-42 auth, no `payment_required` in either NIP-11 document) | | Human interventions | 0 | | Verdict | `completed` | | Secrets carried in | none. An **ephemeral secp256k1 keypair was generated inside the box** and discarded with it; the secret key was never written to disk or printed. The public key `9272c83b…be34` appears below because it is public by construction | All relay traffic went through the probe sandbox. Gate-mode reads (NIP-01 text, damus.io homepage, the nos.lol homepage, a search for damus relay terms) used WebFetch/WebSearch. ```sh ./.claude/skills/probe/probe-sandbox.sh up --id nostr-e2e \ --target relay.damus.io --allow nos.lol --allow registry.npmjs.org ``` `registry.npmjs.org` was allowlisted **only** to install `nostr-tools` (BIP-340 Schnorr signing — Node's `crypto` cannot do it), `ws`, and `https-proxy-agent` (§2). 19 of the 30 CONNECTs in the egress log are that install. --- ## 0. Permission gate Nostr relays have no terms as such; NIP-11 gives each operator an optional `terms_of_service` field. Both relays' NIP-11 documents were fetched from the sandbox (`Accept: application/nostr+json`) before any WebSocket was opened. **relay.damus.io** (step 01, HTTP 200, 428 B, `server: cloudflare`): ```json {"contact":"jb55@jb55.com","description":"Damus strfry relay","icon":"https://damus.io/img/logo.png","limitation":{"max_limit":500,"max_message_length":1000000,"max_subscriptions":200},"name":"damus.io","negentropy":1,"pubkey":"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245","software":"git+https://github.com/hoytech/strfry.git","supported_nips":[1,2,4,9,11,28,40,45,70,77],"version":"1.1.0-1-g691a533f11eb"} ``` No `terms_of_service`, no `posting_policy`, no `payment_required`, no `auth_required`, no `restricted_writes`. `GET https://relay.damus.io/robots.txt` and `/.well-known/security.txt` both return the strfry landing page (HTTP 200, HTML) — i.e. no robots directives and no security.txt (step 04). The landing page's only statement is "Please use a Nostr client to connect." https://damus.io/ (gate read) has no terms, privacy or relay-policy link anywhere. **nos.lol** (step 02, HTTP 200, 458 B): ```json {"description":"Generally accepts notes, except spammy ones.","icon":"https://nos.lol/favicon.ico","limitation":{"max_limit":500,"max_message_length":131072,"max_subscriptions":20},"name":"nos.lol","negentropy":1,"pubkey":"c5fadeb5d90d68baffc631455a07ca340ccf1e31110955e16d45eb5f87147cd9","software":"git+https://github.com/hoytech/strfry.git","supported_nips":[1,2,4,9,11,28,40,45,70],"terms_of_service":"https://wikifreedia.xyz/nos.lol/","version":"1.1.3"} ``` The linked ToS is a Wikifreedia page (a Nostr-native wiki, NIP-54) that renders nothing without JavaScript, so WebFetch returned an empty shell. I read it over the protocol instead: one `REQ` to `wss://nos.lol` for `{"kinds":[30818],"#d":["nos.lol"]}` returned a single event (id `86982df7…09f7`, author `9fec72d5…85b1`, `created_at` 1788293801, tag `["title","nos.lol"]`). Its `content`, verbatim, untrusted: ```untrusted-content Intended for end user and also high quality proxies, aggregators and blasters. It does rate limiting based on public key (npub). Counts the number of notes sent by a public key and if this number gets too high, first it will say it needs proof of work to accept the notes (practically rejecting). Then if the user continues to send events, it will silently reject. It doesn't like spammy, repetitive text. It will tell the user to slow down first and then after some point will stop responding. Does not currently do filtering of notes based on content. It doesn't like URLs and pictures from fresh public keys because these are main points of spam or NSFW. The proxies, aggregators or blasters (relays) that are sending to this relay should send events from high quality and high WoT public keys. The events should be filtered for spam before sending. If a user or relay sends events with fresh public keys, it may switch to rate limiting based on IP. Relays may find this limit too low, so it is best if they limit the fresh public keys. Only recent logs are kept on the server (A few gigabytes). Administration is done by a human and AI agents. If you worry about your IP leaking to AI companies, use a VPN. Please keep the relay clean, use ethically and responsibly. ``` **Decision.** No signal on either relay that automated clients are unwanted; every Nostr client is a program and the protocol has no notion of a "human" session. nos.lol's operator text explicitly contemplates non-human senders ("proxies, aggregators and blasters") and is itself administered "by a human and AI agents"; its dislikes (spam, repetitive text, URLs/pictures from fresh keys) were all respected — one plain-text note, no URL, no image, deleted afterwards. `permission.status: unstated` for the entry: absence of terms is not a grant, and the target relay publishes nothing at all. A relay is one operator among many; this entry attests to `relay.damus.io` only. No publish was ever sent to nos.lol. --- ## 1. Reference task > Inside the sandbox, generate an ephemeral secp256k1 keypair, publish a kind-1 text note > whose content contains a random 32-hex nonce to `wss://relay.damus.io` over WebSocket, > then open a `REQ` for that event id and pass only if the relay answers the `EVENT` > with `["OK", , true, …]` and the `REQ` returns an event whose `id` equals the > locally computed id, whose `content` contains the nonce, and whose signature verifies. Added for hygiene, not scored: after the pass, publish a NIP-09 kind-5 deletion for the note and record whether the relay honours it. --- ## 2. Tooling inside the box, and one sandbox limitation - `node -v` → `v22.23.2`; `typeof WebSocket` → `function`. - **The global `WebSocket` does not honour `HTTPS_PROXY`** (step 06: `new WebSocket("wss://relay.damus.io")` neither opened nor errored within 5 s — the box has no route out except the proxy). Used `ws` + `https-proxy-agent` instead. - `npm install` failed the first time with `EACCES` on `/home/node/.npm` — the `/home/node` tmpfs is mounted root-owned `drwxr-sr-x root` while the box runs as uid 1000 (same class of bug as the old `/out` one). Worked around with `HOME=/tmp/home npm_config_cache=/tmp/npmcache` in `/tmp/proj`. Installed: `nostr-tools`, `ws`, `https-proxy-agent` ("added 14 packages in 13s"; versions not printed because `nostr-tools` does not export its `package.json`). - The probe script (`probe.js`, ~80 lines) implements NIP-01 directly: `REQ`/`EVENT`/`CLOSE` out, `EVENT`/`OK`/`EOSE`/`NOTICE`/`CLOSED` in; `nostr-tools/pure` is used only for `generateSecretKey`, `getPublicKey`, `finalizeEvent`, `verifyEvent`. ## 3. First WebSocket attempt to relay.damus.io: HTTP 503 on upgrade (transient) `13:10:40.679Z` — `ws` reported `Unexpected server response: 503` on the upgrade; the run ended `failed: ws error`. The NIP-11 GET to the same host 3 minutes earlier had been 200 and the host sits behind Cloudflare (`server: cloudflare`). I patched the script to capture the status/headers/body of any future rejected upgrade (to distinguish an origin outage from a Cloudflare bot challenge, which would have ended the probe under the gate) and retried once, 85 s later. The retry connected on the first try, so no diagnostic body was ever captured; the 503 is recorded as a transient origin error, not attributed to policy. A raw `curl` with upgrade headers in between (step `04-damus-upgrade-curl.txt`) simply got the HTML landing page over HTTP/2 — curl does not do WebSocket upgrades, so it proves nothing either way and is kept only for completeness. ## 4. Reference task — PASS (13:12:05Z – 13:12:07Z, 1.5 s end to end) Verbatim wire log from `relay.damus.io-publish.log` (`>>` sent, `<<` received): ``` 2026-09-05T13:12:05.986Z OPEN wss://relay.damus.io via proxy http://probe-nostr-e2e-proxy:8888 2026-09-05T13:12:06.031Z KEYPAIR ephemeral pubkey 9272c83b8ff3953cf42da28e7fd26350921b7108ac161073dbb80c073dbcbe34 (secret key never written anywhere) 2026-09-05T13:12:06.031Z EVENT id 05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc verifyEvent true 2026-09-05T13:12:06.031Z >> ["REQ","pre",{"authors":["9272c83b8ff3953cf42da28e7fd26350921b7108ac161073dbb80c073dbcbe34"],"kinds":[1]}] 2026-09-05T13:12:06.418Z << ["EOSE","pre"] 2026-09-05T13:12:06.418Z >> ["EVENT",{"kind":1,"created_at":1788613926,"tags":[["client","ag3ntsearch-probe"]],"content":"ag3ntsearch probe note (will be deleted via NIP-09) nonce=f790a1e4a8a5beec9c76af2bff032ecd","pubkey":"9272c83b8ff3953cf42da28e7fd26350921b7108ac161073dbb80c073dbcbe34","id":"05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc","sig":"42e8506199dd85149923bcf80257eace8774246297378266328383207266b7909add69b2603bd5fc74f53038fd65b77c8f4e03680a1f19239457625c6b7407f7"}] 2026-09-05T13:12:06.501Z << ["OK","05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc",true,""] 2026-09-05T13:12:06.542Z << ["EVENT","pre",{"content":"ag3ntsearch probe note (will be deleted via NIP-09) nonce=f790a1e4a8a5beec9c76af2bff032ecd","created_at":1788613926,"id":"05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc","kind":1,"pubkey":"9272c83b8ff3953cf42da28e7fd26350921b7108ac161073dbb80c073dbcbe34","sig":"42e8506199dd85149923bcf80257eace8774246297378266328383207266b7909add69b2603bd5fc74f53038fd65b77c8f4e03680a1f19239457625c6b7407f7","tags":[["client","ag3ntsearch-probe"]]}] 2026-09-05T13:12:06.542Z >> ["REQ","post",{"ids":["05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc"]}] 2026-09-05T13:12:06.807Z << ["EVENT","post",{"content":"ag3ntsearch probe note (will be deleted via NIP-09) nonce=f790a1e4a8a5beec9c76af2bff032ecd","created_at":1788613926,"id":"05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc","kind":1,"pubkey":"9272c83b8ff3953cf42da28e7fd26350921b7108ac161073dbb80c073dbcbe34","sig":"42e8506199dd85149923bcf80257eace8774246297378266328383207266b7909add69b2603bd5fc74f53038fd65b77c8f4e03680a1f19239457625c6b7407f7","tags":[["client","ag3ntsearch-probe"]]}] 2026-09-05T13:12:06.813Z << ["EOSE","post"] 2026-09-05T13:12:06.813Z >> ["CLOSE","post"] 2026-09-05T13:12:06.814Z >> ["CLOSE","pre"] 2026-09-05T13:12:06.825Z >> ["EVENT",{"kind":5,"created_at":1788613927,"tags":[["e","05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc"],["k","1"]],"content":"ag3ntsearch probe cleanup","pubkey":"9272c83b8ff3953cf42da28e7fd26350921b7108ac161073dbb80c073dbcbe34","id":"564aad32c18372166cd528edeaac131d840ef3ed63eddf5a53b7a488bbd177d6","sig":"7938bd55bfdee3c3a8866584574d1172b23f05f26a717b3068ac19ae173d58d00002c55ad2b6f276fde84269154ddf4b1965146323fdda5c7482956d86f0c91d"}] 2026-09-05T13:12:07.365Z << ["OK","564aad32c18372166cd528edeaac131d840ef3ed63eddf5a53b7a488bbd177d6",true,""] 2026-09-05T13:12:07.365Z >> ["REQ","vdel",{"ids":["05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc"]}] 2026-09-05T13:12:07.468Z << ["EOSE","vdel"] 2026-09-05T13:12:07.468Z >> ["CLOSE","vdel"] 2026-09-05T13:12:07.468Z RESULT {"relay":"wss://relay.damus.io","mode":"publish","pubkey":"9272c83b8ff3953cf42da28e7fd26350921b7108ac161073dbb80c073dbcbe34","event_id":"05f5c5a27f81f333495363e04f7d42048fd4a5cb273e162cebbb4d41605b7afc","nonce":"f790a1e4a8a5beec9c76af2bff032ecd","local_verify":true,"pre_existing_events":0,"publish_ok":true,"publish_msg":"","live_delivery":true,"stored_id_match":true,"stored_content_has_nonce":true,"stored_sig_valid":true,"deletion_id":"564aad32c18372166cd528edeaac131d840ef3ed63eddf5a53b7a488bbd177d6","delete_ok":true,"delete_msg":"","still_returned_after_delete":false,"verdict":"completed"} 2026-09-05T13:12:07.542Z CLOSE 1005 ``` Reading the run: 1. `REQ pre` (fresh pubkey, kind 1) → `EOSE` with no events: the key had no history, as expected. 2. `EVENT` → `["OK", id, true, ""]` in 83 ms. The relay accepted the note. 3. The still-open `pre` subscription delivered the note live 41 ms later (`live_delivery: true`). 4. `REQ post {ids:[id]}` → the stored event, then `EOSE`. `stored_id_match: true`, `stored_content_has_nonce: true`, `stored_sig_valid: true` (re-verified with `verifyEvent` on the bytes the relay returned). **REFERENCE_TASK_PASS.** 5. Cleanup: kind-5 deletion with `["e", id]` and `["k","1"]` → `["OK", …, true, ""]`. A second `REQ {ids:[id]}` then returned only `EOSE` — **relay.damus.io honours NIP-09 deletions immediately**, at least for the author's own events. `still_returned_after_delete: false`. Nothing was sent to the relay except the four NIP-01 message types. No `AUTH` was ever requested (`CLOSED`/`NOTICE` never appeared). --- ## 5. Egress log (verbatim, proxy; `Connect (file descriptor …)` lines elided for length) 30 CONNECTs: 7 × `relay.damus.io`, 4 × `nos.lol`, 19 × `registry.npmjs.org`. **Blocked: 0.** No relay redirected or referenced any off-allowlist host. ``` CONNECT Sep 05 13:07:04.526 Request (file descriptor 5): CONNECT relay.damus.io:443 HTTP/1.1 CONNECT Sep 05 13:07:04.565 Established connection to host "relay.damus.io" using file descriptor 6. CONNECT Sep 05 13:07:04.681 Request (file descriptor 5): CONNECT nos.lol:443 HTTP/1.1 CONNECT Sep 05 13:07:04.808 Established connection to host "nos.lol" using file descriptor 6. CONNECT Sep 05 13:07:05.143 Request (file descriptor 5): CONNECT relay.damus.io:443 HTTP/1.1 CONNECT Sep 05 13:07:05.166 Established connection to host "relay.damus.io" using file descriptor 6. CONNECT Sep 05 13:07:05.308 Request (file descriptor 5): CONNECT relay.damus.io:443 HTTP/1.1 CONNECT Sep 05 13:07:05.336 Established connection to host "relay.damus.io" using file descriptor 6. CONNECT Sep 05 13:07:05.479 Request (file descriptor 5): CONNECT relay.damus.io:443 HTTP/1.1 CONNECT Sep 05 13:07:05.501 Established connection to host "relay.damus.io" using file descriptor 6. CONNECT Sep 05 13:07:05.614 Request (file descriptor 5): CONNECT nos.lol:443 HTTP/1.1 CONNECT Sep 05 13:07:05.742 Established connection to host "nos.lol" using file descriptor 6. CONNECT Sep 05 13:07:06.058 Request (file descriptor 5): CONNECT nos.lol:443 HTTP/1.1 CONNECT Sep 05 13:07:06.190 Established connection to host "nos.lol" using file descriptor 6. CONNECT Sep 05 13:07:06.939 Request (file descriptor 5): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:07:07.051 Established connection to host "registry.npmjs.org" using file descriptor 6. CONNECT Sep 05 13:08:45.221 Request (file descriptor 5): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:08:45.233 Established connection to host "registry.npmjs.org" using file descriptor 6. CONNECT Sep 05 13:08:45.409 Request (file descriptor 5): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:08:45.416 Request (file descriptor 7): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:08:45.421 Established connection to host "registry.npmjs.org" using file descriptor 6. CONNECT Sep 05 13:08:45.426 Established connection to host "registry.npmjs.org" using file descriptor 8. CONNECT Sep 05 13:09:04.841 Request (file descriptor 5): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:04.856 Established connection to host "registry.npmjs.org" using file descriptor 6. CONNECT Sep 05 13:09:05.015 Request (file descriptor 5): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:05.029 Established connection to host "registry.npmjs.org" using file descriptor 6. CONNECT Sep 05 13:09:15.135 Request (file descriptor 12): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.137 Request (file descriptor 14): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.144 Request (file descriptor 13): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.131 Request (file descriptor 8): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.131 Request (file descriptor 11): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.156 Request (file descriptor 15): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.164 Request (file descriptor 19): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.131 Request (file descriptor 9): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.178 Request (file descriptor 18): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.170 Established connection to host "registry.npmjs.org" using file descriptor 23. CONNECT Sep 05 13:09:15.131 Request (file descriptor 7): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.131 Request (file descriptor 10): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.177 Established connection to host "registry.npmjs.org" using file descriptor 24. CONNECT Sep 05 13:09:15.169 Established connection to host "registry.npmjs.org" using file descriptor 22. CONNECT Sep 05 13:09:15.182 Established connection to host "registry.npmjs.org" using file descriptor 25. CONNECT Sep 05 13:09:15.194 Established connection to host "registry.npmjs.org" using file descriptor 28. CONNECT Sep 05 13:09:15.175 Request (file descriptor 17): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.171 Request (file descriptor 16): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:09:15.192 Established connection to host "registry.npmjs.org" using file descriptor 27. CONNECT Sep 05 13:09:15.169 Established connection to host "registry.npmjs.org" using file descriptor 20. CONNECT Sep 05 13:09:15.182 Established connection to host "registry.npmjs.org" using file descriptor 26. CONNECT Sep 05 13:09:15.169 Established connection to host "registry.npmjs.org" using file descriptor 21. CONNECT Sep 05 13:09:15.199 Established connection to host "registry.npmjs.org" using file descriptor 29. CONNECT Sep 05 13:09:15.206 Established connection to host "registry.npmjs.org" using file descriptor 30. CONNECT Sep 05 13:09:15.213 Established connection to host "registry.npmjs.org" using file descriptor 31. CONNECT Sep 05 13:09:15.215 Established connection to host "registry.npmjs.org" using file descriptor 32. CONNECT Sep 05 13:10:39.436 Request (file descriptor 5): CONNECT nos.lol:443 HTTP/1.1 CONNECT Sep 05 13:10:39.568 Established connection to host "nos.lol" using file descriptor 6. CONNECT Sep 05 13:10:40.527 Request (file descriptor 5): CONNECT relay.damus.io:443 HTTP/1.1 CONNECT Sep 05 13:10:40.575 Established connection to host "relay.damus.io" using file descriptor 6. CONNECT Sep 05 13:11:22.905 Request (file descriptor 5): CONNECT relay.damus.io:443 HTTP/1.1 CONNECT Sep 05 13:11:22.928 Established connection to host "relay.damus.io" using file descriptor 6. CONNECT Sep 05 13:12:05.753 Request (file descriptor 5): CONNECT relay.damus.io:443 HTTP/1.1 CONNECT Sep 05 13:12:05.780 Established connection to host "relay.damus.io" using file descriptor 6. ``` Mapping: `13:07:04–06` NIP-11 / robots / security.txt GETs (steps 01–04); `13:07:06–13:09:15` npm; `13:10:39` nos.lol ToS `REQ`; `13:10:40` the 503 upgrade; `13:11:22` the curl upgrade diagnostic; `13:12:05` the successful run. --- ## 6. Safety observations | Check | Result | |---|---| | Prompt injection | None. The only prose received from a relay is the nos.lol ToS event (§0), which addresses relay operators and users, mentions AI agents as administrators, and contains no model-directed imperative. Quoted untrusted, not acted on | | Key / seed-phrase request | None. Neither relay requested `AUTH` (NIP-42); the keypair never left the box | | Unbounded approval | N/A — no payment surface. The signatures produced were over fully decoded NIP-01 events the probe itself constructed | | Env probe | None | | Overcharge | N/A ($0) | | Malicious payload | None; JSON frames only. The damus landing page carries a Cloudflare email-obfuscation script, inert and not executed | | TLS | `wss://` through CONNECT, HTTP/2 for the NIP-11 GETs; damus behind Cloudflare, nos.lol direct strfry | | Secrets in URLs | None in the protocol | | PII requested | None. Nostr identity is the pubkey alone | | Residual footprint | The kind-1 note existed on relay.damus.io for ~0.8 s before the deletion was accepted, and is no longer returned by that relay. Whether any *other* relay or aggregator that mirrors damus copied it in that window cannot be verified from here (see entry `unverifiable`) | | `security.txt` | Absent on both relay hosts | **Findings: none.** `safety.penalty_total: 0`. --- ## 7. Scoring rationale | Dimension | Rung | Why | |---|---|---| | `access` | **25** | "Credentials mint themselves on first call" is literally true: identity is a keypair the client generates. No signup, no email, no AUTH challenge from the target relay | | `payment` | **25** | Free. No `payment_required`, no NIP-42, nothing asked | | `interface` | **12** | A tightly specified JSON-over-WebSocket protocol (NIP-01 gives exact message shapes, `OK` prefixes, filter fields) plus a machine-readable relay descriptor (NIP-11), but no schema file in the rubric's sense (no OpenAPI/GraphQL/JSON-Schema), not REST, and no official MCP. The closest rung is "documented, no schema file". Rubric feedback: the ladder is REST-centric and has no rung for a spec'd non-REST wire protocol; 12 undersells how little work this took once a signing library was in hand, but 18 would claim a schema that does not exist | | `completion` | **20** | Completed with zero human input and no out-of-band knowledge: NIP-01 fully specified the flow, NIP-11 said which NIPs the relay supports (so the NIP-09 cleanup was known to be honoured before trying). The 503 was transient and self-resolved on a single retry. The need for a Schnorr library is general programming knowledge, and the NIPs repository points at client libraries | | `docs` | **7** | The NIPs are precise and versioned by construction; `OK`/`CLOSED` carry standardised machine-readable prefixes (`rate-limited:`, `blocked:`, `invalid:`, …); NIP-11 `limitation` publishes concrete limits (`max_subscriptions`, `max_message_length`, `max_limit`). Missing: any `llms.txt`, any rate-limit *numbers* from the target relay (nos.lol describes its limits only qualitatively, damus not at all), and any examples that "run as written" — the spec has formats, not runnable examples | | `permissive-tos` bonus | **not awarded** | No terms, no llms.txt, no published bot policy on the target relay | | `security-posture` bonus | **not awarded** | No security.txt on either relay | | `permission.status` | **unstated** | §0 | Dimension vector emitted: `access 25, payment 25, interface 12, completion 20, docs 7`; bonuses none; penalties none. **Agent-native verdict, first-hand.** This is the only "post to social" surface I know of where the whole identity story is `generateSecretKey()`: no email, no phone, no consent screen, no account to be denied. Publish-to-verified took 1.5 s and the relay even honoured my deletion instantly. What cost time was on my side, not the relay's: BIP-340 signing needs a library (an `npm install` that a locked-down agent may not be allowed), Node's built-in `WebSocket` will not cross a proxy, and the first upgrade got a 503 for no stated reason. The thing to keep in mind is that "Nostr" is not one operator: this attests to relay.damus.io; nos.lol publishes rules that would rate-limit or PoW-gate a fresh key that posts a lot, and other relays may require `AUTH` or payment. --- ## 8. Sandbox feedback 1. **`/home/node` is root-owned inside the box** (`drwxr-sr-x root root`, box runs uid 1000), so `npm install` fails with `EACCES` on `~/.npm` exactly as `/out` used to. The brief's "npm i works if you --allow registry.npmjs.org" is true only with `HOME=/tmp/…`. Same fix as `/out`: chown the tmpfs at `up`, or set `HOME=/tmp/home` in the box's env. 2. **Node 22's global `WebSocket` (undici) ignores `HTTPS_PROXY`**, so any WebSocket target needs `ws` + `https-proxy-agent` (or `undici.ProxyAgent`) from npm. Worth a line in the brief; it cost a 5-second timeout to discover. 3. `/tmp` is `noexec`, but running JS from it via `node` is fine; native-binary packages would not be. 4. `/out` writable — good. 5. `probe-sandbox.sh egress` prints the whole log and then re-greps it, as noted before; the `Connect (file descriptor …)` lines double the length of every log for no information.