Architecture
Overview
llm-chat-workspace provides workspace and path-access management for the llm-chat ecosystem. It is used by llm-chat-file and llm-chat-shell.
Design
Access model
DirectoryConfiguration defines which directories are accessible and at what permission level. Each access entry pairs an AccessType (read or write) with an absolute directory path. The configuration can be built from constructor arguments or read from LLM_CHAT_WORKSPACE_* environment variables, and is deduplicated with write access taking precedence over read access.
Workspace
The Workspace class enforces access control for all path operations:
const ws = new Workspace(new DirectoryConfiguration([
{ type: AccessType.Read, path: '/var/log' },
{ type: AccessType.Write, path: '/home/project' },
]));
Key methods:
normalize(input): resolves relative paths againstcurrentPath; absolute paths are returned as-iscanRead(absPath): checks if a resolved path falls within any read or write access directorycanWrite(absPath): checks if a resolved path falls within a write access directorygetAccesses(): returns all configured directory accesses with their types and resolved pathswalk(dir, onError?): async generator that recursively walks directories, skipping names listed inskipDirs; throws when the root path does not exist or is not a directory, while nested unreadable directories are reported viaonError
Path security
All operations follow the same access control pattern:
- The raw user-supplied path is normalized via
ws.normalize(path) - The caller checks
ws.canRead()orws.canWrite()on the resolved path - Path traversal attempts (e.g.
../../etc/passwd) are blocked becausepath.resolve()resolves..segments before the access check
By default, symlinks are followed as-is: if a configured directory contains a symlink pointing outside, the symlink target is accessible. Set LLM_CHAT_WORKSPACE_RESOLVE_SYMLINKS=true (or pass resolveSymlinks: true to DirectoryConfiguration) to resolve symlinks via fs.realpathSync.native() before access checks. This prevents symlink-based path traversal but may break legitimate symlinks.
Access rules:
| Operation | Check |
|---|---|
switch_workspace |
within any configured directory |
Concurrency
switchWorkspace() uses async-mutex to prevent concurrent switches from interleaving. After a successful switch, the optional onSwitch hook is awaited before the tool result is returned. This is how llm-chat-shell keeps its active bash session in sync with the workspace directory. The hook is not fired when the path is set in the constructor, and not fired when the switch throws.
Tool classes
SwitchWorkspaceTool extends Tool from llm-chat:
- The constructor accepts a
Workspaceinstance and callssuper(name, description, params) onExecute()validates parameters, callsswitchWorkspace(), and returnsPartialToolResult- All errors are caught and returned as plain-string messages. Tools never throw
Dependencies
llm-chat: framework providingTool,ToolParameters, etc.async-mutex: mutex used for thread-safe workspace switching