Skip to content

feat: add /check_quote endpoint and revocation list#4

Open
azaidelson wants to merge 1 commit intomainfrom
feat/check-quote-and-revocation
Open

feat: add /check_quote endpoint and revocation list#4
azaidelson wants to merge 1 commit intomainfrom
feat/check-quote-and-revocation

Conversation

@azaidelson
Copy link
Copy Markdown
Contributor

@azaidelson azaidelson commented Apr 20, 2026

User description

Add a lightweight POST /check_quote endpoint that verifies an attestation quote and returns {whitelisted, machine_id} without issuing a JWT. Useful for monitoring, gating, and ops tooling.

Add a baked-in revocation list (src/revoked.txt) that takes precedence over the whitelist. Revoked machines get a distinct error on /get_jwt (403 with revocation timestamp) and a distinct response shape on /check_quote ({revoked: true, revoked_at}).

Other changes:

  • Extract verifyAndLookupMachine helper from processQuote
  • Switch whitelist matching from prefix (startsWith) to strict equality
  • Lowercase whitelist and revocation IDs at load time
  • Surface Intel attester stderr when verification fails
  • Add check_quote.sh client script with mktemp+trap cleanup
  • Gitignore src/whitelist.csv and src/revoked.txt (injected by CI)
  • Update CI workflow to inject revoked.txt with empty-file fallback
  • Update Dockerfile to COPY revoked.txt
  • Update README with /check_quote and revocation documentation

Summary by cubic

Adds POST /check_quote for fast whitelist checks without issuing a JWT, and adds a baked-in revocation list that overrides the whitelist and returns clear 403 errors for revoked machines. Also tightens whitelist matching and surfaces better verifier errors.

  • New Features

    • POST /check_quote: verifies a quote and returns { whitelisted, machine_id }; includes { revoked, revoked_at } when applicable.
    • Revocation list src/revoked.txt (CI-injected) takes precedence over the whitelist; JWT endpoint denies revoked machines with 403 and timestamp.
    • check_quote.sh helper script for quick local checks.
    • CI/Docker: inject and bundle revoked.txt with empty-file fallback.
  • Refactors

    • Extracted verifyAndLookupMachine for shared verification and lookups.
    • Whitelist matching is now strict equality; IDs are lowercased at load.
    • Intel attester stderr is surfaced when verification fails; 403 returned for not-whitelisted or revoked.

Written for commit 62a27f7. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added /check_quote POST endpoint to verify hardware ID status (whitelisted, unknown, or revoked) without issuing authentication tokens
    • Implemented machine revocation list to block and deny service to specific hardware identifiers; revocation takes precedence over whitelist
  • Documentation

    • Updated README with specification for new /check_quote endpoint and revocation list behavior

Generated description

Below is a concise technical summary of the changes proposed in this PR:
Describe how quote verification now loads whitelist and revocation lists, enforces deny-list precedence, and surfaces attester stderr so /get_jwt responds with specific 403 messages for revoked hardware. Introduce the /check_quote handler plus helper and client tooling so callers can poll whitelist status without requesting JWTs while documenting the new lightweight flow.

TopicDetails
Check quote flow Expose POST /check_quote backed by the new checkQuote helper, document the lightweight endpoint and its responses, and provide the check_quote.sh client so monitoring and ops tooling can poll whitelist status without JWT issuance.
Modified files (4)
  • README.md
  • check_quote.sh
  • src/server.js
  • src/services.js
Latest Contributors(2)
UserCommitDate
[email protected]trust-server: added ti...March 29, 2026
ilya.th.kapitonov@gmai...Update README.mdJanuary 29, 2026
Build packaging Ensure CI and images bake revocation data by injecting revoked.txt (with fallback), copying it into the container, and keeping repository artifacts out with .gitignore so the build output matches runtime expectations.
Modified files (3)
  • .github/workflows/build-and-publish.yml
  • .gitignore
  • Dockerfile
Latest Contributors(2)
UserCommitDate
ilya.th.kapitonov@gmai...Fix workflowJanuary 30, 2026
iKapitonauChange whitelist.csv c...December 18, 2025
Quote revocation Harden verifyAndLookupMachine by loading the baked-in revocation list, normalizing hardware IDs, surfacing Intel attester stderr, guarding /get_jwt with revocation-aware 403s, and removing the old src/whitelist.csv stub while explaining the deny-list in the README.
Modified files (5)
  • README.md
  • src/config.js
  • src/server.js
  • src/services.js
  • src/whitelist.csv
Latest Contributors(2)
UserCommitDate
[email protected]trust-server: added ti...March 29, 2026
ilya.th.kapitonov@gmai...Update README.mdJanuary 29, 2026
This pull request is reviewed by Baz. Review like a pro on (Baz).

