---
name: dns_records
kind: function
lang: py
domain: cybersecurity
version: "2.0.0"
purity: impure
signature: "def dns_records(domain: str, record_types: list[str] | None = None, timeout_s: int = 20) -> dict"
description: "Recoleccion OSINT pasiva de registros DNS de un dominio ejecutando `dig +short <domain> <TYPE>` por subprocess para cada tipo (default A, AAAA, MX, NS, SOA, TXT, CNAME). Devuelve dict de estado {status, domain, records:{TYPE:[valores]}, raw} sin lanzar; `raw` concatena un bloque legible por tipo para guardar la evidencia en un vault OSINT. Pasivo: solo consulta DNS publico."
tags: [recon, dns, cybersecurity, osint-passive, dig]
params:
  - name: domain
    desc: "Dominio a resolver, ej. google.com. Vacio devuelve status error."
  - name: record_types
    desc: "Lista de tipos de registro DNS (ej. ['A','MX']). None usa los 7 defaults: A, AAAA, MX, NS, SOA, TXT, CNAME."
  - name: timeout_s
    desc: "Timeout total en segundos repartido entre las consultas (cada dig recibe timeout_s/N, minimo 2s)."
output: "dict de estado. En exito {status:'ok', domain, records:{TYPE:[valores]}, raw}: records es un dict por tipo con la lista de lineas de `dig +short` (lista vacia si no hay registro o el dominio no existe); raw es texto '=== TYPE ===\\n...' por cada tipo. En fallo {status:'error', error:str, raw:''}."
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_py_core"
imports: []
tested: true
tests: ["test_parsea_salida_de_dig", "test_dominio_inexistente_listas_vacias", "test_usa_tipos_default", "test_timeout_devuelve_lista_vacia", "test_dominio_vacio_status_error"]
test_file_path: "python/functions/cybersecurity/dns_records_test.py"
file_path: "python/functions/cybersecurity/dns_records.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from cybersecurity import dns_records

# Resolver todos los tipos por defecto
res = dns_records("google.com")
print(res["status"])             # "ok"
print(res["records"]["A"])       # ['142.250.x.x', ...]
print(res["records"]["MX"])      # ['10 smtp.google.com.', ...]
print(res["raw"])                # bloque "=== A ===\n...\n=== MX ===\n..." para el vault

# Solo los tipos que interesan
solo_a_mx = dns_records("google.com", record_types=["A", "MX"], timeout_s=10)
```

## Cuando usarla

Usala al iniciar el reconocimiento pasivo de un dominio para mapear su
infraestructura DNS publica (IPs, servidores de correo, nameservers, SOA, TXT
con SPF/DKIM/verificaciones). Es el primer paso barato antes de enumerar
subdominios o consultar RDAP. Guarda `raw` directamente en la nota OSINT como
evidencia.

## Gotchas

- Funcion impura: hace red (consulta DNS via `dig`). No determinista entre
  resolvers ni en el tiempo (TTL, propagacion).
- Requiere el binario `dig` en PATH (paquete `dnsutils`/`bind-utils`). Si
  falta, devuelve `{"status":"error",...}` (no lanza).
- Nunca lanza: errores se reportan en `status`. Un dominio inexistente o sin un
  registro concreto devuelve `status:"ok"` con listas vacias — distingue
  "sin datos" mirando las listas, no el status.
- La salida es la de `dig +short` cruda: los MX incluyen prioridad
  ("10 mail..."), los nombres pueden venir con punto final (FQDN), y los TXT
  entre comillas. No se normaliza para mantener fidelidad.
- El `timeout_s` se reparte entre las N consultas (minimo 2s por consulta); si
  una expira, esa clave queda como lista vacia, su bloque `raw` dice "(timeout)"
  y el resto continua.
- Resuelve contra el resolver configurado en el sistema; resultados pueden
  variar segun el DNS recursivo usado.

## Capability growth log

v2.0.0 (2026-06-14) — reescritura del contrato: firma `(domain, record_types, timeout_s)`, retorno dict de estado `{status, domain, records, raw}` sin lanzar (antes `{tipo:[valores]}` con RuntimeError), `raw` legible por tipo para vault OSINT, default amplia a NS+SOA, `error_type` pasa a `error_py_core`.
