Harden remote API security boundaries
This commit is contained in:
@@ -33,8 +33,9 @@ import { useDeviceInfo } from '@/lib/device';
|
||||
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import type { EditorAPI } from '@/lib/api/types';
|
||||
import { isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { getDirectoryForFilePath, isAbsoluteFilePath, normalizeFilePath, toAbsoluteFilePath } from '@/lib/path-utils';
|
||||
import { isDesktopLocalOriginActive, isDesktopShell, isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { ensureOutsideFileGrantForDesktop } from '@/lib/outsideFileGrants';
|
||||
import { getDirectoryForFilePath, isAbsoluteFilePath, isFilePathWithinDirectory, normalizeFilePath, toAbsoluteFilePath } from '@/lib/path-utils';
|
||||
|
||||
const useCurrentMermaidTheme = () => {
|
||||
const themeSystem = useOptionalThemeSystem();
|
||||
@@ -1371,7 +1372,7 @@ const fileReferenceExists = (resolvedPath: string): Promise<boolean> => {
|
||||
};
|
||||
|
||||
const getContextDirectory = (effectiveDirectory: string, resolvedPath: string): string => {
|
||||
return getDirectoryForFilePath(effectiveDirectory, resolvedPath);
|
||||
return effectiveDirectory || getDirectoryForFilePath(effectiveDirectory, resolvedPath);
|
||||
};
|
||||
|
||||
const useFileReferenceInteractions = ({
|
||||
@@ -1441,7 +1442,14 @@ const useFileReferenceInteractions = ({
|
||||
|
||||
linkedCount += 1;
|
||||
|
||||
void fileReferenceExists(resolved.resolvedPath).then((exists) => {
|
||||
const canGrantOutsideFile = isDesktopShell()
|
||||
&& isDesktopLocalOriginActive()
|
||||
&& !isFilePathWithinDirectory(resolved.resolvedPath, effectiveDirectory);
|
||||
const existsPromise = canGrantOutsideFile
|
||||
? Promise.resolve(true)
|
||||
: fileReferenceExists(resolved.resolvedPath);
|
||||
|
||||
void existsPromise.then((exists) => {
|
||||
if (cancelled || !exists || !container.contains(candidate)) {
|
||||
return;
|
||||
}
|
||||
@@ -1464,7 +1472,7 @@ const useFileReferenceInteractions = ({
|
||||
}
|
||||
};
|
||||
|
||||
const openFileReference = (sourceElement: HTMLElement) => {
|
||||
const openFileReference = async (sourceElement: HTMLElement) => {
|
||||
const raw = sourceElement.getAttribute('data-openchamber-file-ref') || extractPathCandidateFromElement(sourceElement);
|
||||
const resolved = getResolvedReference(raw, effectiveDirectory);
|
||||
if (!resolved) {
|
||||
@@ -1485,6 +1493,10 @@ const useFileReferenceInteractions = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFilePathWithinDirectory(resolved.resolvedPath, effectiveDirectory)) {
|
||||
await ensureOutsideFileGrantForDesktop(resolved.resolvedPath, effectiveDirectory);
|
||||
}
|
||||
|
||||
const uiStore = useUIStore.getState();
|
||||
if (Number.isFinite(resolved.line ?? Number.NaN)) {
|
||||
uiStore.openContextFileAtLine(
|
||||
@@ -1514,7 +1526,7 @@ const useFileReferenceInteractions = ({
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
openFileReference(fileRefElement);
|
||||
void openFileReference(fileRefElement);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -1530,7 +1542,7 @@ const useFileReferenceInteractions = ({
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
openFileReference(target);
|
||||
void openFileReference(target);
|
||||
};
|
||||
|
||||
annotateFileLinks();
|
||||
|
||||
@@ -19,11 +19,12 @@ import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useSkillsStore } from '@/stores/useSkillsStore';
|
||||
import { ensureOutsideFileGrantForDesktop } from '@/lib/outsideFileGrants';
|
||||
import ReasoningPart from './ReasoningPart';
|
||||
import JustificationBlock from './JustificationBlock';
|
||||
import { areRenderRelevantPartsEqual } from '../renderCompare';
|
||||
import { getExternalFaviconUrl } from '@/lib/url';
|
||||
import { getDirectoryForFilePath, getRelativeFilePath, normalizeFilePath, toAbsoluteFilePath } from '@/lib/path-utils';
|
||||
import { getDirectoryForFilePath, getRelativeFilePath, isFilePathWithinDirectory, normalizeFilePath, toAbsoluteFilePath } from '@/lib/path-utils';
|
||||
|
||||
const TOOL_ROW_TEXT_CLASS = '!text-[length:var(--text-meta)] !leading-4 sm:!leading-6 tracking-normal';
|
||||
const TOOL_ROW_TITLE_CLASS = cn('typography-meta font-medium', TOOL_ROW_TEXT_CLASS);
|
||||
@@ -638,6 +639,19 @@ const StaticToolRowInner: React.FC<{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFilePathWithinDirectory(absolutePath, currentDirectory)) {
|
||||
void ensureOutsideFileGrantForDesktop(absolutePath, currentDirectory).then(() => {
|
||||
const uiStore = useUIStore.getState();
|
||||
const contextDirectory = currentDirectory || getDirectoryForFilePath(currentDirectory, absolutePath);
|
||||
if (offset && Number.isFinite(offset)) {
|
||||
uiStore.openContextFileAtLine(contextDirectory, absolutePath, Math.max(1, Math.trunc(offset)), 1);
|
||||
return;
|
||||
}
|
||||
uiStore.openContextFile(contextDirectory, absolutePath);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const uiStore = useUIStore.getState();
|
||||
const contextDirectory = getDirectoryForFilePath(currentDirectory, absolutePath);
|
||||
if (offset && Number.isFinite(offset)) {
|
||||
|
||||
Reference in New Issue
Block a user