Files
hermes-web-ui/tests/server/workspace-path.test.ts
Zhicheng Han 904130de32 [codex] 拆分 Group Chat 房间工作区绑定 (#1980)
* Add group chat room workspace binding

* fix group chat workspace modal width

---------

Co-authored-by: ekko <fqsy1416@gmail.com>
2026-07-08 09:00:51 +08:00

103 lines
5.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
describe('workspace path validation', () => {
let root: string
let originalWorkspaceBase: string | undefined
beforeEach(async () => {
vi.resetModules()
root = await mkdtemp(join(tmpdir(), 'hermes-workspace-path-'))
originalWorkspaceBase = process.env.WORKSPACE_BASE
process.env.WORKSPACE_BASE = root
})
afterEach(async () => {
if (originalWorkspaceBase === undefined) delete process.env.WORKSPACE_BASE
else process.env.WORKSPACE_BASE = originalWorkspaceBase
await rm(root, { recursive: true, force: true })
})
it('resolves valid folders under the configured base', async () => {
const folder = join(root, 'project')
await mkdir(folder)
const { resolveAllowedWorkspaceFolder } = await import('../../packages/server/src/services/hermes/workspace-path')
await expect(resolveAllowedWorkspaceFolder('project')).resolves.toEqual({ base: root, fullPath: folder })
await expect(resolveAllowedWorkspaceFolder(folder)).resolves.toEqual({ base: root, fullPath: folder })
})
it('rejects traversal outside the base', async () => {
const outside = await mkdtemp(join(tmpdir(), 'hermes-workspace-path-outside-'))
const { resolveAllowedWorkspaceFolder, assertAllowedWorkspaceFolder } = await import('../../packages/server/src/services/hermes/workspace-path')
await expect(resolveAllowedWorkspaceFolder(outside)).resolves.toBeNull()
await expect(assertAllowedWorkspaceFolder(outside)).rejects.toMatchObject({ status: 403 })
await rm(outside, { recursive: true, force: true })
})
it('rejects files and empty strings as workspace folders', async () => {
await writeFile(join(root, 'file.txt'), 'not a folder')
const { resolveAllowedWorkspaceFolder, assertAllowedWorkspaceFolder } = await import('../../packages/server/src/services/hermes/workspace-path')
await expect(resolveAllowedWorkspaceFolder('file.txt')).resolves.toBeNull()
await expect(resolveAllowedWorkspaceFolder('')).resolves.toBeNull()
await expect(assertAllowedWorkspaceFolder('')).rejects.toMatchObject({ status: 400 })
})
it('normalizes native Windows drive paths when drive mode is active', async () => {
const originalPlatform = process.platform
const originalWorkspaceBase = process.env.WORKSPACE_BASE
Object.defineProperty(process, 'platform', { value: 'win32' })
delete process.env.WORKSPACE_BASE
try {
const { normalizeWindowsWorkspacePath } = await import('../../packages/server/src/services/hermes/workspace-path')
expect(normalizeWindowsWorkspacePath('c:/Users/Alice/Project')).toEqual({
base: 'C:\\',
fullPath: 'c:\\Users\\Alice\\Project',
})
} finally {
Object.defineProperty(process, 'platform', { value: originalPlatform })
if (originalWorkspaceBase === undefined) delete process.env.WORKSPACE_BASE
else process.env.WORKSPACE_BASE = originalWorkspaceBase
}
})
it('requires realpath containment on Windows when WORKSPACE_BASE is configured', async () => {
const originalPlatform = process.platform
Object.defineProperty(process, 'platform', { value: 'win32' })
try {
const { isWorkspaceListPathAllowed } = await import('../../packages/server/src/services/hermes/workspace-path')
const directoryStat = async () => ({ isDirectory: () => true }) as any
const escapedViaJunction = vi.fn(async () => false)
const withinBase = vi.fn(async () => true)
await expect(isWorkspaceListPathAllowed('C:\\base\\link', 'C:\\base', directoryStat, { realPathWithinFn: escapedViaJunction })).resolves.toBe(false)
expect(escapedViaJunction).toHaveBeenCalledWith('C:\\base\\link', 'C:\\base')
await expect(isWorkspaceListPathAllowed('C:\\base\\link', 'C:\\base', directoryStat, { trustWindowsJunctions: true, realPathWithinFn: escapedViaJunction })).resolves.toBe(false)
await expect(isWorkspaceListPathAllowed('C:\\base\\project', 'C:\\base', directoryStat, { realPathWithinFn: withinBase })).resolves.toBe(true)
expect(withinBase).toHaveBeenCalledWith('C:\\base\\project', 'C:\\base')
} finally {
Object.defineProperty(process, 'platform', { value: originalPlatform })
}
})
it('keeps Windows drive-root mode listable without a configured base', async () => {
const originalPlatform = process.platform
Object.defineProperty(process, 'platform', { value: 'win32' })
try {
const { isWorkspaceListPathAllowed } = await import('../../packages/server/src/services/hermes/workspace-path')
const directoryStat = async () => ({ isDirectory: () => true }) as any
const realPathWithinFn = vi.fn(async () => false)
await expect(isWorkspaceListPathAllowed('C:\\Users\\Alice', 'C:\\', directoryStat, { trustWindowsDriveRoot: true, realPathWithinFn })).resolves.toBe(true)
expect(realPathWithinFn).not.toHaveBeenCalled()
} finally {
Object.defineProperty(process, 'platform', { value: originalPlatform })
}
})
})