Behind the Scenes of "Is It Greg?": Building Lightweight, Privacy-First Face Verification for Developer Workflows

We’ve all been there. You’re looking at a Git commit history, a Slack notification, or a pull request approval, and you see a generic avatar or a username like greg_dev_99. You find yourself asking: Is this actually Greg, or has someone hijacked his session? Is this Greg’s automated bot, or is it a malicious actor pushing unauthorized code to production?

This week, a project caught my eye on Hacker News that tackles this exact flavor of developer paranoia in a brilliantly simple, slightly whimsical, but technically fascinating way: "Is It Greg?".

At first glance, it looks like a fun meme project. But as developers, when we see a simple tool that answers a binary question ("Is this person who they say they are?"), our brains immediately start thinking about the underlying architecture. How do you build a lightning-fast, privacy-first, locally executable face verification tool without dragging in gigabytes of heavy machine learning frameworks like TensorFlow or PyTorch? How do you make it secure enough that a developer would actually run it locally to verify identity before running a critical deployment script?

Today, we’re going under the hood to explore how to build a lightweight, privacy-focused face verification system. We’ll look at how to run embedding models in the browser or via lightweight CLI tools, handle vector mathematics for face comparison, and integrate face verification into your developer workflows and CI/CD pipelines.

The Engineering Challenge: Heavy ML vs. Edge Performance

If you ask a traditional data scientist to build a face verification app, they’ll likely spin up a Python environment, import torch or tensorflow, load a pre-trained ResNet model, and run a Flask server. That’s fine for an enterprise cloud API, but for a developer tool, it’s a non-starter. Developers want tools that are:

  • Fast: Sub-second execution times.
  • Lightweight: No 500MB Docker images or heavy Python runtimes just to check an image.
  • Private: Biometric data should never leave the local machine. We shouldn't be sending photos of "Greg" to a third-party SaaS API.

To achieve this, we have to leverage modern web standards and lightweight runtimes: WebAssembly (WASM), ONNX Runtime, and efficient vector embeddings. Instead of running full-scale neural networks on a server, we can run highly optimized, quantized models directly in the user's browser or via a lightweight Node.js/Rust CLI tool.

The Architecture of Local Face Verification

Face verification is not the same as face recognition (which tries to identify a person out of a database of thousands). Verification is a 1:1 comparison: "Is this image (Image A) the same person as this reference image (Image B)?"

The pipeline for local verification looks like this:


[ Input Image ] ---> [ Face Detection (Bounding Box) ] 
                     ---> [ Face Alignment & Cropping ]
                          ---> [ Feature Extraction (Embedding Vector) ]
                               ---> [ Cosine Similarity Comparison ] ---> [ Yes/No Decision ]

Step 1: Face Detection & Alignment

Before we can compare faces, we must find the face in the image. Models like BlazeFace (optimized for mobile/browser) or MTCNN locate the face and identify key landmarks (eyes, nose, mouth). Alignment rotates and scales the image so the eyes are always in the same coordinate space, ensuring the extraction model compares apples to apples.

Step 2: Generating the Embedding

An embedding is a vector (a list of numbers, typically 128 or 512 dimensions) that represents the unique features of a face. Think of it as a cryptographic hash, but instead of being highly sensitive to tiny changes (like SHA-256), it is mathematically "close" to other hashes of the same face. We use lightweight models like MobileFaceNet or FaceNet-compact converted to ONNX format to generate these embeddings in milliseconds.

Step 3: Calculating Similarity

Once we have two vectors, we don't need machine learning anymore. We just need pure, high-performance mathematics. We calculate the Cosine Similarity between the reference vector ($A$) and the target vector ($B$).

Implementing Face Verification in JavaScript/TypeScript

Let's write some actual code to see how we can implement this using modern web tools. We'll use @onnxruntime-web or a highly optimized wrapper like face-api.js (which runs on TensorFlow.js light or WASM backends) to perform the verification locally in a browser environment or a Node.js CLI.

Step 1: Loading the Models and Initializing

First, we need to load our lightweight face detection and recognition models. We can host these static model weights directly on our own server or CDN.


import * as faceapi from 'face-api.js';

// Initialize the models from our public/weights directory
async function bootstrapModels() {
    const MODEL_URL = '/weights';
    
    // Load Tiny Face Detector (highly optimized for speed)
    await faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL);
    // Load Face Landmark model (for alignment)
    await faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL);
    // Load Face Recognition model (for generating 128-dimension embeddings)
    await faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL);
    
    console.log("Models loaded successfully! ready to verify.");
}

Step 2: Extracting Face Descriptors (Embeddings)

Now, let’s write a function that takes an HTML image element (or a canvas/video stream), detects the face, and extracts the 128-float vector embedding.


async function getFaceEmbedding(imageElement: HTMLImageElement): Promise<Float32Array | null> {
    // Detect single face with landmarks and extract descriptor
    const detection = await faceapi
        .detectSingleFace(imageElement, new faceapi.TinyFaceDetectorOptions())
        .withFaceLandmarks()
        .withFaceDescriptor();

    if (!detection) {
        console.warn("No face detected in the provided image.");
        return null;
    }

    // This is our 128-dimensional vector
    return detection.descriptor;
}

Step 3: Comparing the Embeddings (The Math)

To verify if the target face is "Greg", we calculate the Euclidean distance or Cosine similarity between Greg's reference embedding and the new image's embedding. If the distance is below a certain threshold (usually 0.6 for Euclidean distance in 128-D space), we have a match!


function verifyIdentity(referenceEmbedding: Float32Array, targetEmbedding: Float32Array): boolean {
    // face-api.js provides a helper, but under the hood it's Euclidean distance:
    // d = sqrt( sum( (x_i - y_i)^2 ) )
    const distance = faceapi.euclideanDistance(referenceEmbedding, targetEmbedding);
    
    const THRESHOLD = 0.55; // Lower = stricter verification
    console.log(`Euclidean Distance: ${distance.toFixed(4)}`);
    
    return distance < THRESHOLD;
}

Integrating "Is It Greg?" into Git Workflows

Let's take this concept out of the browser and put it where developers spend their time: the terminal. Imagine a pre-commit or pre-push Git hook that uses your webcam to verify that *you* are actually the one signing off on a deployment, preventing session-hijack attacks on local developer machines.

Here is how you could structure a local Bash script (e.g., .git/hooks/pre-push) that triggers a lightweight Node CLI wrapper to verify your identity before pushing code:


#!/bin/bash
# .git/hooks/pre-push

echo "🔒 Verifying developer identity before pushing to production..."

# Capture a quick frame from the webcam using fswebcam or imagesnap
# Save it temporarily
imagesnap -w 1.00 /tmp/current_user.jpg > /dev/null 2>&1

# Run our lightweight verification script
node ./scripts/verify-developer.js --reference ./security/greg_reference.json --input /tmp/current_user.jpg

if [ $? -eq 0 ]; then
    echo "✅ Identity Verified. Proceeding with git push."
    rm /tmp/current_user.jpg
    exit 0
else
    echo "❌ Identity Verification FAILED. Are you sure you are Greg?"
    rm /tmp/current_user.jpg
    exit 1
fi

In this workflow, greg_reference.json doesn't store Greg's actual photo! It only stores the 128-dimensional floating-point array (the face hash). This is incredibly secure: even if your git repository is compromised, attackers cannot reconstruct Greg's face from the 128-dimensional embedding, but they still cannot spoof the git hook without Greg's physical presence at the webcam.

Performance and Security Considerations

Adversarial Attacks & Spoofing

If you're building local face verification for security-sensitive operations, you have to worry about presentation attacks (e.g., someone holding up a photo of Greg to the webcam). To mitigate this, developers use Liveness Detection. This can be implemented by requiring the user to blink, turn their head, or by analyzing the texture/depth of the image using a secondary infrared sensor (like Apple's FaceID) or simple optical flow algorithms in WebGL.

WASM and WebGL Acceleration

Running neural networks on CPU can be sluggish. When deploying these tools in the browser, always ensure your ONNX Runtime or TensorFlow.js build is configured to use WASM with SIMD (Single Instruction, Multiple Data) or WebGL. This bypasses the JavaScript main thread bottleneck and drops execution time from ~1.5 seconds down to under 80 milliseconds.

Conclusion

The "Is It Greg?" project is a great reminder that machine learning doesn't always have to live on massive GPU clusters in the cloud. By leveraging modern optimizations, we can bring face verification, classification, and natural language processing directly into lightweight local tools, browser extensions, and developer CLI utilities.

By shifting processing to the client side, we get instantaneous feedback, zero cloud infrastructure costs, and absolute privacy for our users' biometric data. It’s a win-win-win for developer experience and security engineering.

What do you think? Would you trust a local webcam face-verification step in your CI/CD deployment pipeline, or are you sticklers for traditional YubiKeys and hardware MFA? Let’s chat in the comments below!

Keep coding, keep building, and make sure it's actually Greg. — Alex

Post a Comment

Previous Post Next Post