IntraSpanNet: passive intranet asset discovery from five packets, without scanning

A traffic probe already sees every host. IntraSpanNet names what produced each flow: a security appliance, an endpoint agent, or a common software. 2.86M parameters per arm, explicit unknown class, open weights.

What it does

Asset inventories list what was installed; traffic shows what is running. IntraSpanNet reads the first five packets of a flow, zeroes the IPv4 addresses, and assigns the flow to one of 18 security products and services, or returns unknown when the prototype distance exceeds a calibrated threshold.

The label set covers antivirus agents, endpoint detection and response, VPN endpoints, a web application firewall, an NDR sensor, a bastion host, an internet behaviour management proxy, a browser, and remote administration. Ports and protocol rules do not separate these classes.

0.8787product accuracy, 503-flow test split
0.9245tier accuracy
0.9784gate AUROC
18/18unknown-product bins rejected

What it returns

Three tiers, 18 labels, plus an unknown. Security appliances: VPN gateways (wireguard, openvpn, stunnel), a web application firewall, NDR sensors, a bastion host, an internet behaviour management proxy. Endpoint agents: Windows Defender, ClamAV on two platforms, Velociraptor on two platforms, osquery, Wazuh. Common software: browser HTTP, SSH, FTP.

When a flow does not resemble any of them, the gate rejects it instead of guessing. All 18 unknown-product bins in the acceptance set are rejected at a fixed threshold of 0.1041, stable from q = 0.64 to q = 0.75. A new product can be added by registering three sub-prototypes, without retraining.

Install and run

pip install intraspannet
huggingface-cli download luping123/IntraSpanNet --local-dir weights

python -m intraspannet.cli classify --pcap capture.pcapng --weights weights
python -m intraspannet.cli serve --weights weights --port 8080

It runs on a laptop: the Gated DeltaNet block has a plain PyTorch port that loads the released weights unchanged, at about two seconds per flow per arm. A CUDA device with flash-linear-attention (pip install "intraspannet[gpu]") is roughly a thousand times faster and is what a probe would use. scripts/verify_cpu_backend.py measures the port against captures you have labels for.

Keep training it

The supervised and reinforcement-learning objectives ship with the package, so the 18 labels are a starting point. A new capture with a new product goes in through a JSONL manifest and the same two stages that produced the released arms.

python examples/train_sft.py --weights-dir weights \
    --manifest manifest.jsonl --epochs 5 --out runs/sft/arm.pth

python examples/train_rl.py --manifest manifest.jsonl \
    --init runs/sft/arm.pth --lr 1e-5 --out runs/rl/arm.pth

SFTConfig carries the weights of the product, type, tier, malicious and contrastive terms, RLConfig the three rewards, and load_backbone warm-starts a model whose product head has a different number of classes. Prototypes and the open-set threshold are rebuilt from your own data: the shipped 0.1041 belongs to the shipped label set.

Guide: docs/TRAINING.md in English, docs/TRAINING.zh-CN.md in Chinese.

Limits, stated up front

The split is flow-level with one held-out session per class, so the absolute figures describe those sessions rather than a new organisation. One capture environment was used. The label set is closed at 18 products. Stage 1, the contrastive pretraining, depends on a capture layout that cannot be redistributed and is not released; stages 2 and 3 are, with the checkpoints, prototypes, threshold and the evaluation script.

Cite

DOI: 10.5281/zenodo.23643146

@misc{luping2026intraspannet,
  doi    = {10.5281/zenodo.23643146},
  title  = {IntraSpanNet: Identifying Security Appliances, Endpoint Agents, and Common Software from Five-Packet Intranet Flows without Active Scanning},
  author = {luping},
  year   = {2026},
  note   = {https://github.com/lupingQAQ/IntraSpanNet},
  url    = {https://github.com/lupingQAQ/IntraSpanNet}
}