Omar Ramadan – C00286349 | BSc (Hons) Cybercrime and IT Security | Supervisor: Mark Cummins South East Technological University | April 2026
| Deliverable | File / Location | Status |
|---|---|---|
| Research Report | Research Report.pdf | Submitted |
| Project Report | ProjectReport.pdf, ProjectReport.docx | Submitted |
| Web Application | webapp/web_app.py (run: python webapp/web_app.py) | Complete |
| Training Data | data/processed/gap_dataset_v2.jsonl | 5,154 examples |
| Trained Model | src/models/mobilellama_gap_scratch/ | F1 = 0.930 |
| Evaluation Script | eval_f1.py | F1=0.930, P=0.892, R=0.971 |
| Requirements | requirements.txt | Complete |
PrivacyTotal is a static analysis tool for Android applications that automatically compares an app's declared APK permissions against its published privacy policy. Using a fine-tuned MobileLLaMA 2.7B language model, the system determines whether each permission is disclosed in the policy, assigns a Privacy Health Score (PHS), and presents the results through a web application.
The tool was motivated by an RTÉ PrimeTime investigation (September 2025) revealing that precise location data from tens of thousands of Irish smartphones was being sold by data brokers, highlighting that most users blindly accept privacy policies without reading them.
PrivacyTotal/
├── README.md This document
├── LICENSE MIT licence
├── Research Report.pdf Final research report
├── ProjectReport.pdf Final project report
├── requirements.txt Python dependencies
├── eval_f1.py F1 evaluation script (keyword-oracle)
│
├── src/
│ ├── end_to_end_gap_analysis.py Main pipeline: APK download → permission
│ │ extraction → policy scraping → LLM gap
│ │ analysis → PHS → DB save
│ ├── batch_runner.py Batch runner for sequential multi-app analysis
│ ├── database.py SQLite layer (gap_analysis.db schema, CRUD)
│ ├── acquire_app_artifacts.py Standalone APK + metadata acquisition script
│ ├── train_gap_scratch.py QLoRA training script (final working version)
│ ├── prepare_gap_training_v2.py Builds gap_dataset_v2.jsonl from OPP-115 data
│ │
│ ├── models/
│ │ └── mobilellama_gap_scratch/ Working LoRA adapter (trained from scratch)
│ │ ├── adapter_config.json
│ │ ├── adapter_model.bin ~20MB LoRA weights
│ │ ├── tokenizer.json
│ │ ├── tokenizer.model
│ │ └── tokenizer_config.json
│ │
│ └── acquired/ Per-app result folders (117 apps)
│ └── com.example.app/
│ └── YYYYMMDD_HHMMSS/
│ └── gap_report.csv Permission × coverage × evidence × PHS
│
├── data/
│ ├── gap_analysis.db SQLite database (117 apps, 175 runs)
│ └── processed/
│ └── gap_dataset_v2.jsonl Training dataset (5,154 examples)
│
└── webapp/
├── web_app.py Flask application (PrivacyTotal)
└── templates/
├── base.html Shared layout, navigation, CSS
├── index.html Home: Analyze App / Browse DB / Research Report
├── app.html Per-app: PHS gauge, coverage donut, permission table
├── run.html Single run view with gap report details
├── diff.html Policy text diff between two runs
└── job.html Live job status / log streaming
# Create virtual environment
python -m venv venv311
source venv311/Scripts/activate # Windows Git Bash
pip install -r requirements.txt
# Download base model (MobileLLaMA-2.7B-Chat) from HuggingFace
# The LoRA adapter in src/models/mobilellama_gap_scratch/ is already trained
cd PrivacyTotal/
source venv311/Scripts/activate
python webapp/web_app.py
# Opens at http://localhost:5000
python src/end_to_end_gap_analysis.py --package-id com.example.app
python src/batch_runner.py
# Edit CANDIDATES list in batch_runner.py to specify apps
PYTHONIOENCODING=utf-8 python eval_f1.py
| Research Report | Implemented | |
|---|---|---|
| Target | 1,000 applications | 117 applications |
| Categories | 10 distinct categories × 100 apps each | 10 categories (Social Media, Gaming, Productivity, Finance, Entertainment, Navigation, Health, Shopping, Communication, Security/VPN) |
| Sampling method | Stratified from Google Play "Top Free" charts (October 2025) | Top free apps by download count, sourced across multiple APK mirrors |
| Selection criteria | ≥1M downloads, updated ≤12 months, English policy, free app | Same criteria applied; some apps excluded where no APK was obtainable from any source (e.g., Uber, Roblox) |
Outcome: The research report outlined 1,000 applications but the implemented study analyzed 117 applications across all 10 planned categories. This reduction was driven by practical constraints: APK acquisition is rate-limited, Cloudflare-gated, and time-intensive. The 117-app dataset still represents a statistically meaningful cross-section and satisfies the original intent of cross-category comparison. The tool remains fully capable of scaling to 1,000 apps with additional runtime; all infrastructure is in place.
The research report outlined a three-phase sequential training curriculum:
| Phase | Planned | Implemented |
|---|---|---|
| Phase 1 | Domain adaptation on Princeton-Leuven Corpus (50,000 sampled privacy policies, unsupervised causal LM) | Not used |
| Phase 2 | Supervised fine-tuning on OPP-115 + GDPR NER dataset (token classification, entity extraction) | partially attempted, caused failure |
| Phase 3 | Instruction tuning on PolicyQA + PrivacyQA + synthetic instructions (response generation) | attempted, caused catastrophic forgetting |
| Final approach | n/a | Single-phase scratch training on a custom gap_dataset_v2.jsonl (5,154 examples) targeting the specific covered/not_mentioned classification task |
Why the planned approach was abandoned:
Phases 1 and 2 were implemented during development. The Princeton-Leuven pre-training taught the model to generate policy-style text (domain vocabulary), and OPP-115 fine-tuning reinforced verbatim policy quoting. When Phase 3 instruction tuning was applied on top (using mobilellama_stage3_final), the model suffered catastrophic forgetting; it ignored all input instructions entirely and generated hallucinated boilerplate regardless of the query. Root causes identified:
Solution (training from scratch): A custom training dataset (gap_dataset_v2.jsonl) was built that pairs each Android permission group with policy text excerpts and produces a clean single-line analytical output: "Mentioned: [explanation]" or "Not mentioned: [explanation]". Training directly on this from the base MobileLLaMA model (without any intermediate phases) produced a stable, well-behaved adapter in 3 epochs (~2.5 hours) with training loss converging from 2.91 → 0.34.
| Research Report | Implemented | |
|---|---|---|
| Primary source | OPP-115 Corpus (23,000 labels across 115 policies) | OPP-115 annotations used as input signal for building gap_dataset_v2.jsonl |
| Supporting datasets | PolicyQA (25,017 QA pairs), PrivacyQA (1,750 queries), GDPR NER (44 EU policies) | GDPR NER and PolicyQA/PrivacyQA were explored but not included in final training |
| Dataset size | 18,000–25,000 instruction-response pairs estimated | 5,154 examples in gap_dataset_v2.jsonl |
| Format | Classification, extraction, summarization, risk assessment tasks | Single analytical task: classify permission coverage against policy text (covered / not_mentioned / partially_covered) |
| Princeton-Leuven role | Phase 1 unsupervised domain adaptation | Not used in final training |
Outcome: The final dataset is smaller than planned (5,154 vs 18,000–25,000) but purpose-built for the exact task. As noted by Firecrawl (2025), 5,000–10,000 well-formed instruction pairs works well for focused fine-tuning. The higher-volume multi-task approach outlined in the report was superseded by a leaner, task-focused dataset that produced better results for this specific gap-analysis classification problem.
| Research Report | Implemented | |
|---|---|---|
| Hardware | RTX 2060 Super (8GB VRAM) | RTX 2060 Super (8GB VRAM) ✓ |
| Method | QLoRA via bitsandbytes + PEFT | QLoRA via bitsandbytes + PEFT ✓ |
| LoRA rank | r=8 | r=8 ✓ |
| Alpha | α=16 | α=16 ✓ |
| Learning rate | 2e-4 | 5e-5 (reduced from planned 2e-4 to avoid catastrophic forgetting) |
| Epochs | 3 | 3 ✓ |
| Adapter size | ~16MB estimated | ~20MB actual (adapter_model.bin) ✓ |
| Training time | 50–60 hours | ~2.5 hours (due to smaller, focused dataset) |
| Gradient accumulation | 16 steps | 4 steps (balanced for dataset size) |
Key deviation: The learning rate was reduced from 2e-4 to 5e-5. The report's recommendation of 2e-4 follows Raschka (2023) for general LoRA fine-tuning, but for this specific classification task on a 2.7B model, 2e-4 proved too aggressive and was the root cause of the catastrophic forgetting observed in the mobilellama_stage3_final adapter. At 5e-5 with cosine decay, training converged stably across 3 epochs.
| Research Report | Implemented | |
|---|---|---|
| APK source | APKMirror only | 5-source fallback chain: APKMirror → APKPure → APKCombo → APKMonk → Uptodown |
| Scraping technology | Selenium WebDriver + BeautifulSoup | Playwright (replaced Selenium) for JavaScript-heavy pages + requests for simpler sources |
| Privacy policy retrieval | Google Play Store (Selenium headless Chrome) | Google Play Store via requests-html / httpx with HTML stripping |
| Cloudflare mitigation | Not addressed in report | TLS fingerprint masking (10 signals), direct URL construction bypassing search pages, 7-retry backoff with randomised delays |
| APK verification | Hash/signature checking | Package name verification via AndroidManifest.xml binary inspection (ASCII + UTF-16-LE); rejects wrong-app CDN responses |
| APK storage | Deleted post-processing (ethical consideration) | APKs retained in src/acquired/ during session; deletion is manual |
Key upgrades over the report:
Playwright over Selenium: Playwright was mentioned in the report as a faster alternative but was deemed to have less documentation for legacy pages. In practice, Playwright's expect_download() context manager and response event interception proved essential for capturing APKMirror's CDN download URLs, making it the better choice.
5-source fallback: APKMirror alone is insufficient because Cloudflare blocks automated search requests. The fallback chain (APKPure, APKCombo, APKMonk, Uptodown) enables acquisition of high-profile apps (WhatsApp, Instagram, TikTok, YouTube) that APKMirror's search would block.
Direct URL bypass: A _DEV_SLUG_MAP of ~60 publisher-to-developer-slug mappings enables constructing APKMirror app URLs directly (/apk/{dev-slug}/{app-slug}/) without triggering the Cloudflare-gated search page, unlocking apps like Homescapes, Township, Hulu, and Azure.
Post-download verification: The Uptodown source occasionally served wrong-app CDN responses (e.g., Roblox's slot returned Instagram's APK). Package name verification reads the binary AndroidManifest.xml from the downloaded ZIP and checks for the expected package ID as both ASCII and UTF-16-LE byte patterns, rejecting mismatches before analysis proceeds.
| Research Report | Implemented | |
|---|---|---|
| Base score | 100 | 100 ✓ |
| High-risk mismatch penalty | −15 per undisclosed dangerous permission | −15 per not_mentioned dangerous permission ✓ |
| Medium-risk mismatch penalty | −10 per undisclosed hardware-access permission | −10 per not_mentioned hardware permission ✓ |
| Vagueness index | −2 per hedge word detected ("may", "some", "might") | Vagueness index computed (hedge word frequency in policy); stored in DB but weighted differently in final PHS formula |
| Minimum | Capped at 0 | Capped at 0 ✓ |
| Risk labels | Critical Risk (<50) | Low (<30), Moderate (30–59), High (60–79), Critical (≥80); the scale is inverted so higher PHS means less risk |
| Additional signal | Not described | Keyword score: LLM output corroborated by keyword matching across 14 permission-specific keyword sets; influences coverage classification |
Risk label note: The report defines "Critical Risk" as PHS < 50. The implementation uses a 0–100 scale where 100 = fully disclosed (Low Risk) and 0 = fully undisclosed (Critical Risk), consistent with the report's intent. The risk labels in the implementation are: Low Risk (≥80), Moderate Risk (60–79), High Risk (30–59), Critical Risk (<30), refined from the report's single threshold.
| Research Report | Implemented | |
|---|---|---|
| Name | Not named | PrivacyTotal |
| Interface | Web-based, accepts app name/developer, returns plain-language summaries of data practices, third-party sharing, permission mismatches | Flask web app on localhost:5000 |
| Input method | App name/identifier | Package ID (e.g., com.instagram.android) or app name search |
| Output | Plain-language summaries, permission mismatches, PHS | Full coverage donut chart, PHS gauge, per-permission table (covered/not_mentioned + evidence text), run history, policy diff between versions |
| Research report display | Posted alongside the tool | Full formatted research report displayed in the web app (Abstract, Methodology, Model, Results, Findings tabs) |
| Database | Not specified | SQLite (gap_analysis.db) with all 117 apps, 175 runs, policy snapshots |
| Policy change detection | Not specified | Policy text hash comparison; /diff/<old_id>/<new_id> endpoint shows side-by-side diff of policy changes between runs |
| Live analysis | Not specified | Background job system with live log streaming via /job/<job_id> endpoint |
Additional capabilities not in report:
| Research Report | Implemented | |
|---|---|---|
| Method | Manual human review of 20% of apps (200 apps across 10 categories) to establish ground truth | Automated keyword-oracle F1 (eval_f1.py): keyword matching as ground-truth proxy |
| Metrics | Precision, Recall, F1-Score | Precision, Recall, F1-Score, Accuracy, Macro-avg F1, Weighted-avg F1 ✓ |
| Target | F1 > 80 | Achieved F1 = 0.930 |
| Precision target | Not specified | Achieved Precision = 0.892 |
| Recall target | Not specified | Achieved Recall = 0.971 |
| Coverage | 1,000 apps | 117 apps, 3,483 rows, 121 unique permissions, 175 runs |
| LLM agreement | Not specified | 92.5% (2,931 / 3,167 non-ambiguous responses) |
Validation approach deviation: The research report planned manual review of 200 apps by a human reviewer examining each privacy policy. This would be comprehensive but labour-intensive and non-reproducible. The implementation uses a deterministic keyword-oracle approach: for each permission group, a curated set of 8–14 domain keywords is matched against the policy text to establish a reproducible ground truth label. The LLM's classification is then compared against this keyword oracle. This approach:
eval_f1.py at any time)The trade-off is that the keyword oracle may have its own false positives/negatives. However, an F1 of 0.930 against this oracle, with Precision of 0.892, provides meaningful evidence that the LLM classifications track the keyword retriever closely, though the oracle and the retriever share signal, so the figure is best read as a consistency check rather than an independent accuracy measure.
| Research Report | Implemented | |
|---|---|---|
| Data types analyzed | Open-source programs and public legal texts only | Public APKs and publicly available privacy policies only ✓ |
| Personal data | No personal profiling, no confidential details in output | No personal data collected or stored ✓ |
| Rate limiting | Delays between requests to respect robots.txt | Randomised delays, rotating user agents, TLS fingerprint rotation ✓ |
| APK retention | APKs deleted post-processing | APKs are stored in src/acquired/ for reproducibility; should be deleted per the ethical commitment before final submission |
| Scope | Analysis only | Analysis only; no modification or redistribution of APKs ✓ |
Note on APK retention: The research report commits to deleting APKs post-processing. The current src/acquired/ directory retains downloaded APKs alongside the CSV reports (total ~12GB). For compliance with this commitment, APK files (*.apk, *.xapk) within src/acquired/ should be deleted before final submission; only the gap_report.csv and metadata files are required for reproducibility.
| Component | Research Report | Implemented |
|---|---|---|
| LLM | MobileLLaMA 2.7B | MobileLLaMA 2.7B ✓ |
| Fine-tuning | QLoRA + bitsandbytes + PEFT | QLoRA + bitsandbytes + PEFT ✓ |
| Static analysis | Androguard | Androguard ✓ |
| Web scraping | Selenium + BeautifulSoup | Playwright (primary) + requests + BeautifulSoup ✓ |
| Web framework | Not specified | Flask |
| Database | Not specified | SQLite via Python sqlite3 |
| APK sources | APKMirror | APKMirror + APKPure + APKCombo + APKMonk + Uptodown |
| Alternatives considered | Mistral 7B (too large), TinyLLaMA (too weak), MobSF (GUI-only), Frida (dynamic), Scrapy (no JS) | Same alternatives considered and dismissed for same reasons ✓ |
| Metric | Value |
|---|---|
| Applications analyzed | 117 |
| Total permission rows | 3,483 |
| Unique permissions observed | 121 |
| Analysis runs completed | 175 |
| Permissions covered in policy | 2,272 (65.2%) |
| Permissions not mentioned | 895 (25.7%) |
| Partially covered / unclear | 316 (9.1%) |
| F1 Score | 0.930 |
| Precision | 0.892 |
| Recall | 0.971 |
| Accuracy | 0.912 |
| LLM agreement rate | 92.5% |
| Model training time | ~2.5 hours |
| Model adapter size | ~20MB |
| Training loss (start → end) | 2.91 → 0.34 |
| Training dataset size | 5,154 examples |
App Risk Distribution across 117 apps:
| Risk Level | PHS Range | Count |
|---|---|---|
| Low Risk | ≥80 | ~52 apps |
| Moderate Risk | 60–79 | ~14 apps |
| High Risk | 30–59 | ~21 apps |
| Critical Risk | <30 | ~13 apps |
Notable findings:
NFC, WRITE_CALENDAR, and READ_PHONE_STATE permissions without policy disclosure| What the Report Said | What Was Done | Why |
|---|---|---|
| 1,000 apps | 117 apps | APK acquisition rate-limiting and session time constraints; all infrastructure scales to 1,000 |
| 3-phase training curriculum | Single-phase scratch training | Phases 1–3 caused catastrophic forgetting; scratch training on task-specific data produced superior results (F1=0.930 vs broken output) |
| 18,000–25,000 instruction pairs | 5,154 examples | Smaller focused dataset outperformed larger unfocused dataset for this classification task |
| Selenium for scraping | Playwright (primary) | Playwright's async model and download interception were essential for APKMirror's CDN URLs |
| APKMirror only | 5-source fallback | Cloudflare blocks APKMirror search; fallback chain enables acquisition of high-profile apps |
| Manual 20% validation | Automated keyword-oracle F1 | Fully reproducible, scalable, consistent; keyword oracle provides deterministic ground truth |
| LR = 2e-4 | LR = 5e-5 | 2e-4 caused catastrophic forgetting in early training; 5e-5 converged stably |
PrivacyTotal was built to empower users to understand what data applications collect and whether they are honest about it. The tool bridges the gap between legal privacy policies and user comprehension, exposing the "Privacy Paradox" through automated, evidence-based gap analysis at scale.
1 commits
Python
74.0%
HTML
24.4%
CSS
1.7%
Omar Ramadan – C00286349 | BSc (Hons) Cybercrime and IT Security | Supervisor: Mark Cummins South East Technological University | April 2026
| Deliverable | File / Location | Status |
|---|---|---|
| Research Report | Research Report.pdf | Submitted |
| Project Report | ProjectReport.pdf, ProjectReport.docx | Submitted |
| Web Application | webapp/web_app.py (run: python webapp/web_app.py) | Complete |
| Training Data | data/processed/gap_dataset_v2.jsonl | 5,154 examples |
| Trained Model | src/models/mobilellama_gap_scratch/ | F1 = 0.930 |
| Evaluation Script | eval_f1.py | F1=0.930, P=0.892, R=0.971 |
| Requirements | requirements.txt | Complete |
PrivacyTotal is a static analysis tool for Android applications that automatically compares an app's declared APK permissions against its published privacy policy. Using a fine-tuned MobileLLaMA 2.7B language model, the system determines whether each permission is disclosed in the policy, assigns a Privacy Health Score (PHS), and presents the results through a web application.
The tool was motivated by an RTÉ PrimeTime investigation (September 2025) revealing that precise location data from tens of thousands of Irish smartphones was being sold by data brokers, highlighting that most users blindly accept privacy policies without reading them.
PrivacyTotal/
├── README.md This document
├── LICENSE MIT licence
├── Research Report.pdf Final research report
├── ProjectReport.pdf Final project report
├── requirements.txt Python dependencies
├── eval_f1.py F1 evaluation script (keyword-oracle)
│
├── src/
│ ├── end_to_end_gap_analysis.py Main pipeline: APK download → permission
│ │ extraction → policy scraping → LLM gap
│ │ analysis → PHS → DB save
│ ├── batch_runner.py Batch runner for sequential multi-app analysis
│ ├── database.py SQLite layer (gap_analysis.db schema, CRUD)
│ ├── acquire_app_artifacts.py Standalone APK + metadata acquisition script
│ ├── train_gap_scratch.py QLoRA training script (final working version)
│ ├── prepare_gap_training_v2.py Builds gap_dataset_v2.jsonl from OPP-115 data
│ │
│ ├── models/
│ │ └── mobilellama_gap_scratch/ Working LoRA adapter (trained from scratch)
│ │ ├── adapter_config.json
│ │ ├── adapter_model.bin ~20MB LoRA weights
│ │ ├── tokenizer.json
│ │ ├── tokenizer.model
│ │ └── tokenizer_config.json
│ │
│ └── acquired/ Per-app result folders (117 apps)
│ └── com.example.app/
│ └── YYYYMMDD_HHMMSS/
│ └── gap_report.csv Permission × coverage × evidence × PHS
│
├── data/
│ ├── gap_analysis.db SQLite database (117 apps, 175 runs)
│ └── processed/
│ └── gap_dataset_v2.jsonl Training dataset (5,154 examples)
│
└── webapp/
├── web_app.py Flask application (PrivacyTotal)
└── templates/
├── base.html Shared layout, navigation, CSS
├── index.html Home: Analyze App / Browse DB / Research Report
├── app.html Per-app: PHS gauge, coverage donut, permission table
├── run.html Single run view with gap report details
├── diff.html Policy text diff between two runs
└── job.html Live job status / log streaming
# Create virtual environment
python -m venv venv311
source venv311/Scripts/activate # Windows Git Bash
pip install -r requirements.txt
# Download base model (MobileLLaMA-2.7B-Chat) from HuggingFace
# The LoRA adapter in src/models/mobilellama_gap_scratch/ is already trained
cd PrivacyTotal/
source venv311/Scripts/activate
python webapp/web_app.py
# Opens at http://localhost:5000
python src/end_to_end_gap_analysis.py --package-id com.example.app
python src/batch_runner.py
# Edit CANDIDATES list in batch_runner.py to specify apps
PYTHONIOENCODING=utf-8 python eval_f1.py
| Research Report | Implemented | |
|---|---|---|
| Target | 1,000 applications | 117 applications |
| Categories | 10 distinct categories × 100 apps each | 10 categories (Social Media, Gaming, Productivity, Finance, Entertainment, Navigation, Health, Shopping, Communication, Security/VPN) |
| Sampling method | Stratified from Google Play "Top Free" charts (October 2025) | Top free apps by download count, sourced across multiple APK mirrors |
| Selection criteria | ≥1M downloads, updated ≤12 months, English policy, free app | Same criteria applied; some apps excluded where no APK was obtainable from any source (e.g., Uber, Roblox) |
Outcome: The research report outlined 1,000 applications but the implemented study analyzed 117 applications across all 10 planned categories. This reduction was driven by practical constraints: APK acquisition is rate-limited, Cloudflare-gated, and time-intensive. The 117-app dataset still represents a statistically meaningful cross-section and satisfies the original intent of cross-category comparison. The tool remains fully capable of scaling to 1,000 apps with additional runtime; all infrastructure is in place.
The research report outlined a three-phase sequential training curriculum:
| Phase | Planned | Implemented |
|---|---|---|
| Phase 1 | Domain adaptation on Princeton-Leuven Corpus (50,000 sampled privacy policies, unsupervised causal LM) | Not used |
| Phase 2 | Supervised fine-tuning on OPP-115 + GDPR NER dataset (token classification, entity extraction) | partially attempted, caused failure |
| Phase 3 | Instruction tuning on PolicyQA + PrivacyQA + synthetic instructions (response generation) | attempted, caused catastrophic forgetting |
| Final approach | n/a | Single-phase scratch training on a custom gap_dataset_v2.jsonl (5,154 examples) targeting the specific covered/not_mentioned classification task |
Why the planned approach was abandoned:
Phases 1 and 2 were implemented during development. The Princeton-Leuven pre-training taught the model to generate policy-style text (domain vocabulary), and OPP-115 fine-tuning reinforced verbatim policy quoting. When Phase 3 instruction tuning was applied on top (using mobilellama_stage3_final), the model suffered catastrophic forgetting; it ignored all input instructions entirely and generated hallucinated boilerplate regardless of the query. Root causes identified:
Solution (training from scratch): A custom training dataset (gap_dataset_v2.jsonl) was built that pairs each Android permission group with policy text excerpts and produces a clean single-line analytical output: "Mentioned: [explanation]" or "Not mentioned: [explanation]". Training directly on this from the base MobileLLaMA model (without any intermediate phases) produced a stable, well-behaved adapter in 3 epochs (~2.5 hours) with training loss converging from 2.91 → 0.34.
| Research Report | Implemented | |
|---|---|---|
| Primary source | OPP-115 Corpus (23,000 labels across 115 policies) | OPP-115 annotations used as input signal for building gap_dataset_v2.jsonl |
| Supporting datasets | PolicyQA (25,017 QA pairs), PrivacyQA (1,750 queries), GDPR NER (44 EU policies) | GDPR NER and PolicyQA/PrivacyQA were explored but not included in final training |
| Dataset size | 18,000–25,000 instruction-response pairs estimated | 5,154 examples in gap_dataset_v2.jsonl |
| Format | Classification, extraction, summarization, risk assessment tasks | Single analytical task: classify permission coverage against policy text (covered / not_mentioned / partially_covered) |
| Princeton-Leuven role | Phase 1 unsupervised domain adaptation | Not used in final training |
Outcome: The final dataset is smaller than planned (5,154 vs 18,000–25,000) but purpose-built for the exact task. As noted by Firecrawl (2025), 5,000–10,000 well-formed instruction pairs works well for focused fine-tuning. The higher-volume multi-task approach outlined in the report was superseded by a leaner, task-focused dataset that produced better results for this specific gap-analysis classification problem.
| Research Report | Implemented | |
|---|---|---|
| Hardware | RTX 2060 Super (8GB VRAM) | RTX 2060 Super (8GB VRAM) ✓ |
| Method | QLoRA via bitsandbytes + PEFT | QLoRA via bitsandbytes + PEFT ✓ |
| LoRA rank | r=8 | r=8 ✓ |
| Alpha | α=16 | α=16 ✓ |
| Learning rate | 2e-4 | 5e-5 (reduced from planned 2e-4 to avoid catastrophic forgetting) |
| Epochs | 3 | 3 ✓ |
| Adapter size | ~16MB estimated | ~20MB actual (adapter_model.bin) ✓ |
| Training time | 50–60 hours | ~2.5 hours (due to smaller, focused dataset) |
| Gradient accumulation | 16 steps | 4 steps (balanced for dataset size) |
Key deviation: The learning rate was reduced from 2e-4 to 5e-5. The report's recommendation of 2e-4 follows Raschka (2023) for general LoRA fine-tuning, but for this specific classification task on a 2.7B model, 2e-4 proved too aggressive and was the root cause of the catastrophic forgetting observed in the mobilellama_stage3_final adapter. At 5e-5 with cosine decay, training converged stably across 3 epochs.
| Research Report | Implemented | |
|---|---|---|
| APK source | APKMirror only | 5-source fallback chain: APKMirror → APKPure → APKCombo → APKMonk → Uptodown |
| Scraping technology | Selenium WebDriver + BeautifulSoup | Playwright (replaced Selenium) for JavaScript-heavy pages + requests for simpler sources |
| Privacy policy retrieval | Google Play Store (Selenium headless Chrome) | Google Play Store via requests-html / httpx with HTML stripping |
| Cloudflare mitigation | Not addressed in report | TLS fingerprint masking (10 signals), direct URL construction bypassing search pages, 7-retry backoff with randomised delays |
| APK verification | Hash/signature checking | Package name verification via AndroidManifest.xml binary inspection (ASCII + UTF-16-LE); rejects wrong-app CDN responses |
| APK storage | Deleted post-processing (ethical consideration) | APKs retained in src/acquired/ during session; deletion is manual |
Key upgrades over the report:
Playwright over Selenium: Playwright was mentioned in the report as a faster alternative but was deemed to have less documentation for legacy pages. In practice, Playwright's expect_download() context manager and response event interception proved essential for capturing APKMirror's CDN download URLs, making it the better choice.
5-source fallback: APKMirror alone is insufficient because Cloudflare blocks automated search requests. The fallback chain (APKPure, APKCombo, APKMonk, Uptodown) enables acquisition of high-profile apps (WhatsApp, Instagram, TikTok, YouTube) that APKMirror's search would block.
Direct URL bypass: A _DEV_SLUG_MAP of ~60 publisher-to-developer-slug mappings enables constructing APKMirror app URLs directly (/apk/{dev-slug}/{app-slug}/) without triggering the Cloudflare-gated search page, unlocking apps like Homescapes, Township, Hulu, and Azure.
Post-download verification: The Uptodown source occasionally served wrong-app CDN responses (e.g., Roblox's slot returned Instagram's APK). Package name verification reads the binary AndroidManifest.xml from the downloaded ZIP and checks for the expected package ID as both ASCII and UTF-16-LE byte patterns, rejecting mismatches before analysis proceeds.
| Research Report | Implemented | |
|---|---|---|
| Base score | 100 | 100 ✓ |
| High-risk mismatch penalty | −15 per undisclosed dangerous permission | −15 per not_mentioned dangerous permission ✓ |
| Medium-risk mismatch penalty | −10 per undisclosed hardware-access permission | −10 per not_mentioned hardware permission ✓ |
| Vagueness index | −2 per hedge word detected ("may", "some", "might") | Vagueness index computed (hedge word frequency in policy); stored in DB but weighted differently in final PHS formula |
| Minimum | Capped at 0 | Capped at 0 ✓ |
| Risk labels | Critical Risk (<50) | Low (<30), Moderate (30–59), High (60–79), Critical (≥80); the scale is inverted so higher PHS means less risk |
| Additional signal | Not described | Keyword score: LLM output corroborated by keyword matching across 14 permission-specific keyword sets; influences coverage classification |
Risk label note: The report defines "Critical Risk" as PHS < 50. The implementation uses a 0–100 scale where 100 = fully disclosed (Low Risk) and 0 = fully undisclosed (Critical Risk), consistent with the report's intent. The risk labels in the implementation are: Low Risk (≥80), Moderate Risk (60–79), High Risk (30–59), Critical Risk (<30), refined from the report's single threshold.
| Research Report | Implemented | |
|---|---|---|
| Name | Not named | PrivacyTotal |
| Interface | Web-based, accepts app name/developer, returns plain-language summaries of data practices, third-party sharing, permission mismatches | Flask web app on localhost:5000 |
| Input method | App name/identifier | Package ID (e.g., com.instagram.android) or app name search |
| Output | Plain-language summaries, permission mismatches, PHS | Full coverage donut chart, PHS gauge, per-permission table (covered/not_mentioned + evidence text), run history, policy diff between versions |
| Research report display | Posted alongside the tool | Full formatted research report displayed in the web app (Abstract, Methodology, Model, Results, Findings tabs) |
| Database | Not specified | SQLite (gap_analysis.db) with all 117 apps, 175 runs, policy snapshots |
| Policy change detection | Not specified | Policy text hash comparison; /diff/<old_id>/<new_id> endpoint shows side-by-side diff of policy changes between runs |
| Live analysis | Not specified | Background job system with live log streaming via /job/<job_id> endpoint |
Additional capabilities not in report:
| Research Report | Implemented | |
|---|---|---|
| Method | Manual human review of 20% of apps (200 apps across 10 categories) to establish ground truth | Automated keyword-oracle F1 (eval_f1.py): keyword matching as ground-truth proxy |
| Metrics | Precision, Recall, F1-Score | Precision, Recall, F1-Score, Accuracy, Macro-avg F1, Weighted-avg F1 ✓ |
| Target | F1 > 80 | Achieved F1 = 0.930 |
| Precision target | Not specified | Achieved Precision = 0.892 |
| Recall target | Not specified | Achieved Recall = 0.971 |
| Coverage | 1,000 apps | 117 apps, 3,483 rows, 121 unique permissions, 175 runs |
| LLM agreement | Not specified | 92.5% (2,931 / 3,167 non-ambiguous responses) |
Validation approach deviation: The research report planned manual review of 200 apps by a human reviewer examining each privacy policy. This would be comprehensive but labour-intensive and non-reproducible. The implementation uses a deterministic keyword-oracle approach: for each permission group, a curated set of 8–14 domain keywords is matched against the policy text to establish a reproducible ground truth label. The LLM's classification is then compared against this keyword oracle. This approach:
eval_f1.py at any time)The trade-off is that the keyword oracle may have its own false positives/negatives. However, an F1 of 0.930 against this oracle, with Precision of 0.892, provides meaningful evidence that the LLM classifications track the keyword retriever closely, though the oracle and the retriever share signal, so the figure is best read as a consistency check rather than an independent accuracy measure.
| Research Report | Implemented | |
|---|---|---|
| Data types analyzed | Open-source programs and public legal texts only | Public APKs and publicly available privacy policies only ✓ |
| Personal data | No personal profiling, no confidential details in output | No personal data collected or stored ✓ |
| Rate limiting | Delays between requests to respect robots.txt | Randomised delays, rotating user agents, TLS fingerprint rotation ✓ |
| APK retention | APKs deleted post-processing | APKs are stored in src/acquired/ for reproducibility; should be deleted per the ethical commitment before final submission |
| Scope | Analysis only | Analysis only; no modification or redistribution of APKs ✓ |
Note on APK retention: The research report commits to deleting APKs post-processing. The current src/acquired/ directory retains downloaded APKs alongside the CSV reports (total ~12GB). For compliance with this commitment, APK files (*.apk, *.xapk) within src/acquired/ should be deleted before final submission; only the gap_report.csv and metadata files are required for reproducibility.
| Component | Research Report | Implemented |
|---|---|---|
| LLM | MobileLLaMA 2.7B | MobileLLaMA 2.7B ✓ |
| Fine-tuning | QLoRA + bitsandbytes + PEFT | QLoRA + bitsandbytes + PEFT ✓ |
| Static analysis | Androguard | Androguard ✓ |
| Web scraping | Selenium + BeautifulSoup | Playwright (primary) + requests + BeautifulSoup ✓ |
| Web framework | Not specified | Flask |
| Database | Not specified | SQLite via Python sqlite3 |
| APK sources | APKMirror | APKMirror + APKPure + APKCombo + APKMonk + Uptodown |
| Alternatives considered | Mistral 7B (too large), TinyLLaMA (too weak), MobSF (GUI-only), Frida (dynamic), Scrapy (no JS) | Same alternatives considered and dismissed for same reasons ✓ |
| Metric | Value |
|---|---|
| Applications analyzed | 117 |
| Total permission rows | 3,483 |
| Unique permissions observed | 121 |
| Analysis runs completed | 175 |
| Permissions covered in policy | 2,272 (65.2%) |
| Permissions not mentioned | 895 (25.7%) |
| Partially covered / unclear | 316 (9.1%) |
| F1 Score | 0.930 |
| Precision | 0.892 |
| Recall | 0.971 |
| Accuracy | 0.912 |
| LLM agreement rate | 92.5% |
| Model training time | ~2.5 hours |
| Model adapter size | ~20MB |
| Training loss (start → end) | 2.91 → 0.34 |
| Training dataset size | 5,154 examples |
App Risk Distribution across 117 apps:
| Risk Level | PHS Range | Count |
|---|---|---|
| Low Risk | ≥80 | ~52 apps |
| Moderate Risk | 60–79 | ~14 apps |
| High Risk | 30–59 | ~21 apps |
| Critical Risk | <30 | ~13 apps |
Notable findings:
NFC, WRITE_CALENDAR, and READ_PHONE_STATE permissions without policy disclosure| What the Report Said | What Was Done | Why |
|---|---|---|
| 1,000 apps | 117 apps | APK acquisition rate-limiting and session time constraints; all infrastructure scales to 1,000 |
| 3-phase training curriculum | Single-phase scratch training | Phases 1–3 caused catastrophic forgetting; scratch training on task-specific data produced superior results (F1=0.930 vs broken output) |
| 18,000–25,000 instruction pairs | 5,154 examples | Smaller focused dataset outperformed larger unfocused dataset for this classification task |
| Selenium for scraping | Playwright (primary) | Playwright's async model and download interception were essential for APKMirror's CDN URLs |
| APKMirror only | 5-source fallback | Cloudflare blocks APKMirror search; fallback chain enables acquisition of high-profile apps |
| Manual 20% validation | Automated keyword-oracle F1 | Fully reproducible, scalable, consistent; keyword oracle provides deterministic ground truth |
| LR = 2e-4 | LR = 5e-5 | 2e-4 caused catastrophic forgetting in early training; 5e-5 converged stably |
PrivacyTotal was built to empower users to understand what data applications collect and whether they are honest about it. The tool bridges the gap between legal privacy policies and user comprehension, exposing the "Privacy Paradox" through automated, evidence-based gap analysis at scale.
1 commits
Python
74.0%
HTML
24.4%
CSS
1.7%