Skip to content

Commit 3be9188

Browse files
committed
fix: allow kernel attestation token reuse
1 parent 1e875c1 commit 3be9188

File tree

2 files changed

+77
-10
lines changed

2 files changed

+77
-10
lines changed

src/auth.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -454,16 +454,10 @@ const verifyKernelAttestation = async (
454454
}
455455

456456
pruneKernelTokenJtiCache();
457-
const jti = payload.jti;
458-
const nowMs = Date.now();
459-
const cachedUntilMs = kernelTokenJtiCache.get(jti) ?? 0;
460-
if (cachedUntilMs > nowMs) {
461-
return {
462-
ok: false,
463-
response: openaiError(401, "Unauthorized (kernel attestation replayed)", "invalid_kernel_token"),
464-
};
465-
}
466-
kernelTokenJtiCache.set(jti, payload.exp * 1000);
457+
// This token is expected to be re-used within its TTL (e.g. Codex tool-calling
458+
// results in multiple /v1/responses requests). Enforcing single-use "jti"
459+
// semantics breaks legitimate traffic and is unreliable in serverless anyway.
460+
kernelTokenJtiCache.set(payload.jti, payload.exp * 1000);
467461

468462
return { ok: true, payload };
469463
};

tests/openai-compat.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import assert from "node:assert/strict";
22
import { DEFAULT_MODEL_KEY, DEFAULT_REASONING_EFFORT_KEY } from "../src/defaults.ts";
3+
import { sha256Base64Url } from "../src/utils.ts";
34

45
const keyToString = (key: Deno.KvKey): string => JSON.stringify(key);
56

@@ -61,6 +62,23 @@ const { handleChatCompletions, handleResponses } = await import("../src/openai.t
6162

6263
const TEXT_ENCODER = new TextEncoder();
6364

65+
const encodeBase64 = (bytes: Uint8Array): string => {
66+
let binary = "";
67+
for (const b of bytes) binary += String.fromCharCode(b);
68+
return btoa(binary);
69+
};
70+
71+
const encodeBase64Url = (bytes: Uint8Array): string =>
72+
encodeBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
73+
74+
const encodeJsonBase64Url = (value: unknown): string => encodeBase64Url(TEXT_ENCODER.encode(JSON.stringify(value)));
75+
76+
const toPublicKeyPem = (spki: Uint8Array): string => {
77+
const b64 = encodeBase64(spki);
78+
const lines = b64.match(/.{1,64}/g) ?? [];
79+
return `-----BEGIN PUBLIC KEY-----\n${lines.join("\n")}\n-----END PUBLIC KEY-----`;
80+
};
81+
6482
const sseResponse = (chunks: string[]): Response => {
6583
const stream = new ReadableStream<Uint8Array>({
6684
start(controller) {
@@ -281,6 +299,61 @@ Deno.test("openai: responses accept non-message input items", async () => {
281299
assert.ok(types.includes("function_call_output"));
282300
});
283301

302+
Deno.test("auth: kernel attestation tokens are reusable within TTL", async () => {
303+
const keyPair = await crypto.subtle.generateKey(
304+
{
305+
name: "RSASSA-PKCS1-v1_5",
306+
modulusLength: 2048,
307+
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
308+
hash: "SHA-256",
309+
},
310+
true,
311+
["sign", "verify"],
312+
);
313+
314+
const spki = new Uint8Array(await crypto.subtle.exportKey("spki", keyPair.publicKey));
315+
const publicPem = toPublicKeyPem(spki);
316+
kvStore.set(keyToString(["uos_ai", "kernel_pubkeys"]), [{ pem: publicPem }]);
317+
318+
const bearerToken = "ghs_test_token";
319+
const nowSeconds = Math.floor(Date.now() / 1000);
320+
const payload = {
321+
iss: "ubiquity-os-kernel",
322+
aud: "ai.ubq.fi",
323+
iat: nowSeconds,
324+
exp: nowSeconds + 600,
325+
jti: `jti_${crypto.randomUUID()}`,
326+
owner: "acme",
327+
repo: "demo",
328+
installation_id: null,
329+
auth_token_sha256: await sha256Base64Url(bearerToken),
330+
state_id: "state_test",
331+
};
332+
333+
const header = { alg: "RS256", typ: "JWT" };
334+
const headerB64 = encodeJsonBase64Url(header);
335+
const payloadB64 = encodeJsonBase64Url(payload);
336+
const signingInput = `${headerB64}.${payloadB64}`;
337+
const signature = new Uint8Array(
338+
await crypto.subtle.sign("RSASSA-PKCS1-v1_5", keyPair.privateKey, TEXT_ENCODER.encode(signingInput)),
339+
);
340+
const kernelToken = `${signingInput}.${encodeBase64Url(signature)}`;
341+
342+
const { getKernelAttestationContext } = await import("../src/auth.ts");
343+
344+
const req = new Request("https://ai.ubq.fi/v1/responses", {
345+
method: "POST",
346+
headers: { "X-Ubiquity-Kernel-Token": kernelToken },
347+
body: "{}",
348+
});
349+
350+
const first = await getKernelAttestationContext(req, bearerToken);
351+
const second = await getKernelAttestationContext(req, bearerToken);
352+
assert.ok(first);
353+
assert.ok(second);
354+
assert.deepEqual(second, first);
355+
});
356+
284357
addEventListener("unload", () => {
285358
(Deno as unknown as { openKv?: () => Promise<Deno.Kv> }).openKv = originalOpenKv;
286359
});

0 commit comments

Comments
 (0)