Manav · the signature layer for high-risk actions

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

  1. Your app asks: "Can a human approve this?"
  2. Manav opens a popup. The user's device prompts for Touch ID / Windows Hello.
  3. Touch the sensor. The device signs a challenge with its passkey.
  4. Manav verifies the signature server-side, then signs the canonical proof JSON with HMAC-SHA256.
  5. You get back a proof URL: https://manav.id/proof/mnav_proof_demo_msa
  6. 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
WebAuthn / FIDO2 HMAC-SHA256 server proof Audit log on every mutation No biometric storage Open SDK · 4 KB

03What's in the box

The wedge: human signature

The flagship surface and the only thing on the homepage. Three pages.

URLWhat
/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):

  1. CBOR decode the attestation/assertion object the browser hands us.
  2. Extract the COSE-encoded public key.
  3. Reconstruct an SPKI DER blob and pass it to OpenSSL as a PEM.
  4. Verify the signature over authenticatorData ‖ SHA256(clientDataJSON).
  5. Check that clientDataJSON.origin matches our origin.
  6. Check that authenticatorData.rpIdHash matches SHA256(WEBAUTHN_RP_ID).
  7. 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 under manav/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_DEPTH at 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 using openssl_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)

MethodURLPurpose
GET/manav/api/health-sign/Health check; reports storage backend, DBID, demo mode, RP ID
POST/manav/api/health-sign/?action=seedSeed 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

MethodURLPurpose
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

MethodURLPurpose
POST/manav/live/start.phpOpen a live session
POST/manav/live/heartbeat.phpPeriodic presence ping (15s default)
POST/manav/live/challenge.phpIssue a CAPTCHA-style challenge
POST/manav/live/verify.phpSubmit challenge response
POST/manav/live/score.phpRead current trust score
POST/manav/live/event.phpLog a behavior event
GET/manav/live/status.phpModule health

07Demos to run

Open these in order to see the full surface in 5 minutes.

Sign-with-Manav demos (the wedge)

  1. manav.id/manav - homepage. Click the inline "Sign with Manav" button. You'll get a real proof URL.
  2. manav.id/manav/sign/ - the standalone signing page.
  3. manav.id/manav/proof/?slug=mnav_proof_demo_msa - the public proof page. Copy the openssl line and recompute the HMAC yourself.
  4. manav.id/manav/demo/docusign/ - wrapped around a DocuSign-style flow.
  5. manav.id/manav/demo/admin-wire-transfer/ - wire approval.
  6. manav.id/manav/demo/slack-admin/ - admin role grant.

Presence Guard demos

  1. manav.id/manav/dashboard/ - enterprise dashboard.
  2. /demo/ghost-worker/ - mid-session person swap detected.
  3. /demo/parallel-jobs/ - same identity active in two cities at once.
  4. /demo/fake-commit/ - code commit with no human session.
  5. /demo/ai-worker/ - 4,200 chars in 2 seconds, no human entropy.
  6. /demo/enterprise-dashboard/ - manager view.
  7. /demo/worker-timeline/ - auditor view.
  8. /demo/proof-link/ - what the public sees.

Live Presence demos

  1. manav.id/manav/live/ - the standalone module landing.
  2. /live/test/exam.php - exam proctoring use-case.
  3. /live/test/worker.php - diagnostic harness.

↑ what every proof page looks like

08Security posture

ConcernPosition
AuthenticationWebAuthn / FIDO2 only on the signing path. No password login is ever offered.
Replay attacksEvery challenge expires in ≤ 5 minutes, is single-use, bound to user + RP ID.
Cross-originclientDataJSON.origin must match the configured origin exactly.
RP-ID spoofingauthenticatorData.rpIdHash is checked against SHA256(WEBAUTHN_RP_ID).
Server-secret leakMANAV_SERVER_SIGNING_SECRET lives in /config.php (gitignored, host-only). Rotation invalidates old proofs by design - plan a key-rotation cadence.
SQL injectionImpossible: no raw SQL strings. All DB traffic goes through OpsDB with an allowlisted table set.
XSSEvery output is escaped via h(). No template engine.
Cookie hijackingAll cookies are HttpOnly, Secure on HTTPS, SameSite=Lax.
Insecure transportLocal dev runs on http://localhost; production must run on HTTPS or WebAuthn refuses.
Audit-log gapEvery 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

WhenWhat
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 daysReal OpsDB-backed seed on the live host. WEBAUTHN_RP_ID = manav.id configured. SDK NPM package. Slack and DocuSign first-party connectors.
Next 90 daysMulti-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.
BeyondThreshold 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.