Confidential VMs & Tenant Attestation
A full virtual machine running as an Intel TDX Trust Domain, with root access and a GPU attached. You generate your own attestation quote from inside the VM and verify it offline against Intel's root CA. VoltageGPU is not in the trust path.
VM Tier vs Container Tier
The confidential container tier, retired by our provider on 1 October 2026, gave you a hardware-sealed container inside an Intel TDX enclave: the workload was protected, but attestation happened at the infrastructure level and the container did not expose /dev/tdx_guest, so you had to trust the platform's attestation reports. The comparison below is kept for customers who used it.
The Confidential VM tier removes that trust requirement. You get a full Ubuntu VM that itself runs as a TDX Trust Domain, with the TDX guest device exposed inside the VM. You generate the signed quote yourself, bind it to your own nonce, and verify the signature chain offline with Intel's open-source tooling. Nothing in the verification path is operated by VoltageGPU.
| Capability | Confidential Containers | Confidential VMs |
|---|---|---|
/dev/tdx_guest exposed | No | Yes |
| Attestation | Infrastructure-level | Tenant-generated TDX quote |
| Access level | Container root | Full VM root via sudo |
| Provisioning | Self-serve (API, dashboard, CLI) | Self-serve (deploy page, since 7 Sept 2026) |
| Persistent volumes | Available | Not yet, disk is ephemeral |
What You Get Inside the VM
Ubuntu 24.04 LTS
Ubuntu 24.04 LTS with kernel 6.8, running as an Intel TDX Trust Domain. Full root via sudo and direct SSH access, install anything you need.
GPU Attached
The GPU is attached to the VM and visible from inside it: nvidia-smi works out of the box for your training and inference workloads.
TDX Guest Interface
The VM exposes /dev/tdx_guest plus the tdx_guest and tsm kernel modules, everything you need to generate signed quotes via configfs TSM.
Deploy a Confidential VM
Deploy it yourself from your dashboard: pick a tier, give it a name, and it boots in about two minutes with your SSH key already on it. Add an SSH key to your account first, because a Confidential VM binds its keys at creation and can never be given one afterwards. The VM then appears under Your Pods with its SSH command, its live cost and a Release button, so you never wait on us to start or stop it.
Add Your SSH Key
Add a public SSH key to your account first. A Confidential VM binds its keys at creation and can never be given one afterwards.
Deploy It Yourself
Pick a size on the deploy page and the VM boots in about two and a half minutes as an Intel TDX Trust Domain, with your key on it. Email contact@voltagegpu.com only for a size we do not list.
SSH In
Connect with your key, verify the TDX environment, and run your workload. You have full root via sudo.
Fully self-serve. Deploying, managing and releasing a Confidential VM all happen from your own dashboard, with no email and no waiting. Container-tier pods are self-serve too.
Verify the Environment
Once you are in, confirm you are actually inside a TDX guest: the TDX guest device must exist, the kernel modules must be loaded, and the GPU must be visible.
$ ls -l /dev/tdx_guest crw------- 1 root root 10, 127 ... /dev/tdx_guest
$ lsmod | grep -E 'tdx_guest|tsm' tdx_guest ... tsm ...
$ nvidia-smi
Generate a TDX Quote
Step 1: Create a Report Entry
Quotes are generated through the kernel's configfs TSM interface. Creating a directory under /sys/kernel/config/tsm/report opens a new report request.
sudo mkdir /sys/kernel/config/tsm/report/r1
Step 2: Write Custom report_data
Write exactly 64 bytes of custom report_data into inblob. Binding a hash of a fresh nonce is the standard way to prove the quote was generated now, for you, and is not a replay. SHA-512 output is exactly 64 bytes, so it fits inblob perfectly.
head -c 32 /dev/urandom > nonce.bin openssl dgst -sha512 -binary nonce.bin | \ sudo tee /sys/kernel/config/tsm/report/r1/inblob > /dev/null
Keep the nonce. The verifier needs nonce.bin later to confirm the quote embeds the SHA-512 of that exact nonce in its report_data field.
Step 3: Read the Signed Quote
Reading outblob triggers quote generation inside the Trust Domain and returns the signed quote. On this tier the output is a TDX quote v4 of 5247 bytes.
sudo cat /sys/kernel/config/tsm/report/r1/outblob > quote.bin ls -l quote.bin # 5247 bytes
Step 4: Sanity-Check the Header
Before running full verification, eyeball the first header bytes. A valid quote from this tier starts with 04 00 02 00 81 00.
$ xxd -l 6 quote.bin 00000000: 0400 0200 8100 ......
Header Fields
04 00 | bytes 0-1 | Quote format version 4 |
02 00 | bytes 2-3 | Attestation key type: ECDSA-P256 |
81 00 ... | bytes 4-7 | TEE type 0x81: Intel TDX |
Generate a GPU Attestation Report
The TDX quote above never covers the GPU. On a single-GPU H200, H100 or RTX PRO 6000 Blackwell Confidential VM, the GPU produces its own attestation report, signed by NVIDIA rather than by Intel, and bound to a nonce you choose. That gives you two independent proofs from one session, with two separate roots of trust and VoltageGPU in neither. On the 8x H100 node the same proof exists for all eight GPUs, in NVIDIA's multi-GPU Protected PCIe mode: see the note at the end of this section, the reading differs and the SDK needs one option.
Read the state before anything else. If CC State is not ON, or DevTools Mode is not OFF, stop: a DevTools part produces a report whose measurements will not match NVIDIA reference values.
$ nvidia-smi conf-compute -q
CC State : ON
Multi-GPU Mode : None
CPU CC Capabilities : INTEL TDX
GPU CC Capabilities : CC Capable
CC GPUs Ready State : Ready
$ nvidia-smi conf-compute -d
DevTools Mode: OFFsudo apt-get install -y python3-pip python3-venv
python3 -m venv ~/att && . ~/att/bin/activate # a venv, not --break-system-packages
pip install nvidia-ml-py nv-attestation-sdk cryptography==43.0.1
python3 - <<'PY'
import os, pynvml as n
n.nvmlInit()
h = n.nvmlDeviceGetHandleByIndex(0)
nonce = os.urandom(32) # yours, generated here, now
rep = n.nvmlDeviceGetConfComputeGpuAttestationReport(h, nonce)
open("gpu_report.bin","wb").write(bytes(rep.attestationReport[:rep.attestationReportSize]))
print("report", rep.attestationReportSize, "bytes, nonce", nonce.hex())
PYpython3 - <<'PY'
import secrets, json, base64
from nv_attestation_sdk import attestation
nonce = secrets.token_hex(32)
c = attestation.Attestation()
c.set_name("my-vm"); c.set_nonce(nonce); c.set_claims_version("2.0")
c.add_verifier(attestation.Devices.GPU, attestation.Environment.REMOTE,
"https://nras.attestation.nvidia.com/v3/attest/gpu", "")
ev = c.get_evidence() # attest() without evidence raises in 2.7.3
print("attest ->", c.attest(ev))
open("nras_token.json","w").write(c.get_token())
PYA passing run prints Attestation Successful and writes a signed EAT token. The claims that matter are measres: success, meaning the runtime driver and VBIOS measurements matched NVIDIA's published golden values, and x-nvidia-gpu-attestation-report-nonce-match: true, which is what makes the report impossible to replay from an earlier session. Verified on this SKU on 4 September 2026: driver 595.71.05, VBIOS 96.00.CF.00.02, hwmodel GH100, secboot true, dbgstat disabled.
The deprecated local verifier (python3 -m verifier.cc_admin) currently fails its OCSP request from inside the guest and reaches end of support on 15 September 2026. Use the remote service above, or NVIDIA's C++ SDK.
Multi-GPU nodes read differently. On an 8-GPU node in NVIDIA's Protected PCIe mode, nvidia-smi conf-compute -q reports CC State OFF together with Multi-GPU Mode Protected PCIe: the second line is the one that matters. If CC GPUs Ready State reads Not Ready, run sudo nvidia-smi conf-compute -srs 1. The attestation SDK then needs get_evidence(options={"ppcie_mode": False}) (the option means "standalone mode"; with the default it refuses to run on a PPCIe system). Verified on 10 September 2026 on an 8x H100 VM: NVIDIA's remote service returned Attestation Successful for all eight GPUs, token published at /blog/two-proofs/evidence/ppcie-8x-h100/. NVSwitch attestation is not verified yet. The 8x H200 node reads the same mode (Ready State Not Ready on 4 and 9 September) and its attestation has not been run.
Where this does not work: the container tier gives the tenant no attestation, so the Intel TDX quote is the only hardware evidence available there. RTX PRO 6000 Blackwell VMs were attested on 17 September 2026 (CC State ON, NVIDIA remote attestation successful on a caller nonce, evidence at /blog/two-proofs/evidence/rtx6000b-2026-09-17/). Always run conf-compute -qyourself at first login rather than trusting any page, including this one.
Two Reports Are Never Byte-Identical
If you hash a report today and compare that hash against a fresh report tomorrow, the comparison will fail even on an untouched machine. The report is 4,129 bytes and three regions of it change between calls. Only 3,969 bytes are stable. This catches people on their first day, because the natural way to pin an approved state is to fingerprint the whole blob.
| Offset | Size | What it is | Changes when |
|---|---|---|---|
[4, 36) | 32 bytes | Your nonce, echoed back | You send a different nonce |
[3565, 3597) | 32 bytes | The GPU's own nonce | Every single call |
[4033, 4129) | 96 bytes | ECDSA signature | Every single call |
The first and the third surprise nobody. The middle one does: it changes even when you send the same nonce twice, so you only find it by fetching twice with an identical nonce and diffing the results. The report is a DMTF SPDM 1.1 MEASUREMENTS response, and SPDM has the responder contribute 32 random bytes of its own alongside yours. The signature covers both, which is why the signature moves with it. Neither field is a defect, and neither weakens the proof: the nonce you chose is still in there, still signed, still yours to check.
measres, driver version, VBIOS version, secboot, dbgstat). Those are the values that must not drift. If you insist on a raw-byte fingerprint, exclude the three ranges above, and be aware that the offsets are specific to this report version rather than guaranteed by NVIDIA.python3 - <<'PY'
import pynvml as n
n.nvmlInit()
h = n.nvmlDeviceGetHandleByIndex(0)
nonce = bytes(range(32)) # deliberately fixed, not random
def pull():
r = n.nvmlDeviceGetConfComputeGpuAttestationReport(h, nonce)
return bytes(r.attestationReport[:r.attestationReportSize])
a, b = pull(), pull()
runs, start = [], None
for i, (x, y) in enumerate(zip(a, b)):
if x != y and start is None: start = i
if x == y and start is not None: runs.append((start, i)); start = None
if start is not None: runs.append((start, len(a)))
print("size", len(a), "differing ranges", runs)
print("stable bytes", len(a) - sum(e - s for s, e in runs))
PYFound and reported by Mohammed Zoheb Shaik, who paid for the machine that produced it, ran the diff nobody else had run, and sent us the result. Published with his permission.
Artifacts You Can Verify Without a VM
The raw files from the 4 September 2026 run on a single-GPU H200 VM are published, unchanged since capture, with SHA-256 checksums. They let an auditor check NVIDIA's signature and Intel's quote format before spending anything on a machine. They do not replace a run on your own VM: only your own nonce proves your own session.
| File | Size | What it is | How to check it |
|---|---|---|---|
| nras_token.json | 3,055 bytes | NVIDIA's verdict: two JWTs signed ES384 by nras.attestation.nvidia.com, nonce 6154dbb2…, issued 2026-09-04 12:31:05 UTC | Verify the signatures against NVIDIA's JWKS, then read the claims (measres, driver, VBIOS, secboot, dbgstat) |
| gpu_report.bin + nonce.txt | 4,129 bytes | A raw SPDM measurement report from a second round with a different nonce (4206…) | Confirm the nonce at bytes [4, 36); use it to read the layout above. Not submitted to NRAS, cert chain not included |
| quote.bin + report_data.bin | 5,243 bytes | TDX v4 quote (header 04 00 02 00 81 00) with the tenant's 64-byte report_data at byte 568 | Intel DCAP QVL against Intel's root CA, then compare bytes [568, 632) with report_data.bin |
| SHA256SUMS, README.txt | Checksums and a plain-text description of each file, including what the set does not prove | sha256sum -c SHA256SUMS |
Concurrent Quotes: Retry on EINVAL, and Only on EINVAL
One quote at a time works every time. Several at once do not. Mohammed Zoheb Shaik, who is building Custodian, a key broker for model weights, reported on 9 September 2026 that with six quote requests in flight, roughly one in six came back with [Errno 22] Invalid argument from the kernel's configfs TSM interface (Ubuntu 24.04, kernel 6.8). The failure is in the kernel's TSM object as one entry is torn down while the next is created, not in the hardware and not in the quote itself. It fails closed: you get an error, never a quote bound to someone else's report_data.
EINVAL only. A permission error or a missing /dev/tdx_guest means something else is wrong and must fail immediately; a blanket retry would hide it. With that rule the tenant's broker ran twelve unattended renewals and two batches of eight parallel releases without a single crossed quote.import errno, time
def get_quote(report_data, attempts=5):
for i in range(attempts):
try:
return tsm_quote(report_data) # your configfs TSM call
except OSError as e:
if e.errno != errno.EINVAL or i == attempts - 1:
raise # EACCES, ENOENT and the rest: stop here
time.sleep(0.05 * (i + 1))The Guest Clock: What It Proves, and Where It Stops
If your workload makes a security decision based on elapsed time, a lease that expires, a token that must be refreshed, a window that must close, then the clock is part of your threat model. Inside a confidential VM it is worth knowing exactly how far the guarantee goes, because the answer differs by platform and it is not absolute on either.
On Intel TDX, which is what this tier runs. The TDX module, not the host, owns TSC virtualisation for a Trust Domain. The virtual TSC frequency is a property of the TD fixed when the TD is built, and an attempt to change it afterwards is rejected. That is why the Linux guest forces X86_FEATURE_TSC_RELIABLE: it also stops a fall back to a tick source whose rate the host could bend. A paused TD that resumes cannot be made to believe no time passed, so a lease driven by rdtsc sees the jump.
On AMD SEV-SNP, unless Secure TSC is enabled. The counter stays malleable by the host at runtime, through scaling and offset, so rdtsc alone is not a trusted time source there. AMD addresses this with Secure TSC, which is opt-in through a bit in the SEV_FEATURES field of the VM Save Area; Intel's equivalent feature is called Virtual TSC. This matters when comparing tiers: the confidential GPU VM sold by Azure is built on AMD SEV-SNP, not on Intel TDX.
Measured, not assumed. On a genuine GCP N2D SEV-SNP guest, with /dev/sev-guest present and sev_guest as the TSM provider, there is no Secure TSC at all and tsc_reliable is set on no CPU. So on the SEV-SNP you can actually rent today, the malleable case is the default and the protected one is the exception. The TDX guest was the only one of the three to clear the bar, with tsc_reliable on all four CPUs.
The available clocksources say the same thing more plainly. On TDX they are tsc and acpi_pm. On both AMD guests they are tsc, kvm-clock and acpi_pm. kvm-clock is the host telling the guest what time it is, and TDX does not leave it on the shelf. All three guests also report tsc_known_freq, meaning the kernel was handed its rate rather than measuring it: 2449.998 MHz on both AMD boxes, 2700.000 MHz on the Intel one. You can watch the handover in dmesg and you cannot check it against anything.
These measurements were run and shared by Mohammed Zoheb Shaik on 15 September 2026, on three GCP guests that were deleted afterwards. He also enumerated the TD report body field by field, 584 bytes with every one accounted for by a documented field, which is what turns the claim below from a reading of a specification into a property of a real capture.
DESIRED_TSC_FREQ in the launch request to the security processor. On Intel the host sets the TD's frequency before the virtual CPUs exist, after which it is frozen. So freezing the clock mid-flight is closed, but a host that wants a one-hour lease to last longer in real time can set a slow rate once and never touch it again, and everything inside the guest stays perfectly monotonic. We looked for a way for a guest to verify the frequency it was handed against its own attestation evidence and found none documented: the report carries build and boot measurements, not the rate the counter runs at.What to do about it. Treat the guest counter as a source of monotonicity, which it genuinely is, and not as a source of wall-clock truth. If a deadline has to hold against the operator, let a signed timestamp from the party on the other side of the exchange decide whether the window has closed, and use the guest counter only to order events between those checks. Put another way, and this is the framing we owe to the reporter below: the clock becomes trustworthy at the platform level rather than in your code, so what the bound is worth is reported per deployment rather than assumed.
Credit. The TDX-versus-SEV distinction here was sent to us on 13 September 2026 by Mohammed Zoheb Shaik, who is building Custodian, a key broker for model weights, after he checked the TDX behaviour on real hardware rather than taking our reading on trust. The launch-time frequency caveat is ours, found while verifying his report against the specifications. The published weakness of Secure TSC itself, its use as a co-location detection channel between confidential VMs, was reported to AMD in March 2024 by researchers at TU Graz and placed by AMD outside the current threat model for the feature.
Verify Offline with Intel DCAP
Verification is fully offline and uses Intel's open-source DCAP stack (SGXDataCenterAttestationPrimitives). The quote's ECDSA signature chains through the platform's PCK certificate up to Intel's root CA. VoltageGPU operates nothing in that chain: if the quote verifies, the guarantee comes from Intel silicon, not from us.
git clone https://github.com/intel/SGXDataCenterAttestationPrimitives # Build the Quote Verification Library (QVL) sample and run it # against quote.bin. It validates the signature chain up to the # Intel root CA, entirely offline.
Check report_data Freshness
A valid signature is not enough: also confirm the quote embeds your report_data. Recompute sha512(nonce.bin) and compare it with the 64-byte report_data field inside the quote body. A match proves the quote was generated after you wrote your nonce, inside that specific Trust Domain.
Full walkthrough. The complete step-by-step guide, including the exact DCAP build commands and the report_data offset inside the quote body, is published at voltagegpu.com/blog/tenant-tdx-attestation-guide.
Or bind both proofs to your workload with voltage-verify
voltage-verify is a small open-source tool (MIT, Python 3.10+) that folds the five steps above and the NVIDIA attestation into one flow, bound to a manifest you write: the image digest, the digests of your model or files, and a random challenge. The TDX quote carries SHA-512 of that manifest as report_data, NVIDIA's service signs the GPU claims over SHA-256 of it, and the verifier on your machine checks Intel's chain to the pinned root, TCB status, Quoting Enclave identity, revocation, NVIDIA's ES384 signatures and claims, then runs nine negative tests. Use 0.2.1 or later: 0.2.1 fixes a key-pinning weakness in 0.2.0 (NVIDIA signing keys are now taken only from the bundle and chained to the pinned NRAS intermediate).
# your machine pip install "voltage-verify>=0.2.1" voltage-verify manifest --image ghcr.io/you/app:1.4.2 --artifact model.safetensors -o manifest.json # inside the VM, as root sudo -E python -m voltage_verify attest --manifest manifest.json -o bundle.json # your machine voltage-verify verify bundle.json --challenge <challenge printed by manifest> voltage-verify selftest bundle.json # a bare TDX quote from any provider, no manifest, no NVIDIA (since 0.2.0) voltage-verify quote quote.bin --report-data <64 bytes hex you expect>
Since 0.2.0 the same tool also verifies a bare Intel TDX quote that did not come from a VoltageGPU VM: voltage-verify quote takes the raw bytes written by /sys/kernel/config/tsm/report (or hex, or base64), checks the signature chain up to the pinned Intel root, then the TCB status, Quoting Enclave identity and revocation from Intel PCS. It is tested against Google's production Sapphire Rapids quote from go-tdx-guest, which it verifies and, correctly, reports as TCB OutOfDate.
Show your proof. Drop the bundle.json on voltagegpu.com/verify/share: the checks run again up to the Intel and NVIDIA roots, and you can publish a page with a badge for your clients or your team. The page is unlisted and never indexed, and you get a private link to remove it. Nothing is stored unless you choose to publish.
Source and issues: github.com/VoltageGPU/voltage-verify; package on PyPI; wheel, reference bundle and documentation mirrored with SHA-256 sums at voltagegpu.com/blog/two-proofs/voltage-verify. It proves that both hardware proofs were issued for your manifest after your challenge. It does not prove that the GPU executed the named image; that needs a measured launcher, and the tool says so.
Honest Limits
Confidential computing claims are only worth something when the boundaries are stated plainly. Here is exactly what this tier does and does not attest.
The container tier has no tenant attestation
Confidential container pods do not expose /dev/tdx_guest. On that tier, attestation is infrastructure-level only: you cannot generate your own quote from inside a container. If tenant-side attestation is a requirement, use the VM tier documented on this page.
The quote attests the CPU TEE boundary, not the GPU
NVIDIA GPU attestation is verified on single-GPU H200 VMs (September 4, 2026, CC State ON, remote attestation successful), on the 8x H100 node (September 10, 2026, multi-GPU Protected PCIe mode, all eight GPUs attested; NVSwitch not attestable from the guest), on single-GPU H100 VMs (September 16, 2026) and on RTX PRO 6000 Blackwell VMs (September 17, 2026, CC State ON, verified with voltage-verify). The 8x H200 reads the same mode but is not attested yet, and the container tier gives no tenant-side attestation. The TDX quote never attests the GPU: for GPU evidence, fetch the NVIDIA attestation report (nvmlDeviceGetConfComputeGpuAttestationReport or the NVIDIA attestation SDK) and verify it with NVIDIA's service.
No persistent volumes
The VM disk is ephemeral: when the VM is released, the disk is gone. Persistent volumes are not available on the VM tier yet, so copy models, checkpoints, and results off the box (e.g. via scp or rsync) before tearing it down. Deploying and releasing are both self-serve from Your Pods.
Running It, Stopping It, Paying For It
The single most expensive misunderstanding on this tier is what stops the meter. It is worth two minutes now.
sudo poweroff and sudo shutdown stop the operating system, but the machine stays rented and keeps costing. A powered-off VM is the worst of both: it does no work and still bills. The only thing that stops billing is releasing the VM.Release it yourself, from your dashboard
Your VM appears under Your Pods like any other machine. Open it and press Release VM. Billing stops at that moment, and the unused part of the prepaid hour is credited back to your balance automatically.
Copy your work off first
Releasing destroys the disk. There is no snapshot and no resume. Pull results down with scp -P <port> ubuntu@<ip>:~/results ./ before you press it.
Ask for an automatic stop if you want a hard limit
If you are running a fixed window, ask us to arm an automatic release at the end of it. It runs whether or not anyone is awake, so an overnight finish cannot bill you until morning. It is optional, and you can have it cancelled at any time.
What survives a reboot, and what does not
| Action | Machine | Disk contents | Billing |
|---|---|---|---|
sudo reboot | Restarts | Kept | Keeps running |
sudo poweroff | Stays off | Kept | Keeps running |
| Release VM | Destroyed | Destroyed | Stops, unused prepay refunded |
One consequence worth planning for: a reboot re-runs attestation from scratch. Your TDX quote and GPU attestation report are per boot, so a report you captured before a reboot no longer describes the machine you are on. Take a fresh one after any restart, the same way you took the first.