Riscrivere o tollerare un bridge del browser? La scala a tre livelli per integrare codice reverse-engineered

Ultimo Aggiornamento: 2026-07-30 11:01:35

Il reverse engineering non finisce quando hai capito cosa calcola un pezzo di codice. Dopo la scomposizione meccanica, l'identificazione della famiglia algoritmica e la verifica differenziale, resta una decisione che incide direttamente sull'operatività: come portare quella logica nel prodotto? Può diventare una funzione pura, testabile in CI, oppure restare legata a un processo esterno che qualcuno dovrà sorvegliare. Scegliere la seconda strada quando non serve significa pagare più avanti, con gli interessi, il tempo risparmiato oggi.

La panoramica in quattro fasi liquida il tema in una riga: «Fase 4, degradare per livello di trasporto». Qui allarghiamo il discorso. Il punto centrale è semplice: scendendo di livello, superficie delle dipendenze, modalità di guasto e costi di distribuzione aumentano di un ordine di grandezza. Per impostazione predefinita, quindi, si combatte per salire.

La gerarchia: tre opzioni, in quest'ordine

Le forme di destinazione sono soltanto tre, ordinate dall'alto verso il basso. Questo ordine dovrebbe essere codificato nelle convenzioni del team, non deciso di volta in volta in base a «quella che parte prima»:

  1. Riscrittura nativa. La logica viene riscritta nel linguaggio di destinazione, separata dal runtime originale e dipendente soltanto dalla libreria standard. Il prerequisito è aver identificato correttamente la famiglia algoritmica: superata la fase di fingerprinting, il 90% arriva dall'implementazione pubblica; i restanti punti di divergenza si gestiscono a parte e il risultato diventa una funzione pura.

  2. Motore JS locale con un frammento minimo. Alcune logiche, nel breve periodo, costano troppo da ripulire completamente. In questi casi si conserva una piccola porzione del JavaScript originale e si eseguono le poche decine di righe necessarie su Node/V8 locale, non l'intera pagina.

  3. Bridge passivo del browser. Esiste una classe di stato disponibile solo nel runtime di una pagina reale con sessione attiva: una firma fornita a runtime, un identificatore dinamico vincolato alla sessione. Se la ricostruzione statica non può riprodurlo, per il momento può soltanto essere letto nel browser. È una soluzione temporanea, da segnalare nelle note dell'interfaccia e da non distribuire mai come impostazione predefinita.

Il costo non cresce in modo lineare

L'ordine è fisso perché i costi dei tre livelli non aumentano gradualmente: ogni livello costa un ordine di grandezza più del precedente.

Livello

Superficie delle dipendenze

Modalità di guasto

Utilizzabile in CI?

Riscrittura nativa

Libreria standard, nessun processo esterno

Output non corrispondente, individuato da un assert

Sì: è una funzione pura

Motore JS locale

Un runtime Node aggiuntivo; il contesto V8 non è thread-safe, quindi la concorrenza richiede un lock

Versione del motore, globale richiesta dal frammento ma assente

A malapena: bisogna installare il motore

Bridge passivo del browser

Un Chrome reale + estensione + sessione mantenuta da una persona + canale locale di loopback

Pagina non aperta, sessione scaduta, struttura modificata, scheda chiusa

No: richiede una persona attiva

Un problema al primo livello viene intercettato da un test unitario; al terzo, il problema è «oggi l'utente ha chiuso quella scheda». Trasformare qualcosa che potrebbe essere una funzione pura in una soluzione di terzo livello vincola una persona a ogni chiamata. Un dato di riferimento: una volta identificata la famiglia algoritmica, un SDK di firma offuscato può diventare un'implementazione autonoma di meno di 600 righe, dipendente soltanto dal crypto integrato, ed eseguibile al primo livello. Quella che sembrava una funzione ottenibile solo passando dal bridge del browser, di solito, è un algoritmo che non hai ancora identificato fino in fondo.

Il confine del bridge passivo: una sola istantanea, mai interventi attivi

Il bridge passivo sfugge di mano in un unico modo: quando inizia ad «aiutare» con refresh automatici, login automatici o attese automatiche del caricamento. Ogni automatismo lo sposta da semplice inoltro a crawler. Il perimetro deve quindi essere strettissimo. Questi vincoli sono stati imparati sul campo:

Una sola lettura istantanea, senza mai modificare la pagina. Leggi una volta sola cookie, stato della sessione e runtime di una pagina già aperta. Non creare schede, non aggiornare, non navigare, non portare in primo piano, non fare polling in attesa. Se la pagina non c'è, non c'è: non aprirla al posto dell'utente.

Restituire subito un errore esplicito quando manca qualcosa. Se non esiste una scheda corrispondente, restituisci tab_unavailable; se la pagina è presente ma la sessione non è attiva, not_logged_in; se l'accesso è attivo ma il runtime non è pronto, runtime_unavailable. Ognuno dei tre codici corrisponde a uno stato reale e a una successiva azione precisa — attendere la pagina, effettuare il login o cambiare destinazione — invece di lanciare un generico «non ha funzionato» che il chiamante deve interpretare.