Add a lightweight POST /check_quote endpoint that verifies an attestation
quote and returns {whitelisted, machine_id} without issuing a JWT. Useful
for monitoring, gating, and ops tooling.

Add a baked-in revocation list (src/revoked.txt) that takes precedence
over the whitelist. Revoked machines get a distinct error on /get_jwt
(403 with revocation timestamp) and a distinct response shape on
/check_quote ({revoked: true, revoked_at}).

Other changes:
- Extract verifyAndLookupMachine helper from processQuote
- Switch whitelist matching from prefix (startsWith) to strict equality
- Lowercase whitelist and revocation IDs at load time
- Surface Intel attester stderr when verification fails
- Add check_quote.sh client script with mktemp+trap cleanup
- Gitignore src/whitelist.csv and src/revoked.txt (injected by CI)
- Update CI workflow to inject revoked.txt with empty-file fallback
- Update Dockerfile to COPY revoked.txt
- Update README with /check_quote and revocation documentation
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

This PR introduces a machine revocation feature that complements the existing whitelist. Changes include adding a revoked.txt file tracking denied machines, a new /check_quote endpoint for querying machine status without issuing JWTs, refactored quote verification logic that prioritizes revocation checks, updated build infrastructure to include the revocation file, and a check_quote.sh CLI utility.

Changes

Cohort / File(s) Summary
Build & Infrastructure
.github/workflows/build-and-publish.yml, Dockerfile, .gitignore, src/config.js
Updated build workflow to copy revoked.txt with fallback to empty file; added src/revoked.txt and docs/superpowers/ to gitignore; updated Dockerfile to include revoked.txt in runtime image; added PATHS.REVOKED configuration pointing to revoked.txt.
Documentation & API Endpoint
README.md, src/server.js
Documented new /check_quote endpoint with request/response schema showing three outcomes (whitelisted, not whitelisted, or revoked); updated /get_jwt error documentation to include 403 responses for unknown and revoked machines; added /check_quote Express route with error handling for revocation and non-whitelisted statuses.
Core Service Logic
src/services.js
Extended loadSecrets() to load and parse optional revoked.txt file; normalized whitelist IDs to lowercase; replaced runDcapCheck() to capture both stdout/stderr; introduced verifyAndLookupMachine() helper that validates quotes and checks revocation list (with priority) before whitelist; refactored processQuote() to use new helper and enforce revocation; added checkQuote() export returning machine status without throwing on revocation.
CLI Utilities
check_quote.sh
New executable Bash script for testing POST /check_quote endpoint; validates quote file existence, constructs JSON request, POSTs to configurable server URL (defaults to localhost:8080), and returns HTTP status with parsed response; includes error handling, dependency checks, and response formatting via jq.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Server as Server<br/>/check_quote
    participant Service as checkQuote<br/>Service
    participant Attestation as Attestation<br/>Engine
    participant Revoked as Revoked<br/>List
    participant Whitelist as Whitelist

    Client->>Server: POST /check_quote<br/>{quote}
    Server->>Service: checkQuote(hexQuote)
    Service->>Attestation: runDcapCheck()<br/>or runSevSnpCheck()
    Attestation-->>Service: {stdout, stderr}<br/>with machine-id
    Service->>Revoked: Check revocation<br/>list first
    alt Revoked
        Revoked-->>Service: Entry found<br/>(revocation data)
        Service-->>Server: {whitelisted: false,<br/>revoked: true,<br/>revoked_at: timestamp}
    else Not Revoked
        Revoked-->>Service: Not found
        Service->>Whitelist: Check whitelist
        alt Whitelisted
            Whitelist-->>Service: Entry found
            Service-->>Server: {whitelisted: true,<br/>machine_id}
        else Not Whitelisted
            Whitelist-->>Service: Not found
            Service-->>Server: {whitelisted: false,<br/>machine_id}
        end
    end
    Server-->>Client: HTTP 200/403<br/>JSON response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A revocation list arrives with care,
Denying machines from everywhere,
The whitelist whispers, "Let them through,"
But revoked entries say "No, it's true!"
Priority shines where denial takes hold,
A safer perimeter, brave and bold. 🔐

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately summarizes the two main changes: adding a /check_quote endpoint and introducing a revocation list mechanism.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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/check-quote-and-revocation

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 (1)
src/services.js (1)

242-249: Consider Map for O(1) revocation lookup if list grows.

Currently using Array.find() for revocation checks is O(n). If the revocation list remains small this is fine, but if it grows significantly, consider using a Map keyed by ID for O(1) lookup.

