Skip to content
Guide · Device trust

Enroll only your devices

Let only yourmachines reach your admin console, source, or customer data. Root Herald gives every device a stable, hardware-bound identity that can't be cloned, exported, or moved to another machine. What it deliberately does not do is decide which devices are yours. You anchor that with a trusted enrollment moment, and this guide shows the pattern end to end.

The split: we prove the identity, you own the enrollment.

Attestation can't tell a company laptop from a personal one: both have a TPM, Secure Boot, and factory keys. What makes “only your devices” true is trust on first use, gated by a secret you control: you hand a one-time enrollment token to a sanctioned device through a channel you already trust, and on its first passing attestation you record its hardware identity as enrolled. Your own service can hold the list; Root Herald just returns the hardware-bound, practically-unforgeable id.

1 · Cut a short-lived enrollment token (your side)

During onboarding, either behind your own auth or pushed to issued laptops by your MDM, mint a one-time, expiring secret. Root Herald never sees it; storing a hash is enough.

your backend — issue a one-time enrollment secretts
const enrollToken = crypto.randomUUID();
await db.enrollTokens.insert({
  tokenHash: sha256(enrollToken),        // store the hash, never the raw value
  expiresAt: Date.now() + 10 * 60_000,   // short-lived: 10 minutes
  used: false,
});
// Deliver enrollToken to the sanctioned device ONLY — via MDM, or an
// authenticated onboarding link. This delivery channel is your trust root.

2 · Appraise the device, check the token, persist the identity

During that gated onboarding the dumb collector posts an opaque evidence blob (no keys, no Root Herald contact) back to your backend, alongside your enrollment secret. Your server runs the Background-Check ceremony (mint a challenge, then rh.verify() the evidence) and requires both: a passing appraisal that meets your bar, and a valid, unused enrollment token. Then it records verdict.device.ueid as enrolled and burns the secret so it can never enroll a second machine: classic trust-on-first-use. The verdict comes back directly from rh.verify(); there is no client-supplied token to verify.

your backend — POST /onboard { challengeId, evidence, enrollToken }ts
import { RootHerald } from "@rootherald/node";

const rh = new RootHerald({ secretKey: process.env.RH_SECRET_KEY }); // rh_sk_…

// (0) GET /onboard/challenge — mint a nonce and relay it to the device.
app.get("/onboard/challenge", async (_req, res) => {
  const { challengeId, nonce } = await rh.issueChallenge();
  res.json({ challengeId, nonce });
});

app.post("/onboard", async (req, res) => {
  const { challengeId, evidence, enrollToken } = req.body;

  // (a) appraise server→server; the verdict must clear your posture bar
  const verdict = await rh.verify(evidence, {
    challengeId,
    policy: "rootherald:builtin:strict-hardware", // require strong posture at enroll
  });
  if (verdict.device.verdict !== "pass") return res.status(403).end();

  // (b) the device must have presented a valid, unused enrollment secret
  const row = await db.enrollTokens.findUnused(sha256(enrollToken));
  if (!row || row.expiresAt < Date.now()) return res.status(403).end();

  // (c) trust on first use: record the hardware identity, burn the secret
  await db.enrolledDevices.upsert({ ueid: verdict.device.ueid, enrolledAt: Date.now() });
  await db.enrollTokens.markUsed(row.id);   // one-time: never enrolls a 2nd device
  res.json({ enrolled: true });
});

3 · Gate access to the enrolled set

From then on, no enrollment secret is needed again. Every sensitive request runs a fresh appraisal, reads the stable ueidstraight from the verdict, and checks it's one you enrolled. Because the identity is chip-bound, the evidence can't be replayed from another machine, and a device you never enrolled simply isn't on the list.

your backend — gate every sensitive requestts
const { challengeId, nonce } = await rh.issueChallenge();
// ...relay nonce to the device, get back its opaque evidence...

const verdict = await rh.verify(evidence, {
  challengeId,
  policy: "rootherald:builtin:strict-hardware", // re-require posture each time
});
if (verdict.device.verdict !== "pass") return res.status(403).end();
if (!(await db.enrolledDevices.has(verdict.device.ueid))) {
  return res.status(403).end(); // attesting live, but not one of your devices
}
// Enrolled + attesting live → let it through.
What this proves — and what it doesn't.

This gives you a strong, honest guarantee: exactly one device enrolls per token, it held a valid secret you issued, and it passed attestation, and that device's identity can't later be copied to a personal laptop. It does notmean the device is “clean” or that we vetted that it's company-owned: the security of the enrollment moment is your delivery channel. Deliver the secret only to devices you sanction.

Don't want to run the secret yourself?

Root Herald already ships a one-shot, expiring linkTokenthat binds a passing attestation to a context, if you'd rather we hold the binding than build your own. And fully-managed enrollment, where Root Herald mints, burns, quota-limits, and audits the enrollment keys for you and signs an “authorized to enroll” claim right into the verdict, is a planned paid Device Trust feature, not yet available. The pattern above works today with nothing but the server→server rh.verify()call.