diff --git a/src/auth.js b/src/auth.js
index 1188445..162462c 100644
--- a/src/auth.js
+++ b/src/auth.js
@@ -9,7 +9,7 @@
*/
import { randomUUID } from 'crypto';
-import { readFileSync, writeFileSync, existsSync } from 'fs';
+import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync } from 'fs';
import { config, log } from './config.js';
import { getEffectiveProxy } from './dashboard/proxy-config.js';
import { getTierModels, getModelKeysByEnum, MODELS } from './models.js';
@@ -54,9 +54,12 @@ function saveAccounts() {
userStatus: a.userStatus || null,
userStatusLastFetched: a.userStatusLastFetched || 0,
}));
- writeFileSync(ACCOUNTS_FILE, JSON.stringify(data, null, 2));
+ const tempFile = ACCOUNTS_FILE + '.tmp';
+ writeFileSync(tempFile, JSON.stringify(data, null, 2));
+ renameSync(tempFile, ACCOUNTS_FILE);
} catch (e) {
log.error('Failed to save accounts:', e.message);
+ try { unlinkSync(ACCOUNTS_FILE + '.tmp'); } catch {}
}
}
@@ -552,7 +555,6 @@ export function getAccountList() {
lastUsed: a.lastUsed ? new Date(a.lastUsed).toISOString() : null,
addedAt: new Date(a.addedAt).toISOString(),
keyPrefix: a.apiKey.slice(0, 8) + '...',
- apiKey: a.apiKey,
tier: a.tier || 'unknown',
capabilities: a.capabilities || {},
lastProbed: a.lastProbed || 0,
diff --git a/src/client.js b/src/client.js
index 9bf6195..88c0868 100644
--- a/src/client.js
+++ b/src/client.js
@@ -68,9 +68,11 @@ export class WindsurfClient {
return new Promise((resolve, reject) => {
const chunks = [];
+ let done = false;
grpcStream(this.port, this.csrfToken, `${LS_SERVICE}/RawGetChatMessage`, body, {
onData: (payload) => {
+ if (done) return;
try {
const parsed = parseRawResponse(payload);
if (parsed.text) {
@@ -80,6 +82,7 @@ export class WindsurfClient {
const err = new Error(parsed.text.trim());
// Mark model-level errors so they don't count against the account
err.isModelError = /permission_denied|failed_precondition/.test(parsed.text);
+ done = true;
reject(err);
return;
}
@@ -91,10 +94,14 @@ export class WindsurfClient {
}
},
onEnd: () => {
+ if (done) return;
+ done = true;
onEnd?.(chunks);
resolve(chunks);
},
onError: (err) => {
+ if (done) return;
+ done = true;
onError?.(err);
reject(err);
},
diff --git a/src/connect.js b/src/connect.js
index eb7f2e3..4a5bc37 100644
--- a/src/connect.js
+++ b/src/connect.js
@@ -109,9 +109,13 @@ export class StreamingFrameParser {
/** Drain all complete frames. Returns [{ flags, isEndStream, payload }]. */
drain() {
+ const MAX_FRAME_SIZE = 16 * 1024 * 1024; // 16 MB
const frames = [];
while (this.buffer.length >= 5) {
const len = this.buffer.readUInt32BE(1);
+ if (len > MAX_FRAME_SIZE) {
+ throw new Error(`Frame size ${len} exceeds maximum ${MAX_FRAME_SIZE}`);
+ }
if (this.buffer.length < 5 + len) break;
const flags = this.buffer[0];
diff --git a/src/dashboard/api.js b/src/dashboard/api.js
index e3b5b21..0340c71 100644
--- a/src/dashboard/api.js
+++ b/src/dashboard/api.js
@@ -162,10 +162,12 @@ export async function handleDashboardApi(method, subpath, body, req, res) {
dirtyFiles: dirty.split('\n').slice(0, 20),
});
}
- await runShell(`git fetch origin ${before.branch || 'master'}`);
- await runShell(`git reset --hard origin/${before.branch || 'master'}`);
+ const safeBranch = /^[\w.\-\/]+$/.test(before.branch) ? before.branch : 'master';
+ await runShell(`git fetch origin ${safeBranch}`);
+ await runShell(`git reset --hard origin/${safeBranch}`);
}
- const pullCmd = `git pull origin ${before.branch || 'master'} --ff-only 2>&1`;
+ const safeBranch = /^[\w.\-\/]+$/.test(before.branch) ? before.branch : 'master';
+ const pullCmd = `git pull origin ${safeBranch} --ff-only 2>&1`;
const pull = dirty ? 'hard-reset applied' : await runShell(pullCmd);
const after = await gitStatus();
const changed = before.commit !== after.commit;
diff --git a/src/dashboard/index.html b/src/dashboard/index.html
index e17dbf4..f207c64 100644
--- a/src/dashboard/index.html
+++ b/src/dashboard/index.html
@@ -2133,7 +2133,7 @@
控制台登录
const blockedCount = (a.blockedModels || []).length;
const availCount = tierModels.length - blockedCount;
const capsHtml = tierModels.length
- ? `