Changeset dc0da72 in Klonkt


Ignore:
Timestamp:
08/08/2026 09:03:48 PM (4 weeks ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
a4b903a
Parents:
29eb745
git-author:
Robin <roboburr@…> (08/08/2026 09:03:46 PM)
git-committer:
roboburr <roboburr@…> (08/08/2026 09:03:48 PM)
Message:

Alleen claimen wat je herkent (regressie uit shaer-8ru)

Barts honderd aanbiedingen liepen meteen stuk op "400 unsupported_offer", en dat
was mijn fout van een uur eerder.

Ik zette het gate-voorstel als case 'Offer' in de outbox-switch. Die case
claimde ELKE Offer: wat geen shaer:GatedSetting was kreeg 400 -- dus ook de
adoptie-handshake, en straks elke Offer-vorm die we nog toevoegen. De
guardianship-afhandeling stond ervoor en ving het meestal op, maar zodra die iets
niet herkende viel het in mijn 400 in plaats van door te lopen.

Nu voor de switch, naast de andere Offer-afhandeling, en alleen als
parseGatedSetting iets teruggeeft. De toets die 'unsupported_offer' verwachtte is
vervangen: die legde mijn verkeerde aanname vast. Er staat nu een toets op dat een
adoptie-Offer NIET door de gate-tak wordt afgevangen.

Twee kleinere dingen die dezelfde ronde boven kwamen:

  • Het aanbod-script gebruikte een verzonnen relatie-URI en werd daarom stil geweigerd. isGuardianRelationship accepteert precies twee waarden.
  • De SSRF-toets hing af van wat er toevallig draaide: hij ging ervan uit dat er niets op [::1]:3060 luisterde, en viel om zodra de testkudde daar stond. Nu een poort waar met opzet niets luistert. Een toets die afhangt van wat er draait bewijst niets.

Suite 756/756.

Files:
5 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r29eb745 rdc0da72  
    33813381    if (g) return g;
    33823382  }
     3383  // Een gate-voorstel uit de app (5.6, shaer-8ru): een Offer van een
     3384  // shaer:GatedSetting, de vorm die 5.6 al beschrijft.
     3385  //
     3386  // HIER, EN GEEN `case` IN DE SWITCH. Dat was hij eerst, en die claimde ELKE
     3387  // Offer: wat geen gate-voorstel was kreeg 400 unsupported_offer -- ook de
     3388  // adoptie-handshake, en straks elke Offer-vorm die we nog toevoegen. Barts
     3389  // honderd aanbiedingen liepen er meteen op stuk. Alleen claimen wat je
     3390  // herkent, en de rest laten doorlopen.
     3391  if (type === 'Offer') {
     3392    const gs = Guardianship.gated.parseGatedSetting(activity.object);
     3393    if (gs) {
     3394      const uit = proposeGate(site, gs.ward, gs.feature, gs.value);
     3395      return uit.status === 200 ? { ...uit, status: 201, id: uit.offerId } : uit;
     3396    }
     3397  }
    33833398
    33843399  try {
    33853400    switch (type) {
    3386       // Een gate-voorstel uit de app (5.6, shaer-8ru). De PWA deed dit al over
    3387       // een eigen route; hier komt het binnen als wat het in AP IS -- een Offer
    3388       // van een shaer:GatedSetting. Dezelfde functie erachter, want twee wegen
    3389       // naar hetzelfde besluit is precies wat we vandaag hebben rechtgezet.
    3390       case 'Offer': {
    3391         const gs = Guardianship.gated.parseGatedSetting(object);
    3392         if (!gs) return { status: 400, error: 'unsupported_offer' };
    3393         const uit = proposeGate(site, gs.ward, gs.feature, gs.value);
    3394         if (uit.status !== 200) return uit;
    3395         return { ...uit, status: 201, id: uit.offerId };
    3396       }
    33973401      case 'Create': {
    33983402        if (!object || typeof object !== 'object') return { status: 400, error: 'missing_object' };
  • test/gated-propose-c2s.test.js

    r29eb745 rdc0da72  
    6161});
    6262
    63 test('een Offer dat GEEN gate-voorstel is wordt niet stilletjes geslikt', async () => {
     63test('een Offer dat GEEN gate-voorstel is wordt NIET geclaimd', async () => {
     64  // WAS: dit gaf 400 unsupported_offer, en dat was fout. Deze tak claimde elke
     65  // Offer, dus ook de adoptie-handshake -- Barts honderd aanbiedingen liepen er
     66  // meteen op stuk. Alleen claimen wat je herkent.
    6467  const uit = await AP.ingestOutboxActivity(site(), user, { type: 'Offer', object: { type: 'Note', content: 'hoi' } });
    65   assert.equal(uit.status, 400);
    66   assert.equal(uit.error, 'unsupported_offer');
     68  assert.notEqual(uit && uit.error, 'unsupported_offer');
     69});
     70
     71test('en een adoptie-Offer komt bij de handshake terecht', async () => {
     72  // De regressie die Bart ving, als toets. Een Relationship-offer hoort NOOIT
     73  // door de gate-tak afgevangen te worden.
     74  const uit = await AP.ingestOutboxActivity(site(), user, {
     75    type: 'Offer', to: [KIND],
     76    object: { type: 'Relationship', subject: KIND, object: 'https://oma.test/ap/users/oma', relationship: 'shaer:Guardian' },
     77  });
     78  assert.notEqual(uit && uit.error, 'unsupported_offer');
    6779});
    6880
  • test/ssrf-allowlist.test.js

    r29eb745 rdc0da72  
    1212
    1313process.env.DATABASE_PATH = ':memory:';
    14 process.env.AP_ALLOW_HOSTS = '[::1]:3060';
     14// Een poort waar met opzet NIETS op luistert. Eerst stond hier 3060, en toen de
     15// testkudde daar ging draaien slaagde de fetch en viel de toets om -- een toets
     16// die afhangt van wat er toevallig draait, bewijst niets.
     17process.env.AP_ALLOW_HOSTS = '[::1]:59321';
    1518const AP = await import('../src/services/ActivityPubService.js');
    1619
     
    2528  // niets op 3060, dus we verwachten een VERBINDINGSfout -- het bewijs is dat
    2629  // het geen ssrf-fout is.
    27   const fout = await faalt('http://[::1]:3060/u/w001');
    28   assert.ok(fout, 'er is wel degelijk een fout, want er luistert niets');
     30  const fout = await faalt('http://[::1]:59321/niets');
     31  assert.ok(fout, 'er is wel een fout, want er luistert niets op die poort');
    2932  assert.ok(!String(fout).startsWith('ssrf-'), `door de poort, maar kreeg: ${fout}`);
    3033});
     
    3235test('een adres dat NIET op de lijst staat blijft geweigerd', async () => {
    3336  // Zelfde machine, andere poort. Zou dit doorlaten, dan is de lijst een vlag.
    34   assert.equal(await faalt('http://[::1]:3061/u/w001'), 'ssrf-blocked-ip');
     37  assert.equal(await faalt('http://[::1]:3060/u/w001'), 'ssrf-blocked-ip');
    3538});
    3639
    3740test('en 127.0.0.1 evenmin, ook al is het dezelfde machine', async () => {
    3841  // De lijst opent een HOST:POORT, geen begrip van "lokaal".
    39   assert.equal(await faalt('http://127.0.0.1:3060/u/w001'), 'ssrf-blocked-ip');
     42  assert.equal(await faalt('http://127.0.0.1:59321/niets'), 'ssrf-blocked-ip');
    4043});
    4144
     
    5255  const mod = await import(`../src/services/ActivityPubService.js?leeg=${Date.now()}`);
    5356  let fout = null;
    54   try { await mod.safeFetch('http://[::1]:3060/u/w001'); } catch (e) { fout = e.message; }
     57  try { await mod.safeFetch('http://[::1]:59321/niets'); } catch (e) { fout = e.message; }
    5558  assert.equal(fout, 'ssrf-blocked-ip');
    5659  process.env.AP_ALLOW_HOSTS = eerder;
  • tools/ward-farm/farm.mjs

    r29eb745 rdc0da72  
    286286
    287287server.listen(PORT, '::1', () => {
    288   console.log(`[farm] ${N} wards op ${BASE} (luistert op 127.0.0.1:${PORT})`);
     288  console.log(`[farm] ${N} wards op ${BASE} (luistert op [::1]:${PORT})`);
    289289});
  • tools/ward-farm/offer-many.mjs

    r29eb745 rdc0da72  
    6161      subject: ward,
    6262      object: me,
    63       relationship: 'https://shaer.klonkt.com/ns#guardian',
     63      // De exacte waarde die isGuardianRelationship accepteert. Mijn eerste
     64      // poging gebruikte een verzonnen URI en werd stil geweigerd.
     65      relationship: 'shaer:Guardian',
    6466    },
    6567  };
Note: See TracChangeset for help on using the changeset viewer.