Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision f4f0a6d2b553fe600016875f0c5789076851f2a1)
+++ src/services/ActivityPubService.js	(revision bedd7a6cac5de300b43353212b411a1f4777d295)
@@ -1726,11 +1726,19 @@
 
 async function dereferenceForwarded(act, claimedActor, type, slugParam) {
-  if (type !== 'Create' && type !== 'Update') return null;
+  // Every exit states its reason. Five of the six used to return silently, so a
+  // rejection count could not be told apart from a narrowing that closed too far
+  // — and that is exactly the measurement shaer-drf is waiting for. Bounded by
+  // the signer-mismatch rate (tens per hour), so this is not a noisy log.
+  const skipped = (reason, detail) => {
+    console.log(`[AP] inbox forwarded, skipped (${reason}):`, claimedActor, detail || '');
+    return null;
+  };
+  if (type !== 'Create' && type !== 'Update') return skipped('not Create/Update', type);
   const o = act && act.object;
   const objId = typeof o === 'string' ? o : (o && o.id);
-  if (!objId || typeof objId !== 'string' || !/^https:\/\//i.test(objId)) return null;
-  try {
-    if (new URL(objId).host !== new URL(claimedActor).host) return null;   // ankereis
-  } catch { return null; }
+  if (!objId || typeof objId !== 'string' || !/^https:\/\//i.test(objId)) return skipped('no https object id', objId || '(none)');
+  try {
+    if (new URL(objId).host !== new URL(claimedActor).host) return skipped('host anchor', objId);   // ankereis
+  } catch { return skipped('unparsable id', objId); }
   // Alleen dereferencen als het object beweert een antwoord te zijn op iets van
   // ONS (shaer-drf). Zonder die eis zijn claimedActor en object.id allebei door
@@ -1742,22 +1750,30 @@
     ? (typeof o.inReplyTo === 'string' ? o.inReplyTo : (o.inReplyTo && o.inReplyTo.id))
     : null;
-  if (!knownNoteUri(parent)) {
-    console.log('[AP] inbox forwarded, skipped (unknown inReplyTo):', claimedActor, parent || '(none)');
-    return null;
-  }
-  if (derefRecentlyFailed(objId)) return null;
+  if (!knownNoteUri(parent)) return skipped('unknown inReplyTo', parent || '(none)');
+  if (derefRecentlyFailed(objId)) return skipped('recent failure', objId);
   // Onbetekend eerst; tekenen alleen als terugval. Anders kan een ander ons een
   // ONDERTEKEND verzoek naar een adres van zijn keuze laten sturen -- dezelfde
   // reden als bij fetchActor sinds efe5633.
   let fetched = await apGetJson(objId).catch(() => null);
-  if ((!fetched || fetched.id !== objId) && slugParam) {
-    fetched = await signedGetJson(slugParam, objId).catch(() => null);
+  if (!fetched || fetched.id !== objId) {
+    // The signer used to be slugParam, which is null on the shared inbox — and
+    // that is where forwarded traffic lands, because we advertise a sharedInbox.
+    // signedGetJson falls back to an unsigned GET for a null slug, so a source in
+    // secure mode could never be dereferenced at all. Same fix verifyRequest got
+    // in shaer-afq: any local actor is a valid signer.
+    const asSlug = slugParam || anySigningSlug();
+    if (asSlug) fetched = await signedGetJson(asSlug, objId).catch(() => null);
   }
   const attributed = fetched && (typeof fetched.attributedTo === 'string'
     ? fetched.attributedTo
     : (fetched.attributedTo && fetched.attributedTo.id));
-  if (!fetched || fetched.id !== objId || attributed !== claimedActor) {
+  if (!fetched || fetched.id !== objId) {
     noteDerefFailure(objId);
-    return null;
+    return skipped('fetch failed', objId);
+  }
+  if (attributed !== claimedActor) {
+    // Not a transport hiccup: the source itself says someone else wrote this.
+    noteDerefFailure(objId);
+    return skipped('attributedTo mismatch', `${objId} claims ${attributed || '(none)'}`);
   }
   return fetched;
Index: test/forwarded-activities.test.js
===================================================================
--- test/forwarded-activities.test.js	(revision f4f0a6d2b553fe600016875f0c5789076851f2a1)
+++ test/forwarded-activities.test.js	(revision bedd7a6cac5de300b43353212b411a1f4777d295)
@@ -199,3 +199,30 @@
 });
 
+test('op de GEDEELDE inbox wordt de bron ook ondertekend opgehaald', async () => {
+  // Doorstuurverkeer landt op /ap/inbox, want we adverteren een sharedInbox --
+  // en daar is slugParam null. signedGetJson valt bij een lege slug terug op een
+  // ONBETEKENDE GET, dus een bron in secure mode was langs deze weg helemaal niet
+  // te dereferencen. Elke lokale actor is een geldige ondertekenaar, net als in
+  // verifyRequest sinds shaer-afq.
+  const GESLOTEN = 'https://203.0.113.10/notes/secure-mode';   // eigen id: buiten de negatieve cache
+  const note = {
+    id: GESLOTEN, type: 'Note', attributedTo: AUTEUR, inReplyTo: ONZE_NOTE,
+    content: '<p>uit secure mode</p>', to: ['https://www.w3.org/ns/activitystreams#Public'],
+  };
+  const stub = globalThis.fetch;
+  const pogingen = [];
+  globalThis.fetch = async (url, opts = {}) => {
+    if (String(url) !== GESLOTEN) return stub(url, opts);
+    const ondertekend = !!(opts.headers && (opts.headers.Signature || opts.headers.signature));
+    pogingen.push(ondertekend);
+    return ondertekend
+      ? new Response(JSON.stringify(note), { status: 200, headers: { 'content-type': 'application/activity+json' } })
+      : new Response('unauthorized', { status: 401 });
+  };
+  const status = await AP.handleInbox(req(doorgestuurd(note)), null, alsDoorstuurder);
+  globalThis.fetch = stub;
+  assert.equal(status, 202, 'de doorgestuurde Create hoort geaccepteerd te worden');
+  assert.deepEqual(pogingen, [false, true], 'eerst onbetekend, daarna pas ondertekend');
+});
+
 test.after(() => { globalThis.fetch = echteFetch; });
