visidata: enhance IOC plugins with improved lookups and validation

Expand iplib, iptype, and ioc plugins with better caching, throttling,
and lookup logic. Update validation script and showcase journal accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
tobias
2026-03-07 22:49:49 +01:00
parent 559fa38c04
commit 49db614262
6 changed files with 608 additions and 44 deletions

View File

@@ -21,7 +21,7 @@ from urllib.parse import urlsplit
from visidata import vd
from visidata.sheets import TableSheet
from .iplib import JSONNode, VTInfo, parse_vt_ip
from .iplib import JSONNode, VTInfo, parse_vt_domain, parse_vt_file, parse_vt_url
from .ioclib import MBInfo, URLParts, parse_mb_info, vt_url_id
from .lookupcore import (
auth_tag,
@@ -231,7 +231,43 @@ class DomainValue:
@property
def vt(self) -> VTInfo:
data = _vt_domain_raw(self._d)
return parse_vt_ip(data) if data else VTInfo()
return parse_vt_domain(data) if data else VTInfo(object_type="domain")
@property
def resolveipv4(self):
from .iptype import ip
out = []
for v in self.dns.a:
iv = ip(v)
if iv is not None:
out.append(iv)
return tuple(out)
@property
def resolveipv6(self):
from .iptype import ip
out = []
for v in self.dns.aaaa:
iv = ip(v)
if iv is not None:
out.append(iv)
return tuple(out)
@property
def resolveips(self):
return tuple(list(self.resolveipv4) + list(self.resolveipv6))
@property
def resolveip(self):
ips4 = self.resolveipv4
if ips4:
return ips4[0]
ips6 = self.resolveipv6
if ips6:
return ips6[0]
return None
def _normalize_domain(s: str) -> str:
@@ -308,7 +344,7 @@ class URLValue:
@property
def vt(self) -> VTInfo:
data = _vt_url_raw(self._u)
return parse_vt_ip(data) if data else VTInfo()
return parse_vt_url(data) if data else VTInfo(object_type="url")
def url_ioc(val: Any) -> Optional[URLValue]:
@@ -381,7 +417,7 @@ class HashValue:
@property
def vt(self) -> VTInfo:
data = _vt_file_raw(self._h)
return parse_vt_ip(data) if data else VTInfo()
return parse_vt_file(data) if data else VTInfo(object_type="file")
@property
def mb(self) -> MBInfo:
@@ -411,16 +447,16 @@ vd.addGlobals(domain=domain, url_ioc=url_ioc, hash_ioc=hash_ioc)
vd.addType(
domain,
icon="d",
icon="🌐",
formatter=lambda fmt, v: "" if v is None else str(v),
name="Domain",
)
vd.addType(
url_ioc, icon="u", formatter=lambda fmt, v: "" if v is None else str(v), name="URL"
url_ioc, icon="🔗", formatter=lambda fmt, v: "" if v is None else str(v), name="URL"
)
vd.addType(
hash_ioc,
icon="#",
icon="🔐",
formatter=lambda fmt, v: "" if v is None else str(v),
name="Hash",
)
@@ -444,3 +480,15 @@ TableSheet.addCommand(
vd.addMenuItem("Column", "Type", "Domain", "type-domain")
vd.addMenuItem("Column", "Type", "URL (IOC)", "type-url-ioc")
vd.addMenuItem("Column", "Type", "Hash", "type-hash")
try:
_probe = TableSheet("_probe")
if _probe.getCommand(";d") is None:
TableSheet.bindkey(";d", "type-domain")
if _probe.getCommand(";u") is None:
TableSheet.bindkey(";u", "type-url-ioc")
if _probe.getCommand(";h") is None:
TableSheet.bindkey(";h", "type-hash")
except Exception:
pass