Lo stato sensibile non esce mai dal browser. L'estensione non richiede permessi cookies / webRequest; interroga soltanto una scheda corrispondente già aperta, completa la richiesta nel contesto di quella pagina e rimuove i campi prima della risposta. Cookie e stato della firma della pagina non escono mai da Chrome, neppure per un passaggio; inoltre, per impostazione predefinita il canale si collega a un indirizzo locale di loopback. Il bridge trasporta risultati, non credenziali.

Perché 15 scope coprono solo poche piattaforme

Il bridge passivo inoltra richieste inserite in whitelist: percorso, parametri e referer sono tutti vincolati da un adapter. L'aspetto meno intuitivo è la granularità: in totale ci sono 15 scope per appena una manciata di piattaforme, perché gli scope vengono separati per «contesto della pagina», non per «piattaforma». TikTok, per esempio, ha Creative Center, Top Ads, Creator platform, libreria degli influencer e Ads Manager: cinque scope distinti, cinque stati di sessione indipendenti, cinque runtime di pagina separati. L'accesso al backend pubblicitario non fornisce il runtime della Creator platform. Se si crea un solo perimetro per piattaforma, il primo caso di «sono autenticato sul sottosito A ma non posso servire il sottosito B» riporta al punto di partenza. Anche sito principale di Xiaohongshu, percorsi equivalenti all'app e marketplace per creator sono tre scope distinti.

Di conseguenza, anche la pianificazione segue questa granularità: il lock si applica soltanto a livello di famiglia di piattaforme. Le richieste nella stessa famiglia, quella Douyin, vengono serializzate perché riutilizzano la stessa scheda reale e chiamate concorrenti nello stesso contesto di pagina finirebbero per ostacolarsi; famiglie diverse, come Douyin e Xiaohongshu, procedono invece in parallelo perché usano due schede senza relazione. All'interno di una famiglia va aggiunto anche un intervallo minimo tra le richieste. Un confine troppo ampio serializza lavoro che potrebbe essere parallelo; uno troppo ristretto fa collidere richieste che condividono una scheda. La famiglia di piattaforme è esattamente il confine naturale di ciò che condivide lo stesso runtime di pagina.

Handoff di login esplicito: l'unico intervento umano ammesso

Il bridge passivo non opera sulla pagina, ma le sessioni scadono. La soluzione è ridurre l'intervento umano a una singola azione esplicita: un comando interattivo avvia un handoff, il programma apre tramite il sistema operativo la pagina business corrispondente, attende che tu acceda manualmente e che la pagina sia pronta, quindi ripete la richiesta originale. L'estensione non clicca pulsanti, non compila moduli e non esporta cookie: il login avviene in un browser reale, per mano dell'utente, e il programma riprende la richiesta solo al termine dell'operazione.

Il vincolo decisivo è che non deve mai attivarsi implicitamente. Un comando non interattivo, come CI o un'attività pianificata, non apre mai il browser: restituisce semplicemente un errore di sessione e lascia decidere al livello superiore. L'handoff deve inoltre evitare i falsi positivi: dopo un login riuscito, il runtime ha al massimo una breve attesa aggiuntiva — 20 secondi nell'implementazione — entro la quale deve arrivare un verdetto. Se la pagina business ha già reindirizzato a una pagina account priva del contesto dell'interfaccia richiesta, questa fase va conclusa subito: non bisogna scambiare un deterministico «non puoi arrivarci» per un «sta ancora caricando» e attendere inutilmente. Un flusso che consente il degrado registra questa fase come unavailable e prosegue, anziché far fallire l'intera procedura.

Stati di fase: completato, saltato volutamente, sessione necessaria

Alla base di tutto c'è una regola: nessuna fase deve restituire soltanto «successo» o «errore». In una pipeline di orchestrazione, l'esito di ogni fase può assumere sei forme: completed (completata), empty (eseguita ma senza dati), ready (preparata, in attesa di invio), skipped (saltata deliberatamente da una regola), unavailable (non disponibile al momento, di solito perché serve una sessione), blocked (manca una precondizione).

«Saltata deliberatamente», «richiede una sessione» e «realmente vuota» sono tre segnali del tutto diversi. Con un solo risultato opaco non puoi capire se un valore vuoto sia previsto oppure se la sessione sia scaduta senza che nessuno se ne accorgesse: una pipeline del genere non è gestibile. Modella lo stato della fase come enum finito e il livello di orchestrazione, sia esso uno script o un modello, potrà decidere se degradare, riautenticare o interrompere. È lo stesso principio dei tre codici di errore del bridge passivo, esteso al livello del flusso.

Come usare il modello per scegliere il livello giusto

