Harden remote API security boundaries

This commit is contained in:
Bohdan Triapitsyn
2026-06-12 18:24:07 +03:00
parent c281937406
commit 106b31a407
52 changed files with 1582 additions and 579 deletions
@@ -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)) {