Changeset cae8ded in Klonkt


Ignore:
Timestamp:
07/31/2026 11:03:47 AM (6 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
0ca7e5d0
Parents:
0f48411
Message:

Een eerlijke User-Agent op al het uitgaande federatieverkeer

Robins vraag (31-7): welke UA gebruiken wij? Het federatieverkeer ging
met de kale Node-default de deur uit. safeFetch zet nu standaard
Klonkt/<versie> (+https://klonkt.com) op elk uitgaand verzoek; een
caller die zelf een User-Agent meegeeft (de EmbedResolver) wint. En het
spiegelbeeld-lek dicht: klonkt staat nu ook in de eigen bot-regex,
zodat een Klonkt-preview-fetch op een andere Klonkt niet als bezoeker
telt.

Changed files:
src/services/ActivityPubService.js

  • KLONKT_UA uit package.json-versie; safeFetch merge't hem als default in de headers

src/services/StatsService.js

  • BOT_RE + klonkt

test/stats-bots.test.js

  • de eigen embed-fetcher telt niet op een andere Klonkt

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

Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r0f48411 rcae8ded  
    9696  if (!addrs.length || addrs.some((a) => isBlockedIp(a.address))) throw new Error('ssrf-blocked-host');
    9797}
     98// One honest name on ALL outbound federation traffic (Robins vraag, 31-7):
     99// safeFetch went out with the bare Node default before, and polite fediverse
     100// citizens say who they are (some instances even refuse anonymous UAs). A
     101// caller-provided User-Agent (the EmbedResolver) still wins.
     102let _uaVer = '1.0';
     103try { _uaVer = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url))).version || _uaVer; } catch { /* keep default */ }
     104const KLONKT_UA = `Klonkt/${_uaVer} (+https://klonkt.com)`;
     105
    98106export async function safeFetch(url, opts = {}, maxRedirects = 3) {
    99107  let target = url;
     
    102110    if (u.protocol !== 'https:' && u.protocol !== 'http:') throw new Error('ssrf-bad-scheme');
    103111    await assertPublicHost(u.hostname);
    104     const r = await fetch(target, { ...opts, redirect: 'manual', signal: AbortSignal.timeout(8000) });
     112    const r = await fetch(target, {
     113      ...opts,
     114      headers: { 'User-Agent': KLONKT_UA, ...(opts.headers || {}) },
     115      redirect: 'manual',
     116      signal: AbortSignal.timeout(8000),
     117    });
    105118    const loc = (r.status >= 300 && r.status < 400) ? r.headers.get('location') : null;
    106119    if (loc && hop < maxRedirects) { target = new URL(loc, target).toString(); continue; }
  • src/services/StatsService.js

    r0f48411 rcae8ded  
    4747// Skip known bots/crawlers + link-preview fetchers + scripts so they don't inflate
    4848// view/visitor-day counts. Empty UA = almost always automated.
    49 const BOT_RE = /bot|crawl|spider|slurp|mediapartners|bingpreview|facebookexternalhit|whatsapp|telegram|discord|twitter|linkedin|embedly|pinterest|redditbot|applebot|petalbot|yandex|baidu|duckduckbot|semrush|ahrefs|mj12|dotbot|uptimerobot|pingdom|statuscake|headless|lighthouse|gptbot|claude|ccbot|perplexity|bytespider|amazonbot|googleother|google-read-aloud|python-requests|scrapy|curl|wget|axios|node-fetch|go-http|java\/|okhttp|libwww|httpclient|mastodon|pleroma|akkoma|misskey|calckey|firefish|friendica|hubzilla|lemmy|pixelfed|peertube|gotosocial|honk|http\.rb|activitypub/i;
     49const BOT_RE = /bot|crawl|spider|slurp|mediapartners|bingpreview|facebookexternalhit|whatsapp|telegram|discord|twitter|linkedin|embedly|pinterest|redditbot|applebot|petalbot|yandex|baidu|duckduckbot|semrush|ahrefs|mj12|dotbot|uptimerobot|pingdom|statuscake|headless|lighthouse|gptbot|claude|ccbot|perplexity|bytespider|amazonbot|googleother|google-read-aloud|python-requests|scrapy|curl|wget|axios|node-fetch|go-http|java\/|okhttp|libwww|httpclient|mastodon|pleroma|akkoma|misskey|calckey|firefish|friendica|hubzilla|lemmy|pixelfed|peertube|gotosocial|honk|http\.rb|activitypub|klonkt/i;
    5050function isBot(req) {
    5151  const ua = (req && req.headers && req.headers['user-agent']) || '';
  • test/stats-bots.test.js

    r0f48411 rcae8ded  
    3535});
    3636
     37test("a Klonkt's own embed-fetcher does not count on another Klonkt", () => {
     38  const before = pv();
     39  Stats.recordPageview('s1', fakeReq({ 'user-agent': 'Mozilla/5.0 (compatible; Klonkt/1.0; +https://klonkt.com)' }));
     40  assert.equal(pv(), before);
     41});
     42
    3743test('a SIGNED request never counts, whatever the UA claims', () => {
    3844  const before = pv();
Note: See TracChangeset for help on using the changeset viewer.