Table of Contents

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:

  1. Mark every file or directory parameter with [WorkspacePath] and the correct access mode.
  2. Let WorkspacePathValidationMiddleware authorize the argument at invocation time.
  3. Resolve the same argument through PathHelper before calling System.IO.
  4. 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:

  1. Replace slash direction as needed and translate a matching agent-facing root.
  2. Resolve relative paths against the working directory and normalize the result with Path.GetFullPath.
  3. Require the normalized path to remain inside a local allowed directory.
  4. Select the most specific containing grant when grants overlap.
  5. Prefer ReadOnly when equal directory roots carry conflicting access modes.
  6. Reject existing symlink or reparse-point segments beneath the selected root.
  7. Require ReadWrite when 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's AllowedDirectory list.

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.

API reference