Example refactor using Map
 const state = {
   // ...
-  revoked: [],
+  revoked: new Map(), // Map<machineId, { revokedAt }>
 };

 // In loadSecrets():
-  state.revoked = revokedRaw
-    .split(/\r?\n/)
-    // ...
-    .map((line) => { /* ... */ });
+  state.revoked = new Map(
+    revokedRaw
+      .split(/\r?\n/)
+      .map((line) => line.trim())
+      .filter((line) => line.length > 0 && !line.startsWith("#"))
+      .map((line) => {
+        const [id, revokedAt] = line.split(",");
+        return [id.trim().toLowerCase(), { revokedAt: revokedAt?.trim() || "unknown" }];
+      })
+  );

 // In verifyAndLookupMachine():
-  const revocationEntry = state.revoked.find((e) => e.id === machineId) || null;
-  const revocation = revocationEntry
-    ? { revokedAt: revocationEntry.revokedAt }
-    : null;
+  const revocationEntry = state.revoked.get(machineId);
+  const revocation = revocationEntry ? { revokedAt: revocationEntry.revokedAt } : null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services.js` around lines 242 - 249, Replace the O(n) Array.find lookups
for revocations and whitelist with O(1) Map lookups: change how state.revoked
and state.whitelist are populated (or add cached Maps) keyed by id, then replace
the revocationEntry computation (currently using state.revoked.find(...)
producing revocationEntry) with a Map.get/has lookup for machineId and similarly
replace the whitelist lookup used to compute entry; preserve the existing null
semantics (revocation = { revokedAt: ... } or null, entry = whitelist entry or
null) and ensure any code constructing state.revoked/state.whitelist also keeps
the Maps in sync or initializes them when state is loaded/updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server.js`:
- Around line 25-32: The /check_quote route currently lets validation errors
from checkQuote (which calls verifyAndLookupMachine) bubble to the global error
handler and become HTTP 500; change the route handler (app.post("/check_quote",
asyncHandler(...))) to explicitly validate input and/or catch validation
failures from checkQuote/verifyAndLookupMachine and respond with HTTP 400.
Implement this by detecting known validation error types/messages (or having
verifyAndLookupMachine throw a dedicated ValidationError) inside the async
handler for checkQuote, and return res.status(400).json({ error: <message> })
for those cases while rethrowing other errors to the global handler.

---

Nitpick comments:
In `@src/services.js`:
- Around line 242-249: Replace the O(n) Array.find lookups for revocations and
whitelist with O(1) Map lookups: change how state.revoked and state.whitelist
are populated (or add cached Maps) keyed by id, then replace the revocationEntry
computation (currently using state.revoked.find(...) producing revocationEntry)
with a Map.get/has lookup for machineId and similarly replace the whitelist
lookup used to compute entry; preserve the existing null semantics (revocation =
{ revokedAt: ... } or null, entry = whitelist entry or null) and ensure any code
constructing state.revoked/state.whitelist also keeps the Maps in sync or
initializes them when state is loaded/updated.
🪄 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 Plus

Run ID: bbb7f887-3113-4276-b364-0a704c9f31b1

📥 Commits

Reviewing files that changed from the base of the PR and between b252aab and 62a27f7.

