Skip to content
ClientsIn development

Browser

A page cannot reach the TPM. The Root Herald extension bridges it to a native messaging host installed once on the machine; the page holds no key and never sees a verdict.

page.tsts
import {
  respond, getClientStatus,
  ExtensionMissingError, HostMissingError, NotEnrolledError, AskUnsupportedError, TimeoutError,
} from "@rootherald/browser";

async function verifyThisDevice() {
  const { challengeId, challenge } = await post("/api/challenge", {});   // your backend: rh.issueChallenge()

  let evidence;
  try {
    ({ evidence } = await respond(challenge));          // fresh quote over the nonce; no prompt on a known device
  } catch (err) {
    if (err instanceof ExtensionMissingError) return promptInstallExtension();
    if (err instanceof HostMissingError)      return promptInstallHost();
    if (err instanceof NotEnrolledError)      return enrollThenRetry(challengeId, challenge);
    if (err instanceof AskUnsupportedError)   return askBackendForLess();   // e.g. posture on macOS
    if (err instanceof TimeoutError)          return showRetry();
    throw err;
  }

  return post("/api/verify", { challengeId, evidence });   // your backend: rh.verify()
}

// Feature-detect before you try: extension present, host reachable, app keys supported.
const status = await getClientStatus();

Install

terminalbash
npm install @rootherald/browser

The user installs the extension and the native host once; the host carries the one “Establish hardware key” elevation enrollment needs on Windows. Until the packages are in the stores, /try is the reference install and runs the whole chain on real hardware.

The server half

Identical for every client: issueChallenge, relay challenge, verify, and the two enroll relays on first contact. It is written out once in the quickstart.

What the page can and cannot do

It answers the challenge, bootstraps the device key on first use, and signs with an app key. It never mints tokens, never holds rh_sk_, and never renders a verdict.