You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
calibration: the attestation envelope's reportData is 32 bytes for a 64-byte TEE field, and its binding has no freshness component — a valid report replays #9140
packages/loopover-engine/src/calibration/attestation-envelope.ts (#8541) is the typed seam the attested-
evaluation epic will build on. Two gaps in it will block an independent verifier, and both are cheaper to
fix now — before #8537 persists real envelopes — than after.
1. reportData is 32 bytes; SEV-SNP and TDX both bind 64
buildAttestationReportData returns a hex SHA-256 — 32 bytes — and the validator pins it at exactly 64
hex chars (REPORT_DATA_HEX = 64). The doc comment calls it "the 32-byte payload a TEE binds into its
attestation report."
But the SEV-SNP attestation report's REPORT_DATA field is 64 bytes of guest-supplied data, and
TDX's REPORTDATA is likewise 64 bytes. So the digest must be placed into a field twice its width, and
the envelope specifies no placement convention — left-aligned zero-padded, right-aligned, or
double-hashed. #8538's verification walkthrough asks a skeptic to "recompute reportData … and check it
against the attestation report's user-data binding." Without a stated convention that check is
unspecified, and two honest implementers will disagree.
2. No freshness binding — a valid attestation report replays
reportData commits to corpusChecksum:headSha:baseSha and nothing else. That triple is deterministic
across runs: the same corpus at the same SHAs always yields the same reportData. So one genuine
attested run's report can be presented for any later run with an identical binding, and no field in the
envelope distinguishes them — there is no nonce, no run id, and no timestamp inside the signed payload
(verifiedAt is on the verifier's record, outside the report).
The unused 32 bytes from gap (1) are exactly where a nonce or run id belongs. Fixing both together is
one change.
Why now
Both are pure-schema concerns in a module that is explicitly infrastructure-free, so they are fixable
before any hardware exists. Once #8537 persists envelopes under audit_events metadata, changing the
binding means a schemaVersion bump and a mixed-vintage corpus of evidence.
Requirements
Define and document the exact 64-byte layout, e.g. sha256(binding) || nonce or sha256(binding) || 32 zero bytes, and encode it in buildAttestationReportData's return (or add a
sibling helper returning the full 64-byte field). Publish it as a written spec with test vectors — a
non-JS verifier must be able to reimplement it.
Add a freshness component (nonce or run id) to the binding, and a matching envelope field so a verifier
can tie the report to one specific run.
Validate the binding inputs. buildAttestationReportData currently accepts arbitrary strings and joins
them with :; a corpusChecksum containing a colon makes the commitment ambiguous. Constrain the
three inputs to their expected hex shapes, or length-prefix the concatenation.
Minor: BASE64 = /^[A-Za-z0-9+/]+={0,2}$/ accepts strings whose length is not a multiple of 4, which
are not valid base64.
99%+ patch coverage, branch-counted (this package is graded by node:test — see #9064 for the coverage-
evidence caveat). Property test that the binding is injective over its input triple; published test
vectors asserted.
Schema, binding, and spec only. No verification policy, no certificate-chain checking, no infrastructure —
those remain the parent epic's maintainer work, exactly as #8541 scoped it.
Problem
packages/loopover-engine/src/calibration/attestation-envelope.ts(#8541) is the typed seam the attested-evaluation epic will build on. Two gaps in it will block an independent verifier, and both are cheaper to
fix now — before #8537 persists real envelopes — than after.
1.
reportDatais 32 bytes; SEV-SNP and TDX both bind 64buildAttestationReportDatareturns a hex SHA-256 — 32 bytes — and the validator pins it at exactly 64hex chars (
REPORT_DATA_HEX = 64). The doc comment calls it "the 32-byte payload a TEE binds into itsattestation report."
But the SEV-SNP attestation report's
REPORT_DATAfield is 64 bytes of guest-supplied data, andTDX's
REPORTDATAis likewise 64 bytes. So the digest must be placed into a field twice its width, andthe envelope specifies no placement convention — left-aligned zero-padded, right-aligned, or
double-hashed. #8538's verification walkthrough asks a skeptic to "recompute
reportData… and check itagainst the attestation report's user-data binding." Without a stated convention that check is
unspecified, and two honest implementers will disagree.
2. No freshness binding — a valid attestation report replays
reportDatacommits tocorpusChecksum:headSha:baseShaand nothing else. That triple is deterministicacross runs: the same corpus at the same SHAs always yields the same
reportData. So one genuineattested run's report can be presented for any later run with an identical binding, and no field in the
envelope distinguishes them — there is no nonce, no run id, and no timestamp inside the signed payload
(
verifiedAtis on the verifier's record, outside the report).The unused 32 bytes from gap (1) are exactly where a nonce or run id belongs. Fixing both together is
one change.
Why now
Both are pure-schema concerns in a module that is explicitly infrastructure-free, so they are fixable
before any hardware exists. Once #8537 persists envelopes under
audit_eventsmetadata, changing thebinding means a
schemaVersionbump and a mixed-vintage corpus of evidence.Requirements
sha256(binding) || nonceorsha256(binding) || 32 zero bytes, and encode it inbuildAttestationReportData's return (or add asibling helper returning the full 64-byte field). Publish it as a written spec with test vectors — a
non-JS verifier must be able to reimplement it.
can tie the report to one specific run.
buildAttestationReportDatacurrently accepts arbitrary strings and joinsthem with
:; acorpusChecksumcontaining a colon makes the commitment ambiguous. Constrain thethree inputs to their expected hex shapes, or length-prefix the concatenation.
BASE64 = /^[A-Za-z0-9+/]+={0,2}$/accepts strings whose length is not a multiple of 4, whichare not valid base64.
Test Coverage Requirements
99%+ patch coverage, branch-counted (this package is graded by
node:test— see #9064 for the coverage-evidence caveat). Property test that the binding is injective over its input triple; published test
vectors asserted.
Links & Resources
packages/loopover-engine/src/calibration/attestation-envelope.ts~64-72, ~44, ~148-159REPORT_DATAis 64 bytes, guest-supplied. Intel TDX:REPORTDATA64 bytes.Boundaries
Schema, binding, and spec only. No verification policy, no certificate-chain checking, no infrastructure —
those remain the parent epic's maintainer work, exactly as #8541 scoped it.
maintainer-only — trust-architecture schema.