fix(cursor): address code review feedback (attempt 1/5)

- Fix bitwise shift overflow in generateChecksum: use Math.trunc
  division for >>40/>>32 which wrap modulo 32 in JS
- Add FIXED64/FIXED32 bounds checks in protobuf decoder to prevent
  out-of-bounds slice on truncated buffers
- Consolidate COMPRESS_FLAG to single definition in schema (DRY):
  executor now imports from schema, added GZIP_ALT/GZIP_BOTH values
- Fix token split edge case: use indexOf+slice instead of split('::')[1]
  to handle tokens containing multiple :: delimiters
- Fix AbortSignal listener leak: store handler ref, use once:true,
  remove listener on request end/error
This commit is contained in:
Tam Nhu Tran
2026-02-11 19:27:39 +07:00
parent cc5a9039e4
commit 31f574118d
3 changed files with 27 additions and 17 deletions
+19 -17
View File
@@ -10,13 +10,7 @@ import { generateCursorBody, extractTextFromResponse } from './cursor-protobuf.j
import { buildCursorRequest } from './cursor-translator.js';
import type { CursorTool } from './cursor-protobuf-schema.js';
/** Compression flags for response parsing */
const COMPRESS_FLAG = {
NONE: 0x00,
GZIP: 0x01,
GZIP_ALT: 0x02,
GZIP_BOTH: 0x03,
} as const;
import { COMPRESS_FLAG } from './cursor-protobuf-schema.js';
/** Cursor credentials structure */
interface CursorCredentials {
@@ -163,12 +157,14 @@ export class CursorExecutor {
*/
generateChecksum(machineId: string): string {
const timestamp = Math.floor(Date.now() / 1000000);
// JS bitwise shifts wrap modulo 32, so >>40 and >>32 give wrong results.
// Use Math.trunc division for upper bytes that exceed 32-bit range.
const byteArray = new Uint8Array([
(timestamp >> 40) & 0xff,
(timestamp >> 32) & 0xff,
(timestamp >> 24) & 0xff,
(timestamp >> 16) & 0xff,
(timestamp >> 8) & 0xff,
Math.trunc(timestamp / 2 ** 40) & 0xff,
Math.trunc(timestamp / 2 ** 32) & 0xff,
(timestamp >>> 24) & 0xff,
(timestamp >>> 16) & 0xff,
(timestamp >>> 8) & 0xff,
timestamp & 0xff,
]);
@@ -209,7 +205,8 @@ export class CursorExecutor {
throw new Error('Machine ID is required for Cursor API');
}
const cleanToken = accessToken.includes('::') ? accessToken.split('::')[1] : accessToken;
const delimIdx = accessToken.indexOf('::');
const cleanToken = delimIdx !== -1 ? accessToken.slice(delimIdx + 2) : accessToken;
return {
authorization: `Bearer ${cleanToken}`,
@@ -326,13 +323,18 @@ export class CursorExecutor {
});
if (signal) {
// TODO: AbortSignal listener is not removed after request completes.
// To fix: store handler reference, remove in end/error callbacks.
signal.addEventListener('abort', () => {
const onAbort = () => {
req.close();
client.close();
reject(new Error('Request aborted'));
});
};
signal.addEventListener('abort', onAbort, { once: true });
const cleanup = () => {
signal.removeEventListener('abort', onAbort);
};
req.on('end', cleanup);
req.on('error', cleanup);
}
req.write(body);
+6
View File
@@ -56,9 +56,15 @@ export function decodeField(
value = buffer.slice(pos2, pos2 + length);
pos = pos2 + length;
} else if (wireType === WIRE_TYPE.FIXED64) {
if (pos + 8 > buffer.length) {
return [null, null, null, buffer.length];
}
value = buffer.slice(pos, pos + 8);
pos += 8;
} else if (wireType === WIRE_TYPE.FIXED32) {
if (pos + 4 > buffer.length) {
return [null, null, null, buffer.length];
}
value = buffer.slice(pos, pos + 4);
pos += 4;
} else {
+2
View File
@@ -201,4 +201,6 @@ export interface MessageId {
export const COMPRESS_FLAG = {
NONE: 0x00,
GZIP: 0x01,
GZIP_ALT: 0x02,
GZIP_BOTH: 0x03,
} as const;