Dansk

Privatliv i bugbottle

Sætter du bugbottle på et websted i EU, skal du kunne svare på tre spørgsmål, før nogen spørger: hvad bliver der indsamlet, hvor havner det, og hvor længe bliver det liggende. Siden her svarer felt for felt, og hvert svar står i koden og ikke bare i et løfte. Det er ikke juridisk rådgivning; det er den opgørelse, en jurist ville have brug for for at kunne give dig noget.

Det andet spørgsmål har det korteste svar, så det kommer først: en rapport bliver sendt til den rute, du selv har skrevet, og ingen andre steder hen. Der findes ingen bugbottle-server, ingen konto og ingen nøgle at oprette. Biblioteket laver ét fetch til en adresse, du selv ejer, og derfra havner rapporten præcis dér, hvor din store og dine sinks lægger den: din database, din mappe, dit Slack, dit issue-system. Ingenting i pakken taler med en server, du ikke selv har peget på.

Den engelske udgave af siden her er A privacy checklist.

Hvad en rapport kan indeholde #

Hver række herunder er et felt på øverste niveau i den JSON, der bliver sendt — formen står i The payload. "Slået til som udgangspunkt" betyder: med panelet eller hooket monteret og uden at du har sat andet op. Kontakterne i kolonnen "Afbryderen" er dem, der holder feltet ude af rapporten fra begyndelsen; scrubberen og serverens egne indstillinger kommer bagefter og er kun nævnt, hvor de er den vigtigste knap.

Felt Slået til som udgangspunkt Kan indeholde personoplysninger Afbryderen Hvor det havner
type Altid Nej — enten bug, idea eller other Ingen; det er brugerens valg mellem tre ord Din rute, og overskriften i hver sink
message Altid Ja — brugeren skriver frit og indsætter fra udklipsholderen Ingen: det er rapporten. scrubReport slører de mønstre, den kender, og beforeSend kan kassere hele rapporten Din rute, og derefter din store og dine sinks
contact Nej — slået fra i hooket, panelet og script-tagget, indtil du beder om det Ja, per definition: du har spurgt, hvordan man får fat i et menneske Lad contact stå tom, og lad data-contact blive væk fra tagget. scrubReport(report, { contact: true }) slører hele linjen Din rute; Resend-sinken bruger den som svaradresse, hvis den ligner en mailadresse
context Altid Nogle gange — sti og forespørgselsstreng fra siden, så feltet er personhenførbart præcis når dine egne adresser er det (/patienter/1234) Ingen for selve objektet. query-scrubberen slører værdier under token, key, secret, password og auth, og domænet og fragmentet bliver aldrig samlet op Din rute, og derefter din store og dine sinks
console Kun mens initConsoleBuffer() optager — script-tagget starter den for dig Ja — en fejlbesked indeholder det, der blev sat ind i den. console.log og console.debug bliver aldrig optaget, og det er netop dér, løse værdier plejer at ende Lad være med at kalde initConsoleBuffer(); includeConsole: false holder det ude af én rapport Din rute, og derefter din store og dine sinks
elements Nej — først når brugeren peger på noget Ja — selektoren, elementets synlige tekst og dets data-*-attributter elementPicker: false på panelet; intet vedhæfter et element af sig selv Din rute, og derefter din store og dine sinks
screenshotDataUrl Nej — først når du selv giver den en renderer. Med en er den slået til for fejlrapporter, og brugeren kan fjerne fluebenet Ja, mere end noget andet her bortset fra en optagelse: det er det, der stod på skærmen. Feltværdier bliver maskeret først Ingen renderer; screenshotFor; brugerens eget flueben; handleReport({ screenshot: "drop" }) på serveren Din rute; fileStore afkoder billedet ved siden af JSON'en som <id>.png. Læs Please read this part, før du gemmer det nogen steder
screenshotRegion Nej — kun når brugeren har valgt et område eller et element i stedet for hele siden, og det kræver, at du giver den region Nej — en tilstand, to rektangler i pixels og, for et element, dets selektor. Det er billedet, den beskriver, der er det personlige, og et udsnit viser mindre af siden end hele siden Lad region være; feltet følger aldrig med uden et billede, og handleReport fjerner det, når billedet bliver fjernet Din rute, derefter din store; toMarkdown skriver det som én linje
breadcrumbs Kun mens initBreadcrumbs() optager — script-tagget starter den Ja — et klik bærer elementets tekst med sig, et sideskift bærer stien Lad være med at kalde initBreadcrumbs(); includeBreadcrumbs: false for én rapport Din rute, og derefter din store og dine sinks
network Nej — initNetwork() eller data-network på script-tagget Ja — metode, adresse, statuskode og varighed. Aldrig et indhold og aldrig et header-felt, men en adresse kan bære et id eller et token; scrubUrl kører hen over den Lad den blive slået fra; includeNetwork: false for én rapport Din rute, og derefter din store og dine sinks
perf Nej — initPerf() eller data-perf Nej — LCP, INP, CLS, TTFB og to indlæsningstider, alt sammen tal Lad den blive slået fra; includePerf: false for én rapport Din rute, og derefter din store og dine sinks
storage Nej — samme afbryder som perf Kun hvis du beder om det: nøglenavne og værdilængder, og navnene på cookies. En værdi kommer kun med for en nøgle, du har skrevet i allowValues, og en cookieværdi kommer aldrig med uanset indstilling Lad initPerf være slået fra, eller lad allowValues stå tom; includePerf: false dækker begge dele Din rute, og derefter din store og dine sinks
replay Nej — attachRrweb(record) med din egen rrweb Ja, og mere end noget andet: det er en film af et menneske, der bruger dit program Lad være med at kalde attachRrweb; rrwebs egen maskering, som adapteren sætter til maskAllInputs: true; includeReplay: false; handleReport({ replay: "drop" }) på serveren Din rute og din store. Ingen sink sender en optagelse videre nogen steder
notes Når der er en Nej — bibliotekets egen bemærkning om rapporten, for eksempel at køen ikke kunne gemme et skærmbillede Ingen Din rute, og derefter din store og dine sinks

Det, du selv lægger på med extra, står ikke i tabellen, for det er kun dig, der ved, hvad der er i det. Det bliver lagt ind på øverste niveau i den samme JSON og rejser med på samme måde.

Hvor længe det bliver liggende #

bugbottle gemmer ingenting. I browseren lever en rapport i hukommelsen, indtil den er sendt; køen beholder en mislykket rapport i localStorage, til den kommer af sted, og sletter den, når den gør. Efter afsendelsen er opbevaringen modtagerens ansvar, og modtageren er dig. Der findes ingen maksimal alder nogen steder i pakken og ingen oprydning, der kører af sig selv.

fileStore har både et loft og en alder: maxReports (2000 som standard) sletter de ældste filer, når mappen bliver for stor, maxAgeDays (slået fra som standard) sletter det, der er ældre end det, og reports.prune() anvender begge — indbakke-eksemplet kører den ved start og en gang i timen ud fra RETENTION_DAYS. Er dit svar "slet efter 90 dage", er det maxAgeDays: 90 og et kald til prune() på et skema; reports.list() giver dig receivedAt, og reports.remove(id) sletter både JSON-filen og billedet, hvis reglen er en anden. En store mod en database er det samme stykke arbejde med et DELETE.

To beslutninger hører til i samme omgang. Hvor længe et skærmbillede skal leve, er værd at svare på for sig: billedet er den del, der ældes dårligst, og det koster ikke meget at slette det tidligt. Og en rapport, der allerede er sendt videre til Slack eller til et issue-system, er blevet kopieret — sletter du din egen række, forsvinder kopien ikke, og fra det øjeblik en sink har sendt den af sted, lever den efter det systems regler og ikke efter dine.

Når nogen beder om de data, du allerede har #

