Disallow unsafe type assertions (#18688)

This commit is contained in:
Christian Gunderman
2026-02-10 00:10:15 +00:00
committed by GitHub
parent bce1caefd0
commit fd65416a2f
188 changed files with 592 additions and 47 deletions

View File

@@ -80,6 +80,7 @@ export async function bfsFileSearch(
return { currentDir, entries };
} catch (error) {
// Warn user that a directory could not be read, as this affects search results.
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const message = (error as Error)?.message ?? 'Unknown error';
debugLogger.warn(
`[WARN] Skipping unreadable directory: ${currentDir} (${message})`,
@@ -153,6 +154,7 @@ export function bfsFileSearchSync(
foundFiles,
);
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const message = (error as Error)?.message ?? 'Unknown error';
debugLogger.warn(
`[WARN] Skipping unreadable directory: ${currentDir} (${message})`,

View File

@@ -49,6 +49,7 @@ export function generateCheckpointFileName(
toolCall: ToolCallRequestInfo,
): string | null {
const toolArgs = toolCall.args;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const toolFilePath = toolArgs['file_path'] as string;
if (!toolFilePath) {
@@ -167,6 +168,7 @@ export function getCheckpointInfoList(
for (const [file, content] of checkpointFiles) {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const toolCallData = JSON.parse(content) as ToolCallData;
if (toolCallData.messageId) {
checkpointInfoList.push({

View File

@@ -208,9 +208,12 @@ export async function resolveEditorAsync(
coreEvents.emit(CoreEvent.RequestEditorSelection);
return once(coreEvents, CoreEvent.EditorSelected, { signal })
.then(([payload]) => (payload as EditorSelectedPayload).editor)
.catch(() => undefined);
return (
once(coreEvents, CoreEvent.EditorSelected, { signal })
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
.then(([payload]) => (payload as EditorSelectedPayload).editor)
.catch(() => undefined)
);
}
/**

View File

@@ -98,6 +98,7 @@ interface ResponseData {
export function toFriendlyError(error: unknown): unknown {
if (error && typeof error === 'object' && 'response' in error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const gaxiosError = error as GaxiosError;
const data = parseResponseData(gaxiosError);
if (data && data.error && data.error.message && data.error.code) {
@@ -122,11 +123,13 @@ function parseResponseData(error: GaxiosError): ResponseData | undefined {
// Inexplicably, Gaxios sometimes doesn't JSONify the response data.
if (typeof error.response?.data === 'string') {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return JSON.parse(error.response?.data) as ResponseData;
} catch {
return undefined;
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return error.response?.data as ResponseData | undefined;
}

View File

@@ -199,14 +199,14 @@ export class CoreEventEmitter extends EventEmitter<CoreEvents> {
if (this._eventBacklog.length >= CoreEventEmitter.MAX_BACKLOG_SIZE) {
this._eventBacklog.shift();
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
this._eventBacklog.push({ event, args } as EventBacklogItem);
} else {
(
this.emit as <K extends keyof CoreEvents>(
event: K,
...args: CoreEvents[K]
) => boolean
)(event, ...args);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(this.emit as (event: K, ...args: CoreEvents[K]) => boolean)(
event,
...args,
);
}
}
@@ -319,12 +319,11 @@ export class CoreEventEmitter extends EventEmitter<CoreEvents> {
const backlog = [...this._eventBacklog];
this._eventBacklog.length = 0; // Clear in-place
for (const item of backlog) {
(
this.emit as <K extends keyof CoreEvents>(
event: K,
...args: CoreEvents[K]
) => boolean
)(item.event, ...item.args);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(this.emit as (event: keyof CoreEvents, ...args: unknown[]) => boolean)(
item.event,
...item.args,
);
}
}
}

View File

@@ -102,6 +102,7 @@ export function convertToFunctionResponse(
if (inlineDataParts.length > 0) {
if (isMultimodalFRSupported) {
// Nest inlineData if supported by the model
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(part.functionResponse as unknown as { parts: Part[] }).parts =
inlineDataParts;
} else {
@@ -151,6 +152,7 @@ export function getFunctionCalls(
}
const functionCallParts = parts
.filter((part) => !!part.functionCall)
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
.map((part) => part.functionCall as FunctionCall);
return functionCallParts.length > 0 ? functionCallParts : undefined;
}
@@ -163,6 +165,7 @@ export function getFunctionCallsFromParts(
}
const functionCallParts = parts
.filter((part) => !!part.functionCall)
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
.map((part) => part.functionCall as FunctionCall);
return functionCallParts.length > 0 ? functionCallParts : undefined;
}

View File

@@ -195,6 +195,7 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null {
if (Array.isArray(errorDetails)) {
for (const detail of errorDetails) {
if (detail && typeof detail === 'object') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const detailObj = detail as Record<string, unknown>;
const typeKey = Object.keys(detailObj).find(
(key) => key.trim() === '@type',
@@ -205,6 +206,7 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null {
delete detailObj[typeKey];
}
// We can just cast it; the consumer will have to switch on @type
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
details.push(detailObj as unknown as GoogleApiErrorDetail);
}
}
@@ -253,6 +255,7 @@ function fromGaxiosError(errorObj: object): ErrorShape | undefined {
if (typeof data === 'object' && data !== null) {
if ('error' in data) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
outerError = (data as { error: ErrorShape }).error;
}
}
@@ -309,6 +312,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
if (typeof data === 'object' && data !== null) {
if ('error' in data) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
outerError = (data as { error: ErrorShape }).error;
}
}

View File

@@ -24,9 +24,10 @@ export function getErrorStatus(error: unknown): number | undefined {
typeof (error as { response?: unknown }).response === 'object' &&
(error as { response?: unknown }).response !== null
) {
const response = (
error as { response: { status?: unknown; headers?: unknown } }
).response;
const response =
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(error as { response: { status?: unknown; headers?: unknown } })
.response;
if ('status' in response && typeof response.status === 'number') {
return response.status;
}

View File

@@ -107,6 +107,7 @@ async function generateJsonWithTimeout<T>(
timeoutSignal,
]),
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return result as T;
} catch (err) {
debugLogger.debug(

View File

@@ -54,6 +54,7 @@ async function findProjectRoot(startDir: string): Promise<string | null> {
typeof error === 'object' &&
error !== null &&
'code' in error &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(error as { code: string }).code === 'ENOENT';
// Only log unexpected errors in non-test environments
@@ -63,6 +64,7 @@ async function findProjectRoot(startDir: string): Promise<string | null> {
if (!isENOENT && !isTestEnv) {
if (typeof error === 'object' && error !== null && 'code' in error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const fsError = error as { code: string; message: string };
logger.warn(
`Error checking for .git directory at ${gitPath}: ${fsError.message}`,
@@ -311,6 +313,7 @@ export function concatenateInstructions(
return instructionContents
.filter((item) => typeof item.content === 'string')
.map((item) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const trimmedContent = (item.content as string).trim();
if (trimmedContent.length === 0) {
return null;
@@ -359,6 +362,7 @@ export async function loadGlobalMemory(
.filter((item) => item.content !== null)
.map((item) => ({
path: item.filePath,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
content: item.content as string,
})),
};
@@ -456,6 +460,7 @@ export async function loadEnvironmentMemory(
.filter((item) => item.content !== null)
.map((item) => ({
path: item.filePath,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
content: item.content as string,
})),
};
@@ -640,6 +645,7 @@ export async function loadJitSubdirectoryMemory(
.filter((item) => item.content !== null)
.map((item) => ({
path: item.filePath,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
content: item.content as string,
})),
};

View File

@@ -109,6 +109,7 @@ export async function checkNextSpeaker(
];
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const parsedResponse = (await baseLlmClient.generateJson({
modelConfigKey: { model: 'next-speaker-checker' },
contents,

View File

@@ -30,6 +30,7 @@ export function partToString(
}
// Cast to Part, assuming it might contain project-specific fields
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const part = value as Part & {
videoMetadata?: unknown;
thought?: string;

View File

@@ -20,7 +20,9 @@ export function isApiError(error: unknown): error is ApiError {
typeof error === 'object' &&
error !== null &&
'error' in error &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
typeof (error as ApiError).error === 'object' &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
'message' in (error as ApiError).error
);
}
@@ -30,6 +32,7 @@ export function isStructuredError(error: unknown): error is StructuredError {
typeof error === 'object' &&
error !== null &&
'message' in error &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
typeof (error as StructuredError).message === 'string'
);
}

View File

@@ -68,6 +68,7 @@ function getNetworkErrorCode(error: unknown): string | undefined {
return undefined;
}
if ('code' in obj && typeof (obj as { code: unknown }).code === 'string') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return (obj as { code: string }).code;
}
return undefined;
@@ -196,6 +197,7 @@ export async function retryWithBackoff<T>(
if (
shouldRetryOnContent &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
shouldRetryOnContent(result as GenerateContentResponse)
) {
const jitter = currentDelay * 0.3 * (Math.random() * 2 - 1);
@@ -327,6 +329,7 @@ export async function retryWithBackoff<T>(
// Generic retry logic for other errors
if (
attempt >= maxAttempts ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
!shouldRetryOnError(error as Error, retryFetchErrors)
) {
throw error;

View File

@@ -56,6 +56,7 @@ function removeEmptyObjects(data: any): object {
export function safeJsonStringifyBooleanValuesOnly(obj: any): string {
let configSeen = false;
return JSON.stringify(removeEmptyObjects(obj), (key, value) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
if ((value as Config) !== null && !configSeen) {
configSeen = true;
return value;

View File

@@ -12,9 +12,9 @@ import * as addFormats from 'ajv-formats';
import { debugLogger } from './debugLogger.js';
// Ajv's ESM/CJS interop: use 'any' for compatibility as recommended by Ajv docs
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
const AjvClass = (AjvPkg as any).default || AjvPkg;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
const Ajv2020Class = (Ajv2020Pkg as any).default || Ajv2020Pkg;
const ajvOptions = {
@@ -34,7 +34,7 @@ const ajvDefault: Ajv = new AjvClass(ajvOptions);
// Draft-2020-12 validator for MCP servers using rmcp
const ajv2020: Ajv = new Ajv2020Class(ajvOptions);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
const addFormatsFunc = (addFormats as any).default || addFormats;
addFormatsFunc(ajvDefault);
addFormatsFunc(ajv2020);
@@ -90,6 +90,7 @@ export class SchemaValidator {
// This matches LenientJsonSchemaValidator behavior in mcp-client.ts.
debugLogger.warn(
`Failed to compile schema (${
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(schema as Record<string, unknown>)?.['$schema'] ?? '<no $schema>'
}): ${error instanceof Error ? error.message : String(error)}. ` +
'Skipping parameter validation.',
@@ -121,6 +122,7 @@ export class SchemaValidator {
// Skip validation rather than blocking tool usage.
debugLogger.warn(
`Failed to validate schema (${
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(schema as Record<string, unknown>)?.['$schema'] ?? '<no $schema>'
}): ${error instanceof Error ? error.message : String(error)}. ` +
'Skipping schema validation.',

View File

@@ -66,6 +66,7 @@ export async function isDirectorySecure(
} catch (error) {
return {
secure: false,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
reason: `A security check for the system policy directory '${dirPath}' failed and could not be completed. Please file a bug report. Original error: ${(error as Error).message}`,
};
}
@@ -93,11 +94,13 @@ export async function isDirectorySecure(
return { secure: true };
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return { secure: true };
}
return {
secure: false,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
reason: `Failed to access directory: ${(error as Error).message}`,
};
}

View File

@@ -237,6 +237,7 @@ function parseCommandTree(
progressCallback: () => {
if (performance.now() > deadline) {
timedOut = true;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return true as unknown as void; // Returning true cancels parsing, but type says void
}
},

View File

@@ -52,6 +52,26 @@ export function disableSimulationAfterFallback(): void {
fallbackOccurred = true;
}
/**
* Create a simulated 429 error response
*/
export function createSimulated429Error(): Error {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const error = new Error('Rate limit exceeded (simulated)') as Error & {
status: number;
};
error.status = 429;
return error;
}
/**
* Reset simulation state when switching auth methods
*/
export function resetSimulationState(): void {
fallbackOccurred = false;
resetRequestCounter();
}
/**
* Enable/disable 429 simulation programmatically (for tests)
*/

View File

@@ -88,6 +88,7 @@ function estimateFunctionResponseTokens(part: Part, depth: number): number {
}
// Gemini 3: Handle nested multimodal parts recursively.
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const nestedParts = (fr as unknown as { parts?: Part[] }).parts;
if (nestedParts && nestedParts.length > 0) {
totalTokens += estimateTokenCountSync(nestedParts, depth + 1);

View File

@@ -104,6 +104,7 @@ export function doesToolInvocationMatch(
// This invocation has no command - nothing to check.
continue;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
command = String((invocation.params as { command: string }).command);
}

View File

@@ -37,6 +37,7 @@ export class UserAccountManager {
debugLogger.log('Invalid accounts file schema, starting fresh.');
return defaultState;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const { active, old } = parsed as Partial<UserAccounts>;
const isValid =
(active === undefined || active === null || typeof active === 'string') &&