Acceptance policies, TPM classes & assurance tiers
You decide which kinds of device you trust and how strongly the user must be authenticated. That decision is a named acceptance policy: pick a built-in, or author your own. The default is conservative: hardware-rooted TPMs only.
TPM classes: what kind of device is this?
Every enrolled device is classified into one root-of-trust class, derived from its hardware key's certificate (the EK cert issuer + the TCG manufacturer identifier) or, for mobile, from the platform attestation. Classes roll up into groups you author policy against:
| Group | Classes | What the cert proves |
|---|---|---|
hardware | hardware-discrete-infineon · -st-micro · -nuvoton · -other | Discrete silicon, signed by the TPM vendor's PKI. Highest. |
firmware-tpm | firmware-tpm-intel-ptt · -amd-ftpm · -pluton | Firmware TPM with a manufacturer chain (Intel PTT, AMD fTPM, Microsoft Pluton). High. |
cloud-vtpm | cloud-vtpm-aws-nitro · -azure · -gcp · -hyper-v | A virtual TPM in a VM under that cloud account, not a physical chip. Medium; rejected by default. |
emulated | emulated-swtpm | A software TPM emulator. No external trust anchor. Rejected by default. |
mobile-hardware | mobile-android-strongbox · -tee · mobile-apple-secure-enclave · mobile-apple-app-attest | Hardware-rooted mobile attestation (StrongBox / TEE / Secure Enclave / App Attest). High. |
mobile-software | mobile-android-software | Software-level attestation only. No hardware backing. Rejected by default. |
Tokens carry the device's class as tpm_class (kebab-cased, e.g. hardware-discrete-infineon), so you can branch on it in your own code even when the policy accepted it.
Built-in policies: the easy path
You don't have to author anything. Select a built-in by name from the dashboard, pin it to an API key, or name it per-call as the policy argument to rh.verify(). The appraisal is run against it server→server, so the verdict you get back already reflects it.
| Name | Accepts | Notes |
|---|---|---|
| rootherald:builtin:strict-hardware | hardware, firmware-tpm, mobile-hardware | The default. Requires device:high and full boot-chain verification. Rejects all virtual/emulated TPMs. |
| rootherald:builtin:strict-hardware-permissive-mobile | same, incl. Android TEE & macOS Secure Enclave | No device:high floor. For consumer surfaces where StrongBox isn't universal. |
| rootherald:builtin:cloud-permissive | + cloud-vtpm (cross-validation required) | For B2B customers serving endpoints and cloud workloads. |
| rootherald:builtin:enterprise-managed-only | hardware, firmware-tpm only; device:high | Pair with EK pre-registration: only IT-provisioned devices enrol. Strongest stance against relayed attestations. |
| rootherald:builtin:dev | everything, incl. emulated-swtpm | Development only. Always carries at least a warn verdict. |
Custom policies: the coarse form
Need something between the built-ins? Register a named policy (you get back a UUID you reference afterwards; the policy lives server-side, you don't ship a rule blob in every request). The coarse form is a few fields:
// Authorization: Bearer rh_sk_live_… (Owner role)
{
"name": "My app — accept hardware + Nitro",
"description": "Hardware and firmware TPMs everywhere; AWS NitroTPM for our CI runners.",
"acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware"],
"acceptedSpecificClasses": ["cloud-vtpm-aws-nitro"],
"unacceptedBehavior": "reject", // "reject" | "warn"
"requireCloudCrossValidation": true,
"minAcr": "urn:rootherald:device:high",
"minimumEarStatus": "affirming",
"mode": "strict"
}
// → 201 Created (PolicyDetail):
// {
// "id": "8f3a…",
// "name": "My app — accept hardware + Nitro",
// "isBuiltIn": false,
// "version": 1,
// "acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware"],
// "acceptedSpecificClasses": ["cloud-vtpm-aws-nitro"],
// "unacceptedBehavior": "reject",
// "requireCloudCrossValidation": true,
// "minAcr": "urn:rootherald:device:high",
// "mode": "strict"
// }acceptedClassGroups takes group names (hardware, cloud-vtpm, …); acceptedSpecificClasses pins individual vendor classes (e.g. cloud-vtpm-aws-nitro). Anything outside the accepted set is governed by unacceptedBehavior: "warn" lets out-of-set devices through with a warn verdict and a degraded trustworthiness vector (useful for shadow-banning or review queues), while "reject" blocks them outright.
Assurance tiers (ACR): how strongly is the user authenticated?
A separate axis from TPM class. The acr value says what the authenticationachieved. Root Herald treats a requested tier as a hard requirement: if it can't be satisfied, the attestation does not issue a token at that tier, never a silently-weaker result. A policy may also impose an ACR floor (minAcr).
| URN | Means | NIST AAL |
|---|---|---|
| urn:rootherald:device:any | Attested device, no user identity | — |
| urn:rootherald:device:high | Attested device + verified boot chain + event-log replay | — |
| urn:rootherald:user:1fa | Device + password | AAL1 |
| urn:rootherald:user:2fa | Device + user + any second factor | AAL2 |
| urn:rootherald:user:phr | Device + user + phishing-resistant MFA (passkey) | AAL2+ |
| urn:rootherald:user:phrh | Device + user + hardware-bound phishing-resistant MFA | AAL3 |
| urn:rootherald:user:phrh:fresh | phrh + auth within the last 60 s | AAL3 + freshness |
Higher tiers subsume lower ones. The acr claim in the issued token is the single tier that was actually satisfied, never the preference list, never a partial match.
Cloud vTPMs & cross-validation
If your policy accepts a cloud-vtpm class and requireCloudCrossValidationis on (the default), the device must additionally submit cloud-provider instance-identity evidence so the verifier can bind "this virtual TPM" to "this specific instance, in this account": the defence against relaying a vTPM attestation from a different instance.
The cloud evidence is bundled into the opaque blob the dumb client collects. Your server submits it with rh.verify() (naming a cloud-cross-validating policy) and reads the resulting device verdict returned directly.
cloudEvidence: {
provider: "aws",
nitroAttestationDocument: body.nitroDoc, // CBOR/COSE, from the Nitro hypervisor
ec2InstanceIdentityDocument: body.iid, // from IMDSv2
ec2InstanceIdentitySignature: body.iidSig,
getInstanceTpmEkPubResult: body.ekPub, // from the EC2 GetInstanceTpmEkPub API
}Azure (provider: "azure", IMDS attested document) and GCP (provider: "gcp", GCE instance identity token) follow the same pattern. The verifier checks the cloud doc chains to that cloud's root, the EK matches what was enrolled, and the instance/account/region are what you expect.
cloud-vtpm class and (under cloud-permissive / dev) the attestation proceeds with a warn verdict noting cross-validation was not performed. Under any strict policy, cloud classes are rejected regardless.For server/workload attestation generally, also consider cloud-native primitives (AWS Nitro Enclaves attestation documents, SPIFFE/SPIRE, cloud workload identity). A hardware TPM is the wrong tool for "is this container the workload I expect?". Root Herald is strongest for endpoint devices.