Jedan inline skript na samom vrhu <head> preuzima svaki
browser API — mrežu, skladište, izvršavanje koda, senzore, otisak. Ništa ne prolazi dok politika ne kaže,
sve što je strana pokušala ostaje zapisano, i sve odlazi u jedan jedini beacon pred zatvaranje strane.
Ovo je tačan izvor onoga što build ušije u stranicu. Nijedna linija nije skraćena. Mora biti prvi skript u dokumentu — sve što se izvrši pre njega, izvršilo se van kontrole.
…
Radi tri stvari i ništa više: zapleni mrežu i drži pozive u redu, hvata greške i konzolu od nulte milisekunde, povuče motor asinhrono. Sve ostalo — politika, otisak, vitals, otprema — stiže posle i ne blokira crtanje.
<head>, blokira parser namernoU ovom projektu ga zovemo snippet, a ono što povlači motor. Snippet je jedini deo koji vlasnik strane vidi i jedini koji je na kritičnom putu. Motor sme da zakasni jer snippet drži red.
Napad je jednostavan: skripta koja stigne posle nas napiše
window.fetch = mojFetch i zaplena je gotova. Odgovor je da omotač
ne postavljamo dodelom nego nepromenljivim i nepodesivim svojstvom.
Object.defineProperty(window, 'fetch', {
value: shim,
writable: false, // w.fetch = x → TypeError u strict, tiho ne uspeva u sloppy
configurable: false, // delete w.fetch → ne uspeva; redefinisanje → TypeError
});
| napad | ishod |
|---|---|
| window.fetch = mojFetch | TypeError omotač ostaje |
| delete window.fetch | ne uspeva omotač ostaje |
| Object.defineProperty(window,'fetch',…) | TypeError |
| iframe.contentWindow.fetch | presretnuto dete istog origina se zapleni pre predaje |
| document.createElement('script').src = … | presretnuto setter na HTMLScriptElement.src |
| appendChild(<script>) | presretnuto Node.appendChild gleda tagName |
| setTimeout('kod', 0) | ide kroz eval kapiju |
Isti postupak nosi i sam registar: window.__lpT je nepodesiv,
pa se ne može zameniti lažnim. A S.verify() u svakom trenutku uporedi svaki globalni sa našim omotačem
i vrati listu izmenjenih — pokreće se automatski pri app.ready.
Šta ovo ne rešava. Skripta koja stigne pre snippet-a je van dometa — zato snippet
mora biti prvi u dokumentu. Za to postoji drugi sloj: Content-Security-Policy sa nonce-om,
koji odlučuje na nivou browsera koji skript uopšte sme da se izvrši.
Mehanizam je preuzet iz originala — api/s/b.js sa ltp.ge deploy-a, 2 039 b.
Observeri se kače odmah, telo se sklapa tek pred zatvaranje, i šalje se tačno jednom.
// observeri odmah, buffered:true — hvataju i ono što se desilo pre njih obs('largest-contentful-paint', …) obs('layout-shift', …) obs('event', …) // tačno jedan odlazak; visibilitychange šalje samo ako je strana sakrivena var sent = 0; function send(force) { if (sent || (!force && document.visibilityState !== 'hidden')) return; sent = 1; navigator.sendBeacon(url, telo); } addEventListener('visibilitychange', function () { send(false); }); addEventListener('pagehide', function () { send(true); });
Telo nosi ekran, dpr, zonu, jezike, jezgra, memoriju, dodir, vezu, sklonosti (tema, reduced-motion, pointer), mrežni tajming (DNS, TCP, TLS, TTFB, DCL, load, transferSize), vitals (LCP, FCP, CLS, INP) — i, što original nije imao, presek sandboxa: šta je strana pozvala i šta joj je odbijeno. Samogejtovanje je na kolačiću, kao u originalu: već profilisana sesija ne šalje ponovo.
Grupe se biraju pri build-u: --caps net,exec,store,perm,fp,nav,frame.
Strana koja ne policira otisak ne nosi taj kod.
Dva sasvim različita zahteva, pa dva mehanizma. Ključ u stranici je javan po prirodi — svako ga vidi. Token za čitanje je tajna i mora ostati tajna.
Ne oslanja se na tajnost nego na poreklo:
SBX_SITES| slučaj | odgovor |
|---|---|
| ključ + poreklo | 204 |
| tuđe poreklo | 403 |
| nepoznat ključ | 403 |
| bez ključa | 401 |
| preko praga | 429 |
Bearer token iz SBX_TOKEN, poređen u
konstantnom vremenu — dužina i sadržaj se ne odaju kroz tajming.
Nadzor traži token pri otvaranju i čuva ga u
sessionStorage — nestaje sa karticom. Ako SBX_TOKEN nije podešen,
čitanje je zatvoreno (503), ne otvoreno.
Isti build, dva ponašanja. Otvori /test?mode=report i uporedi sa
/test. Ovako se politika uvodi na živu stranu: prvo gledaš šta bi bilo odbijeno, pa stežeš.
| enforce | report | |
|---|---|---|
| rani poziv na zabranjeni origin | sandbox: fetch nije dozvoljen | Failed to fetch — pravi mrežni pokušaj |
| zahtev napusti browser | nijedan | da, i padne na DNS-u |
| zapisano u beacon | fetch [enforce] | fetch [report] |
| eval | TypeError | prolazi, ali se beleži |
report režim ne lomi ništa — samo beleži
šta bi bilo odbijeno. Tako politika ulazi na živu stranu bez rizika.
Build ušije politiku i adresu motora u snippet, spoji fragmente u jedan IIFE, minifikuje i izmeri.