⛔ Files ignored due to path filters (1)
  • src/whitelist.csv is excluded by !**/*.csv
📒 Files selected for processing (8)
  • .github/workflows/build-and-publish.yml
  • .gitignore
  • Dockerfile
  • README.md
  • check_quote.sh
  • src/config.js
  • src/server.js
  • src/services.js

Comment thread src/server.js
Comment on lines +25 to +32
app.post(
"/check_quote",
asyncHandler(async (req, res) => {
const { quote } = req.body;
const result = await checkQuote(quote);
res.json(result);
}),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Validation errors from checkQuote will return HTTP 500 instead of 400.

The checkQuote function calls verifyAndLookupMachine, which throws errors for invalid input (e.g., "Missing quote", "Invalid quote format"). These will bubble up to the global error handler and return HTTP 500. For a "lightweight check" endpoint, clients would reasonably expect HTTP 400 for malformed requests.

Consider adding input validation here or updating the error handler to detect validation errors:

Proposed fix: Add input validation
 app.post(
   "/check_quote",
   asyncHandler(async (req, res) => {
     const { quote } = req.body;
+    if (!quote) {
+      return res.status(400).json({ error: "Missing quote" });
+    }
     const result = await checkQuote(quote);
     res.json(result);
   }),
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.post(
"/check_quote",
asyncHandler(async (req, res) => {
const { quote } = req.body;
const result = await checkQuote(quote);
res.json(result);
}),
);
app.post(
"/check_quote",
asyncHandler(async (req, res) => {
const { quote } = req.body;
if (!quote) {
return res.status(400).json({ error: "Missing quote" });
}
const result = await checkQuote(quote);
res.json(result);
}),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server.js` around lines 25 - 32, The /check_quote route currently lets
validation errors from checkQuote (which calls verifyAndLookupMachine) bubble to
the global error handler and become HTTP 500; change the route handler
(app.post("/check_quote", asyncHandler(...))) to explicitly validate input
and/or catch validation failures from checkQuote/verifyAndLookupMachine and
respond with HTTP 400. Implement this by detecting known validation error
types/messages (or having verifyAndLookupMachine throw a dedicated
ValidationError) inside the async handler for checkQuote, and return
res.status(400).json({ error: <message> }) for those cases while rethrowing
other errors to the global handler.

Comment thread src/services.js
Comment on lines +74 to +77
const [id, revokedAt] = line.split(",");
return {
id: id.trim().toLowerCase(),
revokedAt: revokedAt ? revokedAt.trim() : "unknown",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If a revocation-list entry omits the timestamp, the loader sets revokedAt: "unknown" and /check_quote//get_jwt surface it as-is, so should we reject malformed revocations or require a parseable ISO-8601 timestamp instead of defaulting to "unknown"?

Finding type: Type Inconsistency | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In src/services.js
around lines 47-83 inside the async loadSecrets() revocation parsing block, the code
currently does `return { id: id.trim().toLowerCase(), revokedAt: revokedAt ?
revokedAt.trim() : "unknown" }`. Refactor this to reject or skip malformed revocation
entries: if a line has no timestamp (or an empty/whitespace-only timestamp), or if the
timestamp is not a parseable ISO-8601 value, log a warning and ignore that line (or
throw to fail fast, depending on your preference). Then verify that checkQuote() (around
lines 406-414) and processQuote() (around lines 276-306) always expose a valid
revoked_at/revocation.revokedAt and never return the literal string "unknown" in
responses or error messages.

Comment thread src/services.js
Comment on lines 171 to +175
function runDcapCheck(hexQuote) {
return execCommand(config.ATTESTER_BIN, ["check", hexQuote]);
// The Intel attester exits 0 even when verification fails — the real error
// is printed to stderr. We need both streams to surface useful errors.
return new Promise((resolve, reject) => {
const child = spawn(config.ATTESTER_BIN, ["check", hexQuote]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

runDcapCheck duplicates the spawn + stdout/stderr capture wiring from execCommand, so should we reuse execCommand (or factor a shared spawnWithStreams helper) instead of keeping two copies?

Finding type: Code Dedup and Conventions | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Comment thread src/server.js
Comment on lines +25 to +32
app.post(
"/check_quote",
asyncHandler(async (req, res) => {
const { quote } = req.body;
const result = await checkQuote(quote);
res.json(result);
}),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

POST /check_quote in src/server.js uses an action-style, verb-ish path that conflicts with the REST rule about noun/resource URLs—should we move it to a resource-style route like POST /quotes/check or POST /quotes/status?

Finding type: REST API Best Practices | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In src/server.js
around lines 25-32, the Express route `app.post('/check_quote', ...)` uses a
verb/action-style URL, which conflicts with REST best practices that prefer
resource/noun-based paths. Refactor this endpoint to a resource-oriented path such as
`POST /quotes/check` or `POST /quotes/status`, keeping the handler logic (`const { quote
} = req.body; const result = await checkQuote(quote); res.json(result);`) unchanged.
Also update any related client calls/tests or docs that reference `/check_quote` so they
use the new path.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/server.js">

<violation number="1" location="src/server.js:28">
P2: Handle missing quote input in /check_quote so client errors return 400 instead of a 500 from the global error handler.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread src/server.js
app.post(
"/check_quote",
asyncHandler(async (req, res) => {
const { quote } = req.body;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 20, 2026

Choose a reason for hiding this comment

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

P2: Handle missing quote input in /check_quote so client errors return 400 instead of a 500 from the global error handler.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/server.js, line 28:

<comment>Handle missing quote input in /check_quote so client errors return 400 instead of a 500 from the global error handler.</comment>

<file context>
@@ -22,6 +22,15 @@ app.post(
+app.post(
+  "/check_quote",
+  asyncHandler(async (req, res) => {
+    const { quote } = req.body;
+    const result = await checkQuote(quote);
+    res.json(result);
</file context>
Suggested change
const { quote } = req.body;
const { quote } = req.body || {};
if (!quote) {
return res.status(400).json({ error: "Missing quote" });
}
Fix with Cubic

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