Skip to content
AFM Studio
  • Pages

    AFM Studio
  • Work
  • Lab
  • About
  • CV
  • Contact
  • Projects

    AksaraA searchable library for Otorita IKN's knowledge, where the server checks every reader's access across three levels
  • Daily TaqwaTamper-resistant prayer attendance system for ~2,000 OIKN government employees at Masjid Negara IKN
  • Lantara v2A permit engine for Otorita IKN where administrators add new permit types from a screen, without a developer
  • eKiosk IKNTwo control rooms for Nusantara: kiosk content management and live bus tracking with arrival times that grade themselves
  • E-Monev IKNA monitoring platform for Nusantara's 23-year build, tracking every unit's quarterly progress against 24 national KPIs
  • JDIH OIKNOIKN's public legal document portal, with a controlled publishing workflow and AI summaries that never leave the server
  • Jejak PenanamanA check-in app for IKN tree-planting events that proves each participant was on site with a geofenced selfie
  • Dashboard SAKTIA nightly pipeline and dashboard that replaces OIKN's manual budget downloads for all six of its budget units
  • DoseRxA bedside dose calculator for 127 drugs that rounds each dose to tablets and syrups Indonesian pharmacies sell
  • CubiqA speedcubing trainer with competition-grade timing and a solver I wrote for each of the 8 major puzzles
  • ScimotionAn interactive science library of 86 articles, each explained next to animations the reader can control
  • WA Daily Scrum BotA WhatsApp bot that chases missing standup reports for a 14-person team and flags tasks stuck for days

↑ ↓ to move · Enter to open · Esc to close

Linkage

An interactive lab that re-identifies people in synthetic data, showing that removing names doesn't make a dataset anonymous

Screenshots
re-identification attacks, each scored against ground truth
6
cases pairing each defence with its attack
5
synthetic records drawn at 60 fps
200,000
ways to load a real dataset
0
On this page

The problem

Government bodies and companies publish "anonymised" data by removing names and ID numbers, and treat the job as done. It isn't. What identifies a person is the combination of ordinary details, such as district, date of birth and gender, none of which looks sensitive alone. A landmark study found 87% of the US population unique on ZIP code, gender and date of birth. A later re-run put it at 63%, and nobody has reconciled the two.

Data stewards usually meet this as a citation. They read "87%" and can't tell whether it applies to their own release. I wanted an instrument in its place: run the attack yourself, see how many people it found, apply the standard defence, then watch the next attack beat that too.

What I built

The app only works on populations it invents

A tool that takes an actual dataset and helps re-identify the people in it is a re-identification tool, whatever its copy says. So Linkage generates its own synthetic populations in the browser and keeps the truth about who every record is. That lets it tell you, for example, that you identified 340 of 500 targets, and show the 160 that failed. There is no way to load a file or paste data, and a test scans the built site and fails the build if one appears.

Checking your own data takes column descriptions only

Data stewards still need to judge their own releases. The schema assessor takes a description of each column: its name, its type and how many distinct values it holds. The input has no place to put an actual row, and a test checks that. The result is a uniqueness estimate, with its assumption stated: it treats columns as independent, so correlated columns make it look safer than it is.

Every attack reports a score

The six attacks can't claim a record is identifiable without the counts behind it: attempted, correct, incorrect and failed. For each target they also report how many candidates were left. An attack that narrows 500,000 records to three has done most of the work, even if it didn't finish, and the screen shows that.

Each case ends with the next attack

There are five scripted cases. Each one runs an attack, applies the defence that stops it, then runs the attack that gets past that defence. The chain goes from linkage to k-anonymity, homogeneity, l-diversity, skewness, differencing and finally differential privacy and its budget. For differential privacy, I derive the noise needed for each kind of query and show the working, because assuming one value for every query quietly breaks the guarantee.

200,000 people on one screen

The central view draws 200,000 synthetic records at 60 frames per second and groups them into classes of people who look identical in the data. As you generalise the data, classes merge on screen. I keep each record's position fixed unless its own class changes, so the merge reads clearly. A dissector for Indonesia's 16-digit national ID number (NIK) shows it contains that same district, birth date and gender triple, so masking its last digits hides the only harmless part.

Result

Linkage is live and public, a finished first version with no backend and no accounts. Beyond the five cases, a free sandbox holds twelve instruments, including a search for the smallest generalisation that meets a target, drawn from the search's own record of what it tested. The whole app downloads as 113.8 KB compressed. It reports what an attack recovered and never dramatises it: no "exposed", no "victim", no congratulations. I built it alone over eight days in September 2026.

Under the hoodTechnical detail for engineers
  • src/engine/ imports nothing, not React, the DOM or Date. Every result is a function of a seed and a configuration, tested against brute-force reference implementations.
  • No FileReader, file input, drag-and-drop or paste path. tests/no-file-input.test.ts greps the built bundle for those APIs; tests/assessor-purity.test.ts asserts at type level and runtime that the assessor input has no values, sample or rows.
  • Attacks: linkage, homogeneity, background knowledge, skewness, differencing and an auxiliary-data join. Each returns a per-target candidateCount.
  • DP bench: derived per-query sensitivity (count 1, sum equal to its clamp range), Laplace and Gaussian mechanisms, sequential budget meter. dp.test.ts checks the Laplace sampler against the analytic density.
  • The lattice search prunes upward by monotonicity and records prunedBy and test order; lattice.test.ts asserts it finds the same minimal set as exhaustive search.
  • The field uses Canvas 2D, three parallel typed arrays, a grid index for hit testing on a single listener, and deterministic packing. tests/performance.test.ts asserts the 200,000-record path finishes under 2 s.
  • The Sweeney and Golle methods are both reconstructed against known populations, and the 87% versus 63% gap is shown as unresolved.
  • Seed, generator parameters, generalisation vector, k target and ε live in the URL. Case progress and theme stay local.
  • Two runtime dependencies, React 18.3 and React DOM. Vite 5.4 with an in-repo linkage-metadata plugin, plain CSS, hand-rolled requestAnimationFrame, no chart or UI library.
  • scripts/bundle-budget.mjs fails CI over 250 KB gzipped; the current 113.8 KB includes 44.3 KB of React.
  • 328 tests across 20 files (about 24 s), 18 views plus 6 case screens, 30 engine modules.
  • About 13,300 lines in src/ and 80 commits from 4 to 11 September 2026, paired with Claude Code.

Screenshots

Linkage
K-Anonymity
Class Inspector

Hiring, or building something similar?

I'm open to full-time roles and select freelance work.