fix(image-analysis): harden runtime and provisioning

This commit is contained in:
Tam Nhu Tran committed 2026-04-02 18:36:19 -04:00
1 parent 6d1c895721
commit 77b488f8b1
21 files changed
+811 -169

No files matched your search

+121 -18
View File
@@ -55,11 +55,11 @@ function getTools() {
inputSchema: {
type: 'object',
properties: {
filePath: {
type: 'string',
description:
'Absolute or workspace-relative path to a local image or PDF file to analyze.',
},
filePath: {
type: 'string',
description:
'Workspace-relative path, or an absolute path inside the current workspace, to a local image or PDF file to analyze.',
},
focus: {
type: 'string',
description:
@@ -128,6 +128,20 @@ function normalizeTemplate(value) {
return TEMPLATE_NAMES.includes(normalized) ? normalized : undefined;
}
function normalizePathForComparison(value) {
return process.platform === 'win32' ? value.toLowerCase() : value;
}
function isPathWithinWorkspace(workspaceRoot, candidatePath) {
const relativePath = path.relative(workspaceRoot, candidatePath);
return (
relativePath === '' ||
(!relativePath.startsWith('..') &&
!relativePath.startsWith(`..${path.sep}`) &&
!path.isAbsolute(relativePath))
);
}
function resolveFilePath(toolArgs) {
if (!toolArgs || typeof toolArgs !== 'object') {
return '';
@@ -143,6 +157,60 @@ function resolveFilePath(toolArgs) {
return '';
}
function resolveWorkspaceFilePath(toolArgs) {
const requestedPath = resolveFilePath(toolArgs);
if (!requestedPath) {
return { filePath: '', error: null };
}
const workspaceRoot = (() => {
try {
return fs.realpathSync(process.cwd());
} catch {
return path.resolve(process.cwd());
}
})();
const absolutePath = path.resolve(process.cwd(), requestedPath);
const comparisonPath = (() => {
if (fs.existsSync(absolutePath)) {
return fs.realpathSync(absolutePath);
}
const suffixSegments = [];
let currentPath = absolutePath;
while (!fs.existsSync(currentPath)) {
const parentPath = path.dirname(currentPath);
if (parentPath === currentPath) {
break;
}
suffixSegments.unshift(path.basename(currentPath));
currentPath = parentPath;
}
const resolvedExistingPath = fs.existsSync(currentPath)
? fs.realpathSync(currentPath)
: path.resolve(currentPath);
return path.join(resolvedExistingPath, ...suffixSegments);
})();
if (
!isPathWithinWorkspace(
normalizePathForComparison(workspaceRoot),
normalizePathForComparison(comparisonPath)
)
) {
return {
filePath: '',
error: 'ImageAnalysis only allows files inside the current workspace.',
};
}
return {
filePath: absolutePath,
error: null,
};
}
function resolveFocus(toolArgs) {
return typeof toolArgs.focus === 'string' && toolArgs.focus.trim().length > 0
? toolArgs.focus.trim()
@@ -213,9 +281,13 @@ async function handleToolCall(message) {
return;
}
const filePath = resolveFilePath(toolArgs);
const { filePath, error: filePathError } = resolveWorkspaceFilePath(toolArgs);
if (!filePath) {
writeError(id, -32602, `Tool "${TOOL_NAME}" requires a non-empty filePath.`);
writeError(
id,
-32602,
filePathError || `Tool "${TOOL_NAME}" requires a non-empty filePath.`
);
return;
}
@@ -296,22 +368,53 @@ let inputBuffer = Buffer.alloc(0);
function processIncomingBuffer() {
while (true) {
const newlineIndex = inputBuffer.indexOf(0x0a);
if (newlineIndex === -1) {
return;
}
let body;
const startsWithLegacyHeaders = inputBuffer
.slice(0, Math.min(inputBuffer.length, 32))
.toString('utf8')
.toLowerCase()
.startsWith('content-length:');
const lineBuffer = inputBuffer.subarray(0, newlineIndex);
inputBuffer = inputBuffer.subarray(newlineIndex + 1);
const line = lineBuffer.toString('utf8').replace(/\r$/, '').trim();
if (!line) {
continue;
if (startsWithLegacyHeaders) {
const headerEnd = inputBuffer.indexOf('\r\n\r\n');
if (headerEnd === -1) {
return;
}
const headerText = inputBuffer.slice(0, headerEnd).toString('utf8');
const contentLengthMatch = headerText.match(/content-length:\s*(\d+)/i);
if (!contentLengthMatch) {
inputBuffer = Buffer.alloc(0);
return;
}
const contentLength = Number.parseInt(contentLengthMatch[1], 10);
const messageEnd = headerEnd + 4 + contentLength;
if (inputBuffer.length < messageEnd) {
return;
}
body = inputBuffer.slice(headerEnd + 4, messageEnd).toString('utf8');
inputBuffer = inputBuffer.slice(messageEnd);
} else {
const newlineIndex = inputBuffer.indexOf('\n');
if (newlineIndex === -1) {
return;
}
body = inputBuffer.slice(0, newlineIndex).toString('utf8').replace(/\r$/, '').trim();
inputBuffer = inputBuffer.slice(newlineIndex + 1);
if (!body) {
continue;
}
}
try {
const message = JSON.parse(line);
const message = JSON.parse(body);
Promise.resolve(handleMessage(message)).catch((error) => {
if (process.env.CCS_DEBUG) {
if (message && message.id !== undefined) {
writeError(message.id, -32603, (error && error.message) || 'Internal error');
} else if (process.env.CCS_DEBUG) {
console.error(`[ccs-image-analysis] ${error instanceof Error ? error.stack : error}`);
}
});