Human Seal

Developers

Implement and verify the Human Seal

Built entirely on proven standards: Ed25519 signatures (RFC 8032), sha256 content binding (FIPS 180-4), and the W3C did:web trust root. Nothing here is a new cryptographic primitive.

The receipt

A Human Seal is a small JSON object. The fields below are signed. The standard says personal data is referenced by a stable identifier and never embedded. The reference issuer does this: approver_ref is hsa_ followed by 32 hex characters, a one-way hash of the person's internal id, so a public receipt carries no name or email and the issuer can still recompute it. Seals issued before that change (a small number, all test or internal) carry an email address or a service name instead.

FieldMeaning
action_idStable identifier of the approved action.
action_typeThe class of action (used for the authority check).
content_hashsha256: of the exact canonical action. Binds the Seal to that action.
tenantThe organization whose model the approval was checked against.
approver_refStable identity reference of the approver. An opaque identifier, never personal data. The reference issuer uses hsa_ plus 32 hex characters (see above).
approver_typeOptional, set by the issuer: human_session when the approver was a signed-in person. Added by the reference issuer after v0.1 and not yet part of the standard.
decisionapproved or refused.
decided_atUTC timestamp (ISO 8601).
issuerThe did:web trust root, e.g. did:web:humanseal.world.
levelThe assurance level (2 = Signed).
oversightOptional (Level 2+): the decision conditions (context hash, decision latency, pre-AI decision, risk tier, rationale), so oversight quality is provable.
signature_ed25519Ed25519 signature over the canonical core. Excluded from the signed bytes.
public_keyThe verification key, base64url. Confirmable against the did document.

The verification algorithm

Small on purpose, so anyone can reimplement it. If any step fails, the Seal is invalid, and it fails in a way that names the reason.

  1. Remove signature_ed25519 and public_key; the rest is the signed core.
  2. Canonicalize the core: keys sorted by code point, no whitespace, non-ASCII escaped as lowercase \uXXXX, numbers exactly as written (see the conformance page).
  3. Verify the Ed25519 signature over those bytes against public_key.
  4. Independently resolve the key from the did document (did:web to https to /.well-known/did.json) and confirm it matches.
  5. If you have the action, recompute sha256 of its canonical form and confirm it equals content_hash.

Verify in the browser (client side)

Use the same library the verifier page runs. It parses receipts losslessly and canonicalizes exactly as the reference issuer does.

<script src="https://humanseal.world/assets/hs-verify.js"></script>
const r = await HSVerify.verifyReceipt(receiptJsonText, actionJsonText /* optional */);
// r = { valid: true, approver_ref, decision, ... }  or  { valid: false, reason: "..." }

The exact canonical rules, the test vectors that any implementation must reproduce, and a standalone Python verifier are on the conformance page.

Trust root and interoperability

After a Seal is issued

The endpoints an enforcement point uses. They live on the reference issuer today (fo.flocore.tech).

EndpointWhoWhat
POST /agent/human-seal/issuea signed-in personIssue a Seal. Optional oversight (decision latency, rationale, context hash, ai_model_digest) and valid_for_seconds (a signed valid_until).
POST /agent/human-seal/consumethe component that acts (a token scoped to the tenant)Record that the Seal was used. The first call wins; a second gets 409. Refuses invalid, expired, revoked, and non-person Seals.
POST /agent/human-seal/revokea person with authority over the tenantWithdraw a Seal.
GET /agent/human-seal/revocationsanyoneThe signed list of revoked Seals and revoked issuer keys. Verify its signature, then refuse a listed Seal or a Seal signed by a listed key.
POST /agent/approvalsan agent or service (a token with agent scope for the tenant)Ask a person to approve ONE exact action. Returns a link. Nothing is approved by asking.
GET /agent/approve/<id>a personThe one-click page: sign in with an emailed code, read the whole request and the exact payload, approve or refuse. Approving waits for the tier's review time; refusing never does. The person who asked cannot approve their own request. The result is a Human Seal, verified in the browser. /agent/approve/new lets a person attest their own gate record.
POST /agent/concernany valid tokenReport that something looks wrong. Always recorded and always reaches the owner.

A Seal's identifier for consume and revocation is sha256: plus the SHA-256 of its signature_ed25519 string. The gate library checks expiry, the expected model digest and the revocation list offline; consuming is the one online step.

Ask a person from your agent

The asking side. One call sends a person the exact action, waits, and returns yes only when a person approved and the Seal verified for this action, this tenant and this payload. Everything else is no.

curl -O https://humanseal.world/sdk/hs-ask.js
curl -O https://humanseal.world/sdk/hs-gate.js
curl -O https://humanseal.world/assets/hs-verify.js
# Node 20 or later, the three files in one folder. The token is a service token with agent scope for your tenant.
const { askAndWait } = require('./hs-ask.js');
const r = await askAndWait({
  base: 'https://fo.flocore.tech', token: process.env.FLOCORE_AGENT_TOKEN, tenant: 'your-tenant',
  actionType: 'pay.supplier', title: 'Pay Acme', payload: { amount: '48200', currency: 'ZAR', payee: 'Acme Supplies' },
  onUrl: (url) => console.error('A person must approve this: ' + url),
});
if (!r.allow) throw new Error(r.decision + ': ' + r.reasons.join('; '));   // fail closed
runTheAction(r.payload);

allow is false for a refusal, an expired or timed out request, a stopped agent, a rejected token, a network failure and a Seal that does not fit the action. Nothing in this library lets the agent approve: the person signs in on the hosted approval page and decides there.

Claude Code: approve tool calls before they run

A PreToolUse hook asks a person for each call it is given. It exits 0 with an allow only for an approved, verified call, and exits 2 (which blocks the call) in every other case, including its own errors.

curl -O https://humanseal.world/sdk/humanseal-claude-hook.js
# in .claude/settings.json (the matcher chooses which tool calls need a person)
{ "hooks": { "PreToolUse": [ { "matcher": "Bash|Write|Edit", "hooks": [
  { "type": "command", "command": "node /path/to/humanseal-claude-hook.js", "timeout": 600 } ] } ] } }
# environment: HS_TOKEN, HS_TENANT, optional HS_BASE, HS_TIER, HS_TIMEOUT_SECONDS, HS_OPEN_CMD
What is not here yet. There is no email or phone alert for a new request, so the link has to reach the person: the hook prints it and can open it for you (HS_OPEN_CMD). A high risk request also needs the person to open every evidence panel and, when it carries an amount, pick the amount. A person can stop your agent from the approval page; a stopped agent cannot ask again until a person resumes it. A permission prompt tool for Claude Code's non-interactive mode is not offered, because its exact contract is not documented.

Use it from an agent (MCP)

Two read-only tools that verify a Seal offline: no network, no keys, nothing written. Issuing a Seal is deliberately not offered, because only a signed-in person can issue one.

curl -O https://humanseal.world/sdk/humanseal-mcp.js
curl -O https://humanseal.world/sdk/hs-gate.js
curl -O https://humanseal.world/assets/hs-verify.js
# keep the three files in one folder, then add to your MCP client:
{ "mcpServers": { "humanseal": { "command": "node", "args": ["/path/to/humanseal-mcp.js"] } } }
Where to call it. Call the check from the component that runs the action, on the payload it is really about to run, or to check a receipt another agent sends you. Do not let an agent check the Seal for its own action: an agent that both judges and executes can show one action and run another. The offline check confirms a receipt is authentic and binds the action. For single use and revocation, call the consume step and check the signed revocation list (see Lifecycle above). Node 20 or later.