Spørgsmålet ovenfor besvares af en indstilling. Det her kommer som et menneske, og det har tre dele: hvilke rapporter deres er, hvad du sletter, og hvad du ikke kan komme til. Intet af det er en funktion, for ingen af opkaldene i pakken kan finde en persons rapporter for dig — pakken vidste aldrig, hvem der sendte den. Det, der er, er en ærlig opgørelse over, hvor kopierne er, og hvad det koster at få hver af dem væk.

At finde dem. Det eneste greb en rapport har på et menneske, er contact, og det er slået fra som standard i panelet, i hooket og i script-tagget, for det er personoplysninger i det øjeblik, det er slået til. Det er den rigtige standard, og den har en følge: en bruger, der aldrig har udfyldt feltet, kan kun findes på det, vedkommende har skrevet, altså ved at læse. Så spørgsmålet er ikke "må vi bede om en adresse" — det er "kan vi finde deres rapporter, når nogen skriver til os om otte måneder". Er svaret nej, så lov ikke en sletning, du ikke kan gennemføre.

fileStore hjælper ikke her, og grunden står i typen. list() svarer med id, file, title, type, url, receivedAt og screenshot — alt hvad en oversigtsside har brug for, og intet der identificerer et menneske. contact er ikke med, så den skal finde én persons rapporter i en mappe ved at read() dem én ad gangen. En store mod en database med et index på contact er det samme arbejde gjort en gang, og det er hele historien: biblioteket gemmer feltet, og indexet er dit.

Id'et er det bedre greb, og det virker i dag. handleReport svarer 201 med {"id": …}, når din store har givet et id tilbage, sendReport læser det, og hooket og panelet giver det videre som onSent(id) og som status.id på tilstandsmaskinen. Panelet skriver det ikke ud, så en bruger kan ikke citere et rapportnummer, medmindre du selv sætter det ind i din bekræftelse — og det er hele betydningen af "B-4711" som reference i din indbakke.

At slette den kopi, du selv har. reports.remove(id) sletter JSON'en og billedet sammen og svarer, om der var noget; en store mod en database er et DELETE. Indbakke-eksemplet binder det til en POST /r/<id>/delete bag den samme adgangskode som resten, så de sidste tyve linjer er at kopiere.

De kopier, du lavede uden at bestemme dig for det. Det er den del, der afgør, hvor ærligt et svar du kan give. En sink er en kopi, og den lever længere end din egen række:

Hvor kopien er Hvem kan slette den Hvad det kræver
Din store — en database eller fileStore-mappen Du DELETE eller reports.remove(id). Billedet følger JSON'en med; en mappe, intet pruner, beholder de ældre
Slack, Discord Platformen, med en bot-token Et chat.delete eller et DELETE på beskeden. Ikke den webhook-adresse, en sink får: en indgående webhook kan poste og ikke slette, og den ved aldrig beskedens id
Microsoft Teams Workflowet, ikke sinken En Workflows-kørsel beholder kortet. Om det kan fjernes er et spørgsmål om tilladelser i workflowet, ikke et kald pakken kan lave
Mail — Resend eller din egen SMTP-server Ingen En sendt mail ligger i din udbyders log, i modtagerens postkasse og i hver backup, nogen af parterne gemmer. Der er ingen tilbagekaldelse; det ærlige svar er en tilbagetaget
Sentry, GlitchTip, Bugsink Udbyderen Dens egen opbevaring, og din egen organisations indstilling. sentrySink uploader skærmbilledet som vedhæftet fil, så billedet er med dér også
GitHub, GitLab, Jira, Linear Platformen, og kun delvist Luk issue, skriv en kommentar, slett vedhæftningen. Brødteksten er en kopi af din rapport, og trackeren gemmer sin egen historik over, hvad der blev redigeret
Brugerens egen browser Brugeren Køen sletter en rapport, så snart den er afsendt. En der aldrig kom ud ligger i localStorage, indtil den gør, og createQueue().clear() er en knap i din side, ikke noget en server kan kalde
Dine backups Ingen, indtil de bliver for gamle En række, der slettes i dag, lå i gårsdags dump. Uanset hvad du lover skal du nævne holdbarheden i backuppen lige så vel som den i liveversionen

