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

@@ -175,6 +175,7 @@ export class ActivateSkillTool extends BaseDeclarativeTool<
} else {
schema = z.object({
name: z
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
.enum(skillNames as [string, ...string[]])
.describe('The name of the skill to activate.'),
});

View File

@@ -875,6 +875,7 @@ class LenientJsonSchemaValidator implements jsonSchemaValidator {
);
return (input: unknown) => ({
valid: true as const,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
data: input as T,
errorMessage: undefined,
});
@@ -889,6 +890,7 @@ export function populateMcpServerCommand(
): Record<string, MCPServerConfig> {
if (mcpServerCommand) {
const cmd = mcpServerCommand;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const args = parse(cmd, process.env) as string[];
if (args.some((arg) => typeof arg !== 'string')) {
throw new Error('failed to parse mcpServerCommand: ' + cmd);
@@ -1068,6 +1070,7 @@ export async function discoverTools(
'error',
`Error discovering tool: '${
toolDef.name
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
}' from MCP server '${mcpServerName}': ${(error as Error).message}`,
error,
);
@@ -1121,6 +1124,7 @@ class McpCallableTool implements CallableTool {
const result = await this.client.callTool(
{
name: call.name!,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
arguments: call.args as Record<string, unknown>,
},
undefined,
@@ -1550,6 +1554,7 @@ export async function connectToMcpServer(
return { client: mcpClient, transport };
} catch (error) {
await transport.close();
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
firstAttemptError = error as Error;
throw error;
}
@@ -1589,6 +1594,7 @@ export async function connectToMcpServer(
);
return { client: mcpClient, transport: sseTransport };
} catch (sseFallbackError) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
sseError = sseFallbackError as Error;
// If SSE also returned 401, handle OAuth below
@@ -1929,6 +1935,7 @@ export async function createTransport(
let transport: Transport = new StdioClientTransport({
command: mcpServerConfig.command,
args: mcpServerConfig.args || [],
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
env: sanitizeEnvironment(
{
...process.env,
@@ -1965,7 +1972,7 @@ export async function createTransport(
const underlyingTransport =
transport instanceof XcodeMcpBridgeFixTransport
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
? // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
(transport as any).transport
: transport;

View File

@@ -373,6 +373,7 @@ function transformResourceLinkBlock(block: McpResourceLinkBlock): Part {
*/
function transformMcpContentToParts(sdkResponse: Part[]): Part[] {
const funcResponse = sdkResponse?.[0]?.functionResponse;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const mcpContent = funcResponse?.response?.['content'] as McpContentBlock[];
const toolName = funcResponse?.name || 'unknown tool';
@@ -410,6 +411,7 @@ function transformMcpContentToParts(sdkResponse: Part[]): Part[] {
* @returns A formatted string representing the tool's output.
*/
function getStringifiedResultForDisplay(rawResponse: Part[]): string {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const mcpContent = rawResponse?.[0]?.functionResponse?.response?.[
'content'
] as McpContentBlock[];

View File

@@ -94,6 +94,7 @@ async function readMemoryFileContent(): Promise<string> {
try {
return await fs.readFile(getGlobalMemoryFilePath(), 'utf-8');
} catch (err) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const error = err as Error & { code?: string };
if (!(error instanceof Error) || error.code !== 'ENOENT') throw err;
return '';

View File

@@ -265,7 +265,9 @@ export class ToolRegistry {
}
if (priorityA === 2) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const serverA = (toolA as DiscoveredMCPTool).serverName;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const serverB = (toolB as DiscoveredMCPTool).serverName;
return serverA.localeCompare(serverB);
}
@@ -319,6 +321,7 @@ export class ToolRegistry {
'Tool discovery command is empty or contains only whitespace.',
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const proc = spawn(cmdParts[0] as string, cmdParts.slice(1) as string[]);
let stdout = '';
const stdoutDecoder = new StringDecoder('utf8');
@@ -398,6 +401,7 @@ export class ToolRegistry {
} else if (Array.isArray(tool['functionDeclarations'])) {
functions.push(...tool['functionDeclarations']);
} else if (tool['name']) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
functions.push(tool as FunctionDeclaration);
}
}
@@ -420,6 +424,7 @@ export class ToolRegistry {
func.name,
DISCOVERED_TOOL_PREFIX + func.name,
func.description ?? '',
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
parameters as Record<string, unknown>,
this.messageBus,
),
@@ -552,6 +557,7 @@ export class ToolRegistry {
getToolsByServer(serverName: string): AnyDeclarativeTool[] {
const serverTools: AnyDeclarativeTool[] = [];
for (const tool of this.getActiveTools()) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
if ((tool as DiscoveredMCPTool)?.serverName === serverName) {
serverTools.push(tool);
}

View File

@@ -195,6 +195,7 @@ export abstract class BaseToolInvocation<
correlationId,
toolCall: {
name: this._toolName,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
args: this.params as Record<string, unknown>,
},
serverName: this._serverName,
@@ -536,6 +537,7 @@ export function isTool(obj: unknown): obj is AnyDeclarativeTool {
obj !== null &&
'name' in obj &&
'build' in obj &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
typeof (obj as AnyDeclarativeTool).build === 'function'
);
}
@@ -590,8 +592,10 @@ export function hasCycleInSchema(schema: object): boolean {
) {
return null;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
current = (current as Record<string, unknown>)[segment];
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return current as object;
}
@@ -639,6 +643,7 @@ export function hasCycleInSchema(schema: object): boolean {
if (Object.prototype.hasOwnProperty.call(node, key)) {
if (
traverse(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(node as Record<string, unknown>)[key],
visitedRefs,
pathRefs,

View File

@@ -194,6 +194,7 @@ ${textContent}
returnDisplay: `Content for ${url} processed using fallback fetch.`,
};
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const error = e as Error;
const errorMessage = `Error during fallback fetch for ${url}: ${error.message}`;
return {
@@ -291,6 +292,7 @@ ${textContent}
const sources = groundingMetadata?.groundingChunks as
| GroundingChunkItem[]
| undefined;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const groundingSupports = groundingMetadata?.groundingSupports as
| GroundingSupportItem[]
| undefined;

View File

@@ -91,6 +91,7 @@ class WebSearchToolInvocation extends BaseToolInvocation<
const sources = groundingMetadata?.groundingChunks as
| GroundingChunkItem[]
| undefined;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const groundingSupports = groundingMetadata?.groundingSupports as
| GroundingSupportItem[]
| undefined;

View File

@@ -75,7 +75,7 @@ export class XcodeMcpBridgeFixTransport
// We can cast because we verified 'result' is in response,
// but TS might still be picky if the type is a strict union.
// Let's treat it safely.
// 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 result = response.result as any;
// Check if we have content but missing structuredContent