DS-0006

Stalkerware permission profiles

3,000 invented rows for a classification exercise: does this Android app's permission and behaviour profile look like stalkerware or like a benign app? The features are values an examiner can read from a phone extraction. Benign parental-control, remote-support, accessibility and backup apps look like stalkerware on purpose, and the set comes with a guide on what a wrong call costs.

Synthetic dataBinary classificationVersion 1.0Data CC BY 4.0Code MIT

At a glance

What the set is, how it was made, and how it may be used.

Feature ideas come from the Android developer documentation, the Coalition Against Stalkerware's description of stalkerware, and Lab reports on App Ops history (LAB-TR-0003), on where device security settings live on disk (LAB-TR-0017) and on last-use records (LAB-TR-0029). Sources are listed at the foot of the page.

Task
Binary classification: stalkerware (1) or benign (0)
Device class
None (generated rows, one per app on a device)
OS
Android, modelled
Evidence type
Synthetic feature table
Made on
A script (generate.py, Python standard library) with seed 20261106. No device, no app and no case data. The same seed gives the same bytes.
Contents
3,000 rows in 45 columns (40 features), 18 app families, 141 app lineages. 225 stalkerware rows (7.5 percent). Train 2,105 rows (171 positive, 8.1 percent), test 895 rows (54 positive, 6.0 percent).
Size
118 KB as a zip (120,610 bytes)
Version
1.0
Licence
Data: CC BY 4.0. Code: MIT.
Maintainer
Daniel Addai, SectraTEK Lab

What is inside

One zip with the training and test tables, the reference answers for the test table, a data dictionary, a teaching guide, two baseline scripts, an evaluation script and the generator. Every column is something an examiner can read from a phone extraction; the dictionary says where.

File tree
stalkerware-permissions/
  README.txt                   what, shape, traps
  LICENCE.txt                  CC BY 4.0, MIT
  DATA_DICTIONARY.md           every column
  data_dictionary.csv          the same, as CSV
  TEACHING_GUIDE.md            plan, exercises, ethics
  data/train.csv               2,105 rows, labelled
  data/test.csv                895 rows, no label
  answers/test_answers.csv     reference answers
  code/baseline_stdlib.py      Python only
  code/baseline_sklearn.py     optional
  code/evaluate.py             scores predictions
  generator/generate.py        seed 20261106
Columns of train.csv by group
GroupColumnsNote
Identifiers and grouprow_id, app_id, lineage_idNever features. lineage_id is the group for cross-validation.
Evidence and installevidence_source, install_source, app_age_daysType of extraction, installer, days since first install.
Requested permissionsn_perms_requested, n_dangerous_requested, req_sms, req_call_log, req_contacts, req_mic, req_camera, req_location, req_bg_location, req_all_files, req_query_all_packages, req_boot_completed, req_overlay, req_install_packagesFrom the manifest. Names follow the Android Manifest.permission reference.
Granted permissionsn_dangerous_granted, granted_sms, granted_call_log, granted_contacts, granted_mic, granted_camera, granted_location, granted_bg_locationRequested is not granted; granted is not used.
Roles and settingsa11y_service_declared, a11y_service_enabled, device_admin_active, notif_listener_enabled, sms_role_holder, launcher_icon_hidden, battery_opt_exempt, fgs_runningAccessibility service, device administrator, notification listener, SMS role, hidden icon.
Behaviourbg_service_hours_7d, net_egress_bucket, appops_mic_count_7d, appops_camera_count_7d, appops_location_count_7d, appops_sms_count_7d, appops_last_access_hoursBackground time, data sent (bucket), App Ops access counts. Counts are blank on bug reports.
Leakage columnprior_triage_flagAn earlier reviewer's note, set after the label. Do not train on it.
Labelis_stalkerwareIn train.csv. For the test file it is in answers/test_answers.csv.

The same columns are in test.csv except is_stalkerware. The data dictionary gives, for every column, its type, its values, its role and the artifact an examiner reads it from. The artifact paths are typical and differ by Android build and vendor; check them on your own test device.

Reference answers

The label of every test row is in answers/test_answers.csv, a separate file, so you can predict first and compare after. Each answer also names the app family that wrote the row and its kind, for error analysis.

