This repository includes artifacts for "Papers, Please: A First Look at Age Verification on the Web" which is to appear at IEEE S&P 2026.
Here, we include our web crawling code, code to detect 3rd party providers, code to analyze the prevalence of the top providers, artifacts from reverse-engineering Yoti's face capture module, and a proof-of-concept browser extension to bypass Yoti's age estimation method (tested with SCM 2.8.0).
@inproceedings{minocha_papersplease_2026,
title = {Papers, Please: A First Look at Age Verification on the Web},
author = {Minocha, Shreyas and Sheridan, Isaac and Oppenheimer, Harry and Pearce, Paul and Specter, Michael A.},
year = {2026},
month = may,
booktitle = {IEEE Symposium on Security and Privacy},
url = {https://shreyasminocha.me/papers/papers-please.pdf},
doi = {10.1109/SP63933.2026.00161},
}- Set up a
uvenvironment inanalysiswithuv sync, extract the.gzfiles inanalysis/data, runprevalence.ipynb, and check that the numbers computed in the notebook match the quoted text. - Check that the partially reverse-engineered Yoti SICAP code and a SICAP bypass PoC are included in
yoti-scm-reandyoti-sicap-bypassrespectively. - Check that the code to run detectors against webpages is included in
analysis/src/prevalence/run_detectors.py. - Check that the code to crawl webpages is included in
crawland the code to fetch data from Klazify is included inanalysis/src/klazify.
It should be possible to run everything but the web crawling code on Google Colab.
-
crawl/: Web crawling code (Section 3.1.3)Although we include this for completeness and for ease of replication, we do not expect artifact evaluators to replicate our setup. This step requires a significant amount of memory, disk storage, and network traffic.
However, we do provide the data from the web crawl and the results of running detectors (see Data). The analysis code described later runs on the preprocessed data included in this repository.
-
analysis/: Third-party age verification provider prevalence analysis (Section 3.1.2 and Section 4)- Includes code for:
- Generating result JSON files from crawl data
- Combining the JSON files into a CSV file (one per state)
- Computing stats and graphs from the processed data
- Evaluators are expected to run only #3 which uses preprocessed results from steps 1 and 2.
- Includes code for:
-
control-webpage/: Code to run the control webpage (Control webpages under Section 3.1.2) -
yoti-scm-re/: Yoti SICAP partially reverse-engineered code (Sections 5.3.1 and 5.5.3) -
yoti-sicap-bypass/: Chrome extension that bypasses Yoti's age estimation method (Section 5.5.3)
-
The data reported in Section 3.3 matches that computed from the data.
Please see the corresponding section in
analysis/prevalence.ipynb. -
The data reported in Section 4 matches that computed from the data.
Please see the corresponding section in
analysis/prevalence.ipynb. -
The figures in the ipynb are semantically equivalent to those in the paper.
Please see the figures generated in
analysis/prevalence.ipynb.
We conducted our web crawls in August 2025 from a machine in Georgia and through two DigitalOcean VPSes in Texas and New York.
We make the data (rendered HTML content) from these crawls available under CC0. Note that these files are very large (~150 GiB compressed, ~750 GiB uncompressed).
For convenience, we include the (compressed) CSV files generated after running our detectors on the raw data in analysis/data.
These are approximately 30 MiB compressed and 500 MiB uncompressed.
The analysis code in our IPython notebooks and figure generation scripts reads and operates on these CSV files.
The API responses from the web classification API (see Section 3.1.4) are also included in analysis/data as klazify.tar.gz.
We provide the IPython notebook analysis/prevalence.ipynb that computes the statistics and figures used in our paper.
We make an effort to structure this notebook to match the structure of the relevant sections of the paper.
-
Install
uv -
Run
uv syncinanalysisto set up a python environment (tested with python 3.12.11 on linux 6.12) -
Extract the
.gzfiles inanalysis/data -
Run
prevalence.ipynbwithin the virtual environment created byuvThis step takes approximately 15 minutes on a typical 2021 laptop.
As noted in our paper, we share the partially reverse-engineered SICAP 2.6.2 JavaScript module. We also include a "pseudocode" version of this module, reconstructed based on our RE results.
Additionally, we include the source code for a proof-of-concept Chrome extension that bypasses Yoti's age estimation. We also used this scaffolding as part of our dynamic analysis.
In the process of conducting our crawls, we sent the minimal amount of traffic necessary to the sites we crawled. During our reverse-engineering efforts, we sent no malicious or abnormal data to any external servers.