From 88d14303f42d9c0cdcfce26e09415de73a1d1f14 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:22:54 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20Fix=20timing=20attack=20vulnerability=20in=20A2A=20token?= =?UTF-8?q?=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jules --- .jules/sentinel.md | 4 ++++ src/cli/commands/serve.ts | 9 +++------ 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..a8710b28 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-05-24 - Timing Attack via Length Check Fast Path +**Vulnerability:** A length check before calling `crypto.timingSafeEqual` for variable-length secrets (like A2A tokens) created a short-circuit fast path, leaking the length of the secret via timing differences. +**Learning:** `crypto.timingSafeEqual` throws if buffer lengths do not match, tempting developers to check length first. However, doing so destroys the constant-time guarantee because different lengths return immediately. +**Prevention:** Always hash both the user input and the expected secret using a cryptographic hash function (e.g., SHA-256) to ensure they are the exact same fixed length before calling `timingSafeEqual`. diff --git a/src/cli/commands/serve.ts b/src/cli/commands/serve.ts index 2c37293e..0bf874ce 100644 --- a/src/cli/commands/serve.ts +++ b/src/cli/commands/serve.ts @@ -346,13 +346,10 @@ export async function handleServeCommand(_options: unknown, command: Command) { let isAuthenticated = false; if (scheme?.toLowerCase() === 'bearer' && token) { - const tokenBuffer = Buffer.from(token); + const tokenHash = crypto.createHash('sha256').update(token).digest(); for (const authToken of authTokens) { - const authTokenBuffer = Buffer.from(authToken); - if ( - tokenBuffer.length === authTokenBuffer.length && - crypto.timingSafeEqual(tokenBuffer, authTokenBuffer) - ) { + const authHash = crypto.createHash('sha256').update(authToken).digest(); + if (crypto.timingSafeEqual(tokenHash, authHash)) { isAuthenticated = true; break; } From c38324fcbbb81b334c301353124d7bf7a6910c42 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:26:24 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20Fix=20timing=20attack=20vulnerability=20in=20A2A=20token?= =?UTF-8?q?=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jules From 850745e37dcc42f0527efc54c1e187d6ecbed256 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:30:51 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20Fix=20timing=20attack=20vulnerability=20in=20A2A=20token?= =?UTF-8?q?=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jules --- tests/integration/prompt_templates.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/prompt_templates.test.ts b/tests/integration/prompt_templates.test.ts index 21905259..f1360d78 100644 --- a/tests/integration/prompt_templates.test.ts +++ b/tests/integration/prompt_templates.test.ts @@ -36,7 +36,9 @@ describe('Prompt templates', () => { expect(planSystem).toContain('You are SalmonLoop.'); expect(patchSystem).toContain('You are PATCH, a phase-native diff compiler.'); - expect(autopilotSystem).toContain('You are a senior software engineer running in "autopilot" mode.'); + expect(autopilotSystem).toContain( + 'You are a senior software engineer running in "autopilot" mode.', + ); expect(answerSystem).toContain('You are a coding assistant in "answer" mode.'); expect(researchSystem).toContain('You are a research assistant.'); });