App families in DS-0006, with rows, test rows and label
FamilyKindRowsIn testLabelProfile
messagingBenign3601190Messaging apps: contacts, microphone, camera, often SMS
socialBenign3601120Social apps: contacts, camera, location
maps_navigationBenign210600Maps: background location, foreground service
camera_mediaBenign240750Camera and media editors
gamesBenign3901070Games: mostly few permissions, some overlay
utilityBenign3601130Utilities: few permissions, quiet
banking_shoppingBenign240660Banking and shopping apps
oem_systemBenign135490Pre-installed helpers: often no icon, boot start, SMS role
parental_controlBenign look-alike105350Location, SMS, call log, accessibility, device admin, boot start. Look-alike on purpose
remote_supportBenign look-alike60180Accessibility, overlay, microphone, all files, large data sent. Look-alike on purpose
assistive_accessibilityBenign look-alike90240Screen readers and switch tools: accessibility service on, microphone, notification access
backup_transferBenign look-alike105300SMS, call log, contacts, all files, large data sent. Look-alike on purpose
device_managementBenign look-alike60200Work device management: device admin, background location, installs
find_my_deviceBenign look-alike60130Anti-theft: background location, device admin, camera
sw_full_monitorStalkerware105231Full monitor: sideloaded, hidden icon, accessibility, device admin, SMS, call log, microphone, location
sw_light_monitorStalkerware51131Lighter monitor: SMS, location, notification access; less hiding
sw_disguisedStalkerware3391Disguised: icon shown under an innocent name, low data sent
dual_use_covertDual-use app used covertly3691Same profile as parental control, icon hidden more often. No feature separates it

Label 1 means an app built to watch a person without their knowledge (stalkerware, as the Coalition Against Stalkerware uses the word), or a dual-use app used that way. Whether the person knew and agreed is not something a permission table can show. That is why the set includes dual_use_covert: its profile is that of parental_control, and the label differs because of how the app was used.

Limits. The train file has no family column, to keep the task honest; the families of test rows are in the answers only. Whole lineages (one app on several devices or versions) go to one side, so the test file holds lineages the training file has not seen. Small groups move a figure a lot: dual_use_covert has only 9 test rows, so one app changes its recall by more than 10 points.

Documented traps

Each is on purpose, listed in the guide, and shows up in a number you can compute.

Traps in DS-0006
TrapWhereHow to notice it
Leakage columnprior_triage_flagAn earlier reviewer's note, written after the label. Adding it lifts average precision from 0.87 to 0.97. At prediction time it does not exist.
Class imbalanceAbout 7 percent positiveAlways saying benign scores a high accuracy on the test file and finds nothing; run it and read it. Precision falls further if the real base rate is lower (about 0.23 at 1 percent for the logistic regression baseline).
Benign look-alikesParental control, remote support, accessibility, backup, device management, find-my-deviceAccessibility service on, device administrator, background location, SMS and call log access and large uploads are common to these and to stalkerware.
Covert use of a dual-use appdual_use_covertSame profile as parental control. No feature separates them; the label depends on consent.
Lineage clusterslineage_idRandom cross-validation reads higher than the test file because rows of one lineage fall on both sides. Group by lineage.
Blank is not zeroappops_*_count_7dBlank on every bug report row: a bug report holds a last-access dump, not the history database.
Ordinal as textnet_egress_bucketnone < low < medium < high < very_high sorts wrongly as text.
Identifiersrow_id, app_idRandom strings. Any signal in them is a bug in the split or the code.

Three apps, read side by side

Test rows r2952 (a full-monitor stalkerware family), r0367 (a parental-control app) and r1550 (a parental-control-class app used covertly). The last two are the closest pair of look-alike and covert rows in the test file.

Three test rows, selected columns, reference answer and baseline score
Columnr2952r0367r1550
install_sourcesideload_browserother_storeunknown
app_age_days25527532
launcher_icon_hidden111
a11y_service_enabled111
device_admin_active110
notif_listener_enabled100
granted_sms111
granted_call_log111
granted_location111
granted_bg_location111
granted_mic111
battery_opt_exempt111
bg_service_hours_7d16.15.55.0
net_egress_buckethighhighmedium
appops_location_count_7d604351
appops_last_access_hours232
prior_triage_flagescalatedreviewed_okescalated
Reference answersw_full_monitor, label 1parental_control, label 0dual_use_covert, label 1
Baseline score (logistic regression)0.982120.973190.95633

How to read it

The first row is the pattern the set calls a full monitor. The app was downloaded in a browser and installed outside a store, about 255 days before the extraction (it is not new on the device), with: launcher icon hidden; accessibility service on; device administrator active; notification listener on; SMS access granted; call log access granted; background location granted; microphone granted; exempt from battery optimisation. Several of these together are an indicator consistent with monitoring software; each alone is not.

The second and third rows are the point of the set. They differ in 3 of the 27 yes/no columns and the install source (other store and unknown); both are above the baseline's threshold and both are flagged. One is a parent's app and the other is the same class of app used to watch an adult. Nothing in this table says who installed the app or whether the person using the phone knew. The reference answers give label 0 and label 1, and the evidence for the difference sits outside the table.

Values are invented. The column prior_triage_flag is shown so you can see that it tracks the label; it is the leakage column.

What the shipped baselines score

Run on the test file with code/baseline_stdlib.py (Python standard library only) and scored with code/evaluate.py. The threshold is chosen on held-out training lineages, not on the test file. Two rows are not printed here, because questions 5 and 6 of the challenge ask for them: run the always-benign answer and the default logistic regression, and read the scores off.

Baseline results on test.csv
ModelAccuracyPrecisionRecallF1Average precisionLook-alikes flagged
Indicator count
Eight indicators, threshold chosen on train
0.9510.5640.8150.6670.74524%
Logistic regression, balanced weights
Positive rows weighted up
0.9600.6070.9440.7390.87423%
Logistic regression, WITH the leakage column
What not to do
0.9940.9450.9630.9540.9751%

Reading it. Accuracy stays high for every model, while precision and recall differ a lot. The leakage row looks like progress and is not. In the logistic regression, 20 of 35 parental-control test apps (57 percent) are flagged: each is a parent's app on a child's phone. Numbers are for this synthetic set and say nothing about real devices.

Try it with free tools

  1. Download the zip below and check its SHA-256 (Windows: certutil -hashfile stalkerware-permissions-1.0.zip SHA256; Linux or macOS: sha256sum).
  2. Unzip it. Open data/train.csv in a plain text editor, a spreadsheet, or DB Browser for SQLite (File, Import, Table from CSV file).
  3. Count before you model. In SQLite, with the table named train, run the query on the right. Which combinations hold most of the stalkerware rows? How many benign apps share them?
  4. Install Python 3, open a terminal in the code folder and run python baseline_stdlib.py --out p.csv, then python evaluate.py p.csv ../answers/test_answers.csv.
  5. Rerun with --with-leak and with --class-weight balanced. Explain each change in your own words.
  6. Read TEACHING_GUIDE.md: exercises with expected results, and the discussion on false positives. If you have scikit-learn, try baseline_sklearn.py, which compares random and grouped cross-validation.
SQL for step 3
SELECT launcher_icon_hidden AS hidden_icon,
       a11y_service_enabled AS a11y_on,
       device_admin_active  AS dev_admin,
       COUNT(*)             AS apps,
       SUM(is_stalkerware)  AS stalkerware
FROM train
GROUP BY 1, 2, 3
ORDER BY apps DESC;

Try the challenge: Separate stalkerware from look-alikes (Practitioner).

When the wrong answer is a family member

The teaching guide works through this: who is harmed by each kind of error, why removing real stalkerware can alert the person who installed it (so safety planning comes first, see the Coalition Against Stalkerware), why no flag should go to a third party or lead to an app being removed on a score alone, and why a report should state the false-positive rate on the look-alike families next to the headline figure.

A sentence that fits the evidence

"App X is an enabled accessibility service and an active device administrator, has its launcher icon hidden, was installed from a browser download, and holds background location and SMS access. These are indicators consistent with monitoring software. They are also present in some parental-control and device-management apps. This examination did not establish who installed the app, whether the user of the device knew of it, or what it sent."

Not this

"Stalkerware found."

Get the dataset

One zip, 120,610 bytes. It holds 12 files: the tables, the reference answers, the guide, the scripts, the licences and the generator. Check it against the hash below.

Download stalkerware-permissions-1.0.zip (118 KB)

Served from this site, versioned. The manifest and the hashes are linked below.

SHA-256 of the zip

efeb5ede7162b6cb2e333d15c647460af819fc15319384276ada7441a02d1d10

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). Every other file is inside the zip.

Licence

Data: Creative Commons Attribution 4.0 (CC BY 4.0). The full text is at creativecommons.org/licenses/by/4.0 and in LICENCE.txt inside the zip. No acceptance step is needed.

  • You may copy and share the files in any medium or format, and adapt them, for any purpose, including teaching, research, casework and commercial work.
  • You must give credit, using the citation below, 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 case.

Code: MIT. generate.py, the baselines 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)

3a5ced10a2dc74990be09d545cc33433daa76546c2e163596d127f342981c67c
Files in DS-0006 with SHA-256 hashes
FileContentSizeSHA-256
stalkerware-permissions-1.0.zipArchive120,610 bytes
efeb5ede7162b6cb2e333d15c647460af819fc15319384276ada7441a02d1d10
data/train.csvTraining table296,757 bytes
df43490dd308358f7142392cd1b16786622a6786f347fd9daca5d8407ba54d22
data/test.csvTest table (no label)124,708 bytes
4d859579b77219045dde591a95a999e535b3ce66ddac09455afb3a246eff9b00
answers/test_answers.csvReference answers30,100 bytes
9f0b6d964ee2cc0084758767c99f681a313442ec6e230f2cc38a7943c906278f
data_dictionary.csvData dictionary (machine readable)5,327 bytes
411f1a46e52bb4758eb95ac85f254eb661faff3000da1bf502fa76998b587134
DATA_DICTIONARY.mdData dictionary7,008 bytes
c22628e3b600e806932004aa16938067e3bf471376d724c008bf2fa257d760a0
TEACHING_GUIDE.mdTeaching guide12,390 bytes
19aad931e567236d38069e2d1dc532614631526435d5a6898afe67fec290de0f
README.txtNotes2,685 bytes
f05ce19f7078285956debd360944ce3a38500796ae3e03e82aa9fd4df1c5490c
LICENCE.txtLicences1,462 bytes
1d3020cfd5914952d7312cba9590bde5d7da7f48ab4865663570a814744cf53e
code/baseline_stdlib.pyBaselines (MIT)8,593 bytes
3aebcf2e0f672f98dfd8f8811987da3c2ae843528e307a194dc6e38938ef1849
code/baseline_sklearn.pyOptional baseline (MIT)5,780 bytes
348e7d6c57e83bf95849a7b85d8d5cdf0300204cedfa6ae11a864e0ed36e692d
code/evaluate.pyEvaluation (MIT)7,444 bytes
af087af56c6d3ca2988ffed1fbd90371351c0b9793865327fbe01b5f17ebc67d
generator/generate.pyGenerator (MIT)32,014 bytes
fd34ea46f6eafb0274d219b03ab7b88d7d2160796d321a0ab2846ad71ae3e15a
manifest.jsonManifest (file, bytes, SHA-256)2,179 bytes
3a5ced10a2dc74990be09d545cc33433daa76546c2e163596d127f342981c67c
SHA256SUMS.txtThe same hashes in sha256sum format1,192 bytes
84ef77c8cf38a37af0a3dc35ed80c3036427f8db4edd356c5991ea7889e72121

The zip holds the same files as the folder (the zip itself, manifest.json and SHA256SUMS.txt excepted). Together the files are 534,268 bytes before compression. Hashes and sizes on this page are read from manifest.json.

Cite this dataset

Cite as

Addai, D., SectraTEK Lab (2026). Stalkerware permission profiles (DS-0006), version 1.0. SectraTEK Lab Datasets. https://lab.sectratek.com/datasets/stalkerware-permissions/. SHA-256 of manifest.json 3a5ced10a2dc74990be09d545cc33433daa76546c2e163596d127f342981c67c

Citation guide
BibTeX
@dataset{sectratek-lab-ds-0006,
  author    = {Addai, Daniel},
  title     = {Stalkerware permission profiles},
  year      = {2026},
  version   = {1.0},
  publisher = {SectraTEK Lab},
  url       = {https://lab.sectratek.com/datasets/stalkerware-permissions/},
  note      = {DS-0006, SHA-256 of manifest.json 3a5ced10a2dc74990be09d545cc33433daa76546c2e163596d127f342981c67c}
}

Sources for the features

Related

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.