Changeset b5a0268c in Klonkt for src/services/ap-inbox.js
- Timestamp:
- 09/04/2026 11:07:47 AM (4 days ago)
- Branches:
- main
- Children:
- 59f404a
- Parents:
- 9041d35
- File:
-
- 1 edited
-
src/services/ap-inbox.js (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/ap-inbox.js
r9041d35 rb5a0268c 142 142 // dagenlang; zonder dit doet elke herhaling de fetch opnieuw, ook als die de 143 143 // vorige twintig keer niets opleverde. Dempt meteen de scherpte van misbruik. 144 // 145 // DE SLEUTEL IS DE HELE BESCHERMING (shaer-qawr). Er zijn twee soorten 146 // mislukking en ze zeggen iets heel verschillends: 147 // 148 // TRANSPORTFOUT -- de note is niet op te halen. Dat is een eigenschap van de 149 // note zelf en geldt voor iedereen die hem doorstuurt, dus de objId alleen is 150 // de goede sleutel. 151 // 152 // attributedTo-MISMATCH -- de bron zegt dat iemand ANDERS de auteur is. Dat 153 // zegt alles over de doorstuurder en niets over de note, dus die onthouden we 154 // per (note, beweerde actor). 155 // 156 // Met een enkele sleutel voor allebei was dit een censuurknop: neem de echte 157 // note-URI van je slachtoffer, zet er je eigen actor op dezelfde host bij en 158 // wijs naar een van onze publieke notes. De fetch slaagt, de mismatch volgt, en 159 // die note-URI stond dertig minuten op de zwarte lijst -- waarna het ECHTE 160 // doorgestuurde antwoord erop stukliep. Elke dertig minuten herhalen gaf 161 // onbeperkte, gerichte onderdrukking van een specifiek antwoord, voor een 162 // verzoek per keer. Nu raakt de leugenaar alleen zijn eigen ingang. 163 // 164 // Query en fragment tellen niet mee. Ze horen zelden bij de identiteit van een 165 // note, en met een kale URL als sleutel waren ?x=1, ?x=2 enzovoort losse 166 // ingangen: dan is de rem geen rem, want varieren kost niets. Zelfde reden dat 167 // de host in kleine letters gaat. 168 // 169 // GEEN rem per HOST, hoe verleidelijk ook: wie een handvol niet-bestaande 170 // URL's op een host laat mislukken zou daarmee die HELE host het zwijgen 171 // opleggen. Dat is een grotere versie van precies de fout die hier gerepareerd 172 // wordt. 144 173 const _derefMiss = new Map(); 145 174 const DEREF_MISS_MS = 30 * 60 * 1000; 146 function derefRecentlyFailed(uri) { 147 const t = _derefMiss.get(uri); 148 if (t === undefined) return false; 149 if (Date.now() - t > DEREF_MISS_MS) { _derefMiss.delete(uri); return false; } 150 return true; 175 const derefKey = (uri, claimedActor) => { 176 let basis = String(uri || ''); 177 try { const u = new URL(basis); basis = `${u.protocol}//${u.host.toLowerCase()}${u.pathname}`; } 178 catch { /* onparseerbaar: de kale string is dan de sleutel */ } 179 // Een NUL-teken als scheiding, als escape geschreven en niet als byte: het 180 // kan in geen enkele URL staan, dus een actor-sleutel is nooit per ongeluk 181 // als note-sleutel te lezen. 182 return claimedActor ? `${basis}\u0000${claimedActor}` : basis; 183 }; 184 function derefRecentlyFailed(uri, claimedActor) { 185 for (const k of [derefKey(uri), derefKey(uri, claimedActor)]) { 186 const t = _derefMiss.get(k); 187 if (t === undefined) continue; 188 if (Date.now() - t > DEREF_MISS_MS) { _derefMiss.delete(k); continue; } 189 return true; 190 } 191 return false; 151 192 } 152 function noteDerefFailure(uri ) {193 function noteDerefFailure(uri, claimedActor) { 153 194 if (_derefMiss.size > 500) { // simpele begrenzing: oudste helft eruit 154 195 const oud = [..._derefMiss.entries()].sort((a, b) => a[1] - b[1]).slice(0, 250); 155 196 for (const [k] of oud) _derefMiss.delete(k); 156 197 } 157 _derefMiss.set( uri, Date.now());198 _derefMiss.set(derefKey(uri, claimedActor), Date.now()); 158 199 } 200 // Alleen voor de toets: de aanval speelt zich af in deze twee functies, en de 201 // weg erheen (dereferenceForwarded) eist https en een echte fetch. De 202 // dienstlaag exporteert ze niet, dus het uitvoeroppervlak blijft gelijk. 203 export const _derefCacheForTests = { derefRecentlyFailed, noteDerefFailure }; 159 204 160 205 async function dereferenceForwarded(act, claimedActor, type, slugParam) { … … 184 229 : null; 185 230 if (!knownNoteUri(parent)) return skipped('unknown inReplyTo', parent || '(none)'); 186 if (derefRecentlyFailed(objId )) return skipped('recent failure', objId);231 if (derefRecentlyFailed(objId, claimedActor)) return skipped('recent failure', objId); 187 232 // Onbetekend eerst; tekenen alleen als terugval. Anders kan een ander ons een 188 233 // ONDERTEKEND verzoek naar een adres van zijn keuze laten sturen -- dezelfde … … 207 252 if (attributed !== claimedActor) { 208 253 // Not a transport hiccup: the source itself says someone else wrote this. 209 noteDerefFailure(objId); 254 // Per (note, beweerde actor), nooit op de note alleen: dit zegt iets over 255 // DEZE doorstuurder, en op de note alleen was het een censuurknop op de 256 // note van een ander (shaer-qawr). 257 noteDerefFailure(objId, claimedActor); 210 258 return skipped('attributedTo mismatch', `${objId} claims ${attributed || '(none)'}`); 211 259 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)