Copy-paste acceptance policies
Acceptance policies decide which TPM classes you trust, at what assurance level, with what cross-validation. Six common shapes are below; each one is valid JSON you can POST /api/v1/admin/policies as-is (Owner role, rh_sk_ bearer).
Built-in policies (the rootherald:builtin:* names) are immutable and always available. Customer-authored policies are created once per tenant, get a UUID, and are referenced by that id afterwards. To change one, PATCH it by id — the built-ins can never be modified.
Strict hardware: the default
When to use it: You want the safest setting and you don't care about cloud workloads or developer VMs signing up. Default for new projects.
{
"name": "strict-hardware",
"acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware"],
"acceptedSpecificClasses": [],
"unacceptedBehavior": "reject",
"requireSignedQuote": true,
"requireEventLog": true,
"requiredPcrs": [0, 1, 2, 3, 4, 7],
"minimumEarStatus": "affirming",
"minAcr": "urn:rootherald:device:high",
"mode": "strict"
}Equivalent to the built-in rootherald:builtin:strict-hardware. Rejects all virtual and emulated TPMs (unacceptedBehavior: "reject"), so cloud workloads cannot sign up. Use for consumer-facing free tiers, airdrop claims, and review/vote actions.
Cloud-permissive: accept reputable cloud vTPMs
When to use it: You're protecting an action that legitimately gets done from a developer's EC2 / Azure VM (API key issuance for a developer SaaS, for example).
{
"name": "cloud-permissive",
"acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware", "cloud-vtpm"],
"acceptedSpecificClasses": [],
"unacceptedBehavior": "reject",
"requireCloudCrossValidation": false,
"requireSignedQuote": true,
"minimumEarStatus": "warning",
"mode": "permissive"
}acceptedClassGroups takes group names (hardware, firmware-tpm, mobile-hardware, cloud-vtpm) that each match every vendor in that group; to pin a single vendor instead, list it in acceptedSpecificClasses (e.g. cloud-vtpm-aws-nitro). Note this is permissive: with requireCloudCrossValidation: false, cuckoo attacks where the attacker leases the same EC2 instance after the legitimate user spins it down are technically possible. Combine with the cross-validation recipe below for the same coverage with cuckoo defense.
Cloud cross-validated: for the paranoid (recommended)
When to use it: You want cloud workloads to be able to sign up, BUT you want to be sure the vTPM evidence and the cloud-provider's own instance-identity evidence agree. Defeats cuckoo replay.
{
"name": "cloud-cross-validated",
"acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware", "cloud-vtpm"],
"acceptedSpecificClasses": [],
"unacceptedBehavior": "reject",
"requireCloudCrossValidation": true,
"requireSignedQuote": true,
"minimumEarStatus": "warning",
"mode": "permissive"
}requireCloudCrossValidation: true means any request whose class is in the cloud-vtpmgroup must include the cloud provider's instance-identity evidence (AWS IID, Azure MSI, GCP attested) and we verify the vTPM's EK belongs to the same instance. Hardware and firmware TPMs are unaffected by the flag. See the cuckoo-defense guide for the full threat model.
Mobile-only: for app-only services
When to use it: Your product is a mobile app and there is no legitimate desktop / browser flow. Accept only hardware-rooted mobile attestation (Android StrongBox/TEE, Apple Secure Enclave / App Attest).
{
"name": "mobile-only",
"acceptedClassGroups": ["mobile-hardware"],
"acceptedSpecificClasses": [],
"unacceptedBehavior": "reject",
"requireSignedQuote": true,
"minimumEarStatus": "warning",
"mode": "strict"
}The mobile-hardwaregroup expands to Android StrongBox, Android TEE, Apple Secure Enclave, and Apple App Attest. Useful for mobile-first social apps, mobile-only banking, mobile-only games. Combine with platform-detection at the SDK level so desktop users get a clear "mobile required" error instead of an opaque rejection.
Discrete hardware only: the highest bar
When to use it: You want the strictest possible root of trust — discrete TPM silicon signed by a TCG vendor PKI, no firmware TPMs and no mobile. High-value or regulated workloads.
{
"name": "discrete-hardware-only",
"acceptedClassGroups": [],
"acceptedSpecificClasses": [
"hardware-discrete-infineon",
"hardware-discrete-st-micro",
"hardware-discrete-nuvoton",
"hardware-discrete-other"
],
"unacceptedBehavior": "reject",
"requireSignedQuote": true,
"requireEventLog": true,
"requiredPcrs": [0, 1, 2, 3, 4, 7],
"minimumEarStatus": "affirming",
"minAcr": "urn:rootherald:device:high",
"mode": "strict"
}Listing the four hardware-discrete-* classes in acceptedSpecificClasses (instead of the hardware group) upgrades the floor to discrete silicon only — firmware TPMs (Intel PTT, AMD fTPM, Pluton) are excluded. Paired with minAcr: "urn:rootherald:device:high" and minimumEarStatus: "affirming", this is the tightest acceptance shape Shield exposes.
Warn-not-fail: observe before enforcing
When to use it: You're rolling Root Herald into a live product. You want signal in the dashboard for two weeks before you actually block anyone.
{
"name": "warn-not-fail",
"acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware"],
"acceptedSpecificClasses": [],
"unacceptedBehavior": "warn",
"requireSignedQuote": true,
"minimumEarStatus": "warning",
"mode": "permissive"
}unacceptedBehavior: "warn" lets out-of-set devices (cloud vTPMs, emulators, software mobile) through with a warnverdict instead of a hard reject, so every device gets through and the dashboard's suspicious-class breakdown gives you a confidence interval on what tightening the policy would block. Standard rollout playbook: 2 weeks warn → switch unacceptedBehavior to reject (or adopt strict-hardware).
Uploading a custom policy
curl -X POST https://api.rootherald.io/api/v1/admin/policies \
-H "Authorization: Bearer rh_sk_live_…" \
-H "Content-Type: application/json" \
-d @policy.json
# response (PolicyDetail):
# {
# "id": "f9e2c1a4-3d27-4b8f-9c1e-a2b7c4d5e6f7",
# "name": "strict-hardware",
# "isBuiltIn": false,
# "version": 1,
# "acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware"],
# "acceptedSpecificClasses": [],
# "unacceptedBehavior": "reject",
# "requireCloudCrossValidation": true,
# "minAcr": "urn:rootherald:device:high",
# "mode": "strict"
# }The create call requires the Owner role and a secret key (Authorization: Bearer rh_sk_…). Reference the returned id in thepolicy argument of your rh.verify() call, or set it as the project default in the dashboard. To change an existing policy later, PATCH /api/v1/admin/policies/{id} with only the fields you want to update.