DEV Community

Cover image for I built a verifiable "human-made" badge without collecting a single file
Camille Guerineau
Camille Guerineau

Posted on

I built a verifiable "human-made" badge without collecting a single file

I spent ten years as a jewelry maker before going back to full-stack development, so I care about this one personally. I wanted a badge that means something: someone actually looked at how the work was made, the badge can be checked by anyone in one click, and it can be revoked if it turns out to be false.

That project is Verumano. Here is how it works under the hood, and the trade-offs I made.

The constraint that shaped everything: no files

The obvious design is "upload your work, we analyse it". I refused that for three reasons:

  1. Creators don't want to hand their unpublished work to yet another platform.
  2. Storing files means storage, security and GDPR headaches.
  3. AI detectors on the final file are unreliable. They produce false positives on real human work.

So Verumano reviews the process (drafts, sketches, timelapses, version history, raw files) shown by the creator through links, and it never keeps the work itself. What it stores is a public record: who, which discipline, which status, since when.

Architecture: boring on purpose

  • PHP 8 + Apache, shared hosting
  • The registry is a JSON file, written with flock() to avoid concurrent write corruption
  • The data folder is outside the web root logic and protected with Require all denied
  • No framework, no database, no build step

For a registry with hundreds (not millions) of entries, this is fast, cheap and easy to back up. A database can come later if it is ever needed.

Badges are rendered, not downloaded

The badge a creator embeds is not a static image. It is an SVG generated on every request from the registry:

<a href="https://lochness-paris.com/verumano/verify/VM-2026-0001">
  <img src="https://lochness-paris.com/verumano/badges/badge-illustration-reviewed-light-0001.svg"
       alt="Verumano: human-made illustration, process reviewed" width="380" height="112">
</a>
Enter fullscreen mode Exit fullscreen mode

Because the server draws it, the status is always current:

Status Colour Meaning
Reviewed green #1E6B45 the creation process was examined
Declared orange #FF8C1A the creator declares it, not reviewed
Revoked red #A3261B no longer valid, shown crossed out

A few details that matter:

  • Status is never colour-only. Each state has its own text and icon, for colour-blind readers.
  • Cache of 1 hour (Cache-Control: public, max-age=3600). A revocation shows everywhere within the hour, without hammering the server.
  • An unlisted discipline returns 404. You can't request badge-music-reviewed-light-0001.svg if you were reviewed for illustration only.
  • The badge links to the public verification page, which is the real proof. The image is just the door.

Detecting copied badges (and why it's only partial)

When a badge is loaded, the server can read the Referer header and compare it with the domains declared by the creator. If a badge shows up on a site that isn't on the list, it gets logged for review.

This is deliberately not used to block automatically. Modern browsers send strict-origin-when-cross-origin by default, many sites send no-referrer, and privacy extensions strip it. So the rule is simple:

No information = no sanction.

The referer is a signal for a human to look at, never a verdict.

Certifying the exact file, without uploading it

Creators can attach a fingerprint of a specific file to their record: a SHA-256 hash, computed in the browser. The file never leaves the computer, only 64 hex characters do.

crypto.subtle.digest() needs the whole file in memory, which is a problem for a 2 GB video. So the file is read in 4 MB chunks and fed to an incremental SHA-256. Simplified:

async function hashFile(file, onProgress) {
  const CHUNK = 4 * 1024 * 1024;           // 4 MB
  const hasher = createSha256();           // incremental SHA-256
  for (let offset = 0; offset < file.size; offset += CHUNK) {
    const buf = await file.slice(offset, offset + CHUNK).arrayBuffer();
    hasher.update(new Uint8Array(buf));
    onProgress?.(Math.min(1, (offset + CHUNK) / file.size));
  }
  return hasher.hex();                     // "9f2c…"
}
Enter fullscreen mode Exit fullscreen mode

Anyone can later drop the same file on the verify page and get "this exact file is registered" or "not found".

The honest limit: a hash identifies bytes, not an image. Re-export, crop or recompress the file, and the hash changes. That's fine for originals, masters and deliverables. It's not a tool to track copies across social networks.

A read-only public API

Platforms, contests and juries need to check entries in bulk, so there is a small read-only API:

# Check a creator record
curl https://lochness-paris.com/verumano/api/v1/creators/VM-2026-0001

# Check whether an exact file is registered
curl "https://lochness-paris.com/verumano/api/v1/works?sha256=9f2c...e41a"
Enter fullscreen mode Exit fullscreen mode
{
  "api_version": "1",
  "creator": {
    "id": "VM-2026-0001",
    "status": "reviewed",
    "demo": true,
    "name": "Nora Lindqvist (demo)",
    "disciplines": ["illustration"],
    "scope": "Example: a series of ink and watercolor illustrations",
    "reviewed_on": "2026-09-15",
    "method": "Live video walkthrough",
    "url": "https://lochness-paris.com/verumano/verify/VM-2026-0001"
  }
}
Enter fullscreen mode Exit fullscreen mode

(Trimmed: the real response also lists the evidence examined, the certified works and the charter version.)

Design choices:

  • CORS open, GET only (405 otherwise), clear 400 / 404 / 429 errors
  • Rate limit of 60 requests per minute, keyed on a salted IP hash whose salt rotates daily. No raw IP is stored
  • 5-minute cache on responses
  • No personal data beyond what is already public on the verification page

On top of it there is a small jury tool: paste a CSV of entries (name, record number, social link), and it checks each one against the registry, including whether the social profile actually matches the one declared. A first version accepted any Instagram URL. Comparing the full profile path fixed that.

Try it in 30 seconds: the npm package

Calling the API by hand is fine, but nobody wants to re-implement chunked hashing. So there is a small client on npm, with zero dependencies, for browsers and Node.js 18+:

npm i verumano
Enter fullscreen mode Exit fullscreen mode
import { getCreator, checkFile } from 'verumano';

// Is this creator registered, and what is their status?
const record = await getCreator('VM-2026-0001');
console.log(record?.creator.status); // "reviewed"

// Is this exact file certified? Hashed locally, never uploaded.
const result = await checkFile(file); // a File in the browser, a path in Node
console.log(result.matches.length ? result.matches[0].status : 'not registered');
Enter fullscreen mode Exit fullscreen mode

And if you just want to show a live badge on a page, there is a web component, no build step needed:

<script type="module" src="https://cdn.jsdelivr.net/npm/verumano@0.1/badge.js"></script>
<verumano-badge creator="VM-2026-0001" discipline="illustration"></verumano-badge>
Enter fullscreen mode Exit fullscreen mode

The badge image is still rendered by the server at display time, so a revoked badge turns red everywhere within the hour. The package is published from GitHub Actions with npm trusted publishing and provenance, so you can check exactly which commit it was built from.

Want to try it without installing anything? Here is a live demo, running the package against the real API:

(Open the demo in StackBlitz if the embed does not load.)

An open specification

A label only becomes a standard if others can read the rules and implement them. So the rules are public:

Docs are CC BY 4.0, code is MIT. The server code itself isn't open yet. The review process is still in a pilot phase, and I'd rather stabilise it before inviting forks of the registry.

What it does not claim

I think this part matters as much as the code:

  • It is not proof that no AI was used. Nothing can prove that. It certifies that a human creation process was shown and examined.
  • A process can be staged. The review raises the cost of lying, it doesn't make lying impossible. That's also why revocation exists.
  • During the pilot, reviews are done by one person (me). More reviewers will come with the specification.

Try it

If you find it useful, a ⭐ on the GitHub repo helps a lot.

I'd really like feedback from developers here, especially on two points: how would you make badge copying harder without tracking people, and what would make you trust (or ignore) a label like this on a portfolio?

Top comments (0)