Skip to content
Guides

Policies

A policy decides which chips count and how strictly. It is named on the challenge; verify may tighten it and never loosen it.

server (Node)ts
const { challengeId, challenge } = await rh.issueChallenge({
  ask: ["posture"],
  policy: "rootherald:builtin:strict-hardware",     // or the id of a policy you created
});

const result = await rh.verify(evidence, { challengeId });                        // judged under the challenge's policy
await rh.verify(evidence, { challengeId, policy: "rootherald:builtin:dev" });     // 422 policy_downgrade
await rh.relayEnroll(enrollRequestBlob, { challengeId });                         // admission under the same policy: 422 admission_refused for a rejected class

The five built-ins

rootherald:builtin:…AcceptsGates
strict-hardwarehardware, firmware-tpm, mobile-hardwareDefault. Event log + signed quote, EAR affirming, device:high. Rejects every virtual and emulated TPM.
strict-hardware-permissive-mobilesame, plus Android TEE and macOS Secure EnclaveNo event-log or device:high floor; EAR warning. For consumer surfaces where StrongBox is not universal.
cloud-permissive+ cloud-vtpm, cross-validatedEAR warning, permissive mode. Endpoints and your own cloud workloads.
enterprise-managed-onlyhardware, firmware-tpmLike strict-hardware without mobile. Pair with your own enrolment gate.
deveverything, including emulated-swtpmNo signed quote required. Development only; never affirming.

Built-ins are immutable. Omit policy and the project default applies (strict-hardware unless you changed it in the dashboard). Classes and groups are on the TPM classes page.

What each knob gates

FieldTypeGates
acceptedClassGroupsstring[]Groups admitted: hardware, firmware-tpm, cloud-vtpm, emulated, mobile-hardware, mobile-software. Expands to new vendors automatically.
acceptedSpecificClassesstring[]Individual classes admitted in addition, e.g. cloud-vtpm-aws-nitro.
unacceptedBehavior"reject" | "warn"What an out-of-set class gets: a fail verdict, or a warn verdict with a degraded trustworthiness vector.
requireSignedQuotebooleanThe evidence must carry a quote signed by the enrolled AK.
requireEventLogbooleanA quote-bound event log must replay to the PCRs. Needs a posture ask.
requiredPcrsint[]PCRs the quote must cover. Built-ins use [0,1,2,3,4,7].
minimumEarStatus"affirming" | "warning"The EAR floor a device must reach to pass.
minAcrURNurn:rootherald:device:high requires the verified boot chain. user:* tiers are unreachable on this path and always reject.
mode"strict" | "permissive"Whether a missing optional check fails the verdict or degrades it.
requireCloudCrossValidationbooleanA cloud-vtpm class must also present provider instance-identity evidence binding the vTPM to one instance.
allowMacosReducedbooleanAdmit macOS Secure Enclave evidence at reduced assurance.
minCohortPrevalence / cohortScope / onNovelProfilesee belowThe additive cohort-prevalence constraint.

Custom policies

Dashboard → Policies → New, or the admin API. The response carries the id you name on challenges or set as the project default. PATCH the same path with only the fields to change.

terminalbash
curl -X POST https://rootherald.io/api/v1/admin/policies \
  -H "Authorization: Bearer rh_sk_…" -H "Content-Type: application/json" -d '{
  "name": "oem-fleet",
  "acceptedClassGroups": ["hardware", "firmware-tpm"],
  "acceptedSpecificClasses": [],
  "unacceptedBehavior": "reject",
  "requireSignedQuote": true,
  "requireEventLog": true,
  "requiredPcrs": [0, 1, 2, 3, 4, 7],
  "minimumEarStatus": "affirming",
  "minAcr": "urn:rootherald:device:high",
  "mode": "strict"
}'
# 201 { "id": "8f3a…", "name": "oem-fleet", "isBuiltIn": false, "version": 1, … }

Two shapes worth knowing. unacceptedBehavior: "warn" with the strict class set lets everything through with a warn verdict, so you can observe before enforcing. Listing the four hardware-discrete-* classes in acceptedSpecificClasses with an empty group list admits discrete silicon only.

Cloud cross-validation

A cloud-vtpm class under requireCloudCrossValidation must also submit the provider's instance-identity evidence, and the verifier binds the vTPM's EK to that instance: AWS Nitro attestation document plus EC2 instance identity, Azure IMDS attested data, GCP instance-identity JWT. Evidence relayed from a different instance fails. The client bundles it into the same opaque blob.

Cohort prevalence

How common this device's boot configuration is among distinct real devices of the same TPM family, firmware build and Secure Boot platform key. Evaluated after every explicit check passes; it can add a reason to reject, never remove one. Off by default.

verdict.device — present on a posture ask when cohort logic ranjson
{
  "cohortKey": "c0a7f31e…",             // opaque id for this exact boot configuration
  "cohortScope": "global",              // "global" | "tenant-fleet"
  "cohortSampleSize": 48211,            // distinct devices in the denominator
  "cohortPrevalence": 0.034,            // min over PCR 0, 4, 7; omitted below the sample floor
  "cohortPrevalencePerPcr": { "0": 0.91, "4": 0.88, "7": 0.034 },
  "novelProfile": false                 // true on cold start or below the floor
}
policy fieldsjson
{
  "minCohortPrevalence": 0.05,          // null = off; claims are still emitted
  "cohortScope": "tenant-fleet",        // "global": every device of the same kind; "tenant-fleet": your own
  "onNovelProfile": "warn"              // "warn" | "step-up" | "reject"
}

A new firmware build is rare until adoption grows, so warn is the default and cohortPrevalence is omitted while the cohort is small. Counters are distinct-device cardinality estimates over authentic evidence only, so re-attesting one device moves nothing. Dashboard → Cohort allowlist approves a known-good cohortKey for your tenant so a rare-but-trusted image stops being flagged.

Fail closed

An unknown, deleted or foreign policy id is 422 unknown_policy; nothing is substituted. A policy whose minAcr is a user:* tier rejects every device on this path.