From 0.8.0 (free, every plan, not gated; beta; catalogue
internet-exposure; internal/exposure, internal/admin/exposure.go).
The threat it answers: a service of the house reachable from the Internet
— a camera, a NAS, a remote desktop, a telnet left on by the maker —
opened by a port forwarding nobody remembers, or by a device itself
through UPnP; the botnets scan the whole Internet for them within hours.
Network → Exposure on the Internet (#/network/exposure; the
roles that read the network; Check now and the acknowledgements need
the right to change it) shows:
- the last check (when, and why: the first after the installation, the week, asked by an administrator, the router changed), the next one, a count by severity, Check now;
- What is exposed: each finding with its severity, one sentence, the advice in plain words, when it was first found, and I know, do not alert again (reversible with Alert again; the acknowledged findings are listed apart);
- Seen from the Internet: for IPv4 (and IPv6 when checked) the public address checked and the ports open, or "nothing open among the N ports checked", or why it was not checked, and what the check cannot see;
- Port mappings of the router (UPnP): the state (on, off, not readable, unknown) and each mapping (protocol, external port → device or address : internal port, the description the device gave, the lease; those disabled faded);
- Telnet and FTP on your network: how many machines present were tried.
How it works.
- The port mappings of the router (UPnP IGD, on the local network
only). An SSDP search (239.255.255.250:1900, IPv4) for an
InternetGatewayDevice (versions 1 and 2), answers read for 3 seconds, 8
gateways and 32 answers at most. An answer is kept only from a private
or link-local address of a network of the box (never the box itself),
when its description (
LOCATION) is plainhttp://on that same address (never followed elsewhere, no account in it, 512 characters at most); the default gateway is asked first. The description is read in 3 seconds, without proxy nor redirection, 64 KiB at most, parsed strictly; the service WANIPConnection:2, WANIPConnection:1 or WANPPPConnection:1, whose control address must be on the gateway itself, is asked each mapping in turn (GetGenericPortMappingEntry; 3 seconds each, 30 seconds in all, 256 mappings at most: "truncated"). Each mapping is checked and named after the machine of the network with that address. - A check from outside (only once the terms of use in force are
accepted:
2026-10-12, which named it, then from 0.8.22026-10-13; otherwise the stateoff): a signedPOST /v1/exposure{"ports": [...]}to the online service, which connects (TCP, nothing sent) to the address the request comes from, and to no other, on a fixed list of risky ports plus the external TCP ports of the enabled mappings that are not in it (32 at most, the lowest first). It is sent once over IPv4 and, when the box has a global IPv6 address (2000::/3), once over IPv6; neither goes through a proxy (the service checks the address the request comes from). These requests do not count as failures of the online service for its failover. States per family:ok,off,limit(the hours to wait shown),busy,unreachable(the service cannot reach that family of addresses: shown as "cannot reach this kind of address yet", never as "nothing open"),not_public,failed. - Telnet and FTP on the network: a TCP connection (nothing sent) from the box to each machine present (seen within 45 minutes) of its networks, never itself, on ports 23, 2323 and 21; 16 at once, 1.5 seconds each, 60 seconds and 256 machines at most.
The fixed list (GET /api/v1/network/exposure gives it with its labels):
21 FTP, 22 SSH, 23 and 2323 Telnet, 25 SMTP, 80, 8080, 8000, 8081 HTTP,
443, 8443 HTTPS, 139 NetBIOS, 445 SMB, 548 AFP, 2049 NFS, 111 RPC, 1433 SQL
Server, 3306 MySQL, 5432 PostgreSQL, 6379 Redis, 27017 MongoDB, 9200
Elasticsearch, 3389 Remote Desktop, 5900 VNC, 5938 TeamViewer, 554 RTSP
(cameras), 37777 Dahua DVR, 34567 DVR, 1883 MQTT, 5000 and 5001 NAS
(Synology), 8291 MikroTik, 10000 Webmin, 7547 TR-069, 9100 and 631
printers, 32400 Plex, and from 0.8.0 53 DNS and 853 DNS over TLS (at the
end of the list, the same list and order as warda-cloud).
The ratings (internal/exposure/rating.go; the kind of a device is
the one chosen, else the one Warda proposes, see
Kinds of devices):
| Severity | Open from the Internet | On the network, or else |
|---|---|---|
| Critical | Telnet (23, 2323), file sharing (445, 139, 548, 2049, 111), remote screens (3389, 5900, 5938), databases (1433, 3306, 5432, 6379, 27017, 9200), MQTT (1883), video recorders (37777, 34567), TR-069 (7547), from 0.8.0 DNS (53, 853: an open resolver) | — |
| High | SSH (22), FTP (21), administration of a NAS or a router (5000, 5001, 10000, 8291), RTSP (554), printers (9100, 631), web (80, 443, 8080, 8443, 8000, 8081) and SMTP (25) on a device that is not a server (the router itself included) | telnet on a camera, a connected object or a TV |
| Medium | web or SMTP to a device of the kind server; a port of a mapping outside the list that leads to a camera, a connected object or a TV | telnet elsewhere; FTP on a camera, a connected object or a TV |
| Information | Plex (32400); another port of a mapping outside the list; a UDP mapping (not checkable from outside) | UPnP on in the router; FTP elsewhere |
A mapping not confirmed from outside (the check over IPv4 did not run) is rated as if open when it is TCP; a TCP mapping checked and found closed is only listed. Over IPv6 the address checked is the Warda box itself (each device has its own IPv6 address): a port open there means the router lets incoming IPv6 in. The advice: remove the port forwarding, turn UPnP off, turn off the administration of the router from the Internet, change the password, update the firmware, turn the service off, prefer a VPN, ask the provider (TR-069), turn on the IPv6 firewall of the router, and from 0.8.0 for DNS "Remove the port forwarding of 53/853 to Warda in the router: Warda must never answer the Internet…" (with the IPv6 firewall over IPv6, or "turn UPnP off" for a mapping).
Rhythm (task Exposure on the Internet, network.exposure, "Every
7 days", see Scheduled tasks): the first check 10 to 60
minutes after the start of a box never checked, then once a week at a slot
of its own; another router (address and hardware address of the gateway,
compared every hour) starts a check, not within an hour of the last.
Check now: one at a time, every 2 minutes at most (409 otherwise); the
online service allows 6 checks a day per box and per address, and a box
with IPv6 uses two per check. On the secondary of a
pair the automatic checks do not run.
Alerts: one per new finding of severity critical, high or medium
(never information), kind network.exposure ("Exposure on the Internet
(High): …", with a link to the page; group Security; SIEM severity 7;
detail source|family|protocol|port|severity|label, the source
internet, upnp or lan). Never again the following weeks, never for an
acknowledged one; a port forwarded by UPnP keeps the identifier of its
mapping whether the check from outside confirmed it that week or not.
Stored: the last report, the findings met (first and last seen,
told), those acknowledged and the time of the last automatic check, in
meta (key exposure, not in the backups). A finding is forgotten 90
days after it was last seen (told again if it comes back); 1,024 findings
and 512 acknowledgements at most. Administration log:
network.exposure.check, network.exposure.acknowledge,
network.exposure.unacknowledge (the id of the finding).
What leaves the box, and to whom: the request itself to warda-cloud
(its source address is what is checked), signed with the key of the box,
and the external TCP ports of the router mappings that are not in the
fixed list (0 to 32 numbers). No address of the network, no name of a
device, no description of a mapping. The UPnP reading and the telnet/FTP
check stay on the local network. The service connects only to the address
the request comes from, on the fixed list and those ports, and keeps
nothing but counts by day.
What it cannot see, said plainly.
- UDP services are not checked from outside (the UDP mappings are listed, as information).
- The IPv6 pinholes the router opens for each device cannot be listed (UPnP IGD gives the IPv4 mappings; the IPv6 firewall control is not read), and the check over IPv6 only tries the address of the Warda box, not each device.
- A router without UPnP (or with UPnP off) gives no mapping, but the check from outside still sees what answers at the public address (a forwarding made by hand, the router's own administration) — on the ports of the fixed list only: a forwarding of, say, 2222 made by hand is not tried.
- Behind a carrier-grade NAT (100.64.0.0/10, some 4G/5G and fibre
offers) or a double NAT, the public address is the provider's or the
outer box's: the service refuses an address that is not public
(
not_public) or checks the outer box. - An online service without IPv6 answers
unreachablefor IPv6: "cannot reach this kind of address yet", never "nothing open". - The SSDP search is IPv4 multicast: a router reachable over IPv6 only is
not read; neither is the router of the house from a container on a
network of its own (use
network_mode: host, as for the network scan). - The telnet/FTP check only tries the machines Warda knows and that are present; a device whose firewall drops the connections looks closed.
API (area network):
| Route | Permission | Body and answer |
|---|---|---|
GET /api/v1/network/exposure |
read | report (at, trigger, upnp {state, gateway, model, service, mappings [{protocol, external_port, internal_client, internal_port, description, lease, enabled, remote_host, device_id, label}], truncated, error}, outside [{family, state, address, checked, open, at, retry_after, error}], lan {machines}, findings [{id, source, family, protocol, port, service, severity, advice, mapped, mapping, addr, device_id, label, kind, first_seen, acknowledged}], gateway), running, next, allowed, ports [{port, label}]; 409 without the database |
POST /api/v1/network/exposure/check |
written | starts a check in the background (the view, running: true); 409 while one runs or less than 2 minutes after the last |
POST /api/v1/network/exposure/acknowledge |
written | {"id": "…", "acknowledged": true} (true by default; false alerts again); 404 unknown finding, 400 without an id |