mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-19 18:15:19 +00:00
fix(image-analysis): harden runtime and provisioning
This commit is contained in:
1 parent
6d1c895721
commit
77b488f8b1
21 files changed
+811
-169
No files matched your search
@@ -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}`);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user