Saltar al contenido principal
Version: 1.0.0

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.

RegistroCampos
network_stateSolo rutas: destination, gateway, interface, metric, flags
neighbor_tablePor vecino: ip_address, hardware_addr, interface, state, type
neighbor_discoveryPor 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_discoveryPor pasarela: dirección, MAC, fabricante, is_default, alcanzabilidad y — solo con gateway_probe — los puertos que respondieron y cualquier banner que presentaran
service_discoveryRespondedores: 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_scan y gateway_probe, y por eso están desactivadas por defecto y condicionadas a un reconocimiento de autorización.
  • hostname solo 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 cabecera Authorization: Basic real, 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_key mientras tls.enabled es 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 anunciaba headers: {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 rechazo es una estructura, no un ajuste

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:

RechazadoPorque
Modo legible por el grupo u otrosTodas 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 regularUn 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ólicoEl 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 /tmpEsa 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:

  1. Genera un par de claves ECDSA P-256 en el host. La mitad privada se escribe en 0600 dentro de agent.data_dir, que se fuerza a 0700 — creado y vuelto a chmodear, porque MkdirAll deja intacto el modo de un directorio existente y una actualización sobre un directorio de datos en 0755 lo conservaría. La clave privada nunca sale de la máquina.
  2. Se registra una vez, presentando el token de enrolamiento en la cabecera X-Agent-Enrollment-Token y su clave pública en el cuerpo.
  3. 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.

El token de enrolamiento no es una credencial portadora

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):

AvisoCVSS¿Explotado en la práctica?De qué se trataCorregido en 0.1.3-ga
CVE-2026-39822 (GO-2026-4970)7.8 AltaNo — no está en CISA KEV, EPSS por debajo del umbralEscape de raíz mediante enlace simbólico más barra final en os✅
CVE-2026-42505 (GO-2026-5856)5.3 MediaNo — no está en CISA KEV, EPSS por debajo del umbralFuga 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​

¿Te resultó útil esta página?