Skip to content

feat: deployment attestation — GET /version, release manifest, verify script#54

Open
alokit-bot wants to merge 1 commit into
mainfrom
feat/deployment-attestation
Open

feat: deployment attestation — GET /version, release manifest, verify script#54
alokit-bot wants to merge 1 commit into
mainfrom
feat/deployment-attestation

Conversation

@alokit-bot

@alokit-bot alokit-bot commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

What

Users of relay.byokrelay.com currently cannot verify that the managed relay runs the public repo code. This PR adds a transparent, independently verifiable attestation chain.

Changes

GET /version endpoint (new)

Returns { version, commit, buildTime, repoUrl, attestationUrl }. COMMIT_SHA and BUILD_TIME are injected at deploy time (null in dev). GET /health now also includes commit.

deploy.yml (updated)

After git pull, writes COMMIT_SHA (full SHA from git rev-parse HEAD) and BUILD_TIME (ISO-8601 UTC) into .env. Post-deploy smoke test now verifies that /version reflects the deployed commit.

.github/workflows/release-attestation.yml (new)

Triggers on v* tags. Computes SHA-256 hashes for the five files that cover all relay behaviour:

  • src/index.js, src/db.js, src/providers.js
  • package.json, package-lock.json

Writes attestation.json (schema byok-relay-attestation/v1) and uploads it as a GitHub Release asset via softprops/action-gh-release (SHA-pinned). No external trust required — the manifest is generated by GitHub Actions from the tagged commit.

scripts/verify-attestation.js (new)

CLI verifier. Clone the repo at the reported commit, point at the downloaded attestation.json, get PASS/FAIL per file + exit code 0/1.

README.md — Security section

New "Verifying the managed relay" subsection with the 3-step curl workflow:

  1. GET /version → note commit + version
  2. Download attestation.json from the matching GitHub Release
  3. Clone at that commit and run node scripts/verify-attestation.js

.env.example

Documents COMMIT_SHA/BUILD_TIME as auto-injected (do not set manually).

Verification

node --check src/index.js     #
node --check scripts/verify-attestation.js  #

Related BACKLOG item: Deployment attestation (Compliance/Trust Documentation section).

Summary by CodeRabbit

  • New Features

    • Added release attestation support, including published verification data for tagged releases.
    • Enhanced the app’s version and health responses with commit/build information.
    • Added a verification script to check a local checkout against the published attestation.
  • Documentation

    • Added guidance for verifying the managed relay and confirming it matches the published source code.

… script

- GET /version endpoint: { version, commit, buildTime, repoUrl, attestationUrl }
  COMMIT_SHA and BUILD_TIME injected at deploy time; null when not set (dev).
- GET /health now includes commit field alongside version.
- deploy.yml: after git pull, writes COMMIT_SHA (full SHA) and BUILD_TIME (ISO-8601
  UTC) into .env; post-deploy smoke test confirms /version reflects the commit.
- .github/workflows/release-attestation.yml: triggers on v* tags; computes SHA-256
  for src/index.js, src/db.js, src/providers.js, package.json, package-lock.json;
  writes attestation.json (schema byok-relay-attestation/v1) and uploads it as a
  GitHub Release asset via softprops/action-gh-release (SHA-pinned).
- scripts/verify-attestation.js: CLI tool — clone repo at commit, point at the
  downloaded attestation.json, get PASS/FAIL per file + overall exit code.
- .env.example: documents COMMIT_SHA/BUILD_TIME as auto-injected (do not set manually).
- README Security section: 'Verifying the managed relay' subsection — 3-step curl
  workflow any user can run independently of trusting the relay operator.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds attestation metadata to runtime responses, injects matching values during deploy, publishes a release attestation manifest on tagged releases, and adds a CLI plus README steps for verifying file hashes against that manifest.

Changes

Attestation and verification flow

Layer / File(s) Summary
Version payload and health response
src/index.js
Startup metadata is cached once, /health returns commit, and /version adds repoUrl, buildTime, and attestationUrl.
Deploy-time attestation injection
.env.example, .github/workflows/deploy.yml
The deploy script writes COMMIT_SHA and BUILD_TIME into the remote .env, then checks /version against the current commit; .env.example documents the injected values.
Release attestation workflow
.github/workflows/release-attestation.yml
The new tagged-release workflow hashes the attested files, builds attestation.json with schema and release metadata, and uploads it to the GitHub release.
Manifest verification CLI
scripts/verify-attestation.js, README.md
scripts/verify-attestation.js validates manifest schema and file hashes, and the README adds the relay verification walkthrough.

Sequence Diagram(s)

sequenceDiagram
  participant Deploy as GitHub Actions deploy workflow
  participant SSH as remote SSH script
  participant Relay as byok-relay
  participant Version as GET /version
  Deploy->>SSH: compute COMMIT and BUILD_TS
  SSH->>Relay: write COMMIT_SHA and BUILD_TIME into .env
  SSH->>Relay: restart byok-relay and run the health check
  SSH->>Version: request the version payload
  Version-->>SSH: return the commit field
  SSH->>SSH: compare commit to COMMIT
Loading
sequenceDiagram
  participant Release as release-attestation.yml
  participant GitHub as GitHub Release
  participant Verify as scripts/verify-attestation.js
  participant Repo as checked-out repository files
  Release->>GitHub: upload attestation.json
  Verify->>GitHub: read attestation.json
  Verify->>Repo: hash attested files
  Repo-->>Verify: file contents
  Verify-->>Verify: compare hashes and print PASS or FAIL
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I hopped through hashes, sniffing dawn,
With attestation carrots piled upon.
/version winked, the release bell rang,
And PASS went the burrow in a jubilant clang.
🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: deployment attestation, version endpoint, manifest, and verification script.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/deployment-attestation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
.github/workflows/deploy.yml (2)

42-44: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Commit mismatch only logs a warning; the deploy still reports success.

Line 44 echoes WARNING: /version commit mismatch but returns success, so a stale or wrong build silently passes deployment. If the attestation contract matters, a mismatch should fail the step.

♻️ Fail on mismatch
-            [ "$VERSION_COMMIT" = "$COMMIT" ] && echo "Attestation OK: commit ${COMMIT}" || echo "WARNING: /version commit mismatch"
+            if [ "$VERSION_COMMIT" = "$COMMIT" ]; then
+              echo "Attestation OK: commit ${COMMIT}"
+            else
+              echo "ERROR: /version commit ${VERSION_COMMIT} != deployed ${COMMIT}" && exit 1
+            fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy.yml around lines 42 - 44, The deploy attestation
check in the workflow currently only prints a warning when /version returns a
different commit, so the step still succeeds. Update the VERSION_COMMIT
validation in the deploy job to fail the step on mismatch instead of continuing,
using the same commit comparison block that echoes the attestation message.

43-43: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Harden the /version probe against hangs and empty responses.

curl -s has no --fail or --max-time, so a hung relay can stall the deploy job, and on a non-JSON/empty body the node parse throws — under set -e that aborts the assignment without a clear message. Add a timeout and a small parse guard.

♻️ Add timeout and guard
-            VERSION_COMMIT=$(curl -s http://localhost:3000/version | node -e "const d=require('fs').readFileSync('/dev/stdin','utf8'); console.log(JSON.parse(d).commit)")
+            VERSION_COMMIT=$(curl -sf --max-time 5 http://localhost:3000/version | node -e "try{const d=require('fs').readFileSync('/dev/stdin','utf8');console.log((JSON.parse(d).commit)||'')}catch(e){console.log('')}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy.yml at line 43, The VERSION_COMMIT probe in the
deploy workflow can hang or fail अस्पष्टly because the `curl` call to `/version`
has no timeout/fail handling and the `node` JSON parse assumes a valid response.
Update that probe to use a bounded request and explicit failure behavior, and
add a small guard in the parsing step so empty or non-JSON responses produce a
clear error instead of aborting silently under `set -e`.
.github/workflows/release-attestation.yml (2)

26-27: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Set persist-credentials: false on checkout.

This job only reads source to hash it; leaving the default credential persistence stores the GITHUB_TOKEN in .git/config on the runner, widening the exfiltration surface. zizmor (artipacked) flags this.

🔒 Proposed change
       - name: Checkout tagged commit
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-attestation.yml around lines 26 - 27, Update the
Checkout tagged commit step in the release attestation workflow to disable
credential persistence by setting persist-credentials to false on the
actions/checkout usage. This workflow only needs read-only source access for
hashing, so adjust the checkout configuration directly in the checkout step to
avoid storing the GITHUB_TOKEN in the runner’s git config.

Source: Linters/SAST tools


36-58: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add set -euo pipefail so a missing/unhashable file fails the build.

The step has no error-exit guard. If any attested file is missing or sha256sum fails, HASH becomes empty and the loop silently emits an entry like "src/db.js":"", producing a malformed attestation that ships to the release.

♻️ Proposed guard
         run: |
+          set -euo pipefail
           COMMIT=$(git rev-parse HEAD)
           TAG=${GITHUB_REF_NAME}
           BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-attestation.yml around lines 36 - 58, The
attestation build step in the release workflow should fail immediately on
missing or unhashable files. Update the shell block that computes COMMIT, TAG,
BUILD_TIME, and the ATTESTED_FILES hash loop to enable strict shell handling
with set -euo pipefail so any sha256sum or file access error stops the job
instead of producing empty hash entries in HASH_JSON.
scripts/verify-attestation.js (2)

59-86: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Verification never confirms the checked-out commit matches the manifest.

The script prints manifest.commit but never compares it against the repo's actual HEAD. A user who skips or mistypes the git checkout <commit> step (README Line 266) can still get an all-PASS result against a different tree, since the hashes are computed against whatever is currently checked out. Asserting git rev-parse HEAD === manifest.commit closes the gap between "/version commit" and "hashed files".

🔒 Suggested check before hashing
 const repoRoot = path.resolve(__dirname, '..');
 let allPassed = true;
+
+try {
+  const head = require('child_process')
+    .execSync('git rev-parse HEAD', { cwd: repoRoot })
+    .toString().trim();
+  if (manifest.commit && head !== manifest.commit) {
+    console.error(`Error: checked-out commit ${head} does not match manifest commit ${manifest.commit}.`);
+    console.error('Run: git checkout ' + manifest.commit);
+    process.exit(1);
+  }
+} catch (_) {
+  console.warn('Warning: could not determine git HEAD; skipping commit match check.');
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-attestation.js` around lines 59 - 86, The attestation verifier
in verify-attestation.js prints manifest.commit but never checks it against the
checked-out repository state. Add a HEAD validation near the existing manifest
logging and before the attestedFiles loop so the script compares the current git
HEAD to manifest.commit and fails early if they differ. Use the existing
repoRoot setup and keep the hash checks in the for...of over
manifest.attestedFiles unchanged once the commit match is confirmed.

54-57: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Guard against a missing attestedFiles map.

A manifest that passes the schema check but lacks attestedFiles makes Object.entries(manifest.attestedFiles) (Line 70) throw a raw TypeError instead of a clear diagnostic.

🛡️ Optional guard
 if (manifest.schema !== 'byok-relay-attestation/v1') {
   console.error(`Error: unrecognised schema "${manifest.schema}" — expected "byok-relay-attestation/v1"`);
   process.exit(2);
 }
+if (!manifest.attestedFiles || typeof manifest.attestedFiles !== 'object') {
+  console.error('Error: manifest is missing a valid "attestedFiles" map.');
+  process.exit(2);
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-attestation.js` around lines 54 - 57, The manifest validation
in verify-attestation.js only checks schema, so a manifest with no attestedFiles
can still reach Object.entries(manifest.attestedFiles) and throw a TypeError.
Add an explicit guard in the manifest validation flow, near the existing schema
check and before the attestedFiles iteration, to verify manifest.attestedFiles
exists and is a map/object; if it is missing or invalid, print a clear error via
console.error and exit with a non-zero status instead of allowing the raw
exception.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/index.js`:
- Around line 144-153: The /version response in app.get currently builds
attestationUrl from PKG_VERSION, which can point to a release tag that does not
match the deployed COMMIT_SHA. Update the version payload logic so
attestationUrl is derived from the actual deployed commit (for example using
COMMIT_SHA) or is only set when the deployed commit matches the tagged release
commit. Keep the change localized to the /version handler and preserve the
existing fields version, commit, buildTime, and repoUrl.

---

Nitpick comments:
In @.github/workflows/deploy.yml:
- Around line 42-44: The deploy attestation check in the workflow currently only
prints a warning when /version returns a different commit, so the step still
succeeds. Update the VERSION_COMMIT validation in the deploy job to fail the
step on mismatch instead of continuing, using the same commit comparison block
that echoes the attestation message.
- Line 43: The VERSION_COMMIT probe in the deploy workflow can hang or fail
अस्पष्टly because the `curl` call to `/version` has no timeout/fail handling and
the `node` JSON parse assumes a valid response. Update that probe to use a
bounded request and explicit failure behavior, and add a small guard in the
parsing step so empty or non-JSON responses produce a clear error instead of
aborting silently under `set -e`.

In @.github/workflows/release-attestation.yml:
- Around line 26-27: Update the Checkout tagged commit step in the release
attestation workflow to disable credential persistence by setting
persist-credentials to false on the actions/checkout usage. This workflow only
needs read-only source access for hashing, so adjust the checkout configuration
directly in the checkout step to avoid storing the GITHUB_TOKEN in the runner’s
git config.
- Around line 36-58: The attestation build step in the release workflow should
fail immediately on missing or unhashable files. Update the shell block that
computes COMMIT, TAG, BUILD_TIME, and the ATTESTED_FILES hash loop to enable
strict shell handling with set -euo pipefail so any sha256sum or file access
error stops the job instead of producing empty hash entries in HASH_JSON.

In `@scripts/verify-attestation.js`:
- Around line 59-86: The attestation verifier in verify-attestation.js prints
manifest.commit but never checks it against the checked-out repository state.
Add a HEAD validation near the existing manifest logging and before the
attestedFiles loop so the script compares the current git HEAD to
manifest.commit and fails early if they differ. Use the existing repoRoot setup
and keep the hash checks in the for...of over manifest.attestedFiles unchanged
once the commit match is confirmed.
- Around line 54-57: The manifest validation in verify-attestation.js only
checks schema, so a manifest with no attestedFiles can still reach
Object.entries(manifest.attestedFiles) and throw a TypeError. Add an explicit
guard in the manifest validation flow, near the existing schema check and before
the attestedFiles iteration, to verify manifest.attestedFiles exists and is a
map/object; if it is missing or invalid, print a clear error via console.error
and exit with a non-zero status instead of allowing the raw exception.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c01b20b8-2cc2-4e57-899b-969dcc6d85b9

📥 Commits

Reviewing files that changed from the base of the PR and between f0e1a43 and d0a3c2c.

📒 Files selected for processing (6)
  • .env.example
  • .github/workflows/deploy.yml
  • .github/workflows/release-attestation.yml
  • README.md
  • scripts/verify-attestation.js
  • src/index.js

Comment thread src/index.js
Comment on lines +144 to +153
app.get('/version', (req, res) => {
res.json({
version: PKG_VERSION,
commit: COMMIT_SHA,
buildTime: BUILD_TIME,
repoUrl: REPO_URL,
attestationUrl: COMMIT_SHA
? `${REPO_URL}/releases/tag/v${PKG_VERSION}`
: null,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

attestationUrl points at a version tag that may not correspond to the deployed commit.

The deploy workflow injects COMMIT_SHA from git rev-parse HEAD on main, but attestationUrl is derived from v${PKG_VERSION}. The release-attestation manifest (attestation.json) is generated per v* tag and pins commit to the tagged commit. If main HEAD is ahead of (or otherwise differs from) the last tagged release, /version returns a commit that won't match the manifest at releases/tag/v${PKG_VERSION}, and the attested file hashes may differ — breaking the documented verification flow.

Consider linking by the actual deployed commit (e.g. ${REPO_URL}/tree/${COMMIT_SHA}) or only emitting attestationUrl when the deployed commit equals a tagged release commit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.js` around lines 144 - 153, The /version response in app.get
currently builds attestationUrl from PKG_VERSION, which can point to a release
tag that does not match the deployed COMMIT_SHA. Update the version payload
logic so attestationUrl is derived from the actual deployed commit (for example
using COMMIT_SHA) or is only set when the deployed commit matches the tagged
release commit. Keep the change localized to the /version handler and preserve
the existing fields version, commit, buildTime, and repoUrl.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant