Skip to content

fix: route tinfoil sends to the assigned enclave and cut GLM latency - #1170

Merged
ital0 merged 23 commits into
mainfrom
italomenezes/thu-758-prewarm-tinfoil-attestation-when-a-confidential-model-is
Jul 31, 2026
Merged

fix: route tinfoil sends to the assigned enclave and cut GLM latency#1170
ital0 merged 23 commits into
mainfrom
italomenezes/thu-758-prewarm-tinfoil-attestation-when-a-confidential-model-is

Conversation

@ital0

@ital0 ital0 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Fixes THU-758

GLM felt slow and unreliable. Live profiling on the preview environment (Firefox, real sends, instrumented end to end) turned that into numbers, and the numbers moved the target twice. This PR ships the fixes that came out of it, plus the instrumentation that made them findable.

The headline fix: requests were going to the wrong enclave

The Tinfoil ATC assigns an enclave per attestation (usually inference.tinfoil.sh, sometimes a pool node like router.inf6.tinfoil.sh), returns that enclave's HPKE key, and the SDK seals the request body for it — telling us where to deliver via X-Tinfoil-Enclave-Url. Our proxy ignored that header and always forwarded to the fixed TINFOIL_ENCLAVE_URL. When the assignment diverged, the sealed body landed on an enclave whose key didn't match:

422 {"type":"urn:ietf:params:ehbp:error:key-config","title":"failed to read decrypted request body"}

which cost a full eviction + re-attestation retry (~1.6–2.3s) on the first send. Measured at ~50% of new-chat sends in one run.

The proxy now forwards to the assigned enclave, allowlisted to HTTPS tinfoil.sh hosts (label-boundary check — evil-tinfoil.sh and tinfoil.sh.evil.com are rejected), preserving the SSRF posture that motivated the fixed URL, and applies the API path prefix derived from the configured enclave URL so no route is lost. Validated live: router.inf6 went 422 → 404 (my first attempt dropped /v1) → 200, and 30 consecutive sends now run without a single key-config failure.

Also fixed

  • Sporadic 502 Bad Gateway after idle — the backend closed keep-alive connections at ~30s while the ALB held them for 60s, so the ALB periodically reused a dead socket. Backend idleTimeout is now 120s (above the ALB's 60s, per AWS's rule) and both Pulumi ALBs pin idleTimeout: 60 so the contract can't silently invert. Proven with a raw-TCP probe: server close 31.5s → 119.1s; reuse after 59s idle went from dead socket to OK.
  • Attestation prewarm on model selection — selecting a Tinfoil model now warms the enclave client in the background, so the first message doesn't pay cold attestation.
  • Prompt prefix stability — volatile content (timestamp, now minute-precision, plus skill/ask notes) moved out of the cached prefix and placed immediately before the user's turn, so the router's prefix cache has a byte-identical prefix to hit and the model still ends on the user message. Honest note: no prefix-cache win was measurable yet (the same step-2 speedup appears on DeepSeek, which is not on this path), so this lands as a correctness/structure change, not a proven speedup.
  • Warm enclave connection — a lightweight periodic probe keeps the backend's connection pool to the enclave warm (cold TLS to the enclave measured 0.67–1.56s vs 0.26s reused).
  • Client aborts no longer telemetered as 500 — they log as 499.
  • Session cookie cache reverted. It was added here for a ~5–30ms/request win, then removed: with it enabled a revoked device's signed cookie stays valid up to the TTL, and within that window it can mint durable credentials (device-approval session, 90-day API key), defeating instant device revocation.

Instrumentation (how the above became findable)

  • Per-request phase timing on the tinfoil proxy: pre (guard chain: auth + rate limiter), fetch, headers — in the structured log and in a CORS-exposed X-Proxy-Timing header plus standard Server-Timing.
  • Timing-Allow-Origin, resolved through the same origin allowlist as CORS, so the browser can decompose cross-origin Resource Timing.

What the measurements say now

Median GLM time-to-first-token, warm connection, 10 sends:

phase time owner
client pre-send (seal + dispatch) 178ms ours (CPU-bound, very consistent)
network browser↔backend 609ms (62%) infra/topology
backend 205ms — guard chain 28ms, enclave headers 172ms already lean

Connection reuse works (9/10 sends, handshake only on the first, 54ms). The enclave returns headers in ~172ms — it is not the bottleneck, and neither is the database or the app. The remaining levers are the network leg (terminating closer to users) and the 178ms client-side pre-send; both are follow-up work, and the network figure should be confirmed against production before anyone invests in it.

Verification

  • bun run check clean; frontend suite green; backend suite green apart from the known same-process WebSocket/socket-bind flakes.
  • Live validation on the preview across several rounds: 0 key-config 422s, 0 404s, 30 consecutive sends without a routing failure.
  • Known-open, not introduced here: rare multi-second stalls whose time is spent outside the backend (one capture: 20.6s wall clock with pre=81ms, backend total 413ms). Did not reproduce in 30 subsequent sends; instrumentation is now in place to catch the next one.

@github-actions

Copy link
Copy Markdown

Semgrep Security Scan

No security issues found.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Preview environment destroyed 🧹

Stack preview-pr-1170 and its Cloudflare subdomain have been cleaned up.

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Reviewed the diff — no issues to report. ✅ Never approves, never requests changes, never gates merge.
head: 9b8cfb76e8ef · mode: single · deferred 0 item(s) already reported by other bots (best-effort dedup)

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

PR Metrics

Metric Value
Lines changed (prod code) +717 / -109
JS bundle size (gzipped) 🟢 586.0 KB → 585.8 KB (-184 B, -0.0%)
Test coverage 🟢 81.12% → 81.12% (+0.0%)
Performance (preview) Preview not ready — Render deploy may have timed out
Accessibility
Best Practices
SEO

Updated Fri, 31 Jul 2026 21:22:46 GMT · run #2539

@ital0 ital0 self-assigned this Jul 30, 2026

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 26e1d0f8f6a6 · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread src/ai/prompt.ts Outdated
Comment thread backend/src/tinfoil/routes.ts Outdated
Comment thread backend/src/tinfoil/keep-warm.ts Outdated

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: b63bb1dd39e1 · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread backend/src/auth/auth.ts Outdated
Comment thread backend/src/tinfoil/routes.ts

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Reviewed the diff — no issues to report. ✅ Never approves, never requests changes, never gates merge.
head: de1842f74c0e · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: b9bb61987575 · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread backend/src/tinfoil/keep-warm.ts Outdated

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 9df015754cf3 · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread src/ai/prompt.ts Outdated
@ital0 ital0 changed the title feat: prewarm tinfoil attestation when a confidential model is selected fix: route tinfoil sends to the assigned enclave and cut GLM latency Jul 31, 2026

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: b4b5cc82385f · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread src/ai/prompt.ts Outdated

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: f2c56f53890e · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread src/ai/step-logic.ts Outdated
Comment thread backend/src/inference/client.ts Outdated
Comment thread backend/src/tinfoil/routes.ts

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 1a853b887aeb · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread src/ai/fetch.ts Outdated

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: a9c164565811 · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread backend/src/config/cors.ts
Comment thread src/ai/prompt.ts

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Reviewed the diff — no issues to report. ✅ Never approves, never requests changes, never gates merge.
head: ff96cb0ec57c · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 2981c27c6d8b · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

Comment thread backend/src/config/cors.test.ts Outdated
@ital0
ital0 deployed to preview July 31, 2026 21:19 — with GitHub Actions Active

@github-actions github-actions 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.

🔭 thunder-deep-review (advisory)

Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 295d0ff598d0 · mode: deep · deferred 0 item(s) already reported by other bots (best-effort dedup)

}

export type InferenceLogger = {
info: (context: InferenceUpstreamAttemptLog | object, message: string) => void

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Convention — InferenceUpstreamAttemptLog | object collapses to just object

The | object here swallows the typed member — the whole parameter widens to object, so the log-shape type buys you nothing and a malformed log entry wouldn't be caught. The sibling TinfoilProxyLogger.info is precisely typed, and this logger is actually called with two shapes (InferenceUpstreamAttemptLog and InferenceProxyLatencyLog). Could we type it as InferenceUpstreamAttemptLog | InferenceProxyLatencyLog instead?

Comment thread backend/src/index.ts
.use(
createInferenceRoutes({
auth,
fetchFn: deps?.fetchFn,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Convention — Inference gets raw deps?.fetchFn while every sibling gets the resolved fetchFn — load-bearing but undocumented

Every other route is handed the resolved fetchFn (deps?.fetchFn ?? globalThis.fetch), but inference is deliberately passed the raw deps?.fetchFn. That's intentional — the client-cache gate does if (client && !fetchFn) return cached, so passing a truthy globalThis.fetch in prod would silently disable client caching and rebuild the client every request. The problem is nothing says so, so a well-meaning refactor "normalizing" this to match the siblings would quietly regress performance. A one-line comment explaining why inference must receive undefined in prod would protect it.

Comment thread src/chats/chat-store.ts

// Fire-and-forget: the wrapper no-ops (before any dynamic import) unless
// this is a Tinfoil system model, so the first send finds a warm client.
void (deps.prewarmSystemModel ?? prewarmSystemModel)(model)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Convention — Fire-and-forget prewarm has no .catch, so a chunk-load failure becomes an unhandled rejection

I traced the wrapper: runSystemModelPrewarm swallows its own errors internally, so the only way prewarmSystemModel actually rejects is a failed dynamic await import('./tinfoil-client') — a real event after a deploy invalidates old chunks. This void (...)(model) attaches no handler, so that path surfaces as an unhandled promise rejection. The tell is that the new test has to pre-attach void prewarmFailure.catch(() => undefined) to suppress exactly the rejection production leaves dangling. Could we add .catch((error) => console.error(...)) here, or wrap the import() inside the wrapper so it can never reject?

@ital0
ital0 merged commit b68d513 into main Jul 31, 2026
31 checks passed
@ital0
ital0 deleted the italomenezes/thu-758-prewarm-tinfoil-attestation-when-a-confidential-model-is branch July 31, 2026 21:44
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