Avai – your first AI antivirus Avai, an open-source host telemetry tool that uses large language models to classify threats, has been released. The tool snapshots 26 system corners on macOS and 21 on Linux, enriches findings with up to 17 threat-intel sources, and delivers verdicts with MITRE-aligned categories and remediation steps. Avai operates without an agent contract, SIEM, or cloud control plane, offering a read-only dashboard accessible via a single Docker command. Know what's actually running on your machines.Open-source host telemetry + LLM threat classifier. One docker run . avai snapshots 26 corners of your host on macOS 21 on Linux — processes, USB, persistence, file integrity, browser extensions, exec events — enriches each new finding with up to 17 threat-intel sources VirusTotal, MalwareBazaar, URLhaus, CISA KEV, Shodan, AbuseIPDB, OSV, NVD, … , and lets a Claude-class LLM tell you which ones are worth caring about. Verdicts come back as malicious / suspicious / unknown / benign with a MITRE-aligned category, a confidence, and a one-line remediation. - No agent contract, no SIEM, no cloud control plane. - Dedup by content hash — the same artifact is never sent to the LLM twice. - 17 plug-and-play threat-intel sources behind the LLM — see ; missing keys disable a source cleanly. .env.example - Read-only Flask + HTMX + Chart.js dashboard on :8765 . - BYO key ANTHROPIC API KEY / CLAUDE CODE OAUTH TOKEN , or swap to any litellm-supported provider. → Marketing site & screenshots: https://getavai.com → Source: https://github.com/iklobato/avai https://github.com/iklobato/avai A read-only Flask + HTMX + Chart.js dashboard on :8765 . Every panel renders from the same SQLite snapshot the monitor writes — no separate control plane. At-a-glance health: runs stored, collectors in the latest cycle with any failures , judgments since the last run, and the verdict-totals donut malicious / suspicious / unknown / benign . The macOS System Integrity panel surfaces FileVault, Firewall, Gatekeeper and remote-access toggles; Collector Errors shows what failed e.g. a TCC permission ; and the 12-hour chart tracks verdicts over time. The findings table below streams the active, non-benign results. The findings table is filterable by status, verdict, collector and category. Beneath it, Rows per collector shows how much each collector pulled in the latest run, and Recent runs lists run history with ok/failed counts and the look-back window. Expand any finding to see the LLM's reasoning , a concrete remediation step, and the exact collected data behind the verdict — for a process that means pid/ppid, the full cmdline , the running user/uid, status, the content hash used for dedup, and when it was first judged vs. last seen. The tcpdump aggregator groups traffic by destination so the classifier can reason about it: here an IPv6 connection to an unusual high port is flagged suspicious as a possible C2 beacon, while CDN, mDNS and LAN traffic come back benign — each with a one-line "why". The same view enriched per destination with the owning process , ASN/geo, traffic volume, and the rationale for each verdict. Run against a different host/cycle — 61 runs and 3,426 verdicts here — with suspicious AirWatch/MDM persistence surfaced for review. | Run | Command | Where it makes sense | |---|---|---| | Dashboard default | docker run iklob1/avai | any host — read-only Flask + HTMX on :8765 | | Monitor | docker run ... iklob1/avai avai monitor ... | Linux hosts only — needs pid=host , network=host , and host filesystem bind-mounts | The image's default CMD is the dashboard. Override the command at docker run / compose level to run the monitor instead. Native install is also possible pip install avai-monitor , then avai monitor / avai dashboard but is not the documented path. The image carries a HEALTHCHECK against the dashboard's /api/notifications/new endpoint — starting → healthy in ~10 s on first launch. docker compose ps and docker inspect --format '{{.State.Health.Status}}' will both reflect it. A safe first run on any host macOS or Linux , no privileges, no credentials, no host bind-mounts. Produces a populated DB and a green dashboard you can poke at. mkdir -p ~/.avai && cd ~/.avai 1. populate the DB with one snapshot of the container's view docker run --rm -v "$PWD":/data iklob1/avai \ avai monitor --once --no-streaming --no-judge --db /data/avai.db 2. serve it docker run -d --name avai -p 8765:8765 -v "$PWD":/data iklob1/avai open http://localhost:8765/ macOS; xdg-open on Linux You'll see ~14 collectors' worth of rows processes , network connections , listening ports , network interfaces , usb devices , launch items , installed apps , mounts , setuid files , etc. — read off the container itself rather than the host, since the run above doesn't bind-mount host state. To get real data, jump to §2 / §3 below. Stop with docker stop avai && docker rm avai . The dashboard reads a SQLite database written by the monitor or by a previous run . It needs no privileges, no host namespace, no capabilities — just a directory containing avai.db mounted at /data . mkdir -p ~/.avai && cd ~/.avai docker run -d \ --name avai-dashboard \ -p 8765:8765 \ -v "$PWD":/data \ iklob1/avai open http://localhost:8765/ If the database file doesn't exist yet, the dashboard creates an empty schema on launch and every panel renders empty until the monitor produces rows. Stop with docker stop avai-dashboard && docker rm avai-dashboard . docker run --rm -p 9000:9000 \ -v /var/lib/avai:/data \ iklob1/avai \ avai dashboard --host 0.0.0.0 --port 9000 --db /data/custom.db The image entry point is avai ; anything after the image name is passed to it. A single cycle on the local Linux host. No streaming, no LLM judge — fast smoke test that the bind mounts are wired right. mkdir -p ~/.avai && cd ~/.avai docker run --rm \ --pid=host \ --network=host \ --user 0:0 \ --cap-add SYS PTRACE --cap-add NET ADMIN --cap-add NET RAW --cap-add DAC READ SEARCH \ -e HOST PREFIX=/host \ -v /proc:/host/proc:ro \ -v /sys:/host/sys:ro \ -v /etc:/host/etc:ro \ -v /var/lib/bluetooth:/host/var/lib/bluetooth:ro \ -v /var/lib/dpkg:/host/var/lib/dpkg:ro \ -v /usr/share/applications:/host/usr/share/applications:ro \ -v /lib/systemd:/host/lib/systemd:ro \ -v /usr/lib/systemd:/host/usr/lib/systemd:ro \ -v /run/systemd:/run/systemd:ro \ -v /run/dbus:/run/dbus:ro \ -v /etc/machine-id:/etc/machine-id:ro \ -v /dev/mapper:/dev/mapper:ro \ -v /home:/host/home:ro \ -v /root:/host/root:ro \ -v "$PWD":/data \ iklob1/avai \ avai monitor --once --no-streaming --no-judge --db /data/avai.db When the command exits, ~/.avai/avai.db contains one collection runs row plus the populated collector tables. Verify: python docker run --rm -v "$PWD":/data iklob1/avai python -c " import sqlite3 c = sqlite3.connect '/data/avai.db' for n, in c.execute \"select name from sqlite master where type='table'\" : print f'{n:<22} {c.execute f\"select count from {n}\" .fetchone 0 }' " To smoke-test on macOS without the bind-mounts no host data, but proves the toolchain works see §0 above. Same bind mounts as §2 but detached, with the LLM judge enabled. The judge needs one credential — either ANTHROPIC API KEY standard Anthropic API or CLAUDE CODE OAUTH TOKEN Claude Code OAuth — and defaults to Claude Haiku 4.5 claude-haiku-4-5-20251001 . Override with --judge-model to point litellm at any other provider. Threat-intel enrichment runs automatically with whatever keys are in the environment VT API KEY , ABUSE CH AUTH KEY , ABUSEIPDB API KEY , … . Easiest pattern is a project-local .env : cp .env.example .env && vi .env fill in only the keys you have docker run -d --env-file .env --name avai-monitor ... iklob1/avai See § Threat-intel enrichment below for the full source list and each source's gate condition. mkdir -p ~/.avai && cd ~/.avai docker run -d --name avai-monitor --restart unless-stopped \ --pid=host --network=host --user 0:0 \ --cap-add SYS PTRACE --cap-add NET ADMIN --cap-add NET RAW --cap-add DAC READ SEARCH \ -e HOST PREFIX=/host \ -e DBUS SYSTEM BUS ADDRESS=unix:path=/run/dbus/system bus socket \ -e ANTHROPIC API KEY \ -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /etc:/host/etc:ro \ -v /var/lib/bluetooth:/host/var/lib/bluetooth:ro \ -v /var/lib/dpkg:/host/var/lib/dpkg:ro \ -v /usr/share/applications:/host/usr/share/applications:ro \ -v /lib/systemd:/host/lib/systemd:ro \ -v /usr/lib/systemd:/host/usr/lib/systemd:ro \ -v /var/log/journal:/host/var/log/journal:ro \ -v /var/spool/cron:/host/var/spool/cron:ro \ -v /run/systemd:/run/systemd:ro -v /run/dbus:/run/dbus:ro \ -v /etc/machine-id:/etc/machine-id:ro \ -v /dev/mapper:/dev/mapper:ro \ -v /home:/host/home:ro -v /root:/host/root:ro \ -v "$PWD":/data \ iklob1/avai \ avai monitor --db /data/avai.db --interval 300 docker logs -f avai-monitor watch the cycle Defaults baked into avai monitor : | Flag | Default | Effect | |---|---|---| --interval | 300 | seconds between snapshot cycles | --lookback-min | 6 | minutes of journal/log history per run | --max-db-mb | 1024 | rotation cap 0 disables ; oldest runs are pruned + VACUUM 'd after each cycle | --judge-model | claude-haiku-4-5-20251001 | any litellm model id | --judge-batch-size | 20 | entries per LLM call | --judge-max-per-collector | 25 | per-cycle cap of new entries judged per collector | --no-streaming | off | disables auth events + process exec events tailers | --no-judge | off | runs collectors but stores no verdicts | --no-enrich | off | skips the whole threat-intel layer; collectors → judge directly | --enrich-only NAME | all | restrict the chain to one named source repeatable ; useful for debugging | Append any flag to the docker run … iklob1/avai avai monitor … command to override. Full reference: docker run --rm iklob1/avai avai monitor --help . docker-compose.yml : x-avai-image: &avai-image image: iklob1/avai:latest services: monitor: <<: avai-image container name: avai-monitor command: "avai","monitor","--db","/data/avai.db","--interval","300" user: "0:0" pid: host network mode: host cap add: SYS PTRACE, NET ADMIN, NET RAW, DAC READ SEARCH Loads LLM-judge + every threat-intel API key from .env. Copy .env.example to .env and fill in only the keys you have. env file: .env environment: - HOST PREFIX=/host - DBUS SYSTEM BUS ADDRESS=unix:path=/run/dbus/system bus socket volumes: - ./data:/data - /proc:/host/proc:ro - /sys:/host/sys:ro - /etc:/host/etc:ro - /var/lib/bluetooth:/host/var/lib/bluetooth:ro - /var/lib/dpkg:/host/var/lib/dpkg:ro - /usr/share/applications:/host/usr/share/applications:ro - /lib/systemd:/host/lib/systemd:ro - /usr/lib/systemd:/host/usr/lib/systemd:ro - /var/log/journal:/host/var/log/journal:ro - /var/spool/cron:/host/var/spool/cron:ro - /run/systemd:/run/systemd:ro - /run/dbus:/run/dbus:ro - /etc/machine-id:/etc/machine-id:ro - /dev/mapper:/dev/mapper:ro - /home:/host/home:ro - /root:/host/root:ro restart: unless-stopped dashboard: <<: avai-image container name: avai-dashboard uses the image's default CMD ports: "8765:8765" volumes: "./data:/data" restart: unless-stopped Then: mkdir -p data cp .env.example .env && vi .env fill in the keys you have docker compose up -d docker compose logs -f monitor open http://localhost:8765/ If you already have an avai.db produced by the monitor on a different machine, dropped into the current directory, etc. : docker run --rm -p 8765:8765 -v "$PWD":/data iklob1/avai The dashboard opens the file with ?mode=ro&immutable=1 , so it never writes and never holds a lock — fine to point at a live database being written by the monitor in another container. Inspect the bundled CLI docker run --rm iklob1/avai avai --help docker run --rm iklob1/avai avai monitor --help docker run --rm iklob1/avai avai dashboard --help docker run --rm iklob1/avai avai --version Healthcheck + status docker inspect avai-dashboard --format '{{.State.Health.Status}}' healthy|unhealthy|starting docker compose ps if using compose docker logs -f avai-monitor follow monitor cycles DB rotation in action — watch the size cap kick in docker exec avai-monitor du -h /data/avai.db Stop / clean up docker compose down if using compose docker stop avai-dashboard avai-monitor 2 /dev/null docker rm avai-dashboard avai-monitor 2 /dev/null Wipe the database also wipes verdicts; monitor will re-judge from scratch rm -f data/avai.db data/avai.db-wal data/avai.db-shm Pull the latest image docker pull iklob1/avai Practical, copy‑paste scenarios beyond the basics above. Inside a container on a real Linux host the monitor already works, but the simplest way to watch a server is to install it natively and let it see everything directly: pip install 'avai-monitor judge ' judge pulls litellm + anthropic export ANTHROPIC API KEY=sk-ant-... or CLAUDE CODE OAUTH TOKEN export ABUSE CH AUTH KEY=... optional, free — adds 3 sources sudo -E avai monitor --db /var/lib/avai/avai.db --interval 300 & avai dashboard --db /var/lib/avai/avai.db --host 0.0.0.0 --port 8765 sudo lets the collectors read root‑owned state /etc/shadow , other users' crontabs, every process . -E preserves your API keys across the sudo boundary. /etc/systemd/system/avai.service : Unit Description=avai host monitor After=network-online.target Service Environment=ANTHROPIC API KEY=sk-ant-... Environment=ABUSE CH AUTH KEY=... ExecStart=/usr/local/bin/avai monitor --db /var/lib/avai/avai.db --interval 300 Restart=always User=root Install WantedBy=multi-user.target sudo systemctl enable --now avai journalctl -u avai -f watch cycles Everything lives in one SQLite file, so you can query it directly — handy for scripting, cron mail, or a server with no browser: The active dangerous + suspicious findings, newest first sqlite3 -box /var/lib/avai/avai.db " SELECT verdict, collector, substr reasoning,1,60 AS why FROM judgements WHERE verdict IN 'malicious','suspicious' ORDER BY created at DESC LIMIT 20;" Count by verdict sqlite3 /var/lib/avai/avai.db \ "SELECT verdict, count FROM judgements GROUP BY verdict;" What did the threat-intel sources say? sqlite3 -box /var/lib/avai/avai.db " SELECT source, verdict hint, substr summary,1,70 FROM enrichment evidence WHERE verdict hint IN 'malicious','suspicious' ;" /etc/cron.d/avai — scan once an hour, no streaming 0 root ANTHROPIC API KEY=sk-ant-... \ avai monitor --once --no-streaming --db /var/lib/avai/avai.db The monitor writes the DB; the dashboard only reads it. Sync the file rsync/scp/NFS and view it anywhere: on the server writer avai monitor --db /var/lib/avai/avai.db --interval 300 pull it to your laptop and view reader — any OS, no privileges scp server:/var/lib/avai/avai.db ./avai.db docker run --rm -p 8765:8765 -v "$PWD":/data iklob1/avai avai monitor \ --judge-model claude-haiku-4-5-20251001 \ cheapest tier default --judge-max-per-collector 20 \ cap new items judged per cycle --judge-batch-size 20 entries per API call Cost is near‑zero in steady state anyway — only new artifacts are judged, and threat‑intel verdicts are cached, so quiet hosts make almost no API calls after the first cycle. avai monitor --no-enrich collectors + judge only avai monitor --enrich-only cisa kev just this source repeatable avai monitor --enrich-only virustotal --enrich-only abuseipdb Source names: malware bazaar urlhaus threatfox circl hashlookup shodan internetdb feodo tracker osv cisa kev nvd endoflife crtsh virustotal abuseipdb greynoise safe browsing phishtank github advisory . --judge-model is a litellm https://docs.litellm.ai/docs/providers model id, so any supported provider works: avai monitor --judge-model gpt-4o-mini OpenAI OPENAI API KEY avai monitor --judge-model ollama/llama3.1 local, free, offline avai monitor --judge-model gemini/gemini-1.5-pro Google Snapshot collectors run every cycle, default 300s : | Group | Sources | |---|---| | Processes / network | processes , network connections , listening ports , network interfaces psutil | | Hardware | usb devices /sys/bus/usb , bluetooth devices /var/lib/bluetooth , wifi state sysfs + iw | | Persistence | launch items systemd unit files + cron | | Files | file integrity passwd / shadow / sudoers / SSH config / dotfiles , setuid files , mounts | | Apps | installed apps dpkg-query + XDG .desktop , browser extensions | | Posture | system integrity SELinux / AppArmor / ufw / sshd / vnc / LUKS | | Posture macOS only | tcc permissions camera/mic/location/screen grants , quarantine events , mdm profiles , kernel extensions , system extensions | Streaming collectors events as they happen : | Collector | Source | |---|---| auth events | journalctl -f Linux / macOS unified log macOS , filtered to security-relevant subsystems. LLM-judged by unique process, subsystem, message pattern — each event template is classified once regardless of how many times it fires. | process exec events | journalctl -f AUDIT TYPE NAME=EXECVE needs auditd auditctl -a always,exit -F arch=b64 -S execve rule | For every entity collected deduped by a content hash over the collector's "judge fields" , the LLM judge classifies it as malicious / suspicious / unknown / benign with a confidence, MITRE-aligned category, and one-line remediation. Judgments are persisted; the same artifact is never sent twice. The Flask + HTMX dashboard at :8765 has full filter and pagination on every table: Findings — filter by verdict, collector, category, status active/resolved , free-text search; sortable columns; configurable page size 10/25/50/100 . Network flows — filter by verdict and IP/host/process search; summary stats destinations, volume, malicious count . Listening ports — filter by verdict and bind scope all interfaces / routable / loopback ; process search. DNS queries — filter by verdict, resolution level DoH / external DNS / local resolver , domain search. Persistence — SSH authorized keys, /etc/hosts mappings, and privilege config each with independent pagination. Auth events — aggregated by unique process, subsystem, message pattern with occurrence counts and last-seen timestamps. Filter by subsystem TCC, securityd, syspolicy, loginwindow, Authorization or verdict. Sort by count or verdict severity. LLM verdicts appear as patterns are classified. TCC permissions macOS — every app's camera, microphone, location, screen-recording, and full-disk-access grant/denial, with LLM verdict and auth-status filter. All sections auto-refresh 30–60 s . Toast notifications + audio alert fire for new malicious/suspicious judgments. Before each finding hits the LLM, avai extracts indicators SHA256, IPv4, domain, URL, CVE, package, OS version and runs them through external threat-intel APIs. The judge then sees the raw evidence inline in the prompt, which dramatically tightens verdicts. Every source is optional. Keyless ones always run. Keyed ones only register if the env var below is set — see .env.example /iklobato/avai/blob/release/0.1.0/.env.example for a copy-paste template. | Source | Indicator | Env var | Quota | What it adds | |---|---|---|---|---| MalwareBazaar abuse.ch | SHA256/1/MD5 | ABUSE CH AUTH KEY | unlimited | Known-malware family | CIRCL hashlookup NSRL | SHA256/1/MD5 | — | unlimited | Known-good vendor binary whitelist | Shodan InternetDB | IPv4 | — | 1 rps | Open ports, CVEs, tags | URLhaus abuse.ch | URL, domain | ABUSE CH AUTH KEY | unlimited | Malware-distribution URLs | Feodo Tracker abuse.ch | IPv4 | — | unlimited | Botnet C2 IPs cached feed | ThreatFox abuse.ch | IPv4 / domain / URL / hash | ABUSE CH AUTH KEY | unlimited | Mixed IOC search | OSV.dev | CVE, package | — | unlimited | Open-source advisories | CISA KEV | CVE | — | static feed | Actively-exploited CVEs | NVD | CVE | NVD API KEY optional | 5 → 50 / 30 s | CVSS + description | crt.sh | domain | — | gentle | Certificate transparency history | endoflife.date | OS version | — | unlimited | EOL'd OS / runtime | VirusTotal | SHA256/1/MD5, URL, domain, IPv4 | VT API KEY | 4/min, 500/day | Multi-engine reputation | AbuseIPDB | IPv4 | ABUSEIPDB API KEY | 1000/day | Abuse confidence score | GreyNoise Community | IPv4 | GREYNOISE API KEY | 50/day | "Is this IP just noise?" | Google Safe Browsing | URL | GOOGLE SAFE BROWSING API KEY | 10k/day | Phishing / malware verdict | PhishTank | URL | PHISHTANK API KEY | generous | Community phishing DB | GitHub Advisory | CVE | GITHUB TOKEN | high | Curated advisories + fix versions | Per-indicator results are cached in the same SQLite enrichment evidence table with a per-source TTL 6 h – 14 d . Fresh cache hits skip the network entirely; the cache survives restarts. Toggle with: avai monitor all enabled sources, default avai monitor --no-enrich collectors + judge, no external lookups avai monitor --enrich-only malware bazaar debugging: only this one The monitor relies on Linux-native facilities — pid=host reaching the host's /proc , sysfs at /sys/bus/usb , journalctl with auditd , systemctl is-active , dpkg-query , dmsetup for LUKS. Docker Desktop on macOS only exposes the Linux VM it ships with, not the macOS host, so a containerised monitor on macOS reports on the VM empty/uninteresting rather than the Mac. The dashboard role works fine on macOS Docker — you'd just need to write the database from somewhere else. If you want full macOS coverage, install natively pip install avai-monitor and run avai monitor with sudo . That's a separate path not documented here. The suite is network-free and runs in seconds. The repo's dev Python may carry plugin conflicts, so run it in a throwaway venv: python3 -m venv /tmp/venv && /tmp/venv/bin/pip install -e . pytest /tmp/venv/bin/python -m pytest tests/ -q 320+ unit tests Coverage spans the enrichment framework and all 18 sources, the indicator extractors, the HTTP client rate limit / backoff / 429 , the CLI dispatcher, the SQLAlchemy repository + DB rotation, the LLM judge's parsing, the dashboard endpoints, and the Linux collectors' file parsing systemd / cron / .desktop / BlueZ . Tests are written to fail when the implementation breaks — verified by mutation testing, not just coverage percentage. Unattended Docker smoke test builds the image, runs the CLI surface, a cold collector pass, and the keyless-enrichment registry check : tests/local.sh all phases; exits non-zero on any failure See CHANGELOG.md /iklobato/avai/blob/release/0.1.0/CHANGELOG.md for version history. MIT — see LICENSE .