Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 5d0d41d1b31f0b9e20b04ba8273cfab6b672a2fa)
+++ src/services/ActivityPubService.js	(revision 0bafe9ee7651334d1e5056f6b9d35b521c2462f2)
@@ -1295,17 +1295,28 @@
   // Geen kip-ei: om ONZE handtekening te controleren haalt de andere kant ons
   // actor-document op, en dat serveert Klonkt publiek.
-  if (opts.asSlug) {
-    const signed = await signedGetJson(opts.asSlug, url).catch(() => null);
-    if (signed && signed.id) return signed;
-    // Geen terugkeer bij mislukking: een instance zonder secure mode moet
-    // blijven werken, en niet elke 401 komt van authorized fetch.
-  }
+  //
+  // ONBETEKEND EERST, en dat is een veiligheidskeuze en geen optimalisatie.
+  // verifyRequest haalt de keyId-URL op VOORDAT er iets geverifieerd is, en die
+  // URL komt uit een header die iedereen mag sturen. Tekenden we dat verzoek
+  // standaard, dan kan een volslagen onbekende ons een ONDERTEKEND verzoek laten
+  // sturen naar een adres van zijn keuze -- met onze identiteit eronder. Dat is
+  // precies hoe een instance op een blocklist belandt. Ondertekenen doen we dus
+  // pas als het onbetekend niet lukt, en dan alleen voor deze ene URL.
+  let doc = null;
   try {
     const r = await safeFetch(url, { headers: { Accept: 'application/activity+json' } });
-    if (!r.ok) return null;
-    const len = Number(r.headers.get('content-length') || 0);
-    if (len > 2_000_000) return null; // refuse oversized actor docs
-    return await r.json();
-  } catch { return null; }
+    if (r.ok) {
+      const len = Number(r.headers.get('content-length') || 0);
+      if (len > 2_000_000) return null; // refuse oversized actor docs
+      doc = await r.json();
+    }
+  } catch { /* val door naar de ondertekende poging */ }
+  // Genoeg? Dan klaar. Sommige instances serveren onbetekend wel een document
+  // maar zonder sleutel; voor een verificatie hebben we daar niets aan, dus die
+  // telt als mislukt.
+  if (doc && (!opts.asSlug || (doc.publicKey && doc.publicKey.publicKeyPem))) return doc;
+  if (!opts.asSlug) return doc;
+  const signed = await signedGetJson(opts.asSlug, url).catch(() => null);
+  return (signed && signed.id) ? signed : doc;
 }
 
Index: test/authorized-fetch.test.js
===================================================================
--- test/authorized-fetch.test.js	(revision 5d0d41d1b31f0b9e20b04ba8273cfab6b672a2fa)
+++ test/authorized-fetch.test.js	(revision 0bafe9ee7651334d1e5056f6b9d35b521c2462f2)
@@ -66,19 +66,39 @@
 });
 
-test('mislukt ondertekend ophalen, dan volgt de onbetekende poging alsnog', async () => {
-  // Niet elke 401 komt van secure mode, en een instance die geen handtekening
-  // verwacht mag er niet door stukgaan. OPEN_ACTOR antwoordt op allebei, dus we
-  // meten dat er ECHT twee pogingen zijn wanneer de eerste niets oplevert.
+test('een OPEN instance wordt NIET ondertekend opgehaald', () => {
+  // De veiligheidskant van shaer-afq: verifyRequest haalt de keyId-URL op
+  // voordat er iets geverifieerd is, en die URL komt uit een header die iedereen
+  // mag sturen. Tekenden we standaard, dan kan een vreemde ons een ondertekend
+  // verzoek naar een adres van zijn keuze laten sturen, met onze identiteit
+  // eronder. Onbetekend eerst dus, en alleen tekenen als het anders niet lukt.
   verzoeken = [];
+  return AP.fetchActor(OPEN_ACTOR, { asSlug: 'me' }).then((actor) => {
+    assert.ok(actor);
+    assert.equal(verzoeken.length, 1, 'één poging, geen tweede');
+    assert.equal(verzoeken[0].ondertekend, false, 'en die was onbetekend');
+  });
+});
+
+test('een document zonder sleutel telt als mislukt en leidt tot een ondertekende poging', async () => {
+  // Sommige instances serveren onbetekend wel iets, maar zonder publicKey. Voor
+  // een verificatie hebben we daar niets aan.
+  const KAAL = 'https://203.0.113.50/users/kaal';
   const stubOrig = globalThis.fetch;
-  let eerste = true;
   globalThis.fetch = async (url, opts = {}) => {
     const ondertekend = !!(opts.headers && (opts.headers.Signature || opts.headers.signature));
-    if (String(url) === OPEN_ACTOR && ondertekend && eerste) { eerste = false; return new Response('nee', { status: 500 }); }
+    if (String(url) === KAAL) {
+      verzoeken.push({ url: String(url), ondertekend });
+      const body = ondertekend
+        ? { id: KAAL, type: 'Person', publicKey: { id: `${KAAL}#k`, owner: KAAL, publicKeyPem: 'x' } }
+        : { id: KAAL, type: 'Person' };   // kaal: geen sleutel
+      return new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/activity+json' } });
+    }
     return stubOrig(url, opts);
   };
-  const actor = await AP.fetchActor(OPEN_ACTOR, { asSlug: 'me' });
+  verzoeken = [];
+  const actor = await AP.fetchActor(KAAL, { asSlug: 'me' });
   globalThis.fetch = stubOrig;
-  assert.ok(actor, 'de terugval hoort hem alsnog op te halen');
+  assert.ok(actor.publicKey && actor.publicKey.publicKeyPem, 'de ondertekende poging levert de sleutel');
+  assert.deepEqual(verzoeken.map((v) => v.ondertekend), [false, true], 'eerst onbetekend, daarna pas ondertekend');
 });
 
