DS-0007
Synthetic network beacon flows
A teaching set for supervised learning: is this phone's flow pattern to one destination command-and-control style beaconing, or normal app traffic? 2,964 destination summaries, train and test files, reference answers and a guide. Every value is invented and no row comes from a device or a network.
At a glance
What the set is, how it was made, and how it may be used.
Plain CSV files, so any spreadsheet, text editor or SQLite browser opens them. Try the challenge: Find the beacon in phone traffic (Expert).
- Question
- Binary classification: beaconing (1) or normal app traffic (0) for one destination seen from one phone over 24 hours.
- Device class
- None (generated rows from 150 invented phones)
- Evidence type
- Synthetic flow-summary table
- Made on
- A script (generate.py, Python standard library) with seed 20261107. No capture, no device and no real address. The same seed gives the same bytes.
- Contents
- 2,964 rows: 2,075 train (11.7% beaconing) from 105 phones and 889 test (11.7% beaconing) from 45 other phones. 18 traffic families, 5 beaconing and 13 normal.
- Features
- Flow count, interval mean, jitter and regularity, up/down ratio, payload spread, time-of-day spread, destination age bucket, SNI present, protocol, session length.
- Size
- 142 KB as a zip (144,993 bytes), 16 files in the zip
- Version
- 1.0
- Licence
- Data: CC BY 4.0. Code: MIT.
- Maintainer
- Daniel Addai, SectraTEK Lab
What is inside
One zip with the data, the reference answers, the code to try it, a data dictionary and a teaching guide.
network-beacon-flows/
data/
train.csv 2075 rows, with label
test.csv 889 rows, no label
answers/
reference_answers.csv flow_id, split, device_id, label, family
reference_summary.json counts, family notes, baseline scores
code/
baseline.py rule, logistic regression, tree, forest
baseline_sklearn.py optional, needs scikit-learn
evaluate.py scores a predictions file
generator/
generate.py writes everything from seed 20261107
templates/ text for README, dictionary, guide
DATA_DICTIONARY.md
TEACHING_GUIDE.md
README.txt
LICENCE.txt| Column | Meaning |
|---|---|
| flow_id, device_id | Row id and the invented phone it belongs to. Identifiers, not features. Train and test hold different phones. |
| protocol | tcp_tls, tcp_plain, udp_quic or udp_other. |
| flow_count | Flows to this destination in 24 hours. |
| interval_mean_s, interval_jitter_pct, interval_regularity | Mean gap between flow starts, its spread as a percent of the mean, and the share of gaps within 20 percent of the median. |
| up_down_ratio, payload_size_cv, bytes_total_kb | Direction of the bytes, how much flow sizes vary, total volume. |
| active_hours, night_hour_share | Clock hours with a flow (1 to 24) and the share of those hours between 00:00 and 05:59. |
| dest_age_bucket, sni_present | Age of the destination (lt_7d, 7_30d, 30_365d, gt_365d, unknown) and whether a server name was sent. |
| session_len_s | Median flow duration. |
| triage_tag | Leakage column: written after review, follows the label. Do not use as a feature. |
| label | train.csv only. 1 beaconing, 0 normal. |
The column ideas are the ones network analysts already use: fixed or jittered sleep times between check-ins (MITRE ATT&CK T1029 Scheduled Transfer), traffic that hides in ordinary protocols (T1071 Application Layer Protocol), encrypted channels (T1573), and the per-connection fields of a Zeek conn.log. The data dictionary says how to compute each column from a capture or a flow log. Hidden endpoints are a real limit on phones: in LAB-TR-0012 the endpoint of 1,235,369 of 1,238,152 network connection lines in one decoded iOS 27.0 log was a hash tag, which is what the unknown destination age stands for.
Reference answers and documented traps
18 traffic families. Regular traffic that is benign (push, keep-alives, time sync, update and device-management check-ins) is regular on purpose, and low-and-slow beacons with jitter are hard on purpose. The reference answers give the label and the family of every row; they are kept outside the data folder so you can predict first and compare after.
| Family | Answer | What it looks like | Train rows | Test rows |
|---|---|---|---|---|
| burst_beacon | beaconing | Sleep, then a short burst of check-ins. | 29 | 14 |
| fixed_beacon | beaconing | Fixed sleep time, almost no jitter. | 61 | 24 |
| fronted_beacon | beaconing | Check-in to an old, widely used destination with an SNI. | 28 | 14 |
| jitter_beacon | beaconing | Sleep time with 10 to 50 percent jitter. | 69 | 23 |
| lowslow_beacon | beaconing | Hours between check-ins, with jitter; few flows a day. | 56 | 29 |
| browsing | normal | Human-driven; irregular, large and variable payloads. | 438 | 186 |
| ip_direct_app | normal | Legitimate app that connects by address, no SNI. | 67 | 27 |
| keepalive | normal | App heartbeat; tiny, regular payloads. | 165 | 57 |
| mdm_checkin | normal | Device-management check-in every few hours. | 52 | 16 |
| new_cdn_app | normal | Legitimate app on a recently registered domain. | 82 | 42 |
| push | normal | Push-notification channel; regular on purpose. | 113 | 50 |
| social_refresh | normal | Feed refresh while an app is in use; large jitter. | 211 | 90 |
| streaming | normal | A few long, download-heavy sessions. | 128 | 51 |
| sync | normal | Mail, calendar or cloud sync; periodic with jitter. | 195 | 100 |
| telemetry | normal | Analytics upload; periodic, upload-heavy. | 208 | 83 |
| timesync | normal | Network time exchange; the most regular, smallest flows. | 50 | 26 |
| update_check | normal | Update check; a handful of flows a day. | 78 | 39 |
| vpn_tunnel | normal | Always-on VPN or proxy keep-alive; small, regular, no SNI. | 45 | 18 |
The traps
- Leakage.
triage_tagis written after an analyst looked at the flow. On the test file, reading "escalated or watchlist" as beaconing gives F1 0.909, higher than every baseline below. A model that uses it has learned the answer from the answer. - Imbalance. About 11.7 percent of the test rows are beaconing. Answering "normal" every time scores a high accuracy and finds nothing; run it and read it.
- The fixed-interval shortcut. The rule "jitter under 5 percent" is one of the shipped baselines (
baseline.py --model rule). Run it and score it to see how many test flows it flags and how many of those are normal. It catches most fixed-sleep beacons and misses most jittered, low-and-slow, burst and fronted ones. The normal flows it flags are mostly push, time-sync and update-check traffic. - Look-alike rows. Beaconing rows from one phone share a configuration. Random folds on the training file give a forest an average precision of 0.803; folds that keep each phone together give 0.760.
- Blank values. Destinations seen once a day have no interval statistics: 25 train rows have no mean gap and 90 have no jitter figure.
- Overlap. About half of the beaconing destinations copy the size, direction, session and destination profile of a normal app.
Baseline scores on the test file
| Model | Precision | Recall | F1 | False-alarm rate | ROC AUC | Average precision |
|---|---|---|---|---|---|---|
| Logistic regression | 0.247 | 0.702 | 0.366 | 0.283 | 0.811 | 0.388 |
| Decision tree, depth 5 | 0.449 | 0.798 | 0.574 | 0.130 | 0.870 | 0.613 |
| Forest of 25 trees | 0.556 | 0.760 | 0.642 | 0.080 | 0.945 | 0.767 |
The fixed-interval rule is not in this table, because question 4 of the challenge asks for its false positives: run it and read it off. Threshold 0.5 on a class-balanced fit. Scores are from the shipped baseline.py and evaluate.py. These numbers are for this synthetic set only. They are a floor for the exercise, not a target, and say nothing about real traffic.
One worked example: two flows that look the same in time
Two training rows chosen because their timing columns almost match: mean gap, jitter and regularity. Row A (f2124, family push) is normal. Row B (f0013, family fixed_beacon) is beaconing. A rule that reads only the interval columns cannot tell them apart.
| Column | Row A, normal | Row B, beaconing |
|---|---|---|
| Flows in 24 h | 141 | 139 |
| Mean gap (s) | 622.7 | 618.8 |
| Jitter (%) | 8.4 | 8.5 |
| Regularity | 0.993 | 0.993 |
| Up / down | 0.765 | 0.906 |
| Payload spread (CV) | 2.012 | 0.810 |
| Total (KiB) | 82.6 | 91.9 |
| Session (s) | 9.47 | 2.55 |
| Destination age | gt_365d | gt_365d |
| SNI present | 1 | 0 |
| Protocol | tcp_tls | tcp_plain |
| triage_tag (leakage) | none | escalated |
The timing columns match; these differ by more than a quarter: payload spread, session length, SNI and protocol. In a real case a difference like that is where to look next, and no single column decides it, which is why the baselines use all of them. The triage_tag row shows the leakage column giving the answer away; a real decision does not have that column yet. Values are invented.
Try it with free tools
- Check the download. Compute the SHA-256 of the zip and compare it with the value below:
python -c "import hashlib,sys;print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" network-beacon-flows-1.0.zip. Then unzip it. - Look at the rows. Open
data/train.csvin a plain text editor or a spreadsheet and sort byinterval_jitter_pct. ReadDATA_DICTIONARY.mdbeside it. - Ask a question in SQLite. In DB Browser for SQLite choose File, Import, Table from CSV file, pick
train.csvand run the query below. - Run a baseline. With Python 3.8 or later and nothing to install:
cd code, thenpython baseline.py --model forest --out predictions.csv. - Score it.
python evaluate.py predictions.csv. Then try--model rule,logregandtree, and readTEACHING_GUIDE.mdfor the session plan.
SELECT dest_age_bucket,
COUNT(*) AS flows,
SUM(CAST(label AS INTEGER)) AS beacons,
ROUND(100.0 * SUM(CAST(label AS INTEGER))
/ COUNT(*), 1) AS pct_beacon
FROM train
GROUP BY dest_age_bucket
ORDER BY pct_beacon DESC;dest_age_bucket | flows | beacons | pct_beacon lt_7d | 75 | 13 | 17.3 unknown | 128 | 21 | 16.4 30_365d | 283 | 41 | 14.5 7_30d | 119 | 16 | 13.4 gt_365d | 1470 | 152 | 10.3
Destination age alone is a weak clue here: young destinations carry a legitimate app family too.
test flows 889, beaconing 104, normal 785 threshold 0.500 (applies to the score column; a label column is used as given) confusion: TP 79 FP 63 FN 25 TN 722 precision 0.556 recall 0.760 F1 0.642 false-alarm rate 0.080 accuracy 0.901 (always answering normal gives 0.883) balanced accuracy 0.840 ROC AUC 0.945 average precision 0.767 precision@104 0.654 recall at 5% false alarms 0.663 beaconing families (share flagged): burst_beacon 14 flows flagged 10 (0.71) fixed_beacon 24 flows flagged 19 (0.79) fronted_beacon 14 flows flagged 13 (0.93) jitter_beacon 23 flows flagged 15 (0.65) lowslow_beacon 29 flows flagged 22 (0.76) normal families (false-alarm share): browsing 186 flows flagged 0 (0.00) ip_direct_app 27 flows flagged 5 (0.19) keepalive 57 flows flagged 21 (0.37) mdm_checkin 16 flows flagged 6 (0.38) new_cdn_app 42 flows flagged 0 (0.00) push 50 flows flagged 3 (0.06) social_refresh 90 flows flagged 0 (0.00) streaming 51 flows flagged 0 (0.00) sync 100 flows flagged 12 (0.12) telemetry 83 flows flagged 13 (0.16) timesync 26 flows flagged 0 (0.00) update_check 39 flows flagged 0 (0.00) vpn_tunnel 18 flows flagged 3 (0.17)
Get the dataset
One zip, 144,993 bytes. It holds 16 files: the data, the reference answers, the code, the data dictionary, the teaching guide, the licences and the generator. Check it against the hash below.
Download network-beacon-flows-1.0.zip (142 KB)
Served from this site, versioned. The manifest and the hashes are linked below.
SHA-256 of the zip
The loose files, manifest.json and SHA256SUMS.txt, sit beside the zip on this site. To rebuild every byte yourself, run python generate.py from the generator folder (Python 3, standard library only).
Licence at a glance
A plain-language summary. The full text is at creativecommons.org/licenses/by/4.0 and in LICENCE.txt inside the zip. This dataset needs no acceptance step: use the direct link above.
Data: Creative Commons Attribution 4.0 (CC BY 4.0)
- You may copy and share the files in any medium or format, adapt them (for example to make test fixtures) and use them for any purpose, including teaching, research, casework and commercial work.
- You must give credit using the citation on this page, link to the licence and say if you changed the files.
- Please keep the hashes beside any copy you share, and say that the files are synthetic. Do not present them as taken from a real phone or a real network.
Code: MIT
generate.py, baseline.py, baseline_sklearn.py and evaluate.py are under the MIT licence. Keep the copyright notice with copies.
The dataset is provided as is, without warranty.
Hashes
Check every file after download. The set hash is the SHA-256 of manifest.json, which lists the size and hash of every other file.
Set hash (SHA-256, manifest.json)
| File | Content | Size | SHA-256 |
|---|---|---|---|
| network-beacon-flows-1.0.zip | Archive | 144,993 bytes | 7b365db45d4b37216e5e3a474faa914e083d58a12dfe780eb60757ca35fa92d4 |
| answers/reference_answers.csv | Reference answers: label and family per row | 86,180 bytes | 714875dec07b5ca5845a32d9aaa0c86a089722d7095603af0578fcd461ba18aa |
| answers/reference_summary.json | Counts and numbers quoted in the guide | 9,557 bytes | 540dcf72e4bc2ac839074fb492bdaade81afd223c37e199c46240643945c2fbf |
| code/baseline.py | Standard-library baselines | 10,132 bytes | 2740ad93d895190fafbff91e107528ccd94240816647bf8dfd41f6fe13be6ddb |
| code/baseline_sklearn.py | Optional scikit-learn baseline | 3,525 bytes | 4676de4ff6e9f8d782d975772fc855642a88d6636be667c196384b2143ce906a |
| code/evaluate.py | Scoring script | 7,754 bytes | 58dd299d2fcc7afc153d32d9fbec8288e5666d991e31189a5be85071cea57f42 |
| data/test.csv | Test rows, no label | 79,113 bytes | 0c1e7e09ff1749501e77318b5d45d375ed36a99e4c803733cbd42a2c42d03b71 |
| data/train.csv | Training rows with label | 188,666 bytes | b5d6f1c0ceea46dba677cfbcca5acd8d3510ec07e6f655b35a5932f53ab67345 |
| DATA_DICTIONARY.md | Data dictionary | 5,948 bytes | 8c15b679101aef7dabab1657c2c3477f2cec64069eb2106c493ff685105235c8 |
| generator/generate.py | The generator | 32,579 bytes | e559f9128e03026da012bea7986dc63e61c0b90305dfaaeef59f88eca1286238 |
| generator/templates/DATA_DICTIONARY.md.tpl | Generator template | 5,321 bytes | 590adcfd4a3a376646b9d04c72ae3ef5cce560209dfde6faebc7636adfd82cd5 |
| generator/templates/LICENCE.txt.tpl | Generator template | 1,462 bytes | 8e91e27d4fb6f725405c8a002f7f6a2554062540ed5adad0e053fb3335db3e96 |
| generator/templates/README.txt.tpl | Generator template | 2,170 bytes | bffd04e65e94eced694f75f7125b117c9a95605caea26b8f85c08049717883e1 |
| generator/templates/TEACHING_GUIDE.md.tpl | Generator template | 9,094 bytes | 5137ce18fd2e28ced9da8800e49fc5c80e9f525c1e793a5ac65551fe7f949836 |
| LICENCE.txt | Licences | 1,446 bytes | 756e527368c48f931c6f481e6e10e8c317d659bc50fbcd8ad7529d394240dac4 |
| README.txt | Short README | 2,114 bytes | ddd7b668d1b1f158bf39966e6b746c3d0e3008d50fe780933bc24d3859c0d51f |
| TEACHING_GUIDE.md | Teaching guide | 9,260 bytes | ff0278f7e1f9eba30ffd3a317debdc883c8d8d2b5abd67261c733c6050b0cf39 |
The zip holds the same files as the folder (the zip itself, manifest.json and SHA256SUMS.txt excepted). Together the files are 454,321 bytes before compression. Hashes and sizes on this page are read from manifest.json.
Cite this dataset
Cite as
Addai, D., SectraTEK Lab (2026). Synthetic network beacon flows (DS-0007), version 1.0. SectraTEK Lab Datasets. https://lab.sectratek.com/datasets/network-beacon-flows/. SHA-256 of manifest.json b41cd1b7f648b4dd4f49177e76ae6a9a275b650e53abf5f085ad432056477285
@dataset{sectratek-lab-ds-0007,
author = {Addai, Daniel},
title = {Synthetic network beacon flows},
year = {2026},
version = {1.0},
publisher = {SectraTEK Lab},
url = {https://lab.sectratek.com/datasets/network-beacon-flows/},
note = {DS-0007, SHA-256 of manifest.json b41cd1b7f648b4dd4f49177e76ae6a9a275b650e53abf5f085ad432056477285}
}Related
- Report: LAB-TR-0012, the iOS unified log and security controlsWhy endpoints are often hidden in phone network logs
- Dataset: Synthetic recovery-history setAnother generated set with reference answers (DS-0004)
Datasets carry their own licence. This one is CC BY 4.0 for data and MIT for code. Articles are CC BY 4.0. All licences.