Qué sale del host
bitscanner envía una descripción de la red de alguien fuera de la máquina en la que se ejecuta. Eso hace que dos preguntas sean determinantes: qué contiene y cómo viaja. Esta página responde a las dos y luego expone con claridad los límites de esta versión.
Para saber qué arma el sondeo en primer lugar, consulte El escaneo y los dos interruptores que lo arman.
Qué hay en la telemetría
Todo lo que envía bitscanner trata sobre la red que rodea al host, nunca sobre el contenido del propio host.
| Registro | Campos |
|---|---|
network_state | Solo rutas: destination, gateway, interface, metric, flags |
neighbor_table | Por vecino: ip_address, hardware_addr, interface, state, type |
neighbor_discovery | Por vecino: dirección, MAC, vendor (búsqueda OUI sin conexión), device_type, is_reachable, first_seen/last_seen — más hostname si reverse_dns está armada, services (puertos, y banners si port_scan está armada), mdns_services/ssdp_info si service_discovery está armada. Más las subredes escaneadas |
gateway_discovery | Por pasarela: dirección, MAC, fabricante, is_default, alcanzabilidad y — solo con gateway_probe — los puertos que respondieron y cualquier banner que presentaran |
service_discovery | Respondedores: name, type, host, port, protocol, discovered_via, registros TXT de mDNS |
Dos consecuencias que conviene explicitar:
- Las sondas más ruidosas registran las versiones de software de otras personas. Un banner
del puerto 22 o 25 es una cadena de versión que pertenece a un dispositivo que puede no ser
suyo. Eso no es un efecto secundario que se descubra después; es para lo que sirven
port_scanygateway_probe, y por eso están desactivadas por defecto y condicionadas a un reconocimiento de autorización. hostnamesolo existe si armóreverse_dns. Sin ella, un vecino es una dirección, una MAC y una conjetura de fabricante.
Qué no recopila deliberadamente
network_state llevaba antes interfaces (nombre, MTU, MAC, direcciones), listeners (el
inventario de sockets locales), una lista connections siempre vacía y un campo dns_servers
que nada rellenaba nunca — un campo nulo que afirmaba un inventario de resolutores que jamás se
recopiló. Todos han desaparecido, junto con el código y los tipos que había detrás.
Los tres primeros eran una repetición de los recopiladores de red y puertos de bitcollector. bitscanner no recopila datos del host — ni procesos, ni paquetes instalados, ni cuentas locales, ni líneas de comandos. Si lo que necesita es un registro sobre la máquina misma, ese viene del recopilador, bajo los valores de privacidad por defecto del recopilador.
Cada registro va envuelto en un sobre que lleva una suma checksum SHA-256 sobre su cabecera y
su carga útil. Eso es una verificación de integridad, no una firma — bitscanner no tiene
ningún equivalente de la cadena de evidencia firmada y encadenada por
hash de bitcollector.
La salida de datos es https, o el agente no arranca
La regla trata sobre los datos, no sobre la credencial: un mapa de la red del cliente que cruza el cable en texto claro es legible y alterable por cualquiera que esté en el camino, viaje o no un token con él.
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 es la única exclusión, solo desde fichero, se anuncia en el
registro en cada arranque, y es para laboratorios. Con una credencial configurada no hay
exclusión alguna — un endpoint en texto claro, o tls.skip_verify: true, se rechaza de plano,
porque cualquier cosa capaz de responder por el endpoint recoge la credencial.
Tres rechazos más de la misma familia:
- Una credencial incrustada en la URL se rechaza, con instrucciones para moverla al bloque
auth. Go convierte la información de usuario en una cabeceraAuthorization: Basicreal, aterriza en cada línea de registro que imprime el endpoint, y la redacción del agente no puede alcanzarla. - El material de certificado que se descartaría en silencio se rechaza. Poner
ca_cert/client_cert/client_keymientrastls.enabledes falso es un error, porque el bloque se leería como TLS mutuo sin presentar nada. headers:no existe. La antigua configuración de ejemplo anunciabaheaders: {Authorization: "Bearer YOUR-TOKEN"}y nunca hubo una línea de Go que lo leyera — había operadores que creían su telemetría autenticada mientras salía de forma anónima. Una configuración que contenga esa clave se rechaza por su nombre.
Nunca se sigue una redirección
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
Ambas mitades de eso son propiedades de la biblioteca estándar de Go, no especulación. Su regla
para volver a adjuntar Authorization compara solo el nombre de host — ni el esquema ni el
puerto — de modo que un servidor que responda 301 Location: http://same-host:80/ recupera el
token portador en texto claro. Una cabecera propia de clave de API no está en absoluto en la
lista de cabeceras sensibles de la biblioteca estándar, así que se copia a cualquier host
por cualquier esquema. Y en un 307/308 el cuerpo de la petición — el inventario de red del
cliente — se reenvía al host que la redirección indique.
El cliente de salida no es un *http.Client. Todos los campos de esa estructura son
exportados, así que c.CheckRedirect = nil o un transporte sustituido habrían eliminado ambos
controles en una sola asignación — y se demostró que ambas ediciones dejaban la batería de
pruebas completamente en verde. El cliente va envuelto en un tipo que expone únicamente Do y
CloseIdleConnections, y el transporte se construye a partir de una configuración TLS en lugar
de aceptarse de quien llama, así que no queda ningún round-tripper suministrado por el llamante
que pueda reescribir una petición.
La credencial se aplica en el momento del envío, no al construir la petición, y falla en cerrado: si el canal o la credencial no cuadran — sin resolver, no https, verificación de certificado desactivada — el lote no se envía. «Pon la cabecera si resulta que tenemos una» es la forma que ya envió telemetría sin autenticar una vez.
Los endpoints se redactan en los registros
Un endpoint se registra en cada fallo de exportación, así que la redacción es denegar por defecto
en lugar de una lista de nombres que parezcan credenciales: se redactan todos los valores de
consulta y el fragmento entero, conservando los nombres de parámetro para que la línea siga
siendo diagnosticable (api_key=[redacted] dice qué parámetro estaba puesto). Los segmentos de
ruta que parezcan portar credenciales también se redactan. Si la URL no se analiza en absoluto,
el redactor devuelve una constante — nunca el texto de quien llama, porque una URL que falla al
analizarse suele ser una cuya contraseña contiene los caracteres que rompen el analizador.
Ese último punto tiene un límite declarado: la heurística de rutas puede pasar por alto un
secreto corto o parecido a una palabra en un segmento de ruta. El control que sostiene todo es
que las credenciales pertenecen al bloque auth, donde están tipadas, revisadas y nunca
formateadas dentro de una cadena.
Ficheros de secretos
Un token solo está realmente fuera del fichero de configuración si nadie más puede leerlo.
bitscanner config validate se niega a arrancar ante cualquiera de estos casos, y el mensaje
nombra la ruta problemática y la solución. Verificado por ejecución — las rutas de abajo son rutas
de documentación sustituidas por las de la transcripción real:
# 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
El conjunto completo de rechazos:
| Rechazado | Porque |
|---|---|
| Modo legible por el grupo u otros | Todas las cuentas locales del host pueden leer el token |
| Propiedad de una tercera cuenta | «Solo el propietario puede leerlo» no vale nada cuando el propietario es otra persona |
| No es un fichero regular | Un FIFO o un dispositivo no es un fichero de secreto — y abrir sin más un FIFO sin escritor dejaría el agente colgado en el arranque sin diagnóstico alguno |
| Alcanzado a través de un enlace simbólico | El destino del enlace vive en un directorio que la comprobación no habría mirado |
Cualquier directorio de la ruta escribible por el grupo o por todos — incluido /tmp | Esa cuenta puede renombrar el subárbol y poner su propio fichero en su lugar |
Se recorren tanto los ancestros de la ruta resuelta como la ruta tal como está escrita,
y se demuestra por dispositivo e inodo que la ruta resuelta es exactamente el fichero cuyo
descriptor se está leyendo. Cada uno de esos puntos fue un agujero real, cerrado en una oleada
distinta: comprobar solo el padre inmediato, pasar por alto un abuelo escribible por todos, y
luego la imagen especular — recorrer solo la cadena resuelta, lo que aceptaba un fichero 0600
en un directorio 0700 alcanzado a través de uno 0777, porque la cadena resuelta es
impecable y es en la cadena escrita donde vive el atacante.
Por último, cada secreto viene de exactamente una fuente: el valor en línea, un
<field>_file o un <field>_env que nombre una variable de entorno. Dos fuentes a la vez son
un error en lugar de una regla de precedencia — con precedencia, un operador que añade
token_file dejando en su sitio un token en línea obsoleto no puede saber cuál está en el
cable.
Enrolamiento: la clave nunca sale del host
Usted acuña un token de enrolamiento de un solo uso en el panel de Cert-IX y lo pone en un fichero que solo el agente puede leer:
enrollment:
endpoints:
- "https://<your-cert-ix-agent-endpoint>"
enrollment_token_file: "/etc/bitscanner/enrollment.token" # chmod 600, owner-only
En el primer arranque el agente:
- Genera un par de claves ECDSA P-256 en el host. La mitad privada se escribe en
0600dentro deagent.data_dir, que se fuerza a0700— creado y vuelto achmodear, porqueMkdirAlldeja intacto el modo de un directorio existente y una actualización sobre un directorio de datos en0755lo conservaría. La clave privada nunca sale de la máquina. - Se registra una vez, presentando el token de enrolamiento en la cabecera
X-Agent-Enrollment-Tokeny su clave pública en el cuerpo. - Guarda el JWT de agente que devuelve la pasarela, en
0600, junto con su caducidad.
Después de eso el token está gastado: el fichero puede borrarse, y un reinicio carga la
identidad almacenada y no vuelve a registrarse. Un segundo registro sería un 409 contra un
token consumido, lo que dejaría al agente muerto. El JWT se renueva contra el endpoint de
refresco una hora antes de caducar, usando el propio token — no interviene ningún token de
enrolamiento.
Se presenta únicamente en X-Agent-Enrollment-Token, en exactamente una petición. Ponerlo
en auth.token_file es la mala configuración que hacía imposible la incorporación en una
compilación anterior: la pasarela de ingesta quiere un JWT de agente, así que cada petición
de telemetría devuelve 401. Use en su lugar auth: {type: enrollment} en el destino —
presenta la identidad almacenada, y no hay nada que pegar.
El endpoint de enrolamiento debe ser https bajo cualquier configuración.
security.i_accept_plaintext_egress no se le aplica: por ahí vuelve la identidad.
Ni la clave privada ni el token se registran, se formatean dentro de un error o se ponen en un
campo de registro jamás: los propios String() y GoString() del tipo de identidad imprimen
solo el identificador del agente y la caducidad, de modo que un %v extraviado no pueda
escribir para siempre el JWT de este host en un fichero de registro.
Límites honestos
Se declaran aquí en lugar de descubrirse después.
Los avisos de la biblioteca estándar de Go que afectaban a 0.1.2 están corregidos en 0.1.3
0.1.3-ga se compila con go1.25.12, que lleva la corrección de los dos avisos a los que
estaba expuesta 0.1.2-ga (compilada con go1.26.4):
| Aviso | CVSS | ¿Explotado en la práctica? | De qué se trata | Corregido en 0.1.3-ga |
|---|---|---|---|---|
CVE-2026-39822 (GO-2026-4970) | 7.8 Alta | No — no está en CISA KEV, EPSS por debajo del umbral | Escape de raíz mediante enlace simbólico más barra final en os | ✅ |
CVE-2026-42505 (GO-2026-5856) | 5.3 Media | No — no está en CISA KEV, EPSS por debajo del umbral | Fuga de privacidad de Encrypted Client Hello en crypto/tls | ✅ |
🪤 Conviene saberlo si usted mismo fija cadenas de herramientas: una versión de Go más alta
no siempre es la corregida. CVE-2026-39822 está corregido en go1.25.12 en la línea 1.25,
pero no hasta go1.26.5 en la línea 1.26, de modo que go1.26.4 — una cadena de herramientas
numéricamente más nueva — todavía lo arrastraba. Un simple suelo de «versión mínima» no puede
expresar un backport por rama, y por eso esta versión fija una imagen exacta y deja la
autoridad al escáner, no al número de versión. Nuestra propia compilación fue rechazada una vez
exactamente por esto, antes de corregir la fijación.
Si todavía ejecuta 0.1.2-ga, arrastra el aviso Alto — actualice. La versión de la cadena
de herramientas la imprime bitscanner version y queda registrada en el SBOM CycloneDX de la
versión, así que compruebe la compilación que tiene delante en lugar de confiar en esta tabla
para siempre.
«Entregado» significa «volvió un 2xx»
El agente reporta lo que encoló y lo que fue aceptado:
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 es la palabra del destino, no la prueba de que el registro se almacenó, y la redacción
dice aceptado en lugar de entregado a propósito. El agente no puede saber más que eso, y una
línea que afirmara lo contrario estaría inventando una garantía. La línea queued_for_delivery
se imprime en cada ciclo — incluso con el escaneo apagado — para que un fallo de entrega nunca
pueda confundirse con un recopilador que no se está ejecutando.
config validate no puede distinguir un token de enrolamiento de un JWT
Ambos son cadenas opacas en un fichero, así que un bitscanner config validate sobre una
configuración cuyo auth.token_file contiene un token de enrolamiento informa:
Configuration is valid.
… y después toda exportación falla en ejecución. La validación comprueba los permisos del
fichero, su propiedad, cada directorio de la ruta hasta él y que hay exactamente una fuente
configurada — no comprueba, ni puede, que los bytes de dentro sean el tipo correcto de
credencial. Si su telemetría devuelve 401 en una instalación nueva, esto es lo primero que
hay que mirar.
Próximos pasos
- El escaneo y los dos interruptores que lo arman — qué pone cada sonda en el cable y el guardián que lo autoriza.
- Visión general de bitscanner — qué produce un ciclo y cómo verificar la descarga.
- Privacidad de bitcollector — los valores por defecto equivalentes para los datos del host: líneas de comandos desactivadas, sin variables de entorno, sin hashes de contraseñas.
- Derechos de los interesados — cómo gestiona Cert-IX las solicitudes sobre datos personales.
¿Te resultó útil esta página?