From 3648b0da4016857d16beb8674a3723bfc07262ad Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:50:55 +0000 Subject: [PATCH 1/2] fix(security): remove default credentials from example config Removes the hardcoded `auth: "admin:password"` from `torch-config.example.json` to prevent users from accidentally deploying with insecure defaults. Adds a regression test `test/verify-example-config-security.test.mjs` to ensure the example config does not contain default credentials in the future. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- test/verify-example-config-security.test.mjs | 14 ++++++++++++++ torch-config.example.json | 3 +-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 test/verify-example-config-security.test.mjs diff --git a/test/verify-example-config-security.test.mjs b/test/verify-example-config-security.test.mjs new file mode 100644 index 0000000..dcbe3f4 --- /dev/null +++ b/test/verify-example-config-security.test.mjs @@ -0,0 +1,14 @@ +import test from 'node:test'; +import assert from 'node:assert'; +import fs from 'node:fs'; +import path from 'node:path'; + +test('torch-config.example.json Security', (t) => { + const configPath = path.resolve('torch-config.example.json'); + const content = fs.readFileSync(configPath, 'utf8'); + const config = JSON.parse(content); + + if (config.dashboard) { + assert.strictEqual(config.dashboard.auth, undefined, 'Example config should not contain auth field'); + } +}); diff --git a/torch-config.example.json b/torch-config.example.json index aafd0bf..df7ea44 100644 --- a/torch-config.example.json +++ b/torch-config.example.json @@ -18,8 +18,7 @@ }, "dashboard": { "defaultCadenceView": "daily", - "defaultStatusView": "active", - "auth": "admin:password" + "defaultStatusView": "active" }, "scheduler": { "firstPromptByCadence": { From b8e2261be3dacc8af07e9fdcb4393d572b000647 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:57:07 +0000 Subject: [PATCH 2/2] fix(ci): resolve lint errors and duplicate definitions - Fix lint error in `test/verify-example-config-security.test.mjs` (unused var). - Fix duplicate definitions of `mergeRelayList` and `relayListLabel` in `src/lock-ops.mjs` and `src/lock-publisher.mjs`. - Fix missing export of `withTimeout` in `src/lock-utils.mjs`. - Fix broken imports in `test/utils.test.mjs` and `test/utils-async.test.mjs`. - Centralize `secureRandom` in `src/lock-utils.mjs`. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- src/lock-ops.mjs | 14 ------------ src/lock-publisher.mjs | 24 ++------------------ src/lock-utils.mjs | 14 ++++++++---- test/utils-async.test.mjs | 2 +- test/utils.test.mjs | 2 +- test/verify-example-config-security.test.mjs | 2 +- 6 files changed, 14 insertions(+), 44 deletions(-) diff --git a/src/lock-ops.mjs b/src/lock-ops.mjs index e3fbcad..4956243 100644 --- a/src/lock-ops.mjs +++ b/src/lock-ops.mjs @@ -13,7 +13,6 @@ import { defaultHealthManager, buildRelayHealthConfig, RelayHealthManager } from import { publishLock, LockPublisher } from './lock-publisher.mjs'; import { withTimeout, - relayListLabel, mergeRelayList, secureRandom, } from './lock-utils.mjs'; @@ -61,19 +60,6 @@ function filterActiveLocks(locks) { return locks.filter((lock) => !lock.expiresAt || lock.expiresAt > now); } -function withTimeout(promise, timeoutMs, timeoutMessage) { - let timeoutHandle; - const timeoutPromise = new Promise((_, reject) => { - timeoutHandle = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); - }); - return Promise.race([promise, timeoutPromise]).finally(() => { - if (timeoutHandle) clearTimeout(timeoutHandle); - }); -} - -function mergeRelayList(primaryRelays, fallbackRelays) { - return [...new Set([...primaryRelays, ...fallbackRelays])]; -} /** * Queries relays for active lock events matching the criteria. diff --git a/src/lock-publisher.mjs b/src/lock-publisher.mjs index 61fba13..34ea44b 100644 --- a/src/lock-publisher.mjs +++ b/src/lock-publisher.mjs @@ -6,23 +6,9 @@ import { getRelayFallbacks, getMinActiveRelayPool, } from './torch-config.mjs'; -import { mergeRelayList } from './utils.mjs'; +import { mergeRelayList, relayListLabel } from './utils.mjs'; import { defaultHealthManager, buildRelayHealthConfig } from './relay-health-manager.mjs'; -import { relayListLabel } from './utils.mjs'; - -function withTimeout(promise, timeoutMs, timeoutMessage) { - let timeoutHandle; - const timeoutPromise = new Promise((_, reject) => { - timeoutHandle = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); - }); - return Promise.race([promise, timeoutPromise]).finally(() => { - if (timeoutHandle) clearTimeout(timeoutHandle); - }); -} - -function mergeRelayList(primaryRelays, fallbackRelays) { - return [...new Set([...primaryRelays, ...fallbackRelays])]; -} +import { withTimeout, secureRandom } from './lock-utils.mjs'; const PUBLISH_ERROR_CODES = { TIMEOUT: 'publish_timeout', @@ -115,12 +101,6 @@ function isTransientPublishCategory(category) { ].includes(category); } -const MAX_RANDOM = 281474976710655; // 2**48 - 1 - -export function secureRandom() { - return randomInt(0, MAX_RANDOM) / MAX_RANDOM; -} - function calculateBackoffDelayMs(attemptNumber, baseMs, capMs, randomFn = secureRandom) { const maxDelay = Math.min(capMs, baseMs * (2 ** Math.max(0, attemptNumber - 1))); return Math.floor(randomFn() * maxDelay); diff --git a/src/lock-utils.mjs b/src/lock-utils.mjs index dbe1820..974cc06 100644 --- a/src/lock-utils.mjs +++ b/src/lock-utils.mjs @@ -3,7 +3,7 @@ import path from 'node:path'; import { randomInt } from 'node:crypto'; import { todayDateStr, getIsoWeekStr } from './utils.mjs'; -export { relayListLabel } from './utils.mjs'; +export { relayListLabel, mergeRelayList } from './utils.mjs'; /** * Scans the local log directory to identify agents that have already completed their task @@ -51,10 +51,14 @@ export async function getCompletedAgents(cadence, logDir, deps) { return completed; } -export { withTimeout }; - -export function mergeRelayList(primaryRelays, fallbackRelays) { - return [...new Set([...primaryRelays, ...fallbackRelays])]; +export function withTimeout(promise, timeoutMs, timeoutMessage) { + let timeoutHandle; + const timeoutPromise = new Promise((_, reject) => { + timeoutHandle = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); + }); + return Promise.race([promise, timeoutPromise]).finally(() => { + if (timeoutHandle) clearTimeout(timeoutHandle); + }); } const MAX_RANDOM = 281474976710655; // 2**48 - 1 diff --git a/test/utils-async.test.mjs b/test/utils-async.test.mjs index 66b602e..c5d8749 100644 --- a/test/utils-async.test.mjs +++ b/test/utils-async.test.mjs @@ -1,6 +1,6 @@ import { describe, it } from 'node:test'; import assert from 'node:assert'; -import { withTimeout } from '../src/utils.mjs'; +import { withTimeout } from '../src/lock-utils.mjs'; describe('Async Utilities', () => { describe('withTimeout', () => { diff --git a/test/utils.test.mjs b/test/utils.test.mjs index 7051687..40db583 100644 --- a/test/utils.test.mjs +++ b/test/utils.test.mjs @@ -2,7 +2,7 @@ import { describe, it, after } from 'node:test'; import assert from 'node:assert'; import fs from 'node:fs'; import path from 'node:path'; -import { getIsoWeekStr, todayDateStr, nowUnix, ensureDir, detectPlatform, relayListLabel } from '../src/utils.mjs'; +import { getIsoWeekStr, todayDateStr, nowUnix, ensureDir, detectPlatform, relayListLabel, mergeRelayList } from '../src/utils.mjs'; describe('Date Utilities', () => { describe('todayDateStr', () => { diff --git a/test/verify-example-config-security.test.mjs b/test/verify-example-config-security.test.mjs index dcbe3f4..fffd5b2 100644 --- a/test/verify-example-config-security.test.mjs +++ b/test/verify-example-config-security.test.mjs @@ -3,7 +3,7 @@ import assert from 'node:assert'; import fs from 'node:fs'; import path from 'node:path'; -test('torch-config.example.json Security', (t) => { +test('torch-config.example.json Security', () => { const configPath = path.resolve('torch-config.example.json'); const content = fs.readFileSync(configPath, 'utf8'); const config = JSON.parse(content);