Ignore:
Timestamp:
08/06/2026 12:26:27 PM (5 weeks ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
279ca0b
Parents:
f4f0a6d
git-author:
Robin <roboburr@…> (08/06/2026 12:26:18 PM)
git-committer:
roboburr <roboburr@…> (08/06/2026 12:26:27 PM)
Message:

Doorstuur-ophaal ondertekenen op de gedeelde inbox, en elke uitgang een reden

Twee dingen die de meting van shaer-drf in de weg stonden.

  1. signedGetJson valt bij een lege slug terug op een ONBETEKENDE GET. Op de gedeelde inbox is slugParam null, en dat is nu juist waar doorstuurverkeer landt, want we adverteren een sharedInbox. Een bron in secure mode was langs die weg dus helemaal niet te dereferencen. Dit is dezelfde fix die verifyRequest in shaer-afq kreeg -- elke lokale actor is een geldige ondertekenaar -- en shaer-afq voorspelde dit gat ook letterlijk, maar nam alleen fetchActor mee. Onbetekend blijft eerst, zodat een vreemde ons geen ondertekend verzoek naar een adres van zijn keuze kan laten sturen.
  1. Vijf van de zes uitgangen van dereferenceForwarded keerden stil terug. Daardoor was een weigering niet te onderscheiden van een versmalling die te ver dicht ging: op boiert stond 5x 'unknown inReplyTo' tegenover 29x signer mismatch, zonder dat te zien was waar de rest bleef. Elke uitgang noemt nu zijn reden, en 'fetch failed' is losgetrokken van 'attributedTo mismatch' -- het eerste is een onbereikbare bron, het tweede zegt de bron zelf dat iemand anders de auteur is.

Gecontroleerd dat de nieuwe test bijt: haal anySigningSlug() eruit en geval 13
valt om. Suite 466/466.

Co-Authored-By: Claude Opus 5 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    rf4f0a6d rbedd7a6  
    17261726
    17271727async function dereferenceForwarded(act, claimedActor, type, slugParam) {
    1728   if (type !== 'Create' && type !== 'Update') return null;
     1728  // Every exit states its reason. Five of the six used to return silently, so a
     1729  // rejection count could not be told apart from a narrowing that closed too far
     1730  // — and that is exactly the measurement shaer-drf is waiting for. Bounded by
     1731  // the signer-mismatch rate (tens per hour), so this is not a noisy log.
     1732  const skipped = (reason, detail) => {
     1733    console.log(`[AP] inbox forwarded, skipped (${reason}):`, claimedActor, detail || '');
     1734    return null;
     1735  };
     1736  if (type !== 'Create' && type !== 'Update') return skipped('not Create/Update', type);
    17291737  const o = act && act.object;
    17301738  const objId = typeof o === 'string' ? o : (o && o.id);
    1731   if (!objId || typeof objId !== 'string' || !/^https:\/\//i.test(objId)) return null;
    1732   try {
    1733     if (new URL(objId).host !== new URL(claimedActor).host) return null;   // ankereis
    1734   } catch { return null; }
     1739  if (!objId || typeof objId !== 'string' || !/^https:\/\//i.test(objId)) return skipped('no https object id', objId || '(none)');
     1740  try {
     1741    if (new URL(objId).host !== new URL(claimedActor).host) return skipped('host anchor', objId);   // ankereis
     1742  } catch { return skipped('unparsable id', objId); }
    17351743  // Alleen dereferencen als het object beweert een antwoord te zijn op iets van
    17361744  // ONS (shaer-drf). Zonder die eis zijn claimedActor en object.id allebei door
     
    17421750    ? (typeof o.inReplyTo === 'string' ? o.inReplyTo : (o.inReplyTo && o.inReplyTo.id))
    17431751    : null;
    1744   if (!knownNoteUri(parent)) {
    1745     console.log('[AP] inbox forwarded, skipped (unknown inReplyTo):', claimedActor, parent || '(none)');
    1746     return null;
    1747   }
    1748   if (derefRecentlyFailed(objId)) return null;
     1752  if (!knownNoteUri(parent)) return skipped('unknown inReplyTo', parent || '(none)');
     1753  if (derefRecentlyFailed(objId)) return skipped('recent failure', objId);
    17491754  // Onbetekend eerst; tekenen alleen als terugval. Anders kan een ander ons een
    17501755  // ONDERTEKEND verzoek naar een adres van zijn keuze laten sturen -- dezelfde
    17511756  // reden als bij fetchActor sinds efe5633.
    17521757  let fetched = await apGetJson(objId).catch(() => null);
    1753   if ((!fetched || fetched.id !== objId) && slugParam) {
    1754     fetched = await signedGetJson(slugParam, objId).catch(() => null);
     1758  if (!fetched || fetched.id !== objId) {
     1759    // The signer used to be slugParam, which is null on the shared inbox — and
     1760    // that is where forwarded traffic lands, because we advertise a sharedInbox.
     1761    // signedGetJson falls back to an unsigned GET for a null slug, so a source in
     1762    // secure mode could never be dereferenced at all. Same fix verifyRequest got
     1763    // in shaer-afq: any local actor is a valid signer.
     1764    const asSlug = slugParam || anySigningSlug();
     1765    if (asSlug) fetched = await signedGetJson(asSlug, objId).catch(() => null);
    17551766  }
    17561767  const attributed = fetched && (typeof fetched.attributedTo === 'string'
    17571768    ? fetched.attributedTo
    17581769    : (fetched.attributedTo && fetched.attributedTo.id));
    1759   if (!fetched || fetched.id !== objId || attributed !== claimedActor) {
     1770  if (!fetched || fetched.id !== objId) {
    17601771    noteDerefFailure(objId);
    1761     return null;
     1772    return skipped('fetch failed', objId);
     1773  }
     1774  if (attributed !== claimedActor) {
     1775    // Not a transport hiccup: the source itself says someone else wrote this.
     1776    noteDerefFailure(objId);
     1777    return skipped('attributedTo mismatch', `${objId} claims ${attributed || '(none)'}`);
    17621778  }
    17631779  return fetched;
Note: See TracChangeset for help on using the changeset viewer.