Skip to content

Commit e282bf9

Browse files
committed
fix: hash sensitive account log references
1 parent 5324178 commit e282bf9

6 files changed

Lines changed: 119 additions & 45 deletions

File tree

src/auth.js

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { isStickyEnabled, getStickyBinding, setStickyBinding, clearStickyBinding
1515
import { isExperimentalEnabled } from './runtime-config.js';
1616
import { readFileSync, writeFileSync, existsSync, unlinkSync, readdirSync } from 'fs';
1717
import { config, log } from './config.js';
18+
import { safeAccountRef } from './log-safety.js';
1819
import { renameSyncWithRetry } from './fs-atomic.js';
1920
import { getEffectiveProxy } from './dashboard/proxy-config.js';
2021
import { getTierModels, getModelKeysByEnum, MODELS, registerDiscoveredFreeModel } from './models.js';
@@ -429,7 +430,7 @@ export function addAccountByKey(apiKey, label = '', apiServerUrl = '') {
429430
account.credits = null;
430431
accounts.push(account);
431432
saveAccounts();
432-
log.info(`Account added: ${account.id} (${account.email}) [api_key]`);
433+
log.info(`Account added: ${safeAccountRef(account)} [api_key]`);
433434
return account;
434435
}
435436

@@ -462,7 +463,7 @@ export async function addAccountByToken(token, label = '') {
462463
};
463464
accounts.push(account);
464465
saveAccounts();
465-
log.info(`Account added: ${account.id} (${account.email}) [token] server=${account.apiServerUrl}`);
466+
log.info(`Account added: ${safeAccountRef(account)} [token] server=${account.apiServerUrl}`);
466467
return account;
467468
}
468469

@@ -507,7 +508,7 @@ export async function addAccountByEmail(email, password) {
507508
});
508509
}
509510
saveAccounts();
510-
log.info(`Account added via email: ${account.id} (${account.email})`);
511+
log.info(`Account added via email: ${safeAccountRef(account)}`);
511512
return account;
512513
}
513514

@@ -666,7 +667,7 @@ export function removeAccount(id) {
666667
// Drop any Cascade conversations owned by this key so future requests
667668
// don't try to resume on an account that no longer exists.
668669
import('./conversation-pool.js').then(m => m.invalidateFor({ apiKey: account.apiKey })).catch(() => {});
669-
log.info(`Account removed: ${id} (${account.email})`);
670+
log.info(`Account removed: ${safeAccountRef(account)}`);
670671
return true;
671672
}
672673

@@ -883,7 +884,7 @@ function startInflightCleanup() {
883884
const now = Date.now();
884885
for (const a of accounts) {
885886
if ((a._inflight || 0) > 0 && a._inflightAt && (now - a._inflightAt) > INFLIGHT_STALE_MS) {
886-
log.warn(`Account ${a.id} (${a.email}) inflight=${a._inflight} stale >${Math.round((now - a._inflightAt) / 1000)}s, auto-resetting`);
887+
log.warn(`Account ${safeAccountRef(a)} inflight=${a._inflight} stale >${Math.round((now - a._inflightAt) / 1000)}s, auto-resetting`);
887888
a._inflight = 0;
888889
a._inflightAt = 0;
889890
}
@@ -1061,10 +1062,10 @@ export function markRateLimited(apiKey, durationMs = 5 * 60 * 1000, modelKey = n
10611062
if (modelKey) {
10621063
if (!account._modelRateLimits) account._modelRateLimits = {};
10631064
account._modelRateLimits[modelKey] = Math.max(account._modelRateLimits[modelKey] || 0, until);
1064-
log.warn(`Account ${account.id} (${account.email}) rate-limited on ${modelKey} for ${Math.round(safeMs / 60000)} min`);
1065+
log.warn(`Account ${safeAccountRef(account)} rate-limited on ${modelKey} for ${Math.round(safeMs / 60000)} min`);
10651066
} else {
10661067
account.rateLimitedUntil = Math.max(account.rateLimitedUntil || 0, until);
1067-
log.warn(`Account ${account.id} (${account.email}) rate-limited (all models) for ${Math.round(safeMs / 60000)} min`);
1068+
log.warn(`Account ${safeAccountRef(account)} rate-limited (all models) for ${Math.round(safeMs / 60000)} min`);
10681069
}
10691070
}
10701071

@@ -1105,7 +1106,7 @@ export function reportError(apiKey) {
11051106
account.errorCount++;
11061107
if (account.errorCount >= 3) {
11071108
account.status = 'error';
1108-
log.warn(`Account ${account.id} (${account.email}) disabled after ${account.errorCount} errors`);
1109+
log.warn(`Account ${safeAccountRef(account)} disabled after ${account.errorCount} errors`);
11091110
}
11101111
}
11111112

@@ -1141,7 +1142,7 @@ export function reportInternalError(apiKey) {
11411142
account.internalErrorStreak = (account.internalErrorStreak || 0) + 1;
11421143
if (account.internalErrorStreak >= 2) {
11431144
account.rateLimitedUntil = Date.now() + 5 * 60 * 1000;
1144-
log.warn(`Account ${account.id} (${account.email}) quarantined 5min after ${account.internalErrorStreak} consecutive upstream internal errors`);
1145+
log.warn(`Account ${safeAccountRef(account)} quarantined 5min after ${account.internalErrorStreak} consecutive upstream internal errors`);
11451146
}
11461147
}
11471148

@@ -1194,13 +1195,13 @@ export function reportBanSignal(apiKey, message, { windowMs = 30 * 60 * 1000 } =
11941195
account._banSignalAt = now;
11951196
account._banSignalCount = (now - last < windowMs) ? (account._banSignalCount || 0) + 1 : 1;
11961197
account._banSignalLastMessage = String(message || '').slice(0, 240);
1197-
log.warn(`Account ${account.id} (${account.email}) emitted ban-shaped error #${account._banSignalCount}: "${account._banSignalLastMessage}"`);
1198+
log.warn(`Account ${safeAccountRef(account)} emitted ban-shaped error #${account._banSignalCount}: "${account._banSignalLastMessage}"`);
11981199
if (account._banSignalCount >= 2) {
11991200
account.status = 'banned';
12001201
account.bannedAt = now;
12011202
account.bannedReason = account._banSignalLastMessage;
12021203
saveAccounts();
1203-
log.error(`Account ${account.id} (${account.email}) marked BANNED after ${account._banSignalCount} ban-shaped errors`);
1204+
log.error(`Account ${safeAccountRef(account)} marked BANNED after ${account._banSignalCount} ban-shaped errors`);
12041205
// Drop any cascade-pool entries owned by this key.
12051206
import('./conversation-pool.js').then(m => m.invalidateFor({ apiKey })).catch(() => {});
12061207
return true;
@@ -1543,7 +1544,7 @@ export async function fetchUserStatus(id, { allowLsStart = true } = {}) {
15431544
try {
15441545
status = await client.getUserStatus();
15451546
} catch (err) {
1546-
log.warn(`GetUserStatus ${account.id} (${account.email}) failed: ${err.message}`);
1547+
log.warn(`GetUserStatus ${safeAccountRef(account)} failed: ${err.message}`);
15471548
return null;
15481549
}
15491550

@@ -1593,9 +1594,9 @@ export async function fetchUserStatus(id, { allowLsStart = true } = {}) {
15931594
}
15941595

15951596
if (prevTier !== account.tier) {
1596-
log.info(`Tier change ${account.id} (${account.email}): ${prevTier}${account.tier} (plan="${status.planName}", ${status.allowedModels.length} allowed models)`);
1597+
log.info(`Tier change ${safeAccountRef(account)}: ${prevTier}${account.tier} (plan="${status.planName}", ${status.allowedModels.length} allowed models)`);
15971598
} else {
1598-
log.info(`UserStatus ${account.id} (${account.email}): tier=${account.tier} plan="${status.planName}" allowed=${status.allowedModels.length}`);
1599+
log.info(`UserStatus ${safeAccountRef(account)}: tier=${account.tier} plan="${status.planName}" allowed=${status.allowedModels.length}`);
15991600
}
16001601
saveAccounts();
16011602
return status;
@@ -1710,7 +1711,7 @@ async function _probeAccountImpl(account, { allowLsStart = true } = {}) {
17101711
});
17111712

17121713
if (needsProbe.length > 0) {
1713-
log.info(`Probing account ${account.id} (${account.email}) across ${needsProbe.length} canary models (GetUserStatus ${status ? 'OK' : 'unavailable'})`);
1714+
log.info(`Probing ${safeAccountRef(account)} across ${needsProbe.length} canary models (GetUserStatus ${status ? 'OK' : 'unavailable'})`);
17141715

17151716
for (const modelKey of needsProbe) {
17161717
const info = getModelInfo(modelKey);
@@ -1758,7 +1759,7 @@ async function _probeAccountImpl(account, { allowLsStart = true } = {}) {
17581759
}).slice(0, MAX_CLOUD_PROBES);
17591760

17601761
if (cloudCandidates.length > 0) {
1761-
log.info(`Dynamic cloud probe: ${cloudCandidates.length} candidates for ${account.email} (cap=${MAX_CLOUD_PROBES})`);
1762+
log.info(`Dynamic cloud probe: ${cloudCandidates.length} candidates for ${safeAccountRef(account)} (cap=${MAX_CLOUD_PROBES})`);
17621763
let rateLimited = false;
17631764
for (const modelKey of cloudCandidates) {
17641765
if (rateLimited) break;
@@ -2010,12 +2011,12 @@ async function refreshAllFirebaseTokens({ skipBusy = false } = {}) {
20102011
// Re-register to get a fresh API key (may be the same key)
20112012
const { apiKey } = await reRegisterWithCodeium(idToken, proxy);
20122013
if (apiKey && apiKey !== a.apiKey) {
2013-
log.info(`Firebase refresh: ${a.email} got new API key`);
2014+
log.info(`Firebase refresh: ${safeAccountRef(a)} got new API key`);
20142015
a.apiKey = apiKey;
20152016
}
20162017
saveAccounts();
20172018
} catch (e) {
2018-
log.warn(`Firebase refresh ${a.email} failed: ${e.message}`);
2019+
log.warn(`Firebase refresh ${safeAccountRef(a)} failed: ${e.message}`);
20192020
}
20202021
}
20212022
}

src/dashboard/windsurf-login.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import http from 'http';
77
import https from 'https';
88
import { log } from '../config.js';
9+
import { safeEmailRef, safeKeyRef, logHash } from '../log-safety.js';
910
import { isSocks, createSocksTunnel } from '../socks.js';
1011

1112
const FIREBASE_API_KEY = 'AIzaSyDsOl-1XpT5err0Tcnx8FFod1H8gVGIycY';
@@ -402,7 +403,7 @@ async function fetchCheckUserLoginMethod(email, fingerprint, proxy) {
402403
const hasUserField = Object.prototype.hasOwnProperty.call(res.data, 'userExists');
403404
const hasPwField = Object.prototype.hasOwnProperty.call(res.data, 'hasPassword');
404405
if (!hasUserField && !hasPwField) {
405-
log.warn(`CheckUserLoginMethod empty body for ${email}, falling back to /_devin-auth/connections`);
406+
log.warn(`CheckUserLoginMethod empty body for ${safeEmailRef(email)}, falling back to /_devin-auth/connections`);
406407
return null;
407408
}
408409
if (res.data.userExists === false) {
@@ -473,7 +474,7 @@ async function windsurfLoginViaAuth1(email, password, fingerprint, proxy) {
473474
throw new Error(`ERR_AUTH1_TOKEN_MISSING:${JSON.stringify(loginRes.data).slice(0, 200)}`);
474475
}
475476

476-
log.info(`Auth1 login OK: ${email}`);
477+
log.info(`Auth1 login OK: ${safeEmailRef(email)}`);
477478

478479
// v2.0.90 (#114 lnqdev / CharwinYAO): drop OneTimeAuthToken + Codeium
479480
// register_user step entirely. Upstream GetOneTimeAuthToken started
@@ -511,7 +512,7 @@ async function windsurfLoginViaAuth1(email, password, fingerprint, proxy) {
511512
}
512513
const sessionToken = br.data.sessionToken;
513514
const accountId = br.data.accountId || 'unknown';
514-
log.info(`Windsurf PostAuth OK (${bl}): ${email} account=${accountId} → using sessionToken as apiKey`);
515+
log.info(`Windsurf PostAuth OK (${bl}): ${safeEmailRef(email)} accountHash=${logHash(accountId)} → using sessionToken as apiKey`);
515516

516517
return {
517518
apiKey: sessionToken,
@@ -541,10 +542,10 @@ async function windsurfLoginViaFirebase(email, password, fingerprint, proxy) {
541542
const idToken = fbRes.data.idToken;
542543
if (!idToken) throw new Error('ERR_FIREBASE_TOKEN_MISSING');
543544

544-
log.info(`Firebase login OK: ${email}, UID=${fbRes.data.localId}`);
545+
log.info(`Firebase login OK: ${safeEmailRef(email)}, uidHash=${logHash(fbRes.data.localId)}`);
545546

546547
const reg = await registerWithCodeium(idToken, fingerprint, proxy);
547-
log.info(`Codeium register OK: ${email}key=${reg.api_key.slice(0, 20)}...`);
548+
log.info(`Codeium register OK: ${safeEmailRef(email)}${safeKeyRef(reg.api_key, 'apiKey')}`);
548549

549550
return {
550551
apiKey: reg.api_key,
@@ -605,7 +606,7 @@ function recordEmailFailure(email, reason) {
605606
if (e.count >= EMAIL_LOCK_THRESHOLD) {
606607
e.lockedUntil = now + EMAIL_LOCK_DURATION_MS;
607608
e.count = 0;
608-
log.warn(`Email lockout: ${k} banned for ${EMAIL_LOCK_DURATION_MS / 60000}min after ${EMAIL_LOCK_THRESHOLD} failed Windsurf logins (last="${e.lastReason}")`);
609+
log.warn(`Email lockout: ${safeEmailRef(k)} banned for ${EMAIL_LOCK_DURATION_MS / 60000}min after ${EMAIL_LOCK_THRESHOLD} failed Windsurf logins (last="${e.lastReason}")`);
609610
}
610611
}
611612

@@ -633,7 +634,7 @@ export async function windsurfLogin(email, password, proxy = null) {
633634
throw err;
634635
}
635636
const fingerprint = generateFingerprint();
636-
log.info(`Windsurf login: ${email} fp=${fingerprint['User-Agent'].slice(0, 40)}... proxy=${proxy?.host || 'none'}`);
637+
log.info(`Windsurf login: ${safeEmailRef(email)} fpHash=${logHash(fingerprint['User-Agent'])} proxy=${proxy?.host || 'none'}`);
637638

638639
// Probe sequence (per Windsurf 2026-04-26 half-migration):
639640
// 1. CheckUserLoginMethod (new Connect-RPC, fast + clean shape)
@@ -645,7 +646,7 @@ export async function windsurfLogin(email, password, proxy = null) {
645646
try {
646647
auth1Connections = await fetchAuth1Connections(email, fingerprint, proxy);
647648
} catch (err) {
648-
log.warn(`Auth1 connections probe failed for ${email}: ${err.message}`);
649+
log.warn(`Auth1 connections probe failed for ${safeEmailRef(email)}: ${err.message}`);
649650
}
650651
// interpretConnections handles BOTH the old `{auth_method:{...}}`
651652
// and the post-2026-04-26 `{connections:[...]}` shape — Windsurf is

0 commit comments

Comments
 (0)