Skip to content
handed over

Digital Dignity: Am I in P*rn?

A Next.js web app helping people find out whether their face appears in online p*rn without their consent. Volunteer work for Digital Dignity.

Victims of non-consensual intimate media had no safe way to search for their own face, and the search itself must not become a new avenue for abuse.

role:
Frontend developer (volunteer): rebuilt the frontend largely solo
stack:
Next.js · React · Node.js · Bootstrap · GitHub CI/CD
when:
2023–2024
published:
Am I in P*rn?: from live capture to takedown An embedded live camera capture with liveness prompts feeds an in-house face vectorization service, which discards the image immediately and searches indexed media for matching vectors. Results go out by email with an official notice and anonymous links to short-lived external viewing pages, where the person confirms the match; confirmed cases go to a law firm that issues a takedown demand. Live camera capture embedded UI · liveness prompts rate-limited Face → vectors in-house service image discarded at once Vector match search across indexed media Results by email official notice · anonymous links Ephemeral viewing short-lived external pages person confirms the match Law firm official takedown demand
From a liveness-checked live capture to a takedown demand: faces are vectorized in-house and images discarded immediately; matches are delivered by email and viewed on short-lived external pages, and confirmed cases go to a partner law firm. · scroll sideways to see all of it

Problem & constraints

Digital Dignity, a Cologne-based organization, runs Am I in P*rn?, a service that lets people check whether their face appears in p*rnographic material published without their consent, and then helps them get it taken down. I joined as a volunteer frontend developer in 2023, alongside my day job, and stayed through the end of 2024.

The product lives inside a knot of constraints that shaped every frontend decision. To search for your face, you have to submit your face, so the search tool itself must not become an instrument of abuse, and it must not hoard the most sensitive biometric data imaginable. On top of that, EU rules treat showing the found material as distributing adult content, so the platform cannot legally display its own search results. And the people using it are, by definition, in a distressing situation, and they are mostly on phones.

The team was small and entirely volunteer: developers guided by a project manager, no designers. Most of my frontend teammates had joined primarily to learn, so much of the rebuild ended up with me, evenings and weekends.

Architecture

The original site was plain HTML/CSS/JS written by the backend developers. I rebuilt the frontend as a responsive Next.js application backed by the team’s Node.js services, with a GitHub CI/CD pipeline, designed mobile-first: the front camera of a phone is the primary capture device.

The search itself works on facial geometry: the browser captures a short live camera sequence, the backend’s in-house service converts the face into a vector representation, and that vector, not the image, is what gets matched against indexed media. Every captured image is discarded immediately after vectorization. Results arrive by email: an official notification first, and if there are matches, links follow from an anonymous address to short-lived external viewing pages, so the platform itself never serves the material. If the person confirms it’s them, the case is handed to a partner law firm that issues a formal takedown demand. The decisions behind it are logged below, each with the alternative it beat.

Outcomes

The frontend went from a static hand-rolled site to a responsive, mobile-first Next.js application with a misuse-resistant capture flow, zero image retention, and lawyer-reviewed GDPR compliance, built in evenings-and-weekends volunteer time.

The limits are worth stating plainly: the rebuilt platform had not gone live by the time I left; other parts of the system still needed work, and my new employment contract ruled out side work during the probation period. The project has since relaunched under the Digital Dignity name at digitaldignity.io, with the finder now available worldwide. I can only speak for the frontend as it stood when I handed it over; what shipped after that is the team’s work, not mine.

Lessons

Two things stay with me. First, the anti-abuse thinking: the most interesting frontend problems here weren’t visual, they were adversarial: every input path had to be examined for how a bad actor could turn a victim-support tool against someone else. Closing the native-camera gallery bypass is a tiny detail no feature list would ever show, and exactly the kind that matters.

Second, the stack: working deeply with React and Next.js is what settled my framework preferences. I found its fast-moving ecosystem and competing conventions harder to build in than Vue’s, and for a project I own I’d still reach for Vue first, a conclusion I could only reach honestly by building something real with React.

Key decisions

  1. 01 Capture: embedded live camera only

    An in-page camera interface with live capture.

    Both would let someone submit a downloaded photo of another person; the embedded interface is the only path that keeps the search self-only.

    On mobile, invoking the native camera also offers the gallery picker, which is exactly the bypass we had to close. The capture flow also prompts the person to change their expression (open and close their mouth or eyes), so the backend's surface-geometry checks can tell a live face from a photo of a photo.

    rejected: File upload · The device's native camera app

  2. 02 Zero image retention

    Discarding every captured image immediately after vectorization.

    A store of faces would be a legal and cost liability no privacy-focused nonprofit should carry; repeating the liveness check each visit is the acceptable price.

    Lawyers reviewed the app's GDPR compliance; the discard-immediately design is what makes the rest of the flow defensible.

    rejected: Storing captures so returning users could skip re-verification

  3. 03 Result delivery

    Email notification, with anonymous-address links to short-lived external viewing pages.

    EU rules treat displaying the found material as distributing adult content, so the platform cannot legally show what it finds on its own domain.

    The official address delivers the notice; the links to the ephemeral viewing pages arrive separately from an anonymous address.

    rejected: Showing matches directly in the app

  4. 04 Stack: rebuild on Next.js

    Rebuilding the hand-rolled HTML/CSS/JS site as a Next.js app.

    The team wanted something more accessible, powerful, and secure than the original, and the framework was a deliberate team decision, which for me doubled as a chance to learn a stack I hadn't used.

    rejected: Continuing to patch the original static site

Key figures

Key figures (self-reported)
MetricValueStatus
Face images stored0reported

Reported: By design: every capture is discarded right after vectorization; GDPR compliance reviewed by the project's lawyers