Workspace Path Safety
Last updated:
Use Oscova's workspace path contract when a tool reads or changes files. The contract gives the agent a stable path vocabulary, translates those paths for host file operations, and authorizes each marked argument against the active run's directory grants.
This contract is part of the reusable Oscova.Agents project. The supported extension points are AllowedDirectory, WorkspacePathAttribute, WorkspacePathValidationMiddleware, and PathHelper.
Keep translation and authorization separate
Path translation answers, "Which host path does this agent-facing path identify?" Authorization answers, "May this run read or write that path?" A safe file tool needs both:
- Mark every file or directory parameter with
[WorkspacePath]and the correct access mode. - Let
WorkspacePathValidationMiddlewareauthorize the argument at invocation time. - Resolve the same argument through
PathHelperbefore callingSystem.IO. - Apply the tool's own existence, format, size, content, and operation-specific checks.
Oscova wires UseWorkspacePathValidation into its agent pipeline. The middleware calls the directory resolver for each invocation, finds marked parameters on the underlying tool method, and authorizes nonblank values before the tool runs. If you build a ChatClientBuilder outside Oscova's pipeline, you must call UseWorkspacePathValidation yourself.
Warning
An empty allowed-directory list makes the middleware skip workspace validation. A standalone host must supply a current, nonempty resolver before treating [WorkspacePath] as a security boundary. Calling a tool method directly also bypasses invocation middleware.
Describe resolved grants
AllowedDirectory describes one directory available to workspace-aware tools:
| Member | Meaning |
|---|---|
Value |
Resolved host directory path |
Label |
Human-readable name shown in workspace context |
Kind |
Source classification. Path resolution operates on LocalPath entries |
AccessMode |
ReadOnly or ReadWrite |
AgentPath |
Optional path root shown to the agent |
EffectiveAgentPath |
AgentPath when present, otherwise Value |
Resolve user-relative directory definitions to trusted host paths before adding them to the active list. Use a live Func<IReadOnlyList<AllowedDirectory>> where configuration can change, so prompt context, middleware, and tool resolution use the same grants.
The Oscova host combines the file sources available to the run. These can include selected managed workspaces, the agent's private data directory, additional folder grants, and temporary run directories such as chat attachments. Access metadata remains part of each grant. For example, the Library workspace and chat attachments are read-only, while agent data is read-write.
Use the agent-facing path vocabulary
Container-sandboxed runs use stable roots:
| File source | Agent-facing root |
|---|---|
| Named managed workspace | /workspace/<name> |
| Agent data | /data |
| Additional or run-scoped directory | /mnt/<label> |
Workspace names and labels are converted to lowercase mount names. Letters and digits are retained, runs of other characters become one hyphen, leading and trailing hyphens are removed, and an empty result becomes workspace. Reject duplicate effective mount paths before starting the run.
Host and Disabled execution modes use real host paths instead. In every mode, relative paths resolve against the writable working directory. The selected default workspace wins when it is writable. Otherwise the first writable local grant is used. /workspace without a named mount is also accepted as shorthand for that working directory.
Agent-facing mount roots are matched exactly and case-sensitively. Host containment follows the operating system's path behavior through Path.GetRelativePath, so Windows host paths compare case-insensitively while Linux host paths remain case-sensitive.
Important
Never show the agent a path that cannot be resolved through PathHelper.TryResolveToHostPath or, in sandbox mode, is not mounted at that same path.
File, directory, search, and PDF tools perform their file operations in the host process. In Container Sandboxed mode, the shell receives bind mounts at the same agent-facing roots, with each mount set to read-only or read-write from its grant. Both surfaces therefore address the same bytes even though they execute in different processes.
Host placement alone does not make these surfaces equivalent. File tools remain bounded by path translation and workspace middleware. A host shell executes arbitrary commands with Oscova's operating-system account permissions, so its file and network reach is not contained by AllowedDirectory grants.
When a host operation discovers a new path, convert it back with PathHelper.TryResolveToAgentPath or ResolveToAgentPath before returning it. The conversion selects the most specific containing grant and prevents a sandboxed result from exposing a host path. Built-in search tools use this rule for matched files. Other built-in tools preserve the agent's original path in their normal output.
Understand the authorization checks
PathHelper.ResolveAuthorizedHostPath performs the checks used by workspace middleware:
- Replace slash direction as needed and translate a matching agent-facing root.
- Resolve relative paths against the working directory and normalize the result with
Path.GetFullPath. - Require the normalized path to remain inside a local allowed directory.
- Select the most specific containing grant when grants overlap.
- Prefer
ReadOnlywhen equal directory roots carry conflicting access modes. - Reject existing symlink or reparse-point segments beneath the selected root.
- Require
ReadWritewhen the parameter declares write access.
Normalization blocks traversal such as ../../outside.txt when it escapes the grant. Textual prefix checks are not sufficient because separators, .. segments, rooted paths, and platform-specific path behavior can change the resolved location.
Missing target segments are allowed. This is necessary for tools that create a new file or directory. Existing inaccessible segments are treated conservatively as unsafe because their reparse-point status cannot be verified.
The grant root itself is trusted host configuration. The symlink check applies to path segments beneath that root.
Mark and resolve a tool path
This example follows the same pattern as the built-in read tools:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using Oscova.Agents;
using Oscova.Agents.Toolsets;
using Oscova.Agents.Utils;
using Oscova.Agents.Workspace;
public sealed class NotesToolset(
Func<IReadOnlyList<AllowedDirectory>> workspaceResolver) : ICrewToolset
{
[DisplayName("read_note")]
[Description("Reads a text note from an approved workspace.")]
public string ReadNote(
[WorkspacePath(Access = WorkspacePathAccess.Read)]
string path)
{
string hostPath = PathHelper.ResolveToHostPath(path, workspaceResolver);
if (!File.Exists(hostPath))
{
throw new FileNotFoundException("Note not found.", path);
}
return File.ReadAllText(hostPath);
}
}
The middleware authorizes path before ReadNote runs. ResolveToHostPath then performs translation using the same live resolver. For a write, use WorkspacePathAccess.Write and perform all file operations only on the resolved host path.
If your tool embeds more paths inside another argument, such as patch text or a manifest, middleware cannot discover those nested values. Parse them, normalize them, and authorize every target explicitly with ResolveAuthorizedHostPath before performing IO.
Handle failures
Workspace failures are surfaced before the tool body runs:
| Condition | Result |
|---|---|
| Path cannot be resolved or is outside every grant | UnauthorizedAccessException from authorization |
| Existing symlink or reparse-point hop | UnauthorizedAccessException |
| Write requested in a read-only grant | UnauthorizedAccessException |
| Invalid platform path syntax | The normal path-format exception from .NET |
| Blank or omitted marked argument | Middleware skips it. The tool must enforce whether it is required |
| Missing file or directory inside a grant | Authorization succeeds. The tool decides whether absence is valid |
Keep errors actionable, but report the agent-facing input rather than a newly discovered host path. Use PathHelper.SanitizeForLog before placing an untrusted path in logs so control characters cannot inject log lines.
Treat MCP execution as a separate boundary
[WorkspacePath] protects tool methods invoked through Oscova's function middleware. It does not inspect filesystem behavior inside an MCP server or another external process.
STDIO MCP servers can run in either environment:
- Host process is the default and runs with Oscova's host permissions. Workspace middleware does not confine the server's internal file access.
- Docker sandbox runs the server in the shared sandbox image and mounts the current user's managed workspaces read-write under
/workspace/<name>. These mounts are server-level access, not the active agent'sAllowedDirectorylist.
Choose the MCP execution environment and server configuration as an independent trust decision. HTTP MCP servers access files only through whatever capabilities their remote endpoint provides.