Cosa lascia l'host
bitscanner invia fuori dalla macchina su cui gira una descrizione della rete di qualcuno. Questo rende portanti due domande: cosa contiene e come viaggia. Questa pagina risponde a entrambe, e poi dichiara senza giri di parole i limiti di questa release.
Per ciò che arma il sondaggio in primo luogo, vedi La scansione, e i due interruttori che la armano.
Cosa c'è nella telemetria
Tutto ciò che bitscanner invia riguarda la rete attorno all'host, mai i contenuti dell'host stesso.
| Record | Campi |
|---|---|
network_state | Solo rotte: destination, gateway, interface, metric, flags |
neighbor_table | Per ogni vicino: ip_address, hardware_addr, interface, state, type |
neighbor_discovery | Per ogni vicino: indirizzo, MAC, vendor (ricerca OUI offline), device_type, is_reachable, first_seen/last_seen — più hostname se reverse_dns è armata, services (porte, e banner se port_scan è armata), mdns_services/ssdp_info se service_discovery è armata. Più le subnet scansionate |
gateway_discovery | Per ogni gateway: indirizzo, MAC, produttore, is_default, raggiungibilità e — solo con gateway_probe — le porte che hanno risposto e l'eventuale banner presentato |
service_discovery | Responder: name, type, host, port, protocol, discovered_via, record TXT mDNS |
Due conseguenze che vale la pena esplicitare:
- Le sonde più rumorose registrano le versioni del software di altre persone. Un banner
dalla porta 22 o 25 è una stringa di versione che appartiene a un dispositivo che potresti
non possedere. Non è un effetto collaterale da scoprire in seguito; è esattamente lo scopo
di
port_scanegateway_probe, ed è il motivo per cui sono disattivate per impostazione predefinita e protette da una dichiarazione di accettazione dell'autorizzazione. hostnameesiste solo se hai armatoreverse_dns. Senza, un vicino è un indirizzo, un MAC e un'ipotesi sul produttore.
Cosa deliberatamente non raccoglie
network_state trasportava interfaces (nome, MTU, MAC, indirizzi), listeners (l'inventario
dei socket locali), un elenco connections sempre vuoto e un campo dns_servers che nulla
popolava mai — un campo nullo che asseriva un inventario dei resolver che non veniva mai
raccolto. Sono tutti spariti, insieme al codice e ai tipi che c'erano dietro.
I primi tre erano una riproposizione dei collector network e port di
bitcollector. bitscanner non raccoglie fatti sull'host —
nessun processo, nessun pacchetto installato, nessun account locale, nessuna riga di comando.
Se ti serve un record sulla macchina stessa, arriva dal collector, sotto le
impostazioni predefinite di privacy del collector.
Ogni record è racchiuso in un envelope che trasporta un checksum SHA-256 sulla propria
intestazione e sul proprio payload. È un controllo di integrità, non una firma —
bitscanner non ha alcun equivalente della catena delle evidenze firmata e concatenata tramite
hash di bitcollector.
Il traffico in uscita è https, oppure l'agente non parte
La regola riguarda i dati, non la credenziale: una mappa della rete del cliente che attraversa la rete in chiaro è leggibile e alterabile da chiunque si trovi sul percorso, che viaggi o meno un token insieme a essa.
telemetry.destinations[0]: endpoint http://es.example.com:9200 is not https — refusing to
start. Everything this agent collects about the customer's network would cross that link
in cleartext, readable and alterable by anyone on the path, credential or no credential.
Use an https endpoint; if this is a lab and you accept that the data is exposed, set
security.i_accept_plaintext_egress: true
security.i_accept_plaintext_egress è l'unica rinuncia possibile, impostabile soltanto da
file, viene annunciata nel log a ogni avvio ed è pensata per i laboratori. Con una
credenziale configurata non esiste alcuna rinuncia — un endpoint in chiaro, o
tls.skip_verify: true, viene rifiutato senza eccezioni, perché qualunque cosa possa
rispondere al posto dell'endpoint raccoglie la credenziale.
Altri tre rifiuti della stessa famiglia:
- Una credenziale incorporata nell'URL viene rifiutata, con le istruzioni per spostarla
nel blocco
auth. Go trasforma la userinfo in un headerAuthorization: Basicattivo, che finisce in ogni riga di log che stampa l'endpoint, e l'oscuramento dell'agente non riesce a raggiungerla. - Il materiale di certificato che verrebbe scartato in silenzio viene rifiutato. Impostare
ca_cert/client_cert/client_keymentretls.enabledè false è un errore, perché la sezione si leggerebbe come mutual TLS pur non presentando nulla. headers:non esiste. La vecchia configurazione di esempio pubblicizzavaheaders: {Authorization: "Bearer YOUR-TOKEN"}e non è mai esistita una riga di Go che la leggesse — gli operatori credevano che la loro telemetria fosse autenticata mentre usciva in forma anonima. Una configurazione che contiene quella chiave viene rifiutata per nome.
Nessun redirect viene mai seguito
refusing to follow the redirect from <from> to <to>: this agent never follows redirects on
egress, because net/http would re-attach the credential when the hostname matches (even
downgrading https to http) and would replay the batch body on a 307/308 to a host the
server chose. Point the endpoint at its final URL instead
Entrambe le metà di questa affermazione sono proprietà della libreria standard di Go, non
congetture. La sua regola per riapplicare Authorization confronta solo l'hostname — non
lo schema, non la porta — quindi un server che risponde 301 Location: http://same-host:80/
si riprende il bearer token in chiaro. Un header personalizzato con chiave API non è affatto
nell'elenco dei campi sensibili della libreria standard, quindi viene copiato verso
qualsiasi host su qualsiasi schema. E su un 307/308 il corpo della richiesta —
l'inventario di rete del cliente — viene ritrasmesso a qualunque host indichi il redirect.
Il client in uscita non è un *http.Client. Ogni campo di quella struct è esportato, quindi
c.CheckRedirect = nil o un transport sostituito avrebbero rimosso entrambi i controlli con
una sola assegnazione — ed è stato dimostrato che entrambe le modifiche lasciano la suite di
test completamente verde. Il client è incapsulato in un tipo che espone soltanto Do e
CloseIdleConnections, e il transport è costruito a partire da una configurazione TLS anziché
essere accettato dal chiamante, così non resta alcun round-tripper fornito dal chiamante con
cui riscrivere una richiesta.
La credenziale viene applicata al momento dell'invio, non al momento della costruzione della richiesta, e fallisce in modo chiuso: se il canale o la credenziale non superano il controllo — non risolti, non https, verifica del certificato disattivata — il batch non viene inviato. "Imposta l'header se per caso ne abbiamo uno" è la forma che ha già portato una volta a spedire telemetria non autenticata.
Gli endpoint vengono oscurati nei log
Un endpoint viene registrato a ogni fallimento di esportazione, quindi l'oscuramento nega per
impostazione predefinita anziché basarsi su un elenco di nomi che sembrano credenziali: ogni
valore di query e l'intero fragment vengono oscurati, mantenendo i nomi dei parametri così
che la riga resti diagnosticabile (api_key=[redacted] dice quale parametro era impostato).
Anche i segmenti di percorso che sembrano contenere credenziali vengono oscurati. Se l'URL non
è affatto analizzabile, l'oscuratore restituisce una costante — mai il testo del chiamante,
perché un URL che non si riesce ad analizzare è di solito uno la cui password contiene proprio
i caratteri che rompono il parser.
Quest'ultimo punto ha un limite dichiarato: l'euristica sul percorso può lasciarsi sfuggire un
segreto corto o simile a una parola dentro un segmento di percorso. Il controllo portante è
che le credenziali stanno nel blocco auth, dove sono tipizzate, vagliate e mai formattate
dentro una stringa.
I file dei segreti
Un token è davvero fuori dal file di configurazione solo se nessun altro può leggerlo.
bitscanner config validate rifiuta di avviarsi in ognuno di questi casi, e il messaggio
nomina il percorso incriminato e la correzione. Verificato per esecuzione — i percorsi qui
sotto sono percorsi da documentazione sostituiti a quelli della trascrizione reale:
# mode
control_plane: auth.token_file "/etc/bitscanner/control-plane.token" is mode 0644 —
readable by group or other; restrict it with `chmod 600 /etc/bitscanner/control-plane.token`
# any directory on the path, not just the one holding the file
control_plane: the directory "/opt/agent" on the path to auth.token_file
"/opt/agent/secrets/control-plane.token" is mode 0775 — group- or world-writable, so
another account can replace the file this agent reads. It is not the directory holding the
file — it is 1 level(s) above it — but another account can rename the whole subtree and
put its own in place. Restrict it with `chmod 755 /opt/agent` (or tighter), or move the
secret somewhere only root and this agent can write
L'insieme completo dei rifiuti:
| Rifiutato | Perché |
|---|---|
| Mode leggibile da gruppo o da altri | Ogni account locale sull'host può leggere il token |
| Di proprietà di un terzo account | "Solo il proprietario può leggerlo" non vale nulla quando il proprietario è qualcun altro |
| Non è un file regolare | Una FIFO o un device non è un file di segreti — e una semplice apertura di una FIFO senza scrittori bloccherebbe l'agente all'avvio senza alcuna diagnostica |
| Raggiunto attraverso un symlink | Il bersaglio del link vive in una directory che il controllo non avrebbe esaminato |
Qualsiasi directory sul percorso scrivibile dal gruppo o da tutti — compresa /tmp | Quell'account può rinominare il sottoalbero e mettere al suo posto il proprio file |
Vengono percorsi sia gli antenati del percorso risolto sia il percorso così come è
scritto, e si dimostra tramite device e inode che il percorso risolto è esattamente il file
il cui descrittore viene letto. Ognuno di questi era un buco reale, chiuso in un'ondata
diversa: controllare solo il genitore immediato, non accorgersi di un nonno scrivibile da
tutti, e poi l'immagine speculare — percorrere solo la catena risolta, che accettava un file
0600 in una directory 0700 raggiunta attraverso una 0777, perché la catena risolta è
immacolata ed è nella catena scritta che vive l'attaccante.
Infine, ogni segreto proviene da esattamente una di queste fonti: il valore inline, un
<field>_file oppure un <field>_env che nomina una variabile d'ambiente. Due fonti insieme
sono un errore anziché una regola di precedenza — con la precedenza, un operatore che aggiunge
token_file lasciando al suo posto un vecchio token inline non può sapere quale dei due è
sulla rete.
Enrolment: la chiave non lascia mai l'host
Generi un token di enrolment monouso nella dashboard Cert-IX e lo metti in un file che solo l'agente può leggere:
enrollment:
endpoints:
- "https://<your-cert-ix-agent-endpoint>"
enrollment_token_file: "/etc/bitscanner/enrollment.token" # chmod 600, owner-only
Al primo avvio l'agente:
- Genera sull'host una coppia di chiavi ECDSA P-256. La metà privata viene scritta con
permessi
0600dentroagent.data_dir, che viene forzata a0700— creata e sottoposta di nuovo achmod, perchéMkdirAlllascia intatto il mode di una directory esistente e altrimenti un aggiornamento dentro una directory dati0755lo manterrebbe. La chiave privata non lascia mai la macchina. - Si registra una sola volta, presentando il token di enrolment nell'header
X-Agent-Enrollment-Tokene la propria chiave pubblica nel corpo. - Conserva il JWT dell'agente restituito dal gateway, con permessi
0600, insieme alla sua scadenza.
Dopodiché il token è consumato: il file può essere eliminato, e un riavvio carica l'identità
memorizzata e non esegue una nuova registrazione. Una seconda registrazione sarebbe un 409
contro un token già consumato, cosa che lascerebbe l'agente morto. Il JWT viene rinnovato
sull'endpoint di refresh un'ora prima della sua scadenza, usando il token stesso — non è
coinvolto alcun token di enrolment.
Viene presentato solo in X-Agent-Enrollment-Token, su esattamente una richiesta. Metterlo
in auth.token_file è l'errore di configurazione che ha reso impossibile l'onboarding in una
build precedente: il gateway di ingest vuole un JWT dell'agente, quindi ogni richiesta di
telemetria risponde 401. Usa invece auth: {type: enrollment} sulla destinazione —
presenta l'identità memorizzata, e non c'è nulla da incollare.
L'endpoint di enrolment deve essere https in qualunque configurazione.
security.i_accept_plaintext_egress non gli si applica — è da lì che torna indietro
l'identità.
Né la chiave privata né il token vengono mai registrati nei log, formattati dentro un errore o
inseriti in un campo di log: i metodi String() e GoString() del tipo identità stampano
soltanto l'ID dell'agente e la scadenza, così un %v sfuggito non può scrivere per sempre il
JWT di questo host dentro un file di log.
Limiti dichiarati con onestà
Dichiarati qui anziché scoperti in seguito.
Le advisory della libreria standard di Go che riguardavano 0.1.2 sono corrette in 0.1.3
0.1.3-ga è compilato con go1.25.12, che porta con sé la correzione di entrambe le
advisory a cui 0.1.2-ga (compilato con go1.26.4) era esposto:
| Advisory | CVSS | Sfruttata attivamente? | Di cosa si tratta | Corretta in 0.1.3-ga |
|---|---|---|---|---|
CVE-2026-39822 (GO-2026-4970) | 7.8 Alta | No — non è nella CISA KEV, EPSS sotto la soglia | Evasione dalla root tramite symlink più slash finale in os | ✅ |
CVE-2026-42505 (GO-2026-5856) | 5.3 Media | No — non è nella CISA KEV, EPSS sotto la soglia | Fuga di privacy di Encrypted Client Hello in crypto/tls | ✅ |
🪤 Utile da sapere se fissi tu stesso le toolchain: una versione di Go più alta non è sempre
quella corretta. CVE-2026-39822 è corretta in go1.25.12 sulla linea 1.25, ma solo in
go1.26.5 sulla linea 1.26, quindi go1.26.4 — una toolchain numericamente più recente — la
portava ancora con sé. Un singolo pavimento di «versione minima» non può esprimere un backport
specifico per linea, ed è per questo che questa release fissa un'immagine esatta e lascia
l'autorità allo scanner, non al numero di versione. La nostra stessa build è stata rifiutata
una volta proprio per questo, prima che il pinning fosse corretto.
Se stai ancora eseguendo 0.1.2-ga, porta con sé l'advisory High — aggiorna. La versione
della toolchain viene stampata da bitscanner version ed è registrata nell'SBOM CycloneDX
della release, quindi verifica la build che hai davanti anziché fidarti di questa tabella per
sempre.
"Consegnato" significa "è tornato un 2xx"
L'agente riporta ciò che ha messo in coda e ciò che è stato accettato:
INFO network_collector collection cycle complete
{"queued_for_delivery": ["network_state", "neighbor_table", "neighbor_discovery"]}
INFO telemetry telemetry batch accepted by destination {"status": 200, …}
Un 2xx è la parola della destinazione, non la prova che il record sia stato memorizzato, e la
formulazione dice accepted anziché delivered di proposito. L'agente non può saperne di
più, e una riga che affermasse il contrario inventerebbe una garanzia. La riga
queued_for_delivery viene stampata a ogni ciclo — anche con la scansione disattivata — così
che un fallimento di consegna non possa mai essere scambiato per un collector che non sta
girando.
config validate non sa distinguere un token di enrolment da un JWT
Entrambi sono stringhe opache in un file, quindi un bitscanner config validate su una
configurazione il cui auth.token_file contiene un token di enrolment riporta:
Configuration is valid.
…e poi ogni esportazione fallisce a runtime. La validazione controlla i permessi del file, la
sua proprietà, ogni directory sul percorso che porta a esso e che sia configurata esattamente
una fonte — non controlla, e non può controllare, che i byte al suo interno siano il tipo
giusto di credenziale. Se la tua telemetria risponde 401 su un'installazione nuova, è la
prima cosa da controllare.
Passi successivi
- La scansione, e i due interruttori che la armano — cosa mette sulla rete ciascuna sonda, e la guardia che la autorizza.
- Panoramica di bitscanner — cosa produce un ciclo, e come verificare il download.
- Privacy di bitcollector — le impostazioni predefinite equivalenti per i dati dell'host: righe di comando disattivate, nessuna variabile d'ambiente, nessun hash di password.
- I tuoi diritti in materia di protezione dei dati (GDPR) — come Cert-IX gestisce le richieste relative ai dati personali.
Questa pagina ti è stata utile?