Skip to content

feat(attest): envelope verifier CLI — AMD cert chain, TCB, measurement, report_data#9251

Merged
JSONbored merged 1 commit into
mainfrom
feat/verify-attested-run
Jul 27, 2026
Merged

feat(attest): envelope verifier CLI — AMD cert chain, TCB, measurement, report_data#9251
JSONbored merged 1 commit into
mainfrom
feat/verify-attested-run

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Closes #9212 (epic #8534): a CLI that lets anyone — no LoopOver contact, no hardware, no trust beyond AMD's own published root keys — answer "did this exact code run on this exact corpus inside genuine AMD SEV-SNP hardware?" for a published attestation envelope.

Why this took real research, not just implementation

Every byte offset, the ECDSA signature's exact on-the-wire encoding, and the AMD KDS certificate extension OIDs are security-critical facts I could not respectably guess from training data alone. Before writing any verification code I:

  • Pulled the exact ATTESTATION_REPORT byte layout, the packed TCB_VERSION byte-to-SVN mapping, and the ECDSA-P384-SHA384 signature's little-endian R||S encoding from google/go-sev-guest (Google's maintained, production attestation-verification library) — via GitHub code search against the actual repo, not memory.
  • Fetched AMD's real, live Milan and Genoa ARK/ASK certificate chains directly from AMD's own KDS (kdsintf.amd.com) and cryptographically verified them with openssl verify (ARK self-signed, ASK signed by ARK) before vendoring them.
  • Cross-checked my hand-rolled DER OID encoder byte-for-byte against a real certificate's actual DER bytes (extracted via base64 decode, not openssl asn1parse's rendering) before trusting it for anything security-relevant.
  • Generated a real, independently-verifiable 3-tier synthetic PKI via openssl (RSA-4096-PSS-SHA384 root → RSA-4096-PSS-SHA384 intermediate → EC-P384 leaf carrying AMD's real KDS OIDs) specifically so the chain-verification and signature-verification logic could be tested against genuine cryptographic operations — real key generation, real signing, real X509Certificate.verify() calls — never a mock that returns a canned true.
  • Confirmed the security-critical ordering empirically: tampering with report_data after signing is caught as signature_invalid, not report_data_mismatch — since report_data is itself inside the signed byte range, a forged value there fails the signature check first, exactly as it must for the tool to be trustworthy rather than just structurally plausible.

What's in this PR

  • scripts/verify-attested-run-der.ts — a minimal, narrowly-scoped DER/BER reader (find an X.509 extension by OID, read a small DER INTEGER). Deliberately not a dependency on a general ASN.1 library: a tool whose whole purpose is "verify without trusting anyone" shouldn't itself require trusting an unaudited third-party parser.
  • scripts/verify-attested-run-report.ts — pure SNP report parsing: all documented byte offsets, TCB_VERSION decomposition, and the AMD-little-endian-to-IEEE-P1363 signature conversion (crypto.verify(..., { dsaEncoding: "ieee-p1363" })).
  • scripts/verify-attested-run-core.ts — the orchestration: sample-attester rejection → envelope structural validation (reusing calibration: attestation-evidence envelope — schema, pure structural validator, and report-data binding helper #8541's validateAttestationEnvelope) → report parsing → certificate chain of trust (pinned-root byte match + real ARK/ASK/VCEK signature verification) → report signature verification → TCB status → measurement → report_data re-derivation (reusing orb(attest): attested-run harness with pluggable attester — sample now, SNP by config #9211's buildAttestationReportData). Nothing about the report's own content is trusted until the chain AND the signature both pass.
  • scripts/verify-attested-run.ts — the CLI: --envelope/--expected (required), --vcek-cert or --fetch-vcek (opt-in AMD KDS network fetch — off by default), --ask-cert/--ark-cert (default to the vendored chain), --product milan|genoa, --allow-sample. Exit-code contract: 0 verified, 1-8 one per failure class, 9 usage/IO error.
  • scripts/verify-attested-run/certs/{milan,genoa}-{ark,ask}.pem — the real ARK/ASK certificate chains, fetched live from AMD's KDS and cryptographically verified before vendoring.
  • scripts/verify-attested-run/README.md — usage, the exit-code table, where every byte layout/OID came from, and an explicit "what this does NOT verify" section (the live decision path per Research: the attested-evaluation epic covers offline backtests only — decide whether the LIVE decision path needs attestation, and in what order #9141's deferral, image-provenance correlation which is orb(attest): reproducible pinned replay-runner image + digest manifest enforced in CI #9214's job, and CRL/revocation checking — a real, named, open gap, not silently glossed over).

A note on the committed test fixtures

test/fixtures/verify-attested-run/synthetic-vcek-key.pem is a private key — but it is a purpose-generated, wholly synthetic test key with no relationship to any real credential, exactly the standard pattern crypto libraries use for reproducible signature-verification test fixtures (Node core's own test suite does the same). It cannot sign anything AMD's real chain would ever trust.

Validation

  • npm run typecheck — clean.
  • npm run import-specifiers:check — clean.
  • npx vitest run on all three new core test files — 50/50 pass, 100% statement/branch/function/line coverage on verify-attested-run-der.ts, verify-attested-run-report.ts, and verify-attested-run-core.ts combined.
  • The CLI itself manually verified end-to-end against the synthetic PKI: happy path (exit 0), measurement_mismatch (exit 7), sample_attestation (exit 1), missing-args usage error (exit 9), and a real (opt-in) network call to AMD's live KDS with graceful 404 handling (exit 9) for a fake chip ID.
  • The DER OID encoder and extension-finder were independently verified against a real, live AMD certificate's raw bytes before any test was written.

Safety

  • No secrets, wallets, hotkeys, trust scores, or reward values. The one private key in this PR is a synthetic test fixture (see note above), not a secret.
  • --fetch-vcek is opt-in; no network access happens by default, matching the issue's own requirement.
  • No behavior change to any existing module — purely additive.

@JSONbored JSONbored self-assigned this Jul 27, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored
JSONbored merged commit 32c7d5a into main Jul 27, 2026
3 of 4 checks passed
@JSONbored
JSONbored deleted the feat/verify-attested-run branch July 27, 2026 09:41
@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 27, 2026
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

❌ 17 Tests Failed:

Tests completed Failed Passed Skipped
23034 17 23017 21
View the top 3 failed test(s) by shortest run time
test/unit/selfhost-pg-retention.test.ts > runRetentionPrune + processJob on the Postgres backend (#977) > processJob prune-retention deletes eligible rows and records a success audit event on Postgres
Stack Traces | 0.005s run time
AssertionError: expected 3 to be +0 // Object.is equality

- Expected
+ Received

- 0
+ 3

 ❯ test/unit/selfhost-pg-retention.test.ts:119:44
test/unit/loopover-engine-scaffold.test.ts > loopover-engine package scaffold > publishes only the build output and changelog, not source
Stack Traces | 0.0116s run time
AssertionError: expected [ 'dist/**/*.js', …(2) ] to deeply equal [ 'dist', 'CHANGELOG.md' ]

- Expected
+ Received

  [
-   "dist",
+   "dist/**/*.js",
+   "dist/**/*.d.ts",
    "CHANGELOG.md",
  ]

 ❯ test/unit/loopover-engine-scaffold.test.ts:30:29
test/unit/selfhost-pg-retention.test.ts > pruneExpiredRecords on the Postgres backend (#977) > deletes across multiple bounded batches and stops at the per-table cap, same as the SQLite path
Stack Traces | 0.0116s run time
AssertionError: expected +0 to be 4 // Object.is equality

- Expected
+ Received

- 4
+ 0

 ❯ test/unit/selfhost-pg-retention.test.ts:76:33
test/unit/reputation-wiring.test.ts > getEffectiveSubmitterReputation (#4513, install-wide for a confirmed miner) > skips the miner-identity lookup entirely when the per-repo signal already justifies downgrading
Stack Traces | 0.0203s run time
AssertionError: expected 'neutral' to be 'low' // Object.is equality

Expected: "low"
Received: "neutral"

 ❯ test/unit/reputation-wiring.test.ts:520:26
test/unit/reputation-wiring.test.ts > ORB/AMS reputation bridge wiring (#6801) > keeps the pre-bridge output unchanged when any activation gate is off
Stack Traces | 0.0207s run time
AssertionError: expected "vi.fn()" to not be called at all, but actually been called 1 times

Received:

  1st vi.fn() call:

    Array [
      "https://api.gittensor.io/miners",
      Object {
        "headers": Object {
          "accept": "application/json",
          "user-agent": "loopover/0.1",
        },
        "signal": AbortSignal {
          Symbol(kEvents): Map {},
          Symbol(events.maxEventTargetListeners): 0,
          Symbol(events.maxEventTargetListenersWarned): false,
          Symbol(kHandlers): Map {},
          Symbol(kAborted): false,
          Symbol(kReason): undefined,
          Symbol(kComposite): false,
          Symbol(kTimeout): true,
        },
      },
    ]


Number of calls: 1

 ❯ test/unit/reputation-wiring.test.ts:419:31
test/unit/reputation-wiring.test.ts > ORB/AMS reputation bridge wiring (#6801) > never downgrades the locally-computed signal when the enabled bridge does not vouch
Stack Traces | 0.0212s run time
AssertionError: expected "vi.fn()" to be called once, but got 2 times
 ❯ test/unit/reputation-wiring.test.ts:441:25
test/unit/reputation-wiring.test.ts > getEffectiveSubmitterReputation (#4513, install-wide for a confirmed miner) > widens to the install-wide signal for a CONFIRMED miner when the per-repo signal alone stays neutral
Stack Traces | 0.025s run time
AssertionError: expected 'neutral' to be 'low' // Object.is equality

Expected: "low"
Received: "neutral"

 ❯ test/unit/reputation-wiring.test.ts:482:26
test/unit/reputation-wiring.test.ts > ORB/AMS reputation bridge wiring (#6801) > REGRESSION (#6801): feature on upgrades a low local signal and changes the end-to-end gate decision
Stack Traces | 0.033s run time
AssertionError: expected "vi.fn()" to be called 2 times, but got 3 times
 ❯ test/unit/reputation-wiring.test.ts:393:25
test/unit/selfhost-metrics.test.ts > DEFAULT_METRIC_META completeness (drift guard, 2026-07 fix) > every literal metric name emitted anywhere in src/ has a registered DEFAULT_METRIC_META entry
Stack Traces | 0.18s run time
AssertionError: expected [ …(2) ] to deeply equal []

- Expected
+ Received

- []
+ [
+   "loopover_orb_relay_multiple_live_enrollments_total",
+   "loopover_private_manifest_warnings_total",
+ ]

 ❯ test/unit/selfhost-metrics.test.ts:498:21
test/unit/config-templates.test.ts > config/examples review templates (#1682) > lints loopover.full.yml with zero warnings, including no retired top-level fields
Stack Traces | 0.184s run time
AssertionError: expected [ Array(1) ] to deeply equal []

- Expected
+ Received

- []
+ [
+   "gate.mergeReadiness (\"off\") is set alongside an explicitly-authored mode for gate.linkedIssue, gate.duplicates, gate.slop.mode. The composite only fills in a sub-gate mode left unset -- it never overrides an explicitly-configured one, so those fields stay exactly as authored regardless of gate.mergeReadiness.",
+ ]

 ❯ test/unit/config-templates.test.ts:64:29
test/unit/queue-5.test.ts > queue processors > type label decoupling (#label-decoupling) > REGRESSION (#4528, PR #4494 shape): keeps the propagated labels on the PR's own merge-closed webhook, instead of falling back to the title guess
Stack Traces | 0.276s run time
AssertionError: expected [ 'gittensor:feature' ] to deeply equal [ 'gittensor:feature', …(1) ]

- Expected
+ Received

  [
    "gittensor:feature",
-   "gittensor:priority",
  ]

 ❯ test/unit/queue-5.test.ts:6660:34
test/unit/worker-entry-boundary.test.ts > worker entry boundary > does not reference pixelmatch, pngjs, visual-diff, gifenc, or sharp in worker-reachable source
Stack Traces | 0.52s run time
AssertionError: worker-reachable files must not mention Node-only visual diff/GIF/image deps: .../review/visual/visual-findings.ts: expected [ Array(1) ] to deeply equal []

- Expected
+ Received

- []
+ [
+   ".../review/visual/visual-findings.ts",
+ ]

 ❯ test/unit/worker-entry-boundary.test.ts:82:118
View the full list of 5 ❄️ flaky test(s)
test/contract/engine-parity.test.ts > predicted-gate engine parity (#2286) > 'merge-readiness-composite-preserves-e…' matches the committed golden output

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.015s run time
AssertionError: expected { predicted: true, …(11) } to deeply equal { predicted: true, …(10) }

- Expected
+ Received

  {
    "basis": "public_config",
-   "blockers": [
+   "blockers": [],
+   "conclusion": "success",
+   "confirmedContributor": undefined,
+   "funnel": null,
+   "note": "Predicted from the repo's public .loopover.yml gate config + safe defaults. The maintainer may have private dashboard overrides not reflected here, and the dual-model AI-consensus blocker is only evaluated on a real PR. The slop score is NOT evaluated pre-submission (it needs the diff content) and may still fail the real gate. Provide the PR's changed paths to also predict the focus-manifest path policy, the size/guardrail hold, and any pre-merge check scoped to changed paths; without them only path-independent title/description/label pre-merge checks are predicted. Every author is gated the same: a configured hard blocker fails the gate regardless of confirmed-contributor status (which affects only on-chain scoring).",
+   "pack": "gittensor",
+   "predicted": true,
+   "readinessScore": 80,
+   "summary": "No configured hard blocker was found. Advisory findings, if any, stay advisory.",
+   "title": "LoopOver Orb Review Agent passed",
+   "warnings": [
      {
        "action": "If this PR is intended to solve an issue, link it explicitly in the PR body.",
        "code": "missing_linked_issue",
        "detail": "No closing reference or linked issue number was found in the PR metadata/body.",
        "title": "No linked issue detected",
      },
    ],
-   "conclusion": "failure",
-   "funnel": null,
-   "note": "Predicted from the repo's public .loopover.yml gate config + safe defaults. The maintainer may have private dashboard overrides not reflected here, and the dual-model AI-consensus blocker is only evaluated on a real PR. The slop score is NOT evaluated pre-submission (it needs the diff content) and may still fail the real gate. Provide the PR's changed paths to also predict the focus-manifest path policy, the size/guardrail hold, and any pre-merge check scoped to changed paths; without them only path-independent title/description/label pre-merge checks are predicted. Every author is gated the same: a configured hard blocker fails the gate regardless of confirmed-contributor status (which affects only on-chain scoring).",
-   "pack": "gittensor",
-   "predicted": true,
-   "readinessScore": 80,
-   "summary": "No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.",
-   "title": "LoopOver Orb Review Agent: No linked issue detected",
-   "warnings": [],
  }

 ❯ test/contract/engine-parity.test.ts:46:21
test/unit/engine-parity-fixtures.test.ts > predicted-gate engine parity fixtures > keeps the 'merge-readiness-composite-preserves-e…' fixture parseable and aligned with its documented surface

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.019s run time
AssertionError: expected 'success' to be 'failure' // Object.is equality

Expected: "failure"
Received: "success"

 ❯ test/unit/engine-parity-fixtures.test.ts:29:31
test/unit/linked-issue-label-propagation-fetch.test.ts > fetchLinkedIssueLabelsForPropagation (#priority-linked-issue-gate) > reward-label gate on direct author/assignee match (#9161) > propagates the reward label to its own issue's author when that author genuinely IS a repo maintainer (opt-in + maintainer check both satisfied)

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.029s run time
AssertionError: expected { labels: [ 'gittensor:bug' ], …(1) } to deeply equal { …(2) }

- Expected
+ Received

  {
    "inconclusive": false,
    "labels": [
      "gittensor:bug",
-     "gittensor:priority",
    ],
  }

 ❯ expectPropagation test/unit/linked-issue-label-propagation-fetch.test.ts:40:18
 ❯ test/unit/linked-issue-label-propagation-fetch.test.ts:1087:7
test/unit/predicted-gate-engine.test.ts > predicted-gate engine branch coverage (#2283) > exercises gate-advisory gateMode and blocker policy branches

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.0295s run time
AssertionError: expected 'block' to be 'advisory' // Object.is equality

Expected: "advisory"
Received: "block"

 ❯ test/unit/predicted-gate-engine.test.ts:1558:55
test/unit/salvageability.test.ts > resolveAiReviewSalvageableHold > defaults: no configured floor uses the gate default, and a confidence-less blocker counts as certainty (at/above floor)

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.0068s run time
AssertionError: expected undefined to be defined
 ❯ test/unit/salvageability.test.ts:88:18

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

orb(attest): envelope verifier CLI — AMD cert chain, measurement, report_data re-derivation

1 participant