Fire beslutninger følger heraf, og de er billigere nu end efter den første anmodning. Beslut før, du har rapporter, om en sink må bære noget personligt, for ellers er dens opbevaring en del af dit svar. Vælg helst en sink, du kan slette fra. Giv skærmbilledet sin egen, kortere levetid end teksten — det er den del, der bliver dårligst, og det koster ikke meget at slette den tidligt. Og skriv holdbarheden i backuppen ned, for det er den, der altid bliver glemt.

Skriv sletningen lige så omhyggeligt op, som du skrev modtagelsen ned. onDecision printer én linje per modtaget rapport, med fingerprint'et og intet ud af rapporten selv, og en sletning er ikke en rapport — så den linje, du gemmer om "hvem der spurgte, hvilke id'er, hvornår, og hvad vi svarede", skriver du selv.

Ingen cookies, ingen fingeraftryk, ingen tredjepart #

Webstedet bugbottle.dev lover det samme om sig selv, og her er løftet håndhævet i stedet for påstået. site/security-headers.conf lægger den her linje på hvert eneste svar:

Content-Security-Policy: default-src 'self'; img-src 'self' data:;
  style-src 'self' 'unsafe-inline'; object-src 'none'; frame-ancestors 'none';
  base-uri 'self'; form-action 'self'

default-src 'self' betyder, at intet script, ingen stilart, ingen skrifttype, intet billede og ingen forbindelse må komme andre steder fra end den server: sidens to skrifttyper ligger på den selv, der er ingen statistik at blokere, og browseren afviser det første kald, der ville bryde løftet. De to direktiver, der er bredere, hører til demopanelet og ikke til siden — data:-billeder, fordi et skærmbillede ankommer som en data-adresse, og indlejret stil, fordi panelet lægger sin stilart ind i en shadow root. Det er webstedets politik og ikke bibliotekets: din egen applikation skriver sin egen, og bugbottle har ikke brug for andet i den end en connect-src, der tillader din rute.

Hvad du kan skrive i din privatlivspolitik #

Tilpas det her, og streg så de sætninger, der handler om de felter, du har ladet være slået fra:

Fejlrapporter. Når du melder en fejl i [Applikation], modtager vi den besked, du skriver, og — hvis du udfylder feltet — den måde, du beder os om at kontakte dig på. Sammen med den modtager vi tekniske oplysninger om den side, du stod på: adressen inde i [Applikation], størrelsen på dit vindue, din browsers user agent og de fejlbeskeder, siden allerede havde noteret. Vedhæfter du et skærmbillede, modtager vi et billede af siden, som du så den, hvor indholdet af formularfelter er skjult. Vi bruger det udelukkende til at finde og rette den fejl, du har meldt. Det bliver gemt på vores egne systemer, [bliver delt med [issue-system], som vi bruger til at holde styr på rettelser,] og bliver slettet efter [90] dage. Du kan altid bede om en kopi af de rapporter, du har sendt os, eller om at få dem slettet, ved at skrive til [adresse]; vi svarer inden for [30] dage.

Tre vaner er mere værd end selve afsnittet. Sig det dér, hvor brugeren står, lige ved knappen, og ikke kun i en politik, ingen åbner. Beskriv det, du har slået til, og ikke det, biblioteket kan. Og når svaret ændrer sig — at slå skærmbilleder eller optagelser til er en ændring af svaret — så ret afsnittet i samme release.

Den sidste sætning er den, folk springer over, og den er den eneste, biblioteket kan hjælpe dig med at holde. Den lover et svar, og et svar kræver afsnittet ovenfor bag sig: uden et contact at søge på kan du måske ikke sige, hvilke rapporter deres var. Skriv lovet kun, hvor indexet bag det findes.

Vil du have det hele på engelsk, med resten af dokumentationen omkring sig, ligger den samme side som A privacy checklist. Er du lige begyndt, er Kom i gang den korte danske vej fra ingenting til den første rapport.