Il modello entra in gioco soltanto qui, con un ruolo limitato: non ripulisce la logica al posto tuo, ma ti aiuta a capire se e fino a che punto convenga farlo. È una valutazione architetturale, non la rottura di una firma. La domanda chiave è una: lo stato richiesto è ricostruibile staticamente oppure esiste solo a runtime? Da lì si confrontano lo sforzo di ripulitura e la frequenza dei cambiamenti. Le varie fasi chiedono capacità diverse al modello:

Fase

Capacità richiesta

Scelta

model id

Leggere l'intero modulo per individuare la superficie delle dipendenze

Contesto lungo, lettura del grafo delle chiamate in un solo passaggio

Kimi K3

kimi-k3

Argomentare entrambi i lati della scelta e contestare il «basta farlo funzionare»

Ragionamento solido; sa motivare perché vale la pena spendere un giorno in più per ripulire

Claude Opus 5

claude-opus-5

Triage iniziale in massa di decine o centinaia di capacità

Economico, alta concorrenza

Claude Sonnet 5

claude-sonnet-5

Attribuzione dopo un degrado

Ragionamento intermedio; spiega il problema a partire da un log di errore

GPT-5.6 Sol

gpt-5.6-sol

Il secondo caso è il più importante. L'errore più comune nell'assegnazione di un livello è che il modello segua il tuo tono da «basta farlo funzionare» e risponda «il bridge del browser è la soluzione più semplice»: sta riflettendo la tua inclinazione, non valutando i costi nel lungo periodo. Un modello con forte capacità di ragionamento oppone resistenza: «questo segmento è un hash standard con una sola perturbazione costante; vale un giorno di lavoro per trasformarlo in funzione pura e non dovrebbe finire sul bridge». Non fidarti sulla parola: prova tu stesso. Scegli 3 logiche, di cui almeno 1 con un'assegnazione che sai già essere corretta come controllo; invia a claude-opus-5 e gpt-5.6-sol lo stesso prompt — «indica un livello, argomentalo e contesta un passaggio prematuro al bridge del browser» — e osserva un solo aspetto: il modello cerca di portarti a un livello superiore o si adagia pigramente sul terzo?

Il vero freno è il costo di cambiare modello

Quattro livelli distribuiti tra tre fornitori significano tre SDK, tre sistemi di autenticazione e tre formati di errore. Riscrivere il client per cambiare modello a ogni fase non vale lo sforzo: per questo molti finiscono per usare un solo modello per tutto e, proprio nella valutazione di assegnazione che richiederebbe più ragionamento, scelgono un modello che si limita a fare eco.

AIReiter appiattisce questo livello: una chiave, un'interfaccia compatibile con OpenAI e tutti e quattro i livelli dietro la stessa API. Per cambiare basta modificare il campo model nel corpo della richiesta.

# Argomentazione sull'assegnazione: livello di ragionamento
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<prompt di assegnazione + frammento di logica ricostruita + elenco delle dipendenze>"}]
  }'

# Triage iniziale in massa: modifica un solo campo
#   "model": "claude-sonnet-5"
# Attribuzione del degrado:
#   "model": "gpt-5.6-sol"

Usi già l'SDK OpenAI? Imposta base_url su https://aireiter.com/api/v1. Con l'SDK Anthropic, invia POST /api/v1/messages usando la stessa chiave. Sul fronte prezzi, i modelli Claude hanno uno sconto del 30% sul listino e i modelli GPT costano la metà. I costi di questo flusso si concentrano in due punti: il triage iniziale in massa di decine o centinaia di capacità, con Sonnet e un alto volume di chiamate, e il singolo input a contesto lungo necessario per leggere un intero modulo e individuarne le dipendenze, con Kimi e molti token per chiamata. Il triage in massa usa un modello Claude, quindi lo sconto incide proprio sulla fase più densa; anche l'argomentazione di assegnazione al livello di ragionamento usa Claude, con il 30% di sconto. Il livello a contesto lungo di Kimi K3 è disponibile con la stessa chiave.

Conclusioni

Integrare logica ottenuta tramite reverse engineering non è soltanto un problema tecnico: è un problema di costo. La scala a tre livelli — riscrittura nativa > motore JS locale > bridge passivo del browser — non si può invertire, perché ogni passo verso il basso sostituisce una funzione pura con un processo fatto di dipendenze esterne, intervento umano e una scheda attiva. Il bridge passivo non è un territorio proibito, ma un componente temporaneo con confini rigorosi: una sola istantanea, mai operazioni attive; errore esplicito appena manca la pagina; stato sensibile che non esce dal browser; login solo tramite handoff esplicito; stato della fase sempre leggibile. Rispettando questi principi resta una soluzione provvisoria affidabile; saltandone anche uno diventa una scatola nera che nessuno vuole mantenere. Il modello aiuta a decidere il livello appropriato per ogni capacità e a contrastare l'inerzia del «basta farlo funzionare»; per verificare invece che ogni riscrittura sia corretta, l'arbitro è il testing differenziale, non il modello.