AI & Agents

Resolving Secure Sandbox Paths with std::filesystem::canonical

The std::filesystem::canonical function resolves all symbolic links, relative segments, and redundant separators in C++ filesystems. Naive path validation checks that do not resolve these components are vulnerable to path traversal attacks, especially in agentic environments where tools execute dynamically generated paths. This guide compares canonical, weakly_canonical, and absolute path operations and shows how to build a secure sandbox boundary in C++17.

Fast.io Editorial Team 9 min read
Configuring C++ canonical filesystem paths to enforce secure sandbox limits.

Why path validation fails in naive multi-agent tools

Two autonomous agents sharing a workspace will compromise host system security if their filesystem tools validate directories using simple substring checks. When a large language model is manipulated by prompt injection to execute a tool call with a path like ../../etc/passwd, a naive prefix check will fail to block the access because it cannot resolve symbolic links or parent directory sequences. The host execution process inherits the system permissions of the tool worker, which means an unvalidated read tool will access sensitive keys or files, while an unvalidated write tool can corrupt files outside the execution directory.

In agentic workspaces where multiple models write to the same folders, file paths are generated dynamically. Coding tools (such as Claude Code, Cursor, and custom agent frameworks) interact with local filesystems by generating path strings. Unlike humans who understand the physical layout of directories, agents rely on standard string representations. If an agent is asked to generate a report and write it to output/report.txt, but a prompt injects a traversal sequence, the tool executor might write to a system location instead.

Most developer guides show basic C++ filesystem operations without addressing the security of tool calls. When building tools in C++ for agents, developers must build a secure sandbox boundary. This boundary requires verifying that every target path resolves within the allowed directory tree before the C++ runtime performs any disk operation.

How to compare path resolution options using std::filesystem::canonical

To validate paths in C++, developers must choose the correct path resolution function from the standard filesystem library introduced in C++17. A common mistake is using std::filesystem::absolute to normalize path inputs. The std::filesystem::absolute function only resolves a relative path against the current working directory. It does not check if the path exists, it does not resolve symbolic links, and it leaves parent directory references (..) and current directory references (.) in the path string. If an agent inputs sandbox/../private_keys, std::filesystem::absolute will return /current/working/directory/sandbox/../private_keys, which still allows path traversal when accessed by standard system file APIs.

In contrast, std::filesystem::canonical resolves all symbolic links, relative path segments, and redundant directory separators to return a unique, absolute path. The resulting path represents the actual physical location on the disk. This function is modeled after the POSIX realpath function.

However, std::filesystem::canonical has a major limitation: the target path must exist on the disk. If any element of the path does not exist, the function throws a std::filesystem::filesystem_error exception. This behavior is necessary because the operating system cannot resolve symbolic links for paths that do not exist. For tools that write new files, calling canonical directly will fail and throw an exception.

To address this, C++17 provides std::filesystem::weakly_canonical. This function does not require the entire path to exist. Instead, it identifies the longest leading sequence of elements in the path that does exist, calls canonical on that existing prefix, and then appends the remaining non-existent elements in normal form. This makes weakly_canonical the standard choice for validating paths before creating files or directories.

Steps to implement sandbox verification in C++

Preventing path traversal in C++ requires resolving the requested path to its canonical form and verifying that it falls within the boundary of the canonical sandbox root directory. Naive validation wrappers often use simple string checks, such as verifying if the target path starts with the sandbox root string. This approach introduces a serious vulnerability. If the sandbox root is /var/tmp/agent_sandbox and the agent requests access to /var/tmp/agent_sandbox_secrets/config.json, a prefix comparison of the strings will return true because /var/tmp/agent_sandbox is a prefix of /var/tmp/agent_sandbox_secrets.

To prevent this partial-name match, developers must ensure that the target path is either identical to the sandbox root or starts with the sandbox root followed immediately by a directory separator. A cleaner, more resilient method compares the individual path components using standard library mismatch utilities or checks the sub-path relationships after canonicalization.

The C++17 implementation below demonstrates a secure boundary validation function using std::filesystem::weakly_canonical to handle both existing and new paths, checking prefix structures, and handling exceptions safely.

#include <filesystem>
#include <string>
#include <system_error>
#include <iostream>
#include <algorithm>
namespace fs = std::filesystem;
// Validates that the requested target path lies strictly inside the sandbox root directory
bool is_sandbox_path_safe(const fs::path& target_path, const fs::path& sandbox_root) {
    try {
        // Resolve all symbolic links, relative segments, and redundant separators
        // Use weakly_canonical because the target file might not exist yet
        fs::path canonical_target = fs::weakly_canonical(target_path);
        fs::path canonical_root = fs::weakly_canonical(sandbox_root);
        // Standard string representation for prefix verification
        std::string target_str = canonical_target.string();
        std::string root_str = canonical_root.string();
        // Target path cannot be shorter than the sandbox root path
        if (target_str.size() < root_str.size()) {
            return false;
        }
        // Verify the target path starts with the sandbox root path prefix
        if (target_str.compare(0, root_str.size(), root_str) != 0) {
            return false;
        }
        // If target is longer, ensure the character immediately following the root path is a separator
        // This prevents partial folder name matching (e.g., /sandbox vs /sandbox_secrets)
        if (target_str.size() > root_str.size()) {
            if (target_str[root_str.size()] != fs::path::preferred_separator) {
                return false;
            }
        }
        return true;
    } catch (const fs::filesystem_error& e) {
        std::cerr << "Path validation failed due to filesystem error: " << e.what() << std::endl;
        return false;
    }
}
// Example usage showing how to use the validation check before file operations
bool safe_write_data(const fs::path& target_file, const std::string& data, const fs::path& sandbox_root) {
    if (!is_sandbox_path_safe(target_file, sandbox_root)) {
        std::cerr << "Security violation: Path is outside the allowed sandbox boundary." << std::endl;
        return false;
    }
    // Perform safe file write operation
    return true;
}

This implementation addresses the primary security gaps in standard path validation by resolving the target path using weakly_canonical before any check is made. This guarantees that any hidden relative parent elements or symbolic links created within the workspace are resolved to their true physical locations. The boundary check then verifies that the root directory is a strict prefix, blocking any partial directory matches.

Fastio features

Secure std::filesystem::canonical path validation in agent workspaces

Protect your agent tools from sandbox escapes with secure collaborative workspaces featuring automatic version history and remote MCP access. Starts with a 14-day free trial.

Why TOCTOU race conditions bypass path checks

Even with canonical path checks, developers must account for the Time-of-Check to Time-of-Use (TOCTOU) vulnerability. This security race condition occurs when the state of the filesystem changes between the path validation check and the actual file access operation. In multi-threaded environments or systems where multiple agents execute commands concurrently, a malicious prompt or agent thread can alter the filesystem topology.

For example, a program might validate that a target path workspace/temp_output is safe because it is inside the sandbox. Immediately after the validation returns true, but before the program opens the file descriptor to write data, another thread replaces workspace/temp_output with a symbolic link pointing to /etc/passwd. When the program performs the write operation, the operating system follows the newly created symlink, writing the data to the sensitive system file.

To mitigate TOCTOU vulnerabilities, developers should consider combining path validation with operating system specific controls. On Unix-like platforms, developers can use low-level system calls such as openat with the O_NOFOLLOW flag to prevent the kernel from following symbolic links when opening files. In addition, execution sandboxes must enforce isolation at the operating system level, ensuring that agent processes run under restricted user accounts with no write access to system directories.

Containerization and temporary virtual filesystems provide another layer of defense. By executing agent code inside isolated containers, developers ensure that even if a sandbox escape occurs at the C++ level, the agent only reaches a virtualized environment containing no sensitive host files.

How to manage agent workspace storage securely

When building tools for agentic systems, managing file storage securely requires moving beyond temporary local sandboxes. Developers often rely on local directories or commodity cloud storage providers like Google Drive, Dropbox, or Box to store agent outputs. However, tools designed for human file synchronization do not address the requirements of multi-agent workflows. They lack built-in RAG indexing on write, suffer from API quota limits under rapid agent updates, and introduce significant latency.

Fast.io provides a collaborative storage and intelligence layer designed for agentic teams. Instead of managing local sandboxes on the host machine, developers can connect their C++ execution engines to shared workspaces. Fast.io Coordination Rooms serve as neutral spaces where agents from different frameworks (such as Claude Code, Codex, Cursor, Gemini, or OpenClaw) can share files, post updates, and hand off work to human team members.

Programmatic integration is straightforward. Fast.io does not distribute language-specific libraries or SDKs, ensuring agents connect using standard web protocols. Agents interact with workspaces using the remote Fast.io Model Context Protocol (MCP) server. Developers configure their agent configurations to connect to the MCP server endpoint at https://mcp.fast.io/mcp, authenticating with a secure API token. This endpoint exposes a consolidated MCP toolset that allows agents to read, write, and manage workspace folders. For more detailed API configurations, refer to the Developer Storage Guide.

When an agent writes to a workspace, Fast.io maintains a complete per-file version history. If an agent overwrites a file or introduces an error, the previous version can be restored, keeping concurrent agent work auditable. Enabling Intelligence Mode on the workspace automatically indexes all files for Retrieval-Augmented Generation (RAG) and hybrid search, making outputs instantly searchable by meaning.

To protect organization data, permissions are scoped at the workspace, folder, and file level. Invites to collaborative shares can be configured with expiration limits, ensuring agents only reach the files they are authorized to touch. Doing real work on the platform requires an organization subscription. Every organization starts with a 14-day free trial, which requires a credit card. Subscription plans are Starter at $29/mo, Business at $99/mo, and Growth at $299/mo. This pricing model allows teams to scale their storage and intelligence capabilities as their agent operations expand. You can learn more details on the pricing page.

Frequently Asked Questions

What is the difference between canonical and absolute in C++?

The std::filesystem::absolute function converts a relative path to an absolute path by prepending the current working directory, but it does not resolve symbolic links or remove parent directory references. In contrast, std::filesystem::canonical resolves all symbolic links, parent directory segments, and redundant separators to return a unique, absolute path representing the actual location on the physical disk.

How do you prevent path traversal in C++?

To prevent path traversal, resolve the requested path into a canonical form using std::filesystem::weakly_canonical and verify that the resulting path starts with the canonical sandbox root directory. To prevent partial directory name matching, ensure that the target path is either identical to the root path or starts with the root path followed immediately by a directory separator.

Why does std::filesystem::canonical throw an exception?

The std::filesystem::canonical function throws a std::filesystem::filesystem_error exception if the target path does not exist on the filesystem. This occurs because the function must query the operating system to resolve symbolic links, which is impossible for non-existent paths. If you need to handle paths that do not exist yet, use std::filesystem::weakly_canonical instead.

Related Resources

Fastio features

Secure std::filesystem::canonical path validation in agent workspaces

Protect your agent tools from sandbox escapes with secure collaborative workspaces featuring automatic version history and remote MCP access. Starts with a 14-day free trial.