Sign means a human did this. We make that provable.
A WebAuthn-backed human signature, an HMAC-signed server proof, and a public URL anyone can verify - wired into your contracts, wires, and admin flows in three lines of JavaScript.
01The promise
Every day, an enterprise approves contracts, releases wires, grants admin access, and merges code. A human is supposed to be in the loop on each of those decisions. The current evidence that a human approved them is one of three things:
- A login session - but sessions get hijacked, shared, or paged through SSO without a fresh user gesture.
- A typed name in a DocuSign field - anyone with the link can type any name.
- An email "approved" reply - emails get spoofed, forwarded, replied-from-phone-while-distracted.
Manav replaces all three with a single artifact: a human signature. A real person, on a known device, deliberately approved this exact action - and you have a public URL anyone can verify, with one openssl command, that we didn't fabricate it.
The product is the URL. Everything else is plumbing.
02How it works in 30 seconds
- Your app asks: "Can a human approve this?"
- Manav opens a popup. The user's device prompts for Touch ID / Windows Hello.
- Touch the sensor. The device signs a challenge with its passkey.
- Manav verifies the signature server-side, then signs the canonical proof JSON with HMAC-SHA256.
- You get back a proof URL:
https://manav.id/proof/mnav_proof_demo_msa - Ship that URL with the artifact (PDF, wire receipt, audit log, commit message). Anyone, anywhere, anytime can verify it.
Three lines of JavaScript on your side
// Drop in once <script src="https://manav.id/manav/sdk/manav-sdk.js"></script> // In your wire-approval flow const proof = await Manav.sign({ apiKey: "mnav_live_…", actionType: "wire_transfer", actionTitle: "Approve $42,000 wire to GreenLeaf Designs", payload: { amountUsd: 42000, beneficiary: "GreenLeaf" } }); // proof.url → https://manav.id/proof/<slug> // proof.status === "verified" → release the wire
03What's in the box
The wedge: human signature
The flagship surface and the only thing on the homepage. Three pages.
| URL | What |
|---|---|
/manav/ | One-promise landing with an inline demo that produces a real proof URL |
/manav/sign/ | The signing page. Action card, signer + privacy panel, WebAuthn flow with a "Skip - demo mode" button |
/manav/proof/?slug=… | The public proof page. Hero status, action + signer cards, canonical JSON, HMAC, paste-able openssl one-liner |
Three integration demos showing what it looks like wrapped around real-world flows:
Demo · DocuSign
/demo/docusign/ - contract signing flow.
Demo · Wire
/demo/admin-wire-transfer/ - wire-approval modal.
Demo · Slack admin
/demo/slack-admin/ - granting workspace owner role.
Supporting modules
The platform also ships two smaller surfaces that share the same engine but solve adjacent problems. They live in the footer, not the top nav.
Presence Guard
/manav/dashboard/ - workforce trust dashboard. Anomaly feed, recent work proofs, signatures-today, audit log. For managers and compliance teams. Backed by the same WebAuthn + signature stack.
Live Presence
/manav/live/ - embeddable JS widget that proves a real human is live on the page. Heartbeats, focus tracking, random challenges, score. Three integration modes: embedded, iframe, second-tab.
04The cryptographic story
This is what gets the engineering team and security review on side.
What the device signs
When the user touches the sensor, the device (not Manav, not the browser) signs a WebAuthn challenge using the private half of its platform passkey (Touch ID, Windows Hello, FIDO2 hardware key). The private key never leaves the secure enclave / TPM. Manav only stores the public half. We use ES256 (ECDSA over the P-256 curve), which is universally supported by platform authenticators and yields a ~70-byte signature.
What Manav verifies
Server-side, in pure PHP (no Composer dependencies):
- CBOR decode the attestation/assertion object the browser hands us.
- Extract the COSE-encoded public key.
- Reconstruct an SPKI DER blob and pass it to OpenSSL as a PEM.
- Verify the signature over
authenticatorData ‖ SHA256(clientDataJSON). - Check that
clientDataJSON.originmatches our origin. - Check that
authenticatorData.rpIdHashmatchesSHA256(WEBAUTHN_RP_ID). - Reject the challenge if it's older than 5 minutes or already used.
If any check fails, the signature is rejected. There is no "soft" path - the assertion either passes the math or it doesn't.
The server proof
Once the WebAuthn assertion verifies, Manav builds the canonical proof JSON - same fields, sorted alphabetically, no whitespace, no escapes:
{ "actionPayloadHash": "5f3c2…", "actionTitle": "Sign Master Services Agreement (Q2 2026)", "actionType": "contract_signature", "actorHandle": "asha", "externalReference": "docusign_demo_msa_001", "organizationSlug": "acme-ai", "signatureSlug": "mnav_sig_…", "signedAt": "2026-05-05T14:23:00Z" }
We hash this with HMAC-SHA256 keyed by MANAV_SERVER_SIGNING_SECRET (a server-only secret, rotatable, never shipped to the client). That hex digest becomes the server proof signature - Manav's stamp that says "we observed this human signature."
Verifying a proof yourself
Pull a proof URL up on any device. The page shows the canonical JSON and the server signature. You can recompute it with one shell line:
$ printf '%s' '<canonical JSON from the page>' \ | openssl dgst -sha256 -hmac "$MANAV_SERVER_SIGNING_SECRET" -hex
If the digest matches what the page shows, the proof is authentic. No Manav servers required to verify. No vendor lock-in.
What we never store
- ❌ Biometric data (fingerprint template, face vector, iris scan)
- ❌ Raw keystrokes or full text the user typed
- ❌ Browser history or open tabs
- ❌ Continuous webcam or microphone feed
- ❌ Government IDs, SSNs, passport numbers
- ❌ Passwords, PINs, security questions
- ❌ Mouse paths or behavioral fingerprints beyond what's needed for the live presence score
We store the public-key half of the passkey, the action that was signed, the timestamp, and the IP that submitted the assertion. That's it.
05Architecture
Stack
- Server: PHP 8.1+ with no framework, no Composer dependencies, no build step.
- Database: MySQL accessed exclusively through OpsDB - a centralized HTTP proxy. The app holds no MySQL password; it auths to OpsDB with
OPSDB_KEY, OpsDB owns the connection. - Storage fallback: When OpsDB is unreachable the same
sign_*helpers fall back to JSON files undermanav/cache/sign/. Identical API, transparent swap. - Routing: Physical-file routing only. Every URL is a real folder with its own
index.php. No.htaccess, no nginx rewrites, no router. - URLs: All links are relative, computed from
APP_DEPTHat bootstrap. The whole app is mount-agnostic - drop the folder anywhere and it works. - Frontend: Plain JS, no framework, no bundler. Pure CSS with a single design-token system.
- WebAuthn: Pure-PHP implementation in
include/sign/{cbor,webauthn,crypto}.php. ~600 lines total. Verifies ES256 / P-256 attestations and assertions usingopenssl_verify().
Three-layer config cascade
Layer 1 · /manav/config.defaults.php ← framework defaults (committed) Layer 2 · /manav/config.php ← app overrides (committed, optional) Layer 3 · /config.php (project root) ← host secrets (gitignored, must win)
bootstrap.php loads them in reverse - Layer 3 first with bare define(), then Layer 2 and Layer 1 with defined() or define(). Once a constant is set in Layer 3, the lower layers' guards see it as already defined and skip - so Layer 3 always wins. Rotating MANAV_SERVER_SIGNING_SECRET is editing one file and reloading PHP-FPM.
The 14-table schema
users - handles, display names, email organizations - tenant grouping organization_members - N:N user↔org with role webauthn_credentials - public keys, sign counters, transports webauthn_challenges - short-lived nonces (≤ 5 min) human_sessions - Presence Guard verified sessions session_heartbeats - periodic presence pings human_signatures - the core artifact: who signed what, when work_events - bound artifacts (commits, docs, …) anomalies - detection-engine output proofs - public-shareable rows + server signature api_keys - per-org, hashed-and-salted, prefix-indexed audit_logs - append-only mutation log
Every mutation writes an audit_logs row. The audit table is the only thing Manav cares about more than the signatures themselves.
File map
manav/
├── index.php Landing
├── sign/index.php Sign with Manav
├── proof/index.php Public proof page
├── dashboard/index.php Enterprise dashboard
├── start/, session/, worker/ Presence Guard surfaces
├── demo/<scenario>/index.php Walkthroughs (10 total)
├── api/
│ ├── webauthn/{register,auth}/{start,finish}/index.php
│ ├── signatures/{create,verify}/index.php
│ ├── proofs/lookup/index.php
│ ├── sdk/sign/index.php
│ ├── dashboard/index.php
│ └── health-sign/index.php
├── live/ Live Presence module
│ ├── widget/{live.js, live.css, iframe.php}
│ ├── start.php heartbeat.php challenge.php verify.php
│ ├── score.php event.php status.php
│ └── test/{exam,worker}.php
├── sdk/manav-sdk.js 4 KB drop-in JS SDK
├── include/
│ ├── bootstrap.php Cascade + URL helpers + logging
│ ├── db_helper.php OpsDB HTTP wrapper
│ └── sign/
│ ├── store.php OpsDB-or-file abstraction
│ ├── crypto.php Canonical JSON, SHA-256, HMAC
│ ├── proof.php Create / load / render proofs
│ ├── audit.php audit_log_write()
│ ├── rbac.php Role gates
│ ├── cbor.php CBOR decoder
│ ├── webauthn.php register/auth helpers
│ ├── signatures.php HumanSignature lifecycle
│ └── seeder.php Idempotent demo seed
├── sql/schema.sql 14-table MySQL DDL
├── tests/sign/runner.php Self-contained test harness
├── cdn/css/manav.css Theme (stark, premium)
├── templates/ Page bodies
├── AGENTS.md MVP_SPEC.md SECURITY.md PRIVACY.md README.md
06The API
Every endpoint returns the same envelope:
{ "ok": true, "data": { … }, "meta": [], "error": null, "error_code": null }
{ "ok": false, "data": null, "meta": [], "error": "missing key", "error_code": "missing_key" }
Public endpoints (no auth)
| Method | URL | Purpose |
|---|---|---|
| GET | /manav/api/health-sign/ | Health check; reports storage backend, DBID, demo mode, RP ID |
| POST | /manav/api/health-sign/?action=seed | Seed demo dataset (DEMO_MODE only) |
| GET | /manav/api/proofs/lookup/?slug=… | Look up a public proof by slug |
| GET | /manav/api/dashboard/ | Dashboard summary counts |
Signing endpoints
| Method | URL | Purpose |
|---|---|---|
| POST | /manav/api/webauthn/register/start/ | Begin passkey registration; returns options |
| POST | /manav/api/webauthn/register/finish/ | Verify attestation; persist credential |
| POST | /manav/api/webauthn/auth/start/ | Begin authentication; returns challenge |
| POST | /manav/api/webauthn/auth/finish/ | Verify assertion; create signature + proof |
| POST | /manav/api/signatures/create/ | Create a signature (used by simulator + auth-finish) |
| POST | /manav/api/signatures/verify/ | Verify a signature by slug |
| POST | /manav/api/sdk/sign/ | SDK entry; needs Authorization: Bearer mnav_… |
Live Presence endpoints
| Method | URL | Purpose |
|---|---|---|
| POST | /manav/live/start.php | Open a live session |
| POST | /manav/live/heartbeat.php | Periodic presence ping (15s default) |
| POST | /manav/live/challenge.php | Issue a CAPTCHA-style challenge |
| POST | /manav/live/verify.php | Submit challenge response |
| POST | /manav/live/score.php | Read current trust score |
| POST | /manav/live/event.php | Log a behavior event |
| GET | /manav/live/status.php | Module health |
07Demos to run
Open these in order to see the full surface in 5 minutes.
Sign-with-Manav demos (the wedge)
- manav.id/manav - homepage. Click the inline "Sign with Manav" button. You'll get a real proof URL.
- manav.id/manav/sign/ - the standalone signing page.
- manav.id/manav/proof/?slug=mnav_proof_demo_msa - the public proof page. Copy the openssl line and recompute the HMAC yourself.
- manav.id/manav/demo/docusign/ - wrapped around a DocuSign-style flow.
- manav.id/manav/demo/admin-wire-transfer/ - wire approval.
- manav.id/manav/demo/slack-admin/ - admin role grant.
Presence Guard demos
- manav.id/manav/dashboard/ - enterprise dashboard.
- /demo/ghost-worker/ - mid-session person swap detected.
- /demo/parallel-jobs/ - same identity active in two cities at once.
- /demo/fake-commit/ - code commit with no human session.
- /demo/ai-worker/ - 4,200 chars in 2 seconds, no human entropy.
- /demo/enterprise-dashboard/ - manager view.
- /demo/worker-timeline/ - auditor view.
- /demo/proof-link/ - what the public sees.
Live Presence demos
- manav.id/manav/live/ - the standalone module landing.
- /live/test/exam.php - exam proctoring use-case.
- /live/test/worker.php - diagnostic harness.
↑ what every proof page looks like
08Security posture
| Concern | Position |
|---|---|
| Authentication | WebAuthn / FIDO2 only on the signing path. No password login is ever offered. |
| Replay attacks | Every challenge expires in ≤ 5 minutes, is single-use, bound to user + RP ID. |
| Cross-origin | clientDataJSON.origin must match the configured origin exactly. |
| RP-ID spoofing | authenticatorData.rpIdHash is checked against SHA256(WEBAUTHN_RP_ID). |
| Server-secret leak | MANAV_SERVER_SIGNING_SECRET lives in /config.php (gitignored, host-only). Rotation invalidates old proofs by design - plan a key-rotation cadence. |
| SQL injection | Impossible: no raw SQL strings. All DB traffic goes through OpsDB with an allowlisted table set. |
| XSS | Every output is escaped via h(). No template engine. |
| Cookie hijacking | All cookies are HttpOnly, Secure on HTTPS, SameSite=Lax. |
| Insecure transport | Local dev runs on http://localhost; production must run on HTTPS or WebAuthn refuses. |
| Audit-log gap | Every mutation calls audit_log_write(). The audit table is append-only. |
SECURITY.md in the repo carries the full posture, including incident-response and rotation procedures.
09Privacy posture
The internal product slogan is "privacy is the product." The signature is worthless if anyone has to surrender personal data to use it.
- Data collected: action approved, timestamp, IP that submitted the assertion, public-key half of the passkey.
- Data not collected: anything biometric, anything behavioral beyond the live-presence module's narrow score, anything from the user's device beyond the WebAuthn output.
- Data retention: signatures live until revoked or org-deleted. Audit logs are kept for compliance windows configured per tenant. WebAuthn challenges are deleted after use or expiry.
- Right to erasure: delete a user → cascades to credentials, signatures, proofs (which become "user removed"), and audit logs of the user's own actions.
- Verification without us: the openssl recomputation works without Manav being online. If we go away, every existing proof is still verifiable from the JSON + the secret a customer's incident-response team retained.
PRIVACY.md in the repo is the canonical posture document.
10Deployment
Three things have to be true on the host.
1 · /config.php at the project root (gitignored)
<?php // Layer 3 - host secrets (project root, gitignored) // OpsDB - owns the MySQL connection define('OPSDB_URL', 'http://192.168.1.156/opsdb/opsdb.php'); define('OPSDB_KEY', '<paste-key>'); // Server proof signing - rotate to invalidate old proofs define('MANAV_SERVER_SIGNING_SECRET', '<openssl rand -base64 32>'); // WebAuthn - production values define('WEBAUTHN_RP_ID', 'manav.id'); define('WEBAUTHN_ORIGIN', 'https://manav.id'); define('WEBAUTHN_RP_NAME', 'Manav.id'); // Demo mode - set to false when going live define('DEMO_MODE', true);
2 · Cache + log directories writable
$ mkdir -p /var/www/manav.id/manav/{cache/sign,cache/live,cache/proofs,logs}
$ chown -R www-data:www-data /var/www/manav.id/manav/cache /var/www/manav.id/manav/logs
$ chmod -R 775 /var/www/manav.id/manav/cache /var/www/manav.id/manav/logs
3 · OpsDB schema loaded
Run sql/schema.sql against the OpsDB-controlled manaviddb database. The seeder hits OpsDB through db_helper.php, so once the schema is in place, POST /manav/api/health-sign/?action=seed populates the demo data.
After those three things, https://manav.id/manav/api/health-sign/ should report:
storage: opsdb dbid: manaviddb opsdb_url_set: true storage_ok: true rp_id: manav.id demo_mode: true | false
11Roadmap
| When | What |
|---|---|
| Now (MVP) | Human-signature wedge live with WebAuthn + HMAC. Sign / Proof / Demo surfaces. Presence Guard + Live Presence as supporting surfaces. SDK at 4 KB. |
| Next 30 days | Real OpsDB-backed seed on the live host. WEBAUTHN_RP_ID = manav.id configured. SDK NPM package. Slack and DocuSign first-party connectors. |
| Next 90 days | Multi-org admin console. RBAC fully wired into the dashboard. Per-org API key issuance and rotation UI. Webhook delivery on signature creation. SOC 2 Type I work in flight. |
| Beyond | Threshold signatures (M-of-N humans). Hardware-attested device chain (TPM EK certs). Ecosystem of connectors: GitHub merge gate, Linear status change, Stripe payout approval, AWS root API key request. |
12Why this matters
The next decade has a problem the last decade didn't: AI agents will outnumber humans on the wire. By 2027 most software-mediated decisions will start with an agent and end with one too. Some of those decisions will be fine. Some will be catastrophic - wires, contracts, code merges, credential grants - and there will need to be a way to prove a human, on a known device, deliberately approved them.
Manav is that proof. The cryptography is boring on purpose: WebAuthn that ships in every browser, HMAC-SHA256 that runs on a calculator. The product surface is small on purpose: one URL per signature, one-line to verify. The wedge is narrow on purpose: high-risk actions only, where the cost of being wrong is enormous and the volume is bounded.
If "human signed" becomes a checkbox on enterprise procurement forms - and it will - Manav is the standards-grade default that fills it.
13Quick links
Live URLs
Repo files
manav/AGENTS.mdmanav/MVP_SPEC.mdmanav/SECURITY.mdmanav/PRIVACY.mdmanav-kb.mdmanavkb.html