Passa al contenuto principale
Versione: 1.0.0

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.

RecordCampi
network_stateSolo rotte: destination, gateway, interface, metric, flags
neighbor_tablePer ogni vicino: ip_address, hardware_addr, interface, state, type
neighbor_discoveryPer 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_discoveryPer ogni gateway: indirizzo, MAC, produttore, is_default, raggiungibilità e — solo con gateway_probe — le porte che hanno risposto e l'eventuale banner presentato
service_discoveryResponder: 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_scan e gateway_probe, ed è il motivo per cui sono disattivate per impostazione predefinita e protette da una dichiarazione di accettazione dell'autorizzazione.
  • hostname esiste solo se hai armato reverse_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 header Authorization: Basic attivo, 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_key mentre tls.enabled è false è un errore, perché la sezione si leggerebbe come mutual TLS pur non presentando nulla.
  • headers: non esiste. La vecchia configurazione di esempio pubblicizzava headers: {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 rifiuto è una struttura, non un'impostazione

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:

RifiutatoPerché
Mode leggibile da gruppo o da altriOgni 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 regolareUna 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 symlinkIl bersaglio del link vive in una directory che il controllo non avrebbe esaminato
Qualsiasi directory sul percorso scrivibile dal gruppo o da tutti — compresa /tmpQuell'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:

  1. Genera sull'host una coppia di chiavi ECDSA P-256. La metà privata viene scritta con permessi 0600 dentro agent.data_dir, che viene forzata a 0700 — creata e sottoposta di nuovo a chmod, perché MkdirAll lascia intatto il mode di una directory esistente e altrimenti un aggiornamento dentro una directory dati 0755 lo manterrebbe. La chiave privata non lascia mai la macchina.
  2. Si registra una sola volta, presentando il token di enrolment nell'header X-Agent-Enrollment-Token e la propria chiave pubblica nel corpo.
  3. 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.

Il token di enrolment non è una credenziale bearer

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:

AdvisoryCVSSSfruttata attivamente?Di cosa si trattaCorretta in 0.1.3-ga
CVE-2026-39822 (GO-2026-4970)7.8 AltaNo — non è nella CISA KEV, EPSS sotto la sogliaEvasione dalla root tramite symlink più slash finale in os✅
CVE-2026-42505 (GO-2026-5856)5.3 MediaNo — non è nella CISA KEV, EPSS sotto la sogliaFuga 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​

Questa pagina ti è stata utile?