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.
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.
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.
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.
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.
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